This commit is contained in:
andyesfly committed 2026-10-02 16:11:04 +08:00
commit 7b6fe46d10
21 files changed
+3856 -110

No files matched your search

+94 -101
View File
@@ -1,124 +1,117 @@
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';
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',
'/api/jobs': 'system:job',
'/api/customers': 'customer:list',
'/api/customer-departments': 'customer:department',
'/api/customer-accounts': 'customer:account',
'/api/customer-visits': 'customer:visit',
'/api/upload': 'customer:visit',
'/api/customer-buildings': 'customer:building',
'/api/customer-rooms': 'customer:room',
'/api/survey-orders': 'project:survey',
'/api/project-plans': 'project:plan',
'/api/contracts': 'project:contract',
'/api/install-orders': 'project:install',
'/api/acceptances': 'project:acceptance',
'/api/visit-records': 'project:visit',
'/api/service-prices': 'dispatch:price',
'/api/dispatch-prices': 'dispatch:cost',
'/api/workers': 'dispatch:worker',
"/api/users": "system:user",
"/api/roles": "system:role",
"/api/menus": "system:menu",
"/api/dicts": "system:dict",
"/api/logs": "system:log",
"/api/jobs": "system:job",
"/api/customers": "customer:list",
"/api/customer-departments": "customer:department",
"/api/customer-accounts": "customer:account",
"/api/customer-visits": "customer:visit",
"/api/upload": "customer:visit",
"/api/customer-buildings": "customer:building",
"/api/customer-rooms": "customer:room",
"/api/survey-orders": "project:survey",
"/api/project-plans": "project:plan",
"/api/contracts": "project:contract",
"/api/install-orders": "project:install",
"/api/acceptances": "project:acceptance",
"/api/visit-records": "project:visit",
"/api/repair-requests": "dispatch:repair",
"/api/repair-dispatchs": "dispatch:repair",
"/api/maintenance-orders": "dispatch:maintenance",
"/api/service-prices": "dispatch:price",
"/api/dispatch-prices": "dispatch:cost",
"/api/workers": "dispatch:worker"
};
// 超级管理员角色编码,拥有全部权限
const ADMIN_ROLE_CODE = 'ADMIN';
const ADMIN_ROLE_CODE = "ADMIN";
@Provide()
export class RbacMiddleware implements IMiddleware<Context, NextFunction> {
// 中间件匹配范围:全局生效,跳过公开接口、仪表盘与电工 APP 接口
// /api/workers/options 与 /api/workers/recommend 放行:业务员在「项目勘察派单」页
// 仍可调用电工下拉与推荐接口,不受 dispatch:worker 权限限制(本次需求有意设计)
ignore = [
'/api/auth/*',
'/api/health',
'/api/dashboard/*',
'/api/worker-app/*',
'/api/workers/options',
'/api/workers/recommend',
];
// 中间件匹配范围:全局生效,跳过公开接口、仪表盘与电工 APP 接口
// /api/workers/options 与 /api/workers/recommend 放行:业务员在「项目勘察派单」页
// 仍可调用电工下拉与推荐接口,不受 dispatch:worker 权限限制(本次需求有意设计)
ignore = ["/api/auth/*", "/api/health", "/api/dashboard/*", "/api/worker-app/*", "/api/workers/options", "/api/workers/recommend"];
@Inject()
permissionService!: PermissionService;
@Inject()
permissionService!: PermissionService;
@InjectRepository(SysUserRole)
userRoleModel!: typeof SysUserRole;
@InjectRepository(SysUserRole)
userRoleModel!: typeof SysUserRole;
@InjectRepository(SysRole)
roleModel!: typeof SysRole;
@InjectRepository(SysRole)
roleModel!: typeof SysRole;
resolve() {
return async (ctx: Context, next: NextFunction) => {
const user = ctx.state.user;
resolve() {
return async (ctx: Context, next: NextFunction) => {
const user = ctx.state.user;
if (!user) {
ctx.status = 401;
ctx.body = {
code: 401,
message: '未登录',
};
return;
}
if (!user) {
ctx.status = 401;
ctx.body = {
code: 401,
message: "未登录"
};
return;
}
// 路径前缀匹配,未匹配到任何权限映射的请求直接放行
const permissionCode = this.matchPermissionCode(ctx.path);
if (!permissionCode) {
await next();
return;
}
// 路径前缀匹配,未匹配到任何权限映射的请求直接放行
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 roleCodes = await this.getUserRoleCodes(user.userId);
if (roleCodes.includes(ADMIN_ROLE_CODE)) {
await next();
return;
}
// 检查用户菜单中是否包含该权限标识
const hasPermission = await this.permissionService.checkUserPermission(
user.userId,
permissionCode
);
// 检查用户菜单中是否包含该权限标识
const hasPermission = await this.permissionService.checkUserPermission(user.userId, permissionCode);
if (!hasPermission) {
ctx.status = 403;
ctx.body = {
code: 403,
message: '没有访问权限',
};
return;
}
if (!hasPermission) {
ctx.status = 403;
ctx.body = {
code: 403,
message: "没有访问权限"
};
return;
}
await next();
};
}
await next();
};
}
private matchPermissionCode(path: string): string {
for (const [prefix, code] of Object.entries(PERMISSION_MAP)) {
if (path === prefix || path.startsWith(prefix + '/')) {
return code;
}
}
return '';
}
private matchPermissionCode(path: string): string {
for (const [prefix, code] of Object.entries(PERMISSION_MAP)) {
if (path === prefix || path.startsWith(prefix + "/")) {
return code;
}
}
return "";
}
private async getUserRoleCodes(userId: number): Promise<string[]> {
const userRoles = await this.userRoleModel.findAll({
where: { userId },
include: [this.roleModel],
});
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[];
}
return userRoles.map((ur) => ur.role?.roleCode).filter(Boolean) as string[];
}
}