storage.ts 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. /**
  2. * 高可用微信小游戏 Storage 封装
  3. *
  4. * 特性:
  5. * - 同步/异步双支持
  6. * - 自动 JSON 序列化/反序列化
  7. * - 过期时间支持
  8. * - 内存缓存层
  9. * - 完善的异常处理
  10. * - TypeScript 类型支持
  11. */
  12. /** 存储数据的包装结构 */
  13. interface StorageWrapper<T> {
  14. data: T;
  15. /** 过期时间戳,null 表示永不过期 */
  16. expireAt: number | null;
  17. /** 创建时间戳 */
  18. createAt: number;
  19. }
  20. /** Storage 配置选项 */
  21. interface StorageOptions {
  22. /** 过期时间(毫秒),默认为 null 表示永不过期 */
  23. expireTime?: number | null;
  24. /** 是否启用内存缓存,默认为 true */
  25. enableCache?: boolean;
  26. }
  27. /** 默认配置 */
  28. const DEFAULT_OPTIONS: Required<StorageOptions> = {
  29. expireTime: null,
  30. enableCache: true,
  31. };
  32. /** 内存缓存 */
  33. const memoryCache = new Map<string, StorageWrapper<unknown>>();
  34. /**
  35. * 检查数据是否过期
  36. */
  37. function isExpired<T>(wrapper: StorageWrapper<T>): boolean {
  38. if (wrapper.expireAt === null) {
  39. return false;
  40. }
  41. return Date.now() > wrapper.expireAt;
  42. }
  43. /**
  44. * 创建存储包装对象
  45. */
  46. function createWrapper<T>(data: T, expireTime: number | null): StorageWrapper<T> {
  47. const now = Date.now();
  48. return {
  49. data,
  50. expireAt: expireTime ? now + expireTime : null,
  51. createAt: now,
  52. };
  53. }
  54. /**
  55. * 安全地解析 JSON
  56. */
  57. function safeJsonParse<T>(str: string): StorageWrapper<T> | null {
  58. try {
  59. return JSON.parse(str) as StorageWrapper<T>;
  60. } catch {
  61. return null;
  62. }
  63. }
  64. // ==================== 同步 API ====================
  65. /**
  66. * 同步设置存储数据
  67. * @param key 存储键名
  68. * @param data 存储数据
  69. * @param options 配置选项
  70. * @returns 是否成功
  71. */
  72. export function setStorage<T>(key: string, data: T, options?: StorageOptions): boolean {
  73. const opts = { ...DEFAULT_OPTIONS, ...options };
  74. const wrapper = createWrapper(data, opts.expireTime ?? null);
  75. try {
  76. tt.setStorageSync(key, JSON.stringify(wrapper));
  77. // 更新内存缓存
  78. if (opts.enableCache) {
  79. memoryCache.set(key, wrapper as StorageWrapper<unknown>);
  80. }
  81. return true;
  82. } catch (error) {
  83. console.error(`[Storage] setStorage failed for key "${key}":`, error);
  84. return false;
  85. }
  86. }
  87. /**
  88. * 同步获取存储数据
  89. * @param key 存储键名
  90. * @param defaultValue 默认值(当数据不存在或过期时返回)
  91. * @param options 配置选项
  92. * @returns 存储的数据或默认值
  93. */
  94. export function getStorage<T>(key: string, defaultValue?: T, options?: StorageOptions): T | undefined {
  95. const opts = { ...DEFAULT_OPTIONS, ...options };
  96. // 优先从内存缓存读取
  97. if (opts.enableCache && memoryCache.has(key)) {
  98. const cached = memoryCache.get(key) as StorageWrapper<T>;
  99. if (!isExpired(cached)) {
  100. return cached.data;
  101. }
  102. // 过期则清除缓存
  103. memoryCache.delete(key);
  104. removeStorage(key);
  105. return defaultValue;
  106. }
  107. try {
  108. const raw = tt.getStorageSync(key);
  109. if (!raw) {
  110. return defaultValue;
  111. }
  112. const wrapper = safeJsonParse<T>(raw);
  113. if (!wrapper) {
  114. // 兼容旧数据格式(非包装格式)
  115. return raw as T;
  116. }
  117. // 检查是否过期
  118. if (isExpired(wrapper)) {
  119. removeStorage(key);
  120. return defaultValue;
  121. }
  122. // 更新内存缓存
  123. if (opts.enableCache) {
  124. memoryCache.set(key, wrapper as StorageWrapper<unknown>);
  125. }
  126. return wrapper.data;
  127. } catch (error) {
  128. console.error(`[Storage] getStorage failed for key "${key}":`, error);
  129. return defaultValue;
  130. }
  131. }
  132. /**
  133. * 同步删除存储数据
  134. * @param key 存储键名
  135. * @returns 是否成功
  136. */
  137. export function removeStorage(key: string): boolean {
  138. try {
  139. tt.removeStorageSync(key);
  140. memoryCache.delete(key);
  141. return true;
  142. } catch (error) {
  143. console.error(`[Storage] removeStorage failed for key "${key}":`, error);
  144. return false;
  145. }
  146. }
  147. /**
  148. * 同步清空所有存储数据
  149. * @returns 是否成功
  150. */
  151. export function clearStorage(): boolean {
  152. try {
  153. tt.clearStorageSync();
  154. memoryCache.clear();
  155. return true;
  156. } catch (error) {
  157. console.error('[Storage] clearStorage failed:', error);
  158. return false;
  159. }
  160. }
  161. /**
  162. * 检查存储键是否存在且未过期
  163. * @param key 存储键名
  164. * @returns 是否存在
  165. */
  166. export function hasStorage(key: string): boolean {
  167. return getStorage(key) !== undefined;
  168. }
  169. // ==================== 异步 API ====================
  170. /**
  171. * 异步设置存储数据
  172. * @param key 存储键名
  173. * @param data 存储数据
  174. * @param options 配置选项
  175. */
  176. export function setStorageAsync<T>(key: string, data: T, options?: StorageOptions): Promise<void> {
  177. const opts = { ...DEFAULT_OPTIONS, ...options };
  178. const wrapper = createWrapper(data, opts.expireTime ?? null);
  179. return new Promise((resolve, reject) => {
  180. tt.setStorage({
  181. key,
  182. data: JSON.stringify(wrapper),
  183. success: () => {
  184. // 更新内存缓存
  185. if (opts.enableCache) {
  186. memoryCache.set(key, wrapper as StorageWrapper<unknown>);
  187. }
  188. resolve();
  189. },
  190. fail: (error) => {
  191. console.error(`[Storage] setStorageAsync failed for key "${key}":`, error);
  192. reject(error);
  193. },
  194. });
  195. });
  196. }
  197. /**
  198. * 异步获取存储数据
  199. * @param key 存储键名
  200. * @param defaultValue 默认值(当数据不存在或过期时返回)
  201. * @param options 配置选项
  202. */
  203. export function getStorageAsync<T>(key: string, defaultValue?: T, options?: StorageOptions): Promise<T | undefined> {
  204. const opts = { ...DEFAULT_OPTIONS, ...options };
  205. // 优先从内存缓存读取
  206. if (opts.enableCache && memoryCache.has(key)) {
  207. const cached = memoryCache.get(key) as StorageWrapper<T>;
  208. if (!isExpired(cached)) {
  209. return Promise.resolve(cached.data);
  210. }
  211. // 过期则清除缓存
  212. memoryCache.delete(key);
  213. return removeStorageAsync(key).then(() => defaultValue);
  214. }
  215. return new Promise((resolve) => {
  216. tt.getStorage({
  217. key,
  218. success: (res) => {
  219. const raw = res.data;
  220. if (!raw) {
  221. resolve(defaultValue);
  222. return;
  223. }
  224. const wrapper = safeJsonParse<T>(raw);
  225. if (!wrapper) {
  226. // 兼容旧数据格式
  227. resolve(raw as T);
  228. return;
  229. }
  230. // 检查是否过期
  231. if (isExpired(wrapper)) {
  232. removeStorageAsync(key).then(() => resolve(defaultValue));
  233. return;
  234. }
  235. // 更新内存缓存
  236. if (opts.enableCache) {
  237. memoryCache.set(key, wrapper as StorageWrapper<unknown>);
  238. }
  239. resolve(wrapper.data);
  240. },
  241. fail: (error) => {
  242. console.error(`[Storage] getStorageAsync failed for key "${key}":`, error);
  243. resolve(defaultValue);
  244. },
  245. });
  246. });
  247. }
  248. /**
  249. * 异步删除存储数据
  250. * @param key 存储键名
  251. */
  252. export function removeStorageAsync(key: string): Promise<void> {
  253. return new Promise((resolve, reject) => {
  254. tt.removeStorage({
  255. key,
  256. success: () => {
  257. memoryCache.delete(key);
  258. resolve();
  259. },
  260. fail: (error) => {
  261. console.error(`[Storage] removeStorageAsync failed for key "${key}":`, error);
  262. reject(error);
  263. },
  264. });
  265. });
  266. }
  267. /**
  268. * 异步清空所有存储数据
  269. */
  270. export function clearStorageAsync(): Promise<void> {
  271. return new Promise((resolve, reject) => {
  272. tt.clearStorage({
  273. success: () => {
  274. memoryCache.clear();
  275. resolve();
  276. },
  277. fail: (error) => {
  278. console.error('[Storage] clearStorageAsync failed:', error);
  279. reject(error);
  280. },
  281. });
  282. });
  283. }
  284. // ==================== 工具方法 ====================
  285. /**
  286. * 获取存储信息
  287. */
  288. export function getStorageInfo(): any {
  289. try {
  290. return tt.getStorageInfoSync();
  291. } catch (error) {
  292. console.error('[Storage] getStorageInfo failed:', error);
  293. return { keys: [], currentSize: 0, limitSize: 0 };
  294. }
  295. }
  296. /**
  297. * 异步获取存储信息
  298. */
  299. export function getStorageInfoAsync(): Promise<any> {
  300. return new Promise((resolve) => {
  301. tt.getStorageInfo({
  302. success: (res) => resolve(res),
  303. fail: (error) => {
  304. console.error('[Storage] getStorageInfoAsync failed:', error);
  305. resolve({ keys: [], currentSize: 0, limitSize: 0 });
  306. },
  307. });
  308. });
  309. }
  310. /**
  311. * 清理所有过期数据
  312. */
  313. export function cleanExpiredStorage(): void {
  314. try {
  315. const { keys } = tt.getStorageInfoSync();
  316. keys.forEach((key) => {
  317. try {
  318. const raw = tt.getStorageSync(key);
  319. if (raw) {
  320. const wrapper = safeJsonParse(raw);
  321. if (wrapper && isExpired(wrapper)) {
  322. removeStorage(key);
  323. }
  324. }
  325. } catch {
  326. // 忽略单个 key 的错误
  327. }
  328. });
  329. } catch (error) {
  330. console.error('[Storage] cleanExpiredStorage failed:', error);
  331. }
  332. }
  333. /**
  334. * 清理内存缓存
  335. */
  336. export function clearMemoryCache(): void {
  337. memoryCache.clear();
  338. }
  339. /**
  340. * 获取内存缓存大小
  341. */
  342. export function getMemoryCacheSize(): number {
  343. return memoryCache.size;
  344. }
  345. // ==================== 导出默认实例 ====================
  346. export default {
  347. // 同步 API
  348. set: setStorage,
  349. get: getStorage,
  350. remove: removeStorage,
  351. clear: clearStorage,
  352. has: hasStorage,
  353. // 异步 API
  354. setAsync: setStorageAsync,
  355. getAsync: getStorageAsync,
  356. removeAsync: removeStorageAsync,
  357. clearAsync: clearStorageAsync,
  358. // 工具方法
  359. getInfo: getStorageInfo,
  360. getInfoAsync: getStorageInfoAsync,
  361. cleanExpired: cleanExpiredStorage,
  362. clearCache: clearMemoryCache,
  363. getCacheSize: getMemoryCacheSize,
  364. };