refactor: 前端登录/权限/界面状态迁移至 zustand
This commit is contained in:
175
apps/admin/src/store/middleware/persist.ts
Normal file
175
apps/admin/src/store/middleware/persist.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 持久化中间件基础设施。
|
||||
*
|
||||
* 为了平滑迁移,这里把旧实现直接读写 localStorage 的 key
|
||||
* (token / user / permissions / gongxue-route-dock)包装成 zustand
|
||||
* persist 的 StateStorage,保证迁移前后数据格式兼容。
|
||||
*/
|
||||
import { createJSONStorage, type StateStorage } from 'zustand/middleware';
|
||||
import type { AppPersistedState, DockTab } from '../app/appTypes';
|
||||
import type { PermissionPersistedState } from '../permission/permissionTypes';
|
||||
import type { UserInfo, UserPersistedState } from '../user/userTypes';
|
||||
|
||||
export const AUTH_STORAGE_NAME = 'gongxue-auth';
|
||||
export const LEGACY_TOKEN_KEY = 'token';
|
||||
export const LEGACY_USER_KEY = 'user';
|
||||
|
||||
export const PERMISSION_STORAGE_NAME = 'permissions';
|
||||
|
||||
export const APP_UI_STORAGE_NAME = 'gongxue-app-ui';
|
||||
export const LEGACY_DOCK_STORAGE_KEY = 'gongxue-route-dock';
|
||||
|
||||
export const SETTINGS_STORAGE_NAME = 'gongxue-settings';
|
||||
|
||||
function isString(value: unknown): value is string {
|
||||
return typeof value === 'string';
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null;
|
||||
}
|
||||
|
||||
function isDockTab(value: unknown): value is DockTab {
|
||||
return (
|
||||
isRecord(value) &&
|
||||
typeof value.key === 'string' &&
|
||||
value.key.startsWith('/') &&
|
||||
typeof value.label === 'string'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户会话持久化:继续使用旧的 `token` / `user` 两个 key,
|
||||
* 保持与后端、既有代码及浏览器缓存格式一致。
|
||||
*/
|
||||
const legacyAuthStorage: StateStorage = {
|
||||
getItem: () => {
|
||||
const token = localStorage.getItem(LEGACY_TOKEN_KEY);
|
||||
const rawUser = localStorage.getItem(LEGACY_USER_KEY);
|
||||
if (token === null && rawUser === null) return null;
|
||||
let user: UserInfo | null = null;
|
||||
if (rawUser !== null) {
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(rawUser);
|
||||
user = isRecord(parsed) ? (parsed as UserInfo) : null;
|
||||
} catch {
|
||||
user = null;
|
||||
}
|
||||
}
|
||||
return JSON.stringify({ state: { token, user }, version: 1 });
|
||||
},
|
||||
setItem: (_name, value) => {
|
||||
try {
|
||||
const persisted = JSON.parse(value) as { state?: UserPersistedState };
|
||||
const { token, user } = persisted.state ?? {};
|
||||
if (token) {
|
||||
localStorage.setItem(LEGACY_TOKEN_KEY, token);
|
||||
} else {
|
||||
localStorage.removeItem(LEGACY_TOKEN_KEY);
|
||||
}
|
||||
if (user) {
|
||||
localStorage.setItem(LEGACY_USER_KEY, JSON.stringify(user));
|
||||
} else {
|
||||
localStorage.removeItem(LEGACY_USER_KEY);
|
||||
}
|
||||
} catch {
|
||||
// 持久化写入失败不应影响应用运行
|
||||
}
|
||||
},
|
||||
removeItem: () => {
|
||||
localStorage.removeItem(LEGACY_TOKEN_KEY);
|
||||
localStorage.removeItem(LEGACY_USER_KEY);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 权限持久化:兼容旧格式(原始 JSON 数组)与 zustand persist 格式。
|
||||
* 无论磁盘上是什么状态,恢复后一律为 `unknown`,保持 fail-closed,
|
||||
* 直到 `/auth/profile` 校验成功。
|
||||
*/
|
||||
const legacyPermissionStorage: StateStorage = {
|
||||
getItem: () => {
|
||||
const raw = localStorage.getItem(PERMISSION_STORAGE_NAME);
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(raw);
|
||||
if (Array.isArray(parsed)) {
|
||||
return JSON.stringify({
|
||||
state: { permissions: parsed.filter(isString), status: 'unknown' },
|
||||
version: 1,
|
||||
});
|
||||
}
|
||||
if (isRecord(parsed) && isRecord(parsed.state)) {
|
||||
const permissions = Array.isArray(parsed.state.permissions)
|
||||
? parsed.state.permissions.filter(isString)
|
||||
: [];
|
||||
return JSON.stringify({
|
||||
state: { permissions, status: 'unknown' },
|
||||
version: 1,
|
||||
});
|
||||
}
|
||||
} catch {
|
||||
// 损坏的缓存按无权限处理
|
||||
}
|
||||
return null;
|
||||
},
|
||||
setItem: (_name, value) => {
|
||||
try {
|
||||
const persisted = JSON.parse(value) as { state?: PermissionPersistedState };
|
||||
const permissions = Array.isArray(persisted.state?.permissions)
|
||||
? persisted.state.permissions.filter(isString)
|
||||
: [];
|
||||
localStorage.setItem(PERMISSION_STORAGE_NAME, JSON.stringify(permissions));
|
||||
} catch {
|
||||
// 忽略损坏数据
|
||||
}
|
||||
},
|
||||
removeItem: () => {
|
||||
localStorage.removeItem(PERMISSION_STORAGE_NAME);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* 应用 UI 状态持久化:新 key `gongxue-app-ui`,
|
||||
* 首次读取时自动迁移旧 key `gongxue-route-dock` 中已打开的页签。
|
||||
*/
|
||||
const appUiStorage: StateStorage = {
|
||||
getItem: (name) => {
|
||||
const current = localStorage.getItem(name);
|
||||
if (current) return current;
|
||||
const legacy = localStorage.getItem(LEGACY_DOCK_STORAGE_KEY);
|
||||
if (!legacy) return null;
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(legacy);
|
||||
const routeDockTabs = Array.isArray(parsed) ? parsed.filter(isDockTab) : [];
|
||||
return JSON.stringify({ state: { routeDockTabs, sidebarCollapsed: false }, version: 1 });
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
setItem: (name, value) => {
|
||||
try {
|
||||
localStorage.setItem(name, value);
|
||||
} catch {
|
||||
// 持久化失败不应影响应用运行
|
||||
}
|
||||
},
|
||||
removeItem: (name) => {
|
||||
try {
|
||||
localStorage.removeItem(name);
|
||||
} catch {
|
||||
// 持久化失败不应影响应用运行
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/** 会话 Store 使用的 persist storage(兼容旧 token/user key) */
|
||||
export const authPersistStorage = createJSONStorage(() => legacyAuthStorage);
|
||||
|
||||
/** 权限 Store 使用的 persist storage(兼容旧 permissions key) */
|
||||
export const permissionPersistStorage = createJSONStorage(() => legacyPermissionStorage);
|
||||
|
||||
/** 应用 UI Store 使用的 persist storage(含旧 RouteDock key 迁移) */
|
||||
export const appUiPersistStorage = createJSONStorage(() => appUiStorage);
|
||||
|
||||
export type { AppPersistedState, PermissionPersistedState, UserPersistedState };
|
||||
Reference in New Issue
Block a user