| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- /**
- * 高可用微信小游戏 Storage 封装
- *
- * 特性:
- * - 同步/异步双支持
- * - 自动 JSON 序列化/反序列化
- * - 过期时间支持
- * - 内存缓存层
- * - 完善的异常处理
- * - TypeScript 类型支持
- */
- /** 存储数据的包装结构 */
- interface StorageWrapper<T> {
- data: T;
- /** 过期时间戳,null 表示永不过期 */
- expireAt: number | null;
- /** 创建时间戳 */
- createAt: number;
- }
- /** Storage 配置选项 */
- interface StorageOptions {
- /** 过期时间(毫秒),默认为 null 表示永不过期 */
- expireTime?: number | null;
- /** 是否启用内存缓存,默认为 true */
- enableCache?: boolean;
- }
- /** 默认配置 */
- const DEFAULT_OPTIONS: Required<StorageOptions> = {
- expireTime: null,
- enableCache: true,
- };
- /** 内存缓存 */
- const memoryCache = new Map<string, StorageWrapper<unknown>>();
- /**
- * 检查数据是否过期
- */
- function isExpired<T>(wrapper: StorageWrapper<T>): boolean {
- if (wrapper.expireAt === null) {
- return false;
- }
- return Date.now() > wrapper.expireAt;
- }
- /**
- * 创建存储包装对象
- */
- function createWrapper<T>(data: T, expireTime: number | null): StorageWrapper<T> {
- const now = Date.now();
- return {
- data,
- expireAt: expireTime ? now + expireTime : null,
- createAt: now,
- };
- }
- /**
- * 安全地解析 JSON
- */
- function safeJsonParse<T>(str: string): StorageWrapper<T> | null {
- try {
- return JSON.parse(str) as StorageWrapper<T>;
- } catch {
- return null;
- }
- }
- // ==================== 同步 API ====================
- /**
- * 同步设置存储数据
- * @param key 存储键名
- * @param data 存储数据
- * @param options 配置选项
- * @returns 是否成功
- */
- export function setStorage<T>(key: string, data: T, options?: StorageOptions): boolean {
- const opts = { ...DEFAULT_OPTIONS, ...options };
- const wrapper = createWrapper(data, opts.expireTime ?? null);
-
- try {
- wx.setStorageSync(key, JSON.stringify(wrapper));
-
- // 更新内存缓存
- if (opts.enableCache) {
- memoryCache.set(key, wrapper as StorageWrapper<unknown>);
- }
-
- return true;
- } catch (error) {
- console.error(`[Storage] setStorage failed for key "${key}":`, error);
- return false;
- }
- }
- /**
- * 同步获取存储数据
- * @param key 存储键名
- * @param defaultValue 默认值(当数据不存在或过期时返回)
- * @param options 配置选项
- * @returns 存储的数据或默认值
- */
- export function getStorage<T>(key: string, defaultValue?: T, options?: StorageOptions): T | undefined {
- const opts = { ...DEFAULT_OPTIONS, ...options };
-
- // 优先从内存缓存读取
- if (opts.enableCache && memoryCache.has(key)) {
- const cached = memoryCache.get(key) as StorageWrapper<T>;
- if (!isExpired(cached)) {
- return cached.data;
- }
- // 过期则清除缓存
- memoryCache.delete(key);
- removeStorage(key);
- return defaultValue;
- }
-
- try {
- const raw = wx.getStorageSync(key);
- if (!raw) {
- return defaultValue;
- }
-
- const wrapper = safeJsonParse<T>(raw);
- if (!wrapper) {
- // 兼容旧数据格式(非包装格式)
- return raw as T;
- }
-
- // 检查是否过期
- if (isExpired(wrapper)) {
- removeStorage(key);
- return defaultValue;
- }
-
- // 更新内存缓存
- if (opts.enableCache) {
- memoryCache.set(key, wrapper as StorageWrapper<unknown>);
- }
-
- return wrapper.data;
- } catch (error) {
- console.error(`[Storage] getStorage failed for key "${key}":`, error);
- return defaultValue;
- }
- }
- /**
- * 同步删除存储数据
- * @param key 存储键名
- * @returns 是否成功
- */
- export function removeStorage(key: string): boolean {
- try {
- wx.removeStorageSync(key);
- memoryCache.delete(key);
- return true;
- } catch (error) {
- console.error(`[Storage] removeStorage failed for key "${key}":`, error);
- return false;
- }
- }
- /**
- * 同步清空所有存储数据
- * @returns 是否成功
- */
- export function clearStorage(): boolean {
- try {
- wx.clearStorageSync();
- memoryCache.clear();
- return true;
- } catch (error) {
- console.error('[Storage] clearStorage failed:', error);
- return false;
- }
- }
- /**
- * 检查存储键是否存在且未过期
- * @param key 存储键名
- * @returns 是否存在
- */
- export function hasStorage(key: string): boolean {
- return getStorage(key) !== undefined;
- }
- // ==================== 异步 API ====================
- /**
- * 异步设置存储数据
- * @param key 存储键名
- * @param data 存储数据
- * @param options 配置选项
- */
- export function setStorageAsync<T>(key: string, data: T, options?: StorageOptions): Promise<void> {
- const opts = { ...DEFAULT_OPTIONS, ...options };
- const wrapper = createWrapper(data, opts.expireTime ?? null);
-
- return new Promise((resolve, reject) => {
- wx.setStorage({
- key,
- data: JSON.stringify(wrapper),
- success: () => {
- // 更新内存缓存
- if (opts.enableCache) {
- memoryCache.set(key, wrapper as StorageWrapper<unknown>);
- }
- resolve();
- },
- fail: (error) => {
- console.error(`[Storage] setStorageAsync failed for key "${key}":`, error);
- reject(error);
- },
- });
- });
- }
- /**
- * 异步获取存储数据
- * @param key 存储键名
- * @param defaultValue 默认值(当数据不存在或过期时返回)
- * @param options 配置选项
- */
- export function getStorageAsync<T>(key: string, defaultValue?: T, options?: StorageOptions): Promise<T | undefined> {
- const opts = { ...DEFAULT_OPTIONS, ...options };
-
- // 优先从内存缓存读取
- if (opts.enableCache && memoryCache.has(key)) {
- const cached = memoryCache.get(key) as StorageWrapper<T>;
- if (!isExpired(cached)) {
- return Promise.resolve(cached.data);
- }
- // 过期则清除缓存
- memoryCache.delete(key);
- return removeStorageAsync(key).then(() => defaultValue);
- }
-
- return new Promise((resolve) => {
- wx.getStorage({
- key,
- success: (res) => {
- const raw = res.data;
- if (!raw) {
- resolve(defaultValue);
- return;
- }
-
- const wrapper = safeJsonParse<T>(raw);
- if (!wrapper) {
- // 兼容旧数据格式
- resolve(raw as T);
- return;
- }
-
- // 检查是否过期
- if (isExpired(wrapper)) {
- removeStorageAsync(key).then(() => resolve(defaultValue));
- return;
- }
-
- // 更新内存缓存
- if (opts.enableCache) {
- memoryCache.set(key, wrapper as StorageWrapper<unknown>);
- }
-
- resolve(wrapper.data);
- },
- fail: (error) => {
- console.error(`[Storage] getStorageAsync failed for key "${key}":`, error);
- resolve(defaultValue);
- },
- });
- });
- }
- /**
- * 异步删除存储数据
- * @param key 存储键名
- */
- export function removeStorageAsync(key: string): Promise<void> {
- return new Promise((resolve, reject) => {
- wx.removeStorage({
- key,
- success: () => {
- memoryCache.delete(key);
- resolve();
- },
- fail: (error) => {
- console.error(`[Storage] removeStorageAsync failed for key "${key}":`, error);
- reject(error);
- },
- });
- });
- }
- /**
- * 异步清空所有存储数据
- */
- export function clearStorageAsync(): Promise<void> {
- return new Promise((resolve, reject) => {
- wx.clearStorage({
- success: () => {
- memoryCache.clear();
- resolve();
- },
- fail: (error) => {
- console.error('[Storage] clearStorageAsync failed:', error);
- reject(error);
- },
- });
- });
- }
- // ==================== 工具方法 ====================
- /**
- * 获取存储信息
- */
- export function getStorageInfo(): WechatMinigame.GetStorageInfoSyncOption {
- try {
- return wx.getStorageInfoSync();
- } catch (error) {
- console.error('[Storage] getStorageInfo failed:', error);
- return { keys: [], currentSize: 0, limitSize: 0 };
- }
- }
- /**
- * 异步获取存储信息
- */
- export function getStorageInfoAsync(): Promise<WechatMinigame.GetStorageInfoSyncOption> {
- return new Promise((resolve) => {
- wx.getStorageInfo({
- success: (res) => resolve(res),
- fail: (error) => {
- console.error('[Storage] getStorageInfoAsync failed:', error);
- resolve({ keys: [], currentSize: 0, limitSize: 0 });
- },
- });
- });
- }
- /**
- * 清理所有过期数据
- */
- export function cleanExpiredStorage(): void {
- try {
- const { keys } = wx.getStorageInfoSync();
- keys.forEach((key) => {
- try {
- const raw = wx.getStorageSync(key);
- if (raw) {
- const wrapper = safeJsonParse(raw);
- if (wrapper && isExpired(wrapper)) {
- removeStorage(key);
- }
- }
- } catch {
- // 忽略单个 key 的错误
- }
- });
- } catch (error) {
- console.error('[Storage] cleanExpiredStorage failed:', error);
- }
- }
- /**
- * 清理内存缓存
- */
- export function clearMemoryCache(): void {
- memoryCache.clear();
- }
- /**
- * 获取内存缓存大小
- */
- export function getMemoryCacheSize(): number {
- return memoryCache.size;
- }
- // ==================== 导出默认实例 ====================
- export default {
- // 同步 API
- set: setStorage,
- get: getStorage,
- remove: removeStorage,
- clear: clearStorage,
- has: hasStorage,
-
- // 异步 API
- setAsync: setStorageAsync,
- getAsync: getStorageAsync,
- removeAsync: removeStorageAsync,
- clearAsync: clearStorageAsync,
-
- // 工具方法
- getInfo: getStorageInfo,
- getInfoAsync: getStorageInfoAsync,
- cleanExpired: cleanExpiredStorage,
- clearCache: clearMemoryCache,
- getCacheSize: getMemoryCacheSize,
- };
|