refactor: 系统权限模块重构,迁移至PostgreSQL并对齐新设计
- 切换数据库为PostgreSQL,更新依赖与配置 - 重构权限体系,使用sys_user/sys_role/sys_menu等新实体替代旧模型 - 移除旧的用户角色/权限/系统设置相关代码与实体 - 新增字典、菜单管理模块与基础CRUD接口 - 优化鉴权逻辑,基于菜单权限标识进行接口校验 - 前端同步更新API、路由与权限控制逻辑 - 删除冗余的v-permission指令与相关代码
This commit is contained in:
1 parent
98c22f6afb
commit
0a8103f33d
67 files changed
+3263
-1924
No files matched your search
@@ -5,6 +5,9 @@ import { TokenUtil } from '../util/token';
|
||||
|
||||
@Provide()
|
||||
export class AuthMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
// 中间件匹配范围:全局生效,但跳过公开接口
|
||||
ignore = ['/api/auth/*', '/api/health'];
|
||||
|
||||
@Inject()
|
||||
tokenUtil!: TokenUtil;
|
||||
|
||||
|
||||
@@ -2,56 +2,41 @@ import { Middleware, IMiddleware } from '@midwayjs/core';
|
||||
import { Context, NextFunction } from '@midwayjs/koa';
|
||||
import { Provide, Inject } from '@midwayjs/core';
|
||||
import { InjectRepository } from '@midwayjs/sequelize';
|
||||
import OperationLog from '../entity/operationLog.entity';
|
||||
import SysOperationLog from '../entity/sysOperationLog.entity';
|
||||
|
||||
@Provide()
|
||||
export class LogMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
// 中间件匹配范围:全局生效,跳过日志查询、健康检查与登录(登录由内部记录)
|
||||
ignore = ['/api/logs/*', '/api/health', '/api/auth/login'];
|
||||
|
||||
@InjectRepository(OperationLog)
|
||||
logModel!: typeof OperationLog;
|
||||
@InjectRepository(SysOperationLog)
|
||||
logModel!: typeof SysOperationLog;
|
||||
|
||||
resolve() {
|
||||
return async (ctx: Context, next: NextFunction) => {
|
||||
const startTime = Date.now();
|
||||
|
||||
let logData: any = {};
|
||||
await next();
|
||||
|
||||
try {
|
||||
logData = {
|
||||
method: ctx.method,
|
||||
url: ctx.url,
|
||||
ipAddress: ctx.ip,
|
||||
userAgent: ctx.header['user-agent'],
|
||||
requestParams: JSON.stringify(ctx.request.body || {}),
|
||||
};
|
||||
|
||||
if (ctx.state.user) {
|
||||
logData.userId = ctx.state.user.userId;
|
||||
logData.username = ctx.state.user.username;
|
||||
}
|
||||
|
||||
await next();
|
||||
|
||||
logData.statusCode = ctx.status;
|
||||
logData.duration = Date.now() - startTime;
|
||||
|
||||
if (['POST', 'PUT', 'DELETE'].includes(ctx.method)) {
|
||||
logData.action = this.getActionDescription(ctx);
|
||||
logData.module = this.getModuleFromUrl(ctx.url);
|
||||
|
||||
this.saveLog(logData).catch(err => {
|
||||
console.error('保存操作日志失败:', err);
|
||||
});
|
||||
}
|
||||
} catch (error: any) {
|
||||
logData.statusCode = 500;
|
||||
logData.duration = Date.now() - startTime;
|
||||
logData.errorMessage = error.message;
|
||||
|
||||
this.saveLog(logData).catch(() => {});
|
||||
|
||||
throw error;
|
||||
// 仅记录写操作,且要求登录用户存在
|
||||
if (!['POST', 'PUT', 'DELETE'].includes(ctx.method)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.state.user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const logData = {
|
||||
userId: ctx.state.user.userId,
|
||||
module: this.getModuleFromUrl(ctx.url),
|
||||
action: this.getActionDescription(ctx),
|
||||
method: ctx.method,
|
||||
params: JSON.stringify(ctx.request.body || {}),
|
||||
ip: ctx.ip,
|
||||
};
|
||||
|
||||
this.saveLog(logData).catch(err => {
|
||||
console.error('保存操作日志失败:', err);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,23 +44,30 @@ export class LogMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
const actionMap: Record<string, Record<string, string>> = {
|
||||
'/api/auth/login': { POST: '用户登录' },
|
||||
'/api/auth/logout': { POST: '用户登出' },
|
||||
'/api/user': {
|
||||
'/api/users': {
|
||||
POST: '创建用户',
|
||||
PUT: '更新用户',
|
||||
DELETE: '删除用户',
|
||||
},
|
||||
'/api/role': {
|
||||
'/api/roles': {
|
||||
POST: '创建角色',
|
||||
PUT: '更新角色',
|
||||
DELETE: '删除角色',
|
||||
},
|
||||
'/api/settings': {
|
||||
PUT: '修改系统设置',
|
||||
'/api/menus': {
|
||||
POST: '创建菜单',
|
||||
PUT: '更新菜单',
|
||||
DELETE: '删除菜单',
|
||||
},
|
||||
'/api/dicts': {
|
||||
POST: '创建字典',
|
||||
PUT: '更新字典',
|
||||
DELETE: '删除字典',
|
||||
},
|
||||
};
|
||||
|
||||
const basePath = ctx.url.split('?')[0];
|
||||
|
||||
|
||||
if (actionMap[basePath]?.[ctx.method]) {
|
||||
return actionMap[basePath][ctx.method];
|
||||
}
|
||||
@@ -92,10 +84,10 @@ export class LogMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
private getModuleFromUrl(url: string): string {
|
||||
const moduleMap: Record<string, string> = {
|
||||
'/api/auth': '认证',
|
||||
'/api/user': '用户管理',
|
||||
'/api/role': '角色管理',
|
||||
'/api/settings': '系统设置',
|
||||
'/api/log': '日志管理',
|
||||
'/api/users': '用户管理',
|
||||
'/api/roles': '角色管理',
|
||||
'/api/menus': '菜单管理',
|
||||
'/api/dicts': '数据字典',
|
||||
'/api/logs': '日志管理',
|
||||
'/api/dashboard': '仪表盘',
|
||||
};
|
||||
|
||||
@@ -1,14 +1,37 @@
|
||||
import { Middleware, IMiddleware } from '@midwayjs/core';
|
||||
import { Context, NextFunction } from '@midwayjs/koa';
|
||||
import { Provide, Inject } from '@midwayjs/core';
|
||||
import { InjectRepository } from '@midwayjs/sequelize';
|
||||
import { PermissionService } from '../service/permission.service';
|
||||
import SysUserRole from '../entity/sysUserRole.entity';
|
||||
import SysRole from '../entity/sysRole.entity';
|
||||
|
||||
// 请求路径前缀 → 菜单权限标识映射
|
||||
const PERMISSION_MAP: Record<string, string> = {
|
||||
'/api/users': 'system:user',
|
||||
'/api/roles': 'system:role',
|
||||
'/api/menus': 'system:menu',
|
||||
'/api/dicts': 'system:dict',
|
||||
'/api/logs': 'system:log',
|
||||
};
|
||||
|
||||
// 超级管理员角色编码,拥有全部权限
|
||||
const ADMIN_ROLE_CODE = 'ADMIN';
|
||||
|
||||
@Provide()
|
||||
export class RbacMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
// 中间件匹配范围:全局生效,跳过公开接口与仪表盘
|
||||
ignore = ['/api/auth/*', '/api/health', '/api/dashboard/*'];
|
||||
|
||||
@Inject()
|
||||
permissionService!: PermissionService;
|
||||
|
||||
@InjectRepository(SysUserRole)
|
||||
userRoleModel!: typeof SysUserRole;
|
||||
|
||||
@InjectRepository(SysRole)
|
||||
roleModel!: typeof SysRole;
|
||||
|
||||
resolve() {
|
||||
return async (ctx: Context, next: NextFunction) => {
|
||||
const user = ctx.state.user;
|
||||
@@ -22,17 +45,21 @@ export class RbacMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前请求路径和方法,转换为权限码
|
||||
// 例如:GET /api/user/list -> user:list
|
||||
const permissionCode = this.extractPermissionCode(ctx);
|
||||
|
||||
// 超级管理员拥有所有权限
|
||||
if (user.roles && user.roles.includes('super_admin')) {
|
||||
// 路径前缀匹配,未匹配到任何权限映射的请求直接放行
|
||||
const permissionCode = this.matchPermissionCode(ctx.path);
|
||||
if (!permissionCode) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户是否拥有该权限
|
||||
// 查询用户角色编码,超级管理员直接放行
|
||||
const roleCodes = await this.getUserRoleCodes(user.userId);
|
||||
if (roleCodes.includes(ADMIN_ROLE_CODE)) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查用户菜单中是否包含该权限标识
|
||||
const hasPermission = await this.permissionService.checkUserPermission(
|
||||
user.userId,
|
||||
permissionCode
|
||||
@@ -51,24 +78,21 @@ export class RbacMiddleware implements IMiddleware<Context, NextFunction> {
|
||||
};
|
||||
}
|
||||
|
||||
private extractPermissionCode(ctx: Context): string {
|
||||
const url = ctx.path.replace('/api/', '').replace(/^\//, '');
|
||||
const method = ctx.method.toLowerCase();
|
||||
|
||||
const methodMap: Record<string, string> = {
|
||||
post: 'create',
|
||||
put: 'update',
|
||||
delete: 'delete',
|
||||
get: 'list',
|
||||
};
|
||||
|
||||
const action = methodMap[method] || method;
|
||||
|
||||
const parts = url.split('/');
|
||||
if (parts.length >= 2) {
|
||||
return `${parts[0]}:${action}`;
|
||||
private matchPermissionCode(path: string): string {
|
||||
for (const [prefix, code] of Object.entries(PERMISSION_MAP)) {
|
||||
if (path === prefix || path.startsWith(prefix + '/')) {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return `${url}:${action}`;
|
||||
private async getUserRoleCodes(userId: number): Promise<string[]> {
|
||||
const userRoles = await this.userRoleModel.findAll({
|
||||
where: { userId },
|
||||
include: [this.roleModel],
|
||||
});
|
||||
|
||||
return userRoles.map(ur => ur.role?.roleCode).filter(Boolean) as string[];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user