storage.ts 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677
  1. import * as fs from 'expo-file-system/legacy';
  2. import { createMMKV } from 'react-native-mmkv';
  3. import type { MMKV } from 'react-native-mmkv';
  4. const TTL_KEY = '$__$t_';
  5. const LAST_CLEAR_KEY = '$__last_clear_time$_';
  6. const CLEAR_INTERVAL = 86400 * 3 * 1000; // 3 天扫一次过期项
  7. const CLEAR_TICK = 60 * 15 * 1000; // 每 15 分钟检查一次
  8. function fixPath(path: string) {
  9. if (path.startsWith('file:///')) return path.substring(7);
  10. if (path.startsWith('file:/')) return path.replace(/^file:\//, '/');
  11. return path;
  12. }
  13. export type KVDB = MMKV & {
  14. getObject: <T = any>(key: string) => T | undefined;
  15. setObject: (key: string, value: any, ttlSeconds: number) => void;
  16. };
  17. function attachObjectMethods(mmkv: MMKV): KVDB {
  18. const db = mmkv as KVDB;
  19. db.setObject = (key, value, ttlSeconds) => {
  20. db.set(
  21. key,
  22. JSON.stringify({ [TTL_KEY]: Date.now() + ttlSeconds * 1000, v: value })
  23. );
  24. };
  25. db.getObject = <T>(key: string) => {
  26. const raw = db.getString(key);
  27. if (!raw) return undefined;
  28. try {
  29. const obj = JSON.parse(raw) as { [TTL_KEY]: number; v: T };
  30. if (Date.now() > obj[TTL_KEY]) {
  31. db.remove(key);
  32. return undefined;
  33. }
  34. return obj.v;
  35. } catch {
  36. db.remove(key);
  37. return undefined;
  38. }
  39. };
  40. return db;
  41. }
  42. function makeStore(id: string) {
  43. let instance: KVDB | null = null;
  44. return () => {
  45. if (!instance) {
  46. instance = attachObjectMethods(
  47. createMMKV({ id, path: fixPath(fs.cacheDirectory ?? '') })
  48. );
  49. }
  50. return instance;
  51. };
  52. }
  53. export const getGlobalStorage = makeStore('global');
  54. export const getCaches = makeStore('caches');
  55. export const getApiCache = makeStore('api_cache');
  56. // 启动后定期扫一遍缓存,清理过期项
  57. setTimeout(() => {
  58. const tick = () => {
  59. const global = getGlobalStorage();
  60. const last = parseInt(global.getString(LAST_CLEAR_KEY) || '0', 10);
  61. const now = Date.now();
  62. if (now - last < CLEAR_INTERVAL) return;
  63. for (const db of [getCaches(), getApiCache(), global]) {
  64. for (const key of db.getAllKeys()) db.getObject(key);
  65. }
  66. global.set(LAST_CLEAR_KEY, now);
  67. };
  68. tick();
  69. setInterval(tick, CLEAR_TICK);
  70. }, 3000);