feat: 添加存储调试页面并优化用户角色查询

This commit is contained in:
zhang1106
2026-04-07 14:15:44 +08:00
parent 1bda91b93e
commit 4f8a44f6b5
4 changed files with 124 additions and 118 deletions
+55 -67
View File
@@ -1,79 +1,66 @@
import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
import React, { createContext, useContext, useState, useEffect, useCallback, useMemo } from 'react';
import { authAPI } from '../api';
import secureStorage, { TOKEN_KEY, USER_KEY } from '../utils/secureStorage';
const AuthContext = createContext(null);
const AuthContext = createContext({
user: null,
token: null,
loading: true,
initialized: false,
login: async () => ({ success: false, message: '认证未初始化' }),
register: async () => ({ success: false, message: '认证未初始化' }),
logout: () => {},
updateUser: () => {},
hasPermission: () => false,
checkAdmin: () => Promise.resolve({ success: true, data: { hasAdmin: false, userCount: 0 } }),
});
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error('useAuth必须在AuthProvider内部使用');
}
return context;
return useContext(AuthContext);
};
export const AuthProvider = ({ children }) => {
console.log('[AuthContext] Initializing...');
const savedToken = secureStorage.get(TOKEN_KEY);
const savedUser = secureStorage.get(USER_KEY);
console.log('[AuthContext] Saved token:', savedToken ? 'exists' : 'null');
console.log('[AuthContext] Saved user:', savedUser ? 'exists' : 'null');
const [user, setUser] = useState(() => savedUser || null);
const [token, setToken] = useState(() => savedToken);
const [loading, setLoading] = useState(false);
const [user, setUser] = useState(savedUser);
const [token, setToken] = useState(savedToken);
const [loading, setLoading] = useState(true);
const [initialized, setInitialized] = useState(false);
useEffect(() => {
const currentToken = secureStorage.get(TOKEN_KEY);
const currentUser = secureStorage.get(USER_KEY);
if (currentToken && currentToken === token) {
if (currentUser && JSON.stringify(currentUser) !== JSON.stringify(user)) {
setUser(currentUser);
}
// 有 token 时调用 fetchProfile 验证有效性并刷新用户信息
fetchProfile();
} else if (!currentToken) {
if (!currentToken) {
setToken(null);
setUser(null);
setInitialized(true);
} else {
setToken(currentToken);
setInitialized(true);
}
}, []);
const fetchProfile = useCallback(async () => {
const currentToken = secureStorage.get(TOKEN_KEY);
if (!currentToken) {
setLoading(false);
setInitialized(true);
return;
}
try {
const response = await authAPI.getProfile();
if (response.success) {
setUser(response.data.user);
secureStorage.set(USER_KEY, response.data.user);
const initializeAuth = async () => {
try {
const response = await authAPI.getProfile();
if (response.success) {
setUser(response.data.user);
secureStorage.set(USER_KEY, response.data.user);
}
} catch {
secureStorage.remove(TOKEN_KEY);
secureStorage.remove(USER_KEY);
setToken(null);
setUser(null);
} finally {
setLoading(false);
setInitialized(true);
}
} catch (error) {
console.error('获取用户信息失败:', error);
// Token 无效,清除登录状态
secureStorage.remove(TOKEN_KEY);
secureStorage.remove(USER_KEY);
setToken(null);
setUser(null);
} finally {
setLoading(false);
setInitialized(true);
}
};
initializeAuth();
}, []);
const login = async (username, password) => {
const login = useCallback(async (username, password) => {
try {
const response = await authAPI.login({ username, password });
if (response.success) {
@@ -89,9 +76,9 @@ export const AuthProvider = ({ children }) => {
const message = error?.response?.data?.message || error?.message || '登录失败,请稍后重试';
return { success: false, message };
}
};
}, []);
const register = async userData => {
const register = useCallback(async userData => {
try {
const response = await authAPI.register(userData);
if (response.success) {
@@ -109,7 +96,7 @@ export const AuthProvider = ({ children }) => {
const message = error?.response?.data?.message || error?.message || '注册失败,请稍后重试';
return { success: false, message };
}
};
}, []);
const logout = useCallback(() => {
secureStorage.remove(TOKEN_KEY);
@@ -118,24 +105,25 @@ export const AuthProvider = ({ children }) => {
setUser(null);
}, []);
const updateUser = newUserData => {
const updatedUser = { ...user, ...newUserData };
setUser(updatedUser);
secureStorage.set(USER_KEY, updatedUser);
};
const updateUser = useCallback(newUserData => {
setUser(prev => {
const updated = { ...prev, ...newUserData };
secureStorage.set(USER_KEY, updated);
return updated;
});
}, []);
const hasPermission = permission => {
const hasPermission = useCallback(permission => {
if (!user) return false;
const roles = user.roles || [];
// 管理员拥有所有权限
if (roles.some(r => r.roleCode === 'admin')) return true;
// 检查具体权限
if (permission === 'admin') return roles.some(r => r.roleCode === 'admin');
// roleCode 格式的权限直接匹配
return roles.some(r => r.roleCode === permission);
};
}, [user]);
const value = {
const checkAdmin = useCallback(() => authAPI.checkAdmin(), []);
const value = useMemo(() => ({
user,
token,
loading,
@@ -145,10 +133,10 @@ export const AuthProvider = ({ children }) => {
logout,
updateUser,
hasPermission,
checkAdmin: () => authAPI.checkAdmin(),
};
checkAdmin,
}), [user, token, loading, initialized, login, register, logout, updateUser, hasPermission, checkAdmin]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
export default AuthContext;
export default AuthContext;
+28 -12
View File
@@ -1,5 +1,4 @@
const STORAGE_PREFIX = 'idc_';
const ENCRYPTION_ENABLED = import.meta.env.PROD;
/**
* 生成运行时密钥(基于浏览器指纹)
@@ -11,7 +10,6 @@ const getOrCreateRuntimeKey = () => {
let key = sessionStorage.getItem(KEY_STORAGE_ID);
if (key) return key;
// 基于浏览器特征生成指纹作为密钥种子
const fingerprint = [
navigator.userAgent,
screen.width,
@@ -21,14 +19,12 @@ const getOrCreateRuntimeKey = () => {
navigator.language,
].join('|');
// 使用简单的哈希混合生成 32 字符密钥
let hash = 0;
for (let i = 0; i < fingerprint.length; i++) {
const char = fingerprint.charCodeAt(i);
hash = ((hash << 5) - hash + char) | 0;
}
// 生成 32 字符的随机密钥
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
key = '';
let seed = Math.abs(hash) + Date.now();
@@ -38,12 +34,11 @@ const getOrCreateRuntimeKey = () => {
}
sessionStorage.setItem(KEY_STORAGE_ID, key);
console.log('[SecureStorage] New runtime key generated:', key.substring(0, 8) + '...');
return key;
};
const simpleEncrypt = (text) => {
if (!ENCRYPTION_ENABLED) return text;
const key = getOrCreateRuntimeKey();
let result = '';
for (let i = 0; i < text.length; i++) {
@@ -53,8 +48,6 @@ const simpleEncrypt = (text) => {
};
const simpleDecrypt = (encrypted) => {
if (!ENCRYPTION_ENABLED) return encrypted;
try {
const key = getOrCreateRuntimeKey();
const decoded = decodeURIComponent(atob(encrypted));
@@ -63,7 +56,8 @@ const simpleDecrypt = (encrypted) => {
result += String.fromCharCode(decoded.charCodeAt(i) ^ key.charCodeAt(i % key.length));
}
return result;
} catch {
} catch (e) {
console.error('[SecureStorage] Decrypt error:', e.message);
return null;
}
};
@@ -80,6 +74,8 @@ export const secureStorage = {
const serialized = JSON.stringify(data);
const encrypted = simpleEncrypt(serialized);
localStorage.setItem(storageKey, encrypted);
console.log('[SecureStorage] Set:', key, '=', value ? value.substring(0, 30) + '...' : 'null');
console.log('[SecureStorage] Stored encrypted value length:', encrypted.length);
return true;
} catch (error) {
console.error('[SecureStorage] Set error:', error);
@@ -92,10 +88,25 @@ export const secureStorage = {
const storageKey = `${STORAGE_PREFIX}${key}`;
const encrypted = localStorage.getItem(storageKey);
if (!encrypted) return null;
if (!encrypted) {
console.log('[SecureStorage] Get:', key, '- not found in localStorage');
return null;
}
const decrypted = simpleDecrypt(encrypted);
console.log('[SecureStorage] Get:', key, '- found encrypted data, length:', encrypted.length);
let decrypted = simpleDecrypt(encrypted);
if (!decrypted) {
console.log('[SecureStorage] Get:', key, '- decryption failed, trying raw JSON parse');
try {
const parsed = JSON.parse(encrypted);
if (parsed && parsed.value !== undefined) {
console.log('[SecureStorage] Get:', key, '- recovered from raw JSON');
return parsed.value;
}
} catch {
console.log('[SecureStorage] Get:', key, '- raw JSON parse also failed');
}
localStorage.removeItem(storageKey);
return null;
}
@@ -103,10 +114,13 @@ export const secureStorage = {
const data = JSON.parse(decrypted);
if (data.expiry && Date.now() > data.expiry) {
console.log('[SecureStorage] Get:', key, '- expired');
localStorage.removeItem(storageKey);
return null;
}
const valuePreview = typeof data.value === 'string' ? data.value.substring(0, 30) + '...' : typeof data.value;
console.log('[SecureStorage] Get:', key, '- success:', valuePreview);
return data.value;
} catch (error) {
console.error('[SecureStorage] Get error:', error);
@@ -118,6 +132,7 @@ export const secureStorage = {
try {
const storageKey = `${STORAGE_PREFIX}${key}`;
localStorage.removeItem(storageKey);
console.log('[SecureStorage] Remove:', key);
return true;
} catch (error) {
console.error('[SecureStorage] Remove error:', error);
@@ -135,6 +150,7 @@ export const secureStorage = {
}
}
keys.forEach((key) => localStorage.removeItem(key));
console.log('[SecureStorage] Clear: removed', keys.length, 'keys');
return true;
} catch (error) {
console.error('[SecureStorage] Clear error:', error);
@@ -148,4 +164,4 @@ export const USER_KEY = 'user';
export const USER_ID_KEY = 'userId';
export const TICKET_COLUMNS_KEY = 'ticketVisibleColumns';
export default secureStorage;
export default secureStorage;