前端权限缓存设计与实现踩坑总结
权限缓存的核心代码就这么几行
最近重构了一个老项目,权限管理这块之前写得乱七八糟,每次进入页面都重新请求权限数据,用户体验差得要命。这次直接搞了个权限缓存系统,效果立竿见影。
先上核心代码:
class PermissionCache {
constructor() {
this.cacheKey = 'user_permissions';
this.expireTime = 24 * 60 * 60 * 1000; // 24小时过期
}
// 获取权限数据
async getPermissions(forceRefresh = false) {
const cached = this.getCachedData();
if (cached && !this.isExpired(cached.timestamp) && !forceRefresh) {
return cached.data;
}
// 缓存失效或强制刷新时重新获取
const permissions = await this.fetchFromServer();
this.setCachedData(permissions);
return permissions;
}
getCachedData() {
try {
const cached = localStorage.getItem(this.cacheKey);
return cached ? JSON.parse(cached) : null;
} catch (error) {
console.error('读取权限缓存失败:', error);
return null;
}
}
setCachedData(data) {
const cacheData = {
data,
timestamp: Date.now()
};
try {
localStorage.setItem(this.cacheKey, JSON.stringify(cacheData));
} catch (error) {
console.error('保存权限缓存失败:', error);
}
}
isExpired(timestamp) {
return Date.now() - timestamp > this.expireTime;
}
clearCache() {
localStorage.removeItem(this.cacheKey);
}
async fetchFromServer() {
const response = await fetch('/api/user/permissions', {
headers: {
'Authorization': Bearer ${localStorage.getItem('token')}
}
});
if (!response.ok) {
throw new Error('获取权限失败');
}
return response.json();
}
}
这个类很简单,就是把权限数据缓存在 localStorage 里,设置个过期时间。关键在于时间控制和错误处理,这两个地方我踩过不少坑。
Vue项目中的具体应用
在 Vue 项目里,我是这么用的:
// permissionManager.js
import PermissionCache from './PermissionCache';
const permissionCache = new PermissionCache();
export default {
// 初始化权限
async initPermissions() {
try {
const permissions = await permissionCache.getPermissions();
this.permissions = permissions;
this.updateRouteAccess();
} catch (error) {
console.error('权限初始化失败:', error);
// 清除缓存并重试
permissionCache.clearCache();
}
},
// 检查是否有某项权限
hasPermission(permissionCode) {
if (!this.permissions) {
console.warn('权限数据未加载');
return false;
}
return this.permissions.includes(permissionCode);
},
// 更新路由访问权限
updateRouteAccess() {
// 根据权限动态更新路由
this.updateRoutesBasedOnPermissions();
}
};
// 在 main.js 中全局注册
import permissionManager from './utils/permissionManager';
// 路由守卫中使用
router.beforeEach(async (to, from, next) => {
if (to.meta.requiresAuth) {
await permissionManager.initPermissions();
if (to.meta.permission && !permissionManager.hasPermission(to.meta.permission)) {
next('/unauthorized'); // 没有权限跳转到无权页面
} else {
next();
}
} else {
next();
}
});
这样做的好处很明显:用户第一次登录后,后续页面切换都不需要重复请求权限数据,响应速度快了很多。
踩坑提醒:这三点一定注意
这里要重点说几个坑,都是我亲自踩过的:
- 缓存大小限制:localStorage 有容量限制,一般5MB左右。权限数据如果太复杂,建议只缓存核心的权限码数组,不要把整个用户信息都塞进去。
- 并发请求处理:多个组件同时请求权限数据时,会出现多次请求的问题。我加了个 pending 状态:
class PermissionCache {
constructor() {
this.cacheKey = 'user_permissions';
this.expireTime = 24 * 60 * 60 * 1000;
this.pendingPromise = null; // 用于防止并发请求
}
async getPermissions(forceRefresh = false) {
const cached = this.getCachedData();
if (cached && !this.isExpired(cached.timestamp) && !forceRefresh) {
return cached.data;
}
// 如果已经有请求在进行中,直接返回之前的 promise
if (this.pendingPromise && !forceRefresh) {
return this.pendingPromise;
}
// 创建新的请求 promise
this.pendingPromise = this.fetchFromServer()
.then(permissions => {
this.setCachedData(permissions);
this.pendingPromise = null;
return permissions;
})
.catch(error => {
this.pendingPromise = null;
throw error;
});
return this.pendingPromise;
}
}
- 权限变更同步:用户权限修改后,缓存要及时清理。我在权限修改接口后加上了缓存清理:
// 权限修改成功后清除缓存
async updateUserPermission(userId, permissions) {
const response = await fetch(/api/users/${userId}/permissions, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Authorization': Bearer ${localStorage.getItem('token')}
},
body: JSON.stringify({ permissions })
});
if (response.ok) {
// 清除权限缓存,下次进入时重新获取
permissionCache.clearCache();
}
return response;
}
React项目里的实现方式
React 项目中我用自定义 Hook 的方式实现:
// hooks/usePermissions.js
import { useState, useEffect } from 'react';
function usePermissions() {
const [permissions, setPermissions] = useState(null);
const [loading, setLoading] = useState(false);
const loadPermissions = async () => {
setLoading(true);
try {
const cached = getCachedPermissions();
if (cached && !isCacheExpired(cached.timestamp)) {
setPermissions(cached.data);
} else {
const data = await fetchPermissions();
setCachedPermissions(data);
setPermissions(data);
}
} catch (error) {
console.error('加载权限失败:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
loadPermissions();
}, []);
const hasPermission = (permissionCode) => {
return permissions?.includes(permissionCode) || false;
};
return { permissions, loading, hasPermission, refresh: loadPermissions };
}
// 在组件中使用
function ProtectedComponent() {
const { hasPermission, loading } = usePermissions();
if (loading) {
return <div>Loading...</div>;
}
if (!hasPermission('view_dashboard')) {
return <div>无访问权限</div>;
}
return <div>Dashboard Content</div>;
}
React 的方式更符合其数据流的设计理念,状态管理和副作用处理都比较清晰。
高级玩法:内存+本地双重缓存
有些项目对性能要求更高,我会做双重缓存:内存 + 本地存储。内存缓存优先级最高,避免重复计算:
class AdvancedPermissionCache {
constructor() {
this.memoryCache = new Map(); // 内存缓存
this.localStorageKey = 'user_permissions';
this.cacheVersion = 'v1.0'; // 缓存版本,方便清空旧缓存
}
async getPermissions() {
// 1. 先检查内存缓存
if (this.memoryCache.has('permissions')) {
return this.memoryCache.get('permissions');
}
// 2. 检查本地缓存
const localCache = this.getLocalCache();
if (localCache) {
// 设置内存缓存
this.memoryCache.set('permissions', localCache.data);
return localCache.data;
}
// 3. 请求服务器数据
const serverData = await this.fetchFromServer();
this.setLocalCache(serverData);
this.memoryCache.set('permissions', serverData);
return serverData;
}
getLocalCache() {
try {
const cached = localStorage.getItem(this.localStorageKey);
if (!cached) return null;
const parsed = JSON.parse(cached);
if (parsed.version !== this.cacheVersion) {
// 版本不匹配,清除缓存
this.clearLocalCache();
return null;
}
return parsed;
} catch (error) {
console.error('读取本地缓存失败:', error);
return null;
}
}
setLocalCache(data) {
const cacheData = {
version: this.cacheVersion,
data,
timestamp: Date.now()
};
try {
localStorage.setItem(this.localStorageKey, JSON.stringify(cacheData));
} catch (error) {
console.error('保存本地缓存失败:', error);
}
}
clearLocalCache() {
localStorage.removeItem(this.localStorageKey);
}
// 页面卸载时清理内存缓存
static cleanup() {
// 这里可以做一些清理工作
}
}
这种双重缓存的方式,对于频繁访问权限数据的场景特别有效,内存级别的缓存查询几乎无延迟。
缓存策略的选择很重要
缓存策略选择直接影响用户体验和系统性能。我一般根据业务特点来决定:
实时性要求高的场景:比如金融系统,权限变更需要立即生效,缓存时间设置短一些(5-10分钟)或者提供手动刷新机制。
一般业务系统:缓存24小时基本够用,用户一天内再次访问不需要重新获取权限。
离线应用场景:需要考虑网络不稳定的情况,缓存时间可以适当延长,并配合降级策略。
还有个细节需要注意:不同角色的用户权限缓存应该区分,不能混用。我的做法是在缓存key中加入用户ID:
// 用户隔离的缓存key
getUserCacheKey(userId) {
return user_permissions_${userId};
}
这个技巧的拓展用法还有很多,后续会继续分享这类博客。以上是我踩坑后的总结,希望对你有帮助。
本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。

暂无评论