Next.js中RBAC实现:高效数据库查询与缓存优化最佳实践
Next.js中RBAC的高效数据库查询最佳实践
一、缓存角色权限数据
1. 分布式缓存(生产环境推荐)
用Redis这类分布式缓存存储用户的角色与权限集合,以user_id为键,序列化后存储用户的角色列表和权限列表。每次API请求优先从缓存获取数据:
- 缓存命中:直接用缓存数据做权限校验
- 缓存未命中:执行数据库联合查询获取数据,写入缓存后再校验
示例代码(Next.js API路由中使用Redis):
import redis from '@/lib/redis'; export async function getUserPermissions(userId) { const cacheKey = `user:permissions:${userId}`; const cached = await redis.get(cacheKey); if (cached) { return JSON.parse(cached); } // 数据库联合查询 const result = await sql` SELECT ARRAY_AGG(DISTINCT r.role_name) AS roles, ARRAY_AGG(DISTINCT p.permission_name) AS permissions FROM Users u JOIN UserRoles ur ON u.user_id = ur.user_id JOIN Roles r ON ur.role_id = r.role_id JOIN RolePermissions rp ON r.role_id = rp.role_id JOIN Permissions p ON rp.permission_id = p.permission_id WHERE u.user_id = ${userId} GROUP BY u.user_id `; const permissionsData = result.rows[0] || { roles: [], permissions: [] }; await redis.set(cacheKey, JSON.stringify(permissionsData), 'EX', 3600); // 1小时过期 return permissionsData; }
2. 内存缓存(开发/测试场景适用)
用lru-cache这类内存缓存库在应用进程内缓存数据,适合流量较小的场景,避免分布式缓存的部署成本。注意单实例重启后缓存会清空,生产环境建议用分布式缓存。
二、基于令牌的授权优化
1. JWT嵌入权限数据
用户登录时,一次性查询其角色和权限,将这些数据嵌入JWT的Payload中。后续请求只需解析JWT就能获取权限,无需查询数据库:
- 注意:Payload不宜过大,只保留必要的角色和权限标识(如
role_name和permission_name的数组) - 缺点:权限更新后,已颁发的JWT不会自动失效,需配合刷新令牌机制解决
示例登录接口生成JWT:
import jwt from 'jsonwebtoken'; export async function loginUser(email, password) { // 验证用户密码逻辑... const user = await sql`SELECT user_id FROM Users WHERE email = ${email}`; const { roles, permissions } = await getUserPermissions(user.rows[0].user_id); const accessToken = jwt.sign( { sub: user.rows[0].user_id, roles, permissions }, process.env.JWT_SECRET, { expiresIn: '15m' } // 短有效期,减少权限不一致窗口 ); const refreshToken = jwt.sign( { sub: user.rows[0].user_id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' } ); // 存储refreshToken到数据库Sessions表... return { accessToken, refreshToken }; }
2. 刷新令牌机制
当用户权限更新时,强制用户下次使用刷新令牌获取新的AccessToken,新令牌会包含更新后的权限数据。同时在Sessions表中可标记旧令牌失效,确保权限变更及时生效。
三、缓存过期与失效策略
1. 主动失效(优先策略)
当用户的角色或权限被修改时(如后台管理界面更新用户角色),立即删除对应缓存:
- 修改UserRoles表后,删除该
user_id对应的缓存键 - 修改RolePermissions表后,删除所有关联该
role_id的用户缓存(可通过UserRoles表查询关联用户ID)
示例主动失效代码:
// 更新用户角色后删除缓存 export async function updateUserRoles(userId, roleIds) { // 执行数据库更新逻辑... const cacheKey = `user:permissions:${userId}`; await redis.del(cacheKey); }
2. 被动过期(兜底策略)
给缓存设置合理的过期时间(如15分钟到1小时),即使主动失效操作遗漏,缓存过期后会自动从数据库重新加载最新数据,避免长期的数据不一致。
3. 分层缓存优化
采用“内存缓存+分布式缓存”的分层策略:
- 内存缓存:存储高频访问的用户权限数据,过期时间设为5分钟,减少分布式缓存的请求量
- 分布式缓存:存储全量用户权限数据,过期时间设为1小时,保证多实例间的缓存一致性
四、数据库层查询优化
1. 联合查询替代多次单表查询
用一次JOIN查询获取用户的所有角色和权限,减少数据库交互次数,示例SQL:
SELECT ARRAY_AGG(DISTINCT r.role_name) AS roles, ARRAY_AGG(DISTINCT p.permission_name) AS permissions FROM Users u JOIN UserRoles ur ON u.user_id = ur.user_id JOIN Roles r ON ur.role_id = r.role_id JOIN RolePermissions rp ON r.role_id = rp.role_id JOIN Permissions p ON rp.permission_id = p.permission_id WHERE u.user_id = $1 GROUP BY u.user_id;
2. 索引优化
为关联表的外键字段添加索引,加速JOIN查询:
-- 为UserRoles添加索引 CREATE INDEX idx_userroles_userid ON UserRoles(user_id); CREATE INDEX idx_userroles_roleid ON UserRoles(role_id); -- 为RolePermissions添加索引 CREATE INDEX idx_rolepermissions_roleid ON RolePermissions(role_id); CREATE INDEX idx_rolepermissions_permissionid ON RolePermissions(permission_id);
内容的提问来源于stack exchange,提问作者hantoren
相关产品推荐
相关产品推荐

