import * as fs from 'expo-file-system/legacy'; import { createMMKV } from 'react-native-mmkv'; import type { MMKV } from 'react-native-mmkv'; const TTL_KEY = '$__$t_'; const LAST_CLEAR_KEY = '$__last_clear_time$_'; const CLEAR_INTERVAL = 86400 * 3 * 1000; // 3 天扫一次过期项 const CLEAR_TICK = 60 * 15 * 1000; // 每 15 分钟检查一次 function fixPath(path: string) { if (path.startsWith('file:///')) return path.substring(7); if (path.startsWith('file:/')) return path.replace(/^file:\//, '/'); return path; } export type KVDB = MMKV & { getObject: (key: string) => T | undefined; setObject: (key: string, value: any, ttlSeconds: number) => void; }; function attachObjectMethods(mmkv: MMKV): KVDB { const db = mmkv as KVDB; db.setObject = (key, value, ttlSeconds) => { db.set( key, JSON.stringify({ [TTL_KEY]: Date.now() + ttlSeconds * 1000, v: value }) ); }; db.getObject = (key: string) => { const raw = db.getString(key); if (!raw) return undefined; try { const obj = JSON.parse(raw) as { [TTL_KEY]: number; v: T }; if (Date.now() > obj[TTL_KEY]) { db.remove(key); return undefined; } return obj.v; } catch { db.remove(key); return undefined; } }; return db; } function makeStore(id: string) { let instance: KVDB | null = null; return () => { if (!instance) { instance = attachObjectMethods( createMMKV({ id, path: fixPath(fs.cacheDirectory ?? '') }) ); } return instance; }; } export const getGlobalStorage = makeStore('global'); export const getCaches = makeStore('caches'); export const getApiCache = makeStore('api_cache'); // 启动后定期扫一遍缓存,清理过期项 setTimeout(() => { const tick = () => { const global = getGlobalStorage(); const last = parseInt(global.getString(LAST_CLEAR_KEY) || '0', 10); const now = Date.now(); if (now - last < CLEAR_INTERVAL) return; for (const db of [getCaches(), getApiCache(), global]) { for (const key of db.getAllKeys()) db.getObject(key); } global.set(LAST_CLEAR_KEY, now); }; tick(); setInterval(tick, CLEAR_TICK); }, 3000);