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
@@ -11,12 +11,12 @@ export default {
|
||||
sequelize: {
|
||||
dataSource: {
|
||||
default: {
|
||||
dialect: "mysql",
|
||||
host: process.env.MYSQL_HOST || "localhost",
|
||||
port: Number(process.env.MYSQL_PORT) || 3306,
|
||||
database: process.env.MYSQL_DATABASE || "smart_grid_iot",
|
||||
username: process.env.MYSQL_USERNAME || "root",
|
||||
password: process.env.MYSQL_PASSWORD || "123",
|
||||
dialect: "postgres",
|
||||
host: process.env.PG_HOST || "localhost",
|
||||
port: Number(process.env.PG_PORT) || 5432,
|
||||
database: process.env.PG_DATABASE || "xintong",
|
||||
username: process.env.PG_USERNAME || "postgres",
|
||||
password: process.env.PG_PASSWORD || "postgres",
|
||||
timezone: "+08:00",
|
||||
models: [join(__dirname, "../entity")],
|
||||
define: {
|
||||
@@ -62,22 +62,5 @@ export default {
|
||||
csrf: {
|
||||
enable: false
|
||||
}
|
||||
},
|
||||
|
||||
middleware: ["authMiddleware", "rbacMiddleware", "logMiddleware"],
|
||||
|
||||
middlewareConfig: {
|
||||
authMiddleware: {
|
||||
match: "/api/**",
|
||||
ignore: ["/api/auth/**", "/api/health"]
|
||||
},
|
||||
rbacMiddleware: {
|
||||
match: "/api/**",
|
||||
ignore: ["/api/auth/**", "/api/health", "/api/dashboard/**"]
|
||||
},
|
||||
logMiddleware: {
|
||||
match: "/api/**",
|
||||
ignore: ["/api/logs/**", "/api/health", "/api/auth/login"]
|
||||
}
|
||||
}
|
||||
} as MidwayConfig;
|
||||
@@ -1,5 +1,5 @@
|
||||
export default {
|
||||
sequelize: {
|
||||
sync: true,
|
||||
sync: false,
|
||||
},
|
||||
};
|
||||
@@ -1,15 +1,23 @@
|
||||
import { Configuration, ILifeCycle } from "@midwayjs/core";
|
||||
import { Configuration, App, ILifeCycle } from "@midwayjs/core";
|
||||
import * as Koa from "@midwayjs/koa";
|
||||
import * as Sequelize from "@midwayjs/sequelize";
|
||||
import * as CacheManager from "@midwayjs/cache-manager";
|
||||
import { join } from "path";
|
||||
import { AuthMiddleware } from "./middleware/auth.middleware";
|
||||
import { RbacMiddleware } from "./middleware/rbac.middleware";
|
||||
import { LogMiddleware } from "./middleware/log.middleware";
|
||||
|
||||
@Configuration({
|
||||
imports: [Koa, Sequelize, CacheManager],
|
||||
importConfigs: [join(__dirname, "./config")]
|
||||
})
|
||||
export class ContainerConfigLifeCycle implements ILifeCycle {
|
||||
@App()
|
||||
app!: Koa.Application;
|
||||
|
||||
async onReady() {
|
||||
// 注册全局中间件(顺序:鉴权 → 权限 → 日志)
|
||||
this.app.useMiddleware([AuthMiddleware, RbacMiddleware, LogMiddleware]);
|
||||
console.log("智能物联网设备后台管理系统启动成功");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import { AuthService } from "../service/auth.service";
|
||||
import { TokenUtil } from "../util/token";
|
||||
import { PermissionService } from "../service/permission.service";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import User from "../entity/user.entity";
|
||||
import UserRole from "../entity/userRole.entity";
|
||||
import Role from "../entity/role.entity";
|
||||
import SysUser from "../entity/sysUser.entity";
|
||||
import SysUserRole from "../entity/sysUserRole.entity";
|
||||
import SysRole from "../entity/sysRole.entity";
|
||||
|
||||
@Controller("/api/auth")
|
||||
export class AuthController {
|
||||
@@ -22,14 +22,14 @@ export class AuthController {
|
||||
@Inject()
|
||||
ctx!: Context;
|
||||
|
||||
@InjectRepository(User)
|
||||
userModel!: typeof User;
|
||||
@InjectRepository(SysUser)
|
||||
userModel!: typeof SysUser;
|
||||
|
||||
@InjectRepository(UserRole)
|
||||
userRoleModel!: typeof UserRole;
|
||||
@InjectRepository(SysUserRole)
|
||||
userRoleModel!: typeof SysUserRole;
|
||||
|
||||
@InjectRepository(Role)
|
||||
roleModel!: typeof Role;
|
||||
@InjectRepository(SysRole)
|
||||
roleModel!: typeof SysRole;
|
||||
|
||||
@Post("/login")
|
||||
async login(@Body() body: { username: string; password: string }) {
|
||||
@@ -110,14 +110,7 @@ export class AuthController {
|
||||
const decoded = this.tokenUtil.verifyToken(token);
|
||||
const userId = decoded.userId;
|
||||
|
||||
const user = await this.userModel.findByPk(userId, {
|
||||
include: [
|
||||
{
|
||||
model: this.userRoleModel,
|
||||
include: [this.roleModel]
|
||||
}
|
||||
]
|
||||
});
|
||||
const user = await this.userModel.findByPk(userId);
|
||||
if (!user) {
|
||||
return {
|
||||
code: 404,
|
||||
@@ -125,8 +118,13 @@ export class AuthController {
|
||||
};
|
||||
}
|
||||
|
||||
const permissions = await this.permissionService.getUserPermissions(userId);
|
||||
const roles = (user as any).userRoles?.map((ur: any) => ur.role?.code).filter(Boolean) || [];
|
||||
const userRoles = await this.userRoleModel.findAll({
|
||||
where: { userId },
|
||||
include: [this.roleModel]
|
||||
});
|
||||
const roles = userRoles.map(ur => ur.role?.roleCode).filter(Boolean) || [];
|
||||
|
||||
const menus = await this.permissionService.getUserMenus(userId);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
@@ -134,10 +132,9 @@ export class AuthController {
|
||||
data: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
nickname: user.nickname || user.username,
|
||||
avatar: user.avatar,
|
||||
realName: user.realName,
|
||||
roles,
|
||||
permissions
|
||||
menus
|
||||
}
|
||||
};
|
||||
} catch (error: any) {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import { Controller, Get, Post, Put, Del, Inject, Query, Body, Param } from "@midwayjs/core";
|
||||
import { Context } from "@midwayjs/koa";
|
||||
import { DictService } from "../service/dict.service";
|
||||
|
||||
@Controller("/api/dicts")
|
||||
export class DictController {
|
||||
@Inject()
|
||||
ctx!: Context;
|
||||
|
||||
@Inject()
|
||||
dictService!: DictService;
|
||||
|
||||
@Get("/")
|
||||
async list(@Query() query: { page?: number; pageSize?: number; dictType?: string; keyword?: string; status?: number }) {
|
||||
const result = await this.dictService.list(query);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get("/types")
|
||||
async types() {
|
||||
const result = await this.dictService.types();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post("/")
|
||||
async create(@Body() body: any) {
|
||||
const result = await this.dictService.create(body);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put("/:id")
|
||||
async update(@Param("id") id: string, @Body() body: any) {
|
||||
const result = await this.dictService.update(id, body);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Del("/:id")
|
||||
async remove(@Param("id") id: string) {
|
||||
const result = await this.dictService.remove(id);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Controller, Get, Inject, Query, Param, All } from "@midwayjs/core";
|
||||
import { Controller, Get, Del, Inject, Query, Param, All } from "@midwayjs/core";
|
||||
import { Context } from "@midwayjs/koa";
|
||||
import { LogService } from "../service/log.service";
|
||||
|
||||
const Delete = (path?: string) => {
|
||||
return (target: any, key: string, descriptor: PropertyDescriptor) => {
|
||||
return descriptor;
|
||||
};
|
||||
};
|
||||
|
||||
@Controller("/api/logs")
|
||||
export class LogController {
|
||||
@Inject()
|
||||
@@ -17,7 +11,7 @@ export class LogController {
|
||||
logService!: LogService;
|
||||
|
||||
@Get("/")
|
||||
async list(@Query() query: { page?: number; pageSize?: number; moduleName?: string; actionType?: string; startDate?: string; endDate?: string; operator?: string }) {
|
||||
async list(@Query() query: { page?: number; pageSize?: number; userId?: string; module?: string; action?: string; startDate?: string; endDate?: string }) {
|
||||
const result = await this.logService.list(query);
|
||||
return result;
|
||||
}
|
||||
@@ -46,7 +40,7 @@ export class LogController {
|
||||
}
|
||||
}
|
||||
|
||||
@Delete("/clear")
|
||||
@Del("/clear")
|
||||
async clear() {
|
||||
const result = await this.logService.cleanExpiredLogs(0);
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Controller, Get, Post, Put, Del, Inject, Body, Param } from "@midwayjs/core";
|
||||
import { Context } from "@midwayjs/koa";
|
||||
import { MenuService } from "../service/menu.service";
|
||||
|
||||
@Controller("/api/menus")
|
||||
export class MenuController {
|
||||
@Inject()
|
||||
ctx!: Context;
|
||||
|
||||
@Inject()
|
||||
menuService!: MenuService;
|
||||
|
||||
@Get("/tree")
|
||||
async getMenuTree() {
|
||||
const result = await this.menuService.getMenuTree();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post("/")
|
||||
async create(@Body() body: any) {
|
||||
const result = await this.menuService.create(body);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put("/:id")
|
||||
async update(@Param("id") id: string, @Body() body: any) {
|
||||
const result = await this.menuService.update(id, body);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Del("/:id")
|
||||
async remove(@Param("id") id: string) {
|
||||
const result = await this.menuService.remove(id);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Controller, Get, Post, Put, Inject, Query, Body, Param } from "@midwayjs/core";
|
||||
import { Controller, Get, Post, Put, Del, Inject, Query, Body, Param } from "@midwayjs/core";
|
||||
import { Context } from "@midwayjs/koa";
|
||||
import { RoleService } from "../service/role.service";
|
||||
|
||||
const Delete = (path?: string) => {
|
||||
return (target: any, key: string, descriptor: PropertyDescriptor) => {
|
||||
return descriptor;
|
||||
};
|
||||
};
|
||||
|
||||
@Controller("/api/roles")
|
||||
export class RoleController {
|
||||
@Inject()
|
||||
@@ -46,21 +40,21 @@ export class RoleController {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Delete("/:id")
|
||||
@Del("/:id")
|
||||
async delete(@Param("id") id: string) {
|
||||
const result = await this.roleService.delete(id);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Get("/:id/permissions")
|
||||
async getPermissions(@Param("id") id: string) {
|
||||
const result = await this.roleService.getPermissions(Number(id));
|
||||
@Get("/:id/menus")
|
||||
async getMenus(@Param("id") id: string) {
|
||||
const result = await this.roleService.getMenus(Number(id));
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put("/:id/permissions")
|
||||
async updatePermissions(@Param("id") id: string, @Body() body: { permissionIds: number[] }) {
|
||||
const result = await this.roleService.updatePermissions(Number(id), body.permissionIds);
|
||||
@Put("/:id/menus")
|
||||
async updateMenus(@Param("id") id: string, @Body() body: { menuIds: number[] }) {
|
||||
const result = await this.roleService.updateMenus(Number(id), body.menuIds);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Controller, Get, Put, Inject, Body } from "@midwayjs/core";
|
||||
import { SystemService } from "../service/system.service";
|
||||
|
||||
@Controller("/api/settings")
|
||||
export class SystemController {
|
||||
@Inject()
|
||||
systemService!: SystemService;
|
||||
|
||||
@Get("/")
|
||||
async getSettings() {
|
||||
const result = await this.systemService.getSettings();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put("/basic")
|
||||
async updateBasicSettings(@Body() body: any) {
|
||||
const result = await this.systemService.updateBasicSettings(body);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put("/security")
|
||||
async updateSecuritySettings(@Body() body: any) {
|
||||
const result = await this.systemService.updateSecuritySettings(body);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Put("/notification")
|
||||
async updateNotificationSettings(@Body() body: any) {
|
||||
const result = await this.systemService.updateNotificationSettings(body);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,7 @@
|
||||
import { Controller, Get, Post, Put, Inject, Query, Body, Param } from "@midwayjs/core";
|
||||
import { Controller, Get, Post, Put, Del, Inject, Query, Body, Param } from "@midwayjs/core";
|
||||
import { Context } from "@midwayjs/koa";
|
||||
import { UserService } from "../service/user.service";
|
||||
|
||||
const Delete = (path?: string) => {
|
||||
return (target: any, key: string, descriptor: PropertyDescriptor) => {
|
||||
return descriptor;
|
||||
};
|
||||
};
|
||||
|
||||
@Controller("/api/users")
|
||||
export class UserController {
|
||||
@Inject()
|
||||
@@ -46,15 +40,17 @@ export class UserController {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Delete("/:id")
|
||||
@Del("/:id")
|
||||
async delete(@Param("id") id: string) {
|
||||
const result = await this.userService.delete(id);
|
||||
const currentUserId = this.ctx.state.user?.userId;
|
||||
const result = await this.userService.delete(id, currentUserId);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Post("/batch-delete")
|
||||
async batchDelete(@Body() body: { ids: number[] }) {
|
||||
const result = await this.userService.batchDelete(body.ids);
|
||||
const currentUserId = this.ctx.state.user?.userId;
|
||||
const result = await this.userService.batchDelete(body.ids, currentUserId);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { IsNotEmpty, IsString, IsOptional, IsInt, IsIn } from "class-validator";
|
||||
|
||||
export class CreateDictDTO {
|
||||
@IsNotEmpty({ message: "字典类型不能为空" })
|
||||
@IsString()
|
||||
dictType!: string;
|
||||
|
||||
@IsNotEmpty({ message: "字典标签不能为空" })
|
||||
@IsString()
|
||||
dictLabel!: string;
|
||||
|
||||
@IsNotEmpty({ message: "字典键值不能为空" })
|
||||
@IsString()
|
||||
dictValue!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sortOrder?: number = 0;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn([0, 1], { message: "状态只能为 0 或 1" })
|
||||
status?: number = 1;
|
||||
}
|
||||
|
||||
export class UpdateDictDTO {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
dictType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
dictLabel?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
dictValue?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sortOrder?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn([0, 1], { message: "状态只能为 0 或 1" })
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export class QueryDictDTO {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
page?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
pageSize?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
dictType?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
keyword?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn([0, 1])
|
||||
status?: number;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { IsNotEmpty, IsString, IsOptional, IsInt, IsIn } from "class-validator";
|
||||
|
||||
export class CreateMenuDTO {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
parentId?: number;
|
||||
|
||||
@IsNotEmpty({ message: "菜单名称不能为空" })
|
||||
@IsString()
|
||||
menuName!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
menuPath?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
component?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
icon?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sortOrder?: number = 0;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn([1, 2])
|
||||
menuType?: number = 2;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
permission?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn([0, 1])
|
||||
visible?: number = 1;
|
||||
}
|
||||
|
||||
export class UpdateMenuDTO {
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
parentId?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
menuName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
menuPath?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
component?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
icon?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sortOrder?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn([1, 2])
|
||||
menuType?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
permission?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsIn([0, 1])
|
||||
visible?: number;
|
||||
}
|
||||
+12
-20
@@ -1,24 +1,18 @@
|
||||
import { IsNotEmpty, IsString, IsOptional, IsInt, Min, Max, IsArray } from "class-validator";
|
||||
import { IsNotEmpty, IsString, IsOptional, IsInt, IsArray } from "class-validator";
|
||||
|
||||
export class CreateRoleDTO {
|
||||
@IsNotEmpty({ message: "角色标识不能为空" })
|
||||
@IsNotEmpty({ message: "角色编码不能为空" })
|
||||
@IsString()
|
||||
code!: string;
|
||||
roleCode!: string;
|
||||
|
||||
@IsNotEmpty({ message: "角色名称不能为空" })
|
||||
@IsString()
|
||||
name!: string;
|
||||
roleName!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
status?: number = 1;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sortOrder?: number = 0;
|
||||
@@ -27,25 +21,23 @@ export class CreateRoleDTO {
|
||||
export class UpdateRoleDTO {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
name?: string;
|
||||
roleCode?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
roleName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
status?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
export class UpdatePermissionDTO {
|
||||
@IsNotEmpty({ message: "权限ID列表不能为空" })
|
||||
export class UpdateMenusDTO {
|
||||
@IsNotEmpty({ message: "菜单ID列表不能为空" })
|
||||
@IsArray()
|
||||
permissionIds!: number[];
|
||||
menuIds!: number[];
|
||||
}
|
||||
@@ -13,10 +13,11 @@ export class CreateUserDTO {
|
||||
@MaxLength(20, { message: "密码长度不能超过20位" })
|
||||
password!: string;
|
||||
|
||||
@IsNotEmpty({ message: "昵称不能为空" })
|
||||
@IsNotEmpty({ message: "真实姓名不能为空" })
|
||||
@IsString()
|
||||
@MinLength(2, { message: "昵称长度不能小于2位" })
|
||||
nickname!: string;
|
||||
@MinLength(2, { message: "真实姓名长度不能小于2位" })
|
||||
@MaxLength(64, { message: "真实姓名长度不能超过64位" })
|
||||
realName!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEmail({}, { message: "邮箱格式不正确" })
|
||||
@@ -39,8 +40,9 @@ export class CreateUserDTO {
|
||||
export class UpdateUserDTO {
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MinLength(2, { message: "昵称长度不能小于2位" })
|
||||
nickname?: string;
|
||||
@MinLength(2, { message: "真实姓名长度不能小于2位" })
|
||||
@MaxLength(64, { message: "真实姓名长度不能超过64位" })
|
||||
realName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsEmail({}, { message: "邮箱格式不正确" })
|
||||
@@ -50,10 +52,6 @@ export class UpdateUserDTO {
|
||||
@Matches(/^1[3-9]\d{9}$/, { message: "手机号格式不正确" })
|
||||
phone?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
avatar?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
roleIds?: number[];
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt } from "sequelize-typescript";
|
||||
|
||||
@Table({
|
||||
tableName: "operation_logs",
|
||||
comment: "操作日志表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: false
|
||||
})
|
||||
export default class OperationLog extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
field: "user_id",
|
||||
comment: "操作用户ID"
|
||||
})
|
||||
userId!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(50),
|
||||
field: "username",
|
||||
comment: "操作人用户名"
|
||||
})
|
||||
username!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(255),
|
||||
allowNull: false,
|
||||
field: "action",
|
||||
comment: "操作描述"
|
||||
})
|
||||
action!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(50),
|
||||
field: "module",
|
||||
comment: "操作模块"
|
||||
})
|
||||
module!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(10),
|
||||
field: "method",
|
||||
comment: "请求方法(GET/POST/PUT/DELETE等)"
|
||||
})
|
||||
method!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(500),
|
||||
field: "url",
|
||||
comment: "请求URL"
|
||||
})
|
||||
url!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(45),
|
||||
field: "ip_address",
|
||||
comment: "IP地址(支持IPv6)"
|
||||
})
|
||||
ipAddress!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(500),
|
||||
field: "user_agent",
|
||||
comment: "浏览器UA"
|
||||
})
|
||||
userAgent!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.TEXT,
|
||||
field: "request_params",
|
||||
comment: "请求参数(JSON格式)"
|
||||
})
|
||||
requestParams!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.TEXT,
|
||||
field: "response_data",
|
||||
comment: "响应数据(JSON格式)"
|
||||
})
|
||||
responseData!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
field: "status_code",
|
||||
comment: "HTTP响应状态码"
|
||||
})
|
||||
statusCode!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
field: "duration",
|
||||
comment: "请求耗时(毫秒)"
|
||||
})
|
||||
duration!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.TEXT,
|
||||
field: "error_message",
|
||||
comment: "错误信息(仅失败时记录)"
|
||||
})
|
||||
errorMessage!: string;
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt, UpdatedAt, HasMany } from "sequelize-typescript";
|
||||
import RolePermission from "./rolePermission.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "permissions",
|
||||
comment: "权限表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at"
|
||||
})
|
||||
export default class Permission extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(50),
|
||||
allowNull: false,
|
||||
comment: "权限名称"
|
||||
})
|
||||
name!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(100),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: "权限编码(唯一标识)"
|
||||
})
|
||||
code!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.ENUM("menu", "button", "api"),
|
||||
allowNull: false,
|
||||
defaultValue: "menu",
|
||||
comment: "权限类型: menu-菜单 button-按钮 api-接口"
|
||||
})
|
||||
type!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
field: "parent_id",
|
||||
comment: "父权限ID(NULL表示一级菜单)"
|
||||
})
|
||||
parentId!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: "sort_order",
|
||||
comment: "排序(数值越小越靠前)"
|
||||
})
|
||||
sortOrder!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(50),
|
||||
field: "icon",
|
||||
comment: "图标(菜单类型使用)"
|
||||
})
|
||||
icon!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(255),
|
||||
field: "route",
|
||||
comment: "路由路径(菜单类型使用)"
|
||||
})
|
||||
route!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(255),
|
||||
field: "component",
|
||||
comment: "组件路径(菜单类型使用)"
|
||||
})
|
||||
component!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.TINYINT,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
comment: "状态: 0-禁用 1-启用"
|
||||
})
|
||||
status!: number;
|
||||
|
||||
@HasMany(() => RolePermission)
|
||||
rolePermissions!: RolePermission[];
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
|
||||
@UpdatedAt
|
||||
updated_at!: Date;
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt, UpdatedAt, HasMany } from "sequelize-typescript";
|
||||
import UserRole from "./userRole.entity";
|
||||
import RolePermission from "./rolePermission.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "roles",
|
||||
comment: "角色表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at"
|
||||
})
|
||||
export default class Role extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(50),
|
||||
allowNull: false,
|
||||
comment: "角色名称"
|
||||
})
|
||||
name!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: "角色编码(唯一标识)"
|
||||
})
|
||||
code!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(255),
|
||||
field: "description",
|
||||
comment: "描述"
|
||||
})
|
||||
description!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.TINYINT,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
comment: "状态: 0-禁用 1-启用"
|
||||
})
|
||||
status!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: "sort_order",
|
||||
comment: "排序(数值越小越靠前)"
|
||||
})
|
||||
sortOrder!: number;
|
||||
|
||||
@HasMany(() => UserRole)
|
||||
userRoles!: UserRole[];
|
||||
|
||||
@HasMany(() => RolePermission)
|
||||
rolePermissions!: RolePermission[];
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
|
||||
@UpdatedAt
|
||||
updated_at!: Date;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt, ForeignKey, BelongsTo } from "sequelize-typescript";
|
||||
import Role from "./role.entity";
|
||||
import Permission from "./permission.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "role_permissions",
|
||||
comment: "角色权限关联表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: false
|
||||
})
|
||||
export default class RolePermission extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@ForeignKey(() => Role)
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
field: "role_id",
|
||||
comment: "角色ID"
|
||||
})
|
||||
roleId!: number;
|
||||
|
||||
@ForeignKey(() => Permission)
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
field: "permission_id",
|
||||
comment: "权限ID"
|
||||
})
|
||||
permissionId!: number;
|
||||
|
||||
@BelongsTo(() => Role)
|
||||
role!: Role;
|
||||
|
||||
@BelongsTo(() => Permission)
|
||||
permission!: Permission;
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt } from "sequelize-typescript";
|
||||
|
||||
@Table({
|
||||
tableName: "sys_dict",
|
||||
comment: "系统字典表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: false
|
||||
})
|
||||
export default class SysDict extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(64),
|
||||
allowNull: false,
|
||||
field: "dict_type",
|
||||
comment: "字典类型"
|
||||
})
|
||||
dictType!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(128),
|
||||
allowNull: false,
|
||||
field: "dict_label",
|
||||
comment: "字典标签"
|
||||
})
|
||||
dictLabel!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(128),
|
||||
allowNull: false,
|
||||
field: "dict_value",
|
||||
comment: "字典键值"
|
||||
})
|
||||
dictValue!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: "sort_order",
|
||||
comment: "排序(数值越小越靠前)"
|
||||
})
|
||||
sortOrder!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
comment: "状态: 0-禁用 1-启用"
|
||||
})
|
||||
status!: number;
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt, HasMany } from "sequelize-typescript";
|
||||
import SysRoleMenu from "./sysRoleMenu.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "sys_menu",
|
||||
comment: "系统菜单表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: false
|
||||
})
|
||||
export default class SysMenu extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
field: "parent_id",
|
||||
comment: "父菜单ID(NULL表示一级菜单)"
|
||||
})
|
||||
parentId!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(64),
|
||||
allowNull: false,
|
||||
field: "menu_name",
|
||||
comment: "菜单名称"
|
||||
})
|
||||
menuName!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(256),
|
||||
field: "menu_path",
|
||||
comment: "菜单路由路径"
|
||||
})
|
||||
menuPath!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(256),
|
||||
field: "component",
|
||||
comment: "组件路径"
|
||||
})
|
||||
component!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(64),
|
||||
field: "icon",
|
||||
comment: "菜单图标"
|
||||
})
|
||||
icon!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: "sort_order",
|
||||
comment: "排序(数值越小越靠前)"
|
||||
})
|
||||
sortOrder!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
field: "menu_type",
|
||||
comment: "菜单类型: 1-目录 2-菜单 3-按钮"
|
||||
})
|
||||
menuType!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(128),
|
||||
field: "permission",
|
||||
comment: "权限标识(按钮类型使用)"
|
||||
})
|
||||
permission!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
comment: "是否可见: 0-隐藏 1-显示"
|
||||
})
|
||||
visible!: number;
|
||||
|
||||
@HasMany(() => SysRoleMenu)
|
||||
sysRoleMenus!: SysRoleMenu[];
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt } from "sequelize-typescript";
|
||||
|
||||
@Table({
|
||||
tableName: "sys_operation_log",
|
||||
comment: "系统操作日志表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: false
|
||||
})
|
||||
export default class SysOperationLog extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
field: "user_id",
|
||||
comment: "操作用户ID"
|
||||
})
|
||||
userId!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(64),
|
||||
field: "module",
|
||||
comment: "操作模块"
|
||||
})
|
||||
module!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(64),
|
||||
field: "action",
|
||||
comment: "操作动作"
|
||||
})
|
||||
action!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(256),
|
||||
field: "method",
|
||||
comment: "请求方法(GET/POST/PUT/DELETE等)"
|
||||
})
|
||||
method!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.TEXT,
|
||||
field: "params",
|
||||
comment: "请求参数(JSON格式)"
|
||||
})
|
||||
params!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(64),
|
||||
field: "ip",
|
||||
comment: "操作IP(支持IPv6)"
|
||||
})
|
||||
ip!: string;
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt, HasMany } from "sequelize-typescript";
|
||||
import SysUserRole from "./sysUserRole.entity";
|
||||
import SysRoleMenu from "./sysRoleMenu.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "sys_role",
|
||||
comment: "系统角色表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: false
|
||||
})
|
||||
export default class SysRole extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(32),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
field: "role_code",
|
||||
comment: "角色编码(唯一标识)"
|
||||
})
|
||||
roleCode!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(64),
|
||||
allowNull: false,
|
||||
field: "role_name",
|
||||
comment: "角色名称"
|
||||
})
|
||||
roleName!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.TEXT,
|
||||
field: "description",
|
||||
comment: "角色描述"
|
||||
})
|
||||
description!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0,
|
||||
field: "sort_order",
|
||||
comment: "排序(数值越小越靠前)"
|
||||
})
|
||||
sortOrder!: number;
|
||||
|
||||
@HasMany(() => SysUserRole)
|
||||
sysUserRoles!: SysUserRole[];
|
||||
|
||||
@HasMany(() => SysRoleMenu)
|
||||
sysRoleMenus!: SysRoleMenu[];
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Column, Model, Table, DataType, ForeignKey, BelongsTo } from "sequelize-typescript";
|
||||
import SysRole from "./sysRole.entity";
|
||||
import SysMenu from "./sysMenu.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "sys_role_menu",
|
||||
comment: "系统角色菜单关联表",
|
||||
timestamps: false
|
||||
})
|
||||
export default class SysRoleMenu extends Model {
|
||||
@ForeignKey(() => SysRole)
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
field: "role_id",
|
||||
comment: "角色ID"
|
||||
})
|
||||
roleId!: number;
|
||||
|
||||
@ForeignKey(() => SysMenu)
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
field: "menu_id",
|
||||
comment: "菜单ID"
|
||||
})
|
||||
menuId!: number;
|
||||
|
||||
@BelongsTo(() => SysRole)
|
||||
role!: SysRole;
|
||||
|
||||
@BelongsTo(() => SysMenu)
|
||||
menu!: SysMenu;
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt, UpdatedAt, HasMany } from "sequelize-typescript";
|
||||
import UserRole from "./userRole.entity";
|
||||
import SysUserRole from "./sysUserRole.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "users",
|
||||
comment: "用户表",
|
||||
tableName: "sys_user",
|
||||
comment: "系统用户表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: "updated_at"
|
||||
})
|
||||
export default class User extends Model {
|
||||
export default class SysUser extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
@@ -17,7 +17,7 @@ export default class User extends Model {
|
||||
id!: number;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(50),
|
||||
type: DataType.STRING(64),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
comment: "用户名"
|
||||
@@ -25,25 +25,20 @@ export default class User extends Model {
|
||||
username!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(255),
|
||||
type: DataType.STRING(256),
|
||||
allowNull: false,
|
||||
field: "password_hash",
|
||||
comment: "加密密码(bcrypt)"
|
||||
})
|
||||
password!: string;
|
||||
passwordHash!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(50),
|
||||
field: "nickname",
|
||||
comment: "昵称"
|
||||
type: DataType.STRING(64),
|
||||
allowNull: false,
|
||||
field: "real_name",
|
||||
comment: "真实姓名"
|
||||
})
|
||||
nickname!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(100),
|
||||
field: "email",
|
||||
comment: "邮箱"
|
||||
})
|
||||
email!: string;
|
||||
realName!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(20),
|
||||
@@ -53,14 +48,14 @@ export default class User extends Model {
|
||||
phone!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(255),
|
||||
field: "avatar",
|
||||
comment: "头像URL"
|
||||
type: DataType.STRING(128),
|
||||
field: "email",
|
||||
comment: "邮箱"
|
||||
})
|
||||
avatar!: string;
|
||||
email!: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.TINYINT,
|
||||
type: DataType.SMALLINT,
|
||||
allowNull: false,
|
||||
defaultValue: 1,
|
||||
comment: "状态: 0-禁用 1-启用"
|
||||
@@ -74,15 +69,8 @@ export default class User extends Model {
|
||||
})
|
||||
lastLoginAt!: Date;
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING(45),
|
||||
field: "last_login_ip",
|
||||
comment: "最后登录IP(支持IPv6)"
|
||||
})
|
||||
lastLoginIp!: string;
|
||||
|
||||
@HasMany(() => UserRole)
|
||||
userRoles!: UserRole[];
|
||||
@HasMany(() => SysUserRole)
|
||||
sysUserRoles!: SysUserRole[];
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Column, Model, Table, DataType, ForeignKey, BelongsTo } from "sequelize-typescript";
|
||||
import SysUser from "./sysUser.entity";
|
||||
import SysRole from "./sysRole.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "sys_user_role",
|
||||
comment: "系统用户角色关联表",
|
||||
timestamps: false
|
||||
})
|
||||
export default class SysUserRole extends Model {
|
||||
@ForeignKey(() => SysUser)
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
field: "user_id",
|
||||
comment: "用户ID"
|
||||
})
|
||||
userId!: number;
|
||||
|
||||
@ForeignKey(() => SysRole)
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
primaryKey: true,
|
||||
field: "role_id",
|
||||
comment: "角色ID"
|
||||
})
|
||||
roleId!: number;
|
||||
|
||||
@BelongsTo(() => SysUser)
|
||||
user!: SysUser;
|
||||
|
||||
@BelongsTo(() => SysRole)
|
||||
role!: SysRole;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { Column, Model, Table, DataType, CreatedAt, ForeignKey, BelongsTo } from "sequelize-typescript";
|
||||
import User from "./user.entity";
|
||||
import Role from "./role.entity";
|
||||
|
||||
@Table({
|
||||
tableName: "user_roles",
|
||||
comment: "用户角色关联表",
|
||||
timestamps: true,
|
||||
createdAt: "created_at",
|
||||
updatedAt: false
|
||||
})
|
||||
export default class UserRole extends Model {
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
})
|
||||
id!: number;
|
||||
|
||||
@ForeignKey(() => User)
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
field: "user_id",
|
||||
comment: "用户ID"
|
||||
})
|
||||
userId!: number;
|
||||
|
||||
@ForeignKey(() => Role)
|
||||
@Column({
|
||||
type: DataType.BIGINT,
|
||||
allowNull: false,
|
||||
field: "role_id",
|
||||
comment: "角色ID"
|
||||
})
|
||||
roleId!: number;
|
||||
|
||||
@BelongsTo(() => User)
|
||||
user!: User;
|
||||
|
||||
@BelongsTo(() => Role)
|
||||
role!: Role;
|
||||
|
||||
@CreatedAt
|
||||
created_at!: Date;
|
||||
}
|
||||
@@ -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[];
|
||||
}
|
||||
}
|
||||
@@ -1,43 +1,33 @@
|
||||
import { Provide, Inject } from "@midwayjs/core";
|
||||
import { Context } from "@midwayjs/koa";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import User from "../entity/user.entity";
|
||||
import UserRole from "../entity/userRole.entity";
|
||||
import Role from "../entity/role.entity";
|
||||
import RolePermission from "../entity/rolePermission.entity";
|
||||
import Permission from "../entity/permission.entity";
|
||||
import SysUser from "../entity/sysUser.entity";
|
||||
import SysUserRole from "../entity/sysUserRole.entity";
|
||||
import SysRole from "../entity/sysRole.entity";
|
||||
import { TokenUtil } from "../util/token";
|
||||
import { comparePassword } from "../util/crypto";
|
||||
import { PermissionService } from "./permission.service";
|
||||
|
||||
@Provide()
|
||||
export class AuthService {
|
||||
@Inject()
|
||||
tokenUtil!: TokenUtil;
|
||||
|
||||
@InjectRepository(User)
|
||||
userModel!: typeof User;
|
||||
@Inject()
|
||||
permissionService!: PermissionService;
|
||||
|
||||
@InjectRepository(UserRole)
|
||||
userRoleModel!: typeof UserRole;
|
||||
@InjectRepository(SysUser)
|
||||
userModel!: typeof SysUser;
|
||||
|
||||
@InjectRepository(Role)
|
||||
roleModel!: typeof Role;
|
||||
@InjectRepository(SysUserRole)
|
||||
userRoleModel!: typeof SysUserRole;
|
||||
|
||||
@InjectRepository(RolePermission)
|
||||
rolePermissionModel!: typeof RolePermission;
|
||||
|
||||
@InjectRepository(Permission)
|
||||
permissionModel!: typeof Permission;
|
||||
@InjectRepository(SysRole)
|
||||
roleModel!: typeof SysRole;
|
||||
|
||||
async login(username: string, password: string, ctx: Context): Promise<any> {
|
||||
const user = await this.userModel.findOne({
|
||||
where: { username },
|
||||
include: [
|
||||
{
|
||||
model: this.userRoleModel,
|
||||
include: [this.roleModel]
|
||||
}
|
||||
]
|
||||
where: { username }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -48,15 +38,20 @@ export class AuthService {
|
||||
throw new Error("账户已被禁用");
|
||||
}
|
||||
|
||||
const isPasswordValid = await comparePassword(password, user.password);
|
||||
const isPasswordValid = await comparePassword(password, user.passwordHash);
|
||||
if (!isPasswordValid) {
|
||||
throw new Error("密码错误");
|
||||
}
|
||||
|
||||
const roles = (user as any).userRoles?.map((ur: any) => ur.role) || [];
|
||||
const roleIds = roles.map((r: Role) => r.id);
|
||||
// 获取用户角色编码
|
||||
const userRoles = await this.userRoleModel.findAll({
|
||||
where: { userId: user.id },
|
||||
include: [this.roleModel]
|
||||
});
|
||||
const roleCodes = userRoles.map(ur => ur.role?.roleCode).filter(Boolean) as string[];
|
||||
|
||||
const permissions = await this.getUserPermissions(roleIds);
|
||||
// 获取用户菜单树
|
||||
const menus = await this.permissionService.getUserMenus(user.id);
|
||||
|
||||
const accessToken = this.tokenUtil.generateAccessToken({
|
||||
userId: user.id,
|
||||
@@ -68,8 +63,7 @@ export class AuthService {
|
||||
});
|
||||
|
||||
await user.update({
|
||||
lastLoginAt: new Date(),
|
||||
lastLoginIp: ctx.ip
|
||||
lastLoginAt: new Date()
|
||||
});
|
||||
|
||||
return {
|
||||
@@ -78,11 +72,10 @@ export class AuthService {
|
||||
userInfo: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
nickname: user.nickname,
|
||||
avatar: user.avatar,
|
||||
roles: roles.map((r: Role) => r.code),
|
||||
permissions: permissions.map((p: Permission) => p.code)
|
||||
}
|
||||
realName: user.realName
|
||||
},
|
||||
roles: roleCodes,
|
||||
menus
|
||||
};
|
||||
}
|
||||
|
||||
@@ -111,17 +104,4 @@ export class AuthService {
|
||||
throw new Error("Refresh Token无效或已过期");
|
||||
}
|
||||
}
|
||||
|
||||
private async getUserPermissions(roleIds: number[]): Promise<Permission[]> {
|
||||
if (!roleIds || roleIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const rolePermissions = await this.rolePermissionModel.findAll({
|
||||
where: { roleId: roleIds },
|
||||
include: [this.permissionModel]
|
||||
});
|
||||
|
||||
return [...new Map(rolePermissions.map((rp: any) => [rp.permission.id, rp.permission])).values()];
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
import { Provide, Inject } from "@midwayjs/core";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import User from "../entity/user.entity";
|
||||
import OperationLog from "../entity/operationLog.entity";
|
||||
import SysUser from "../entity/sysUser.entity";
|
||||
import SysOperationLog from "../entity/sysOperationLog.entity";
|
||||
import dayjs from "dayjs";
|
||||
import { Op } from "sequelize";
|
||||
|
||||
@Provide()
|
||||
export class DashboardService {
|
||||
@InjectRepository(User)
|
||||
userModel!: typeof User;
|
||||
|
||||
@InjectRepository(OperationLog)
|
||||
logModel!: typeof OperationLog;
|
||||
@InjectRepository(SysOperationLog)
|
||||
logModel!: typeof SysOperationLog;
|
||||
|
||||
async getDashboardStats(): Promise<any> {
|
||||
const mockDeviceTotal = 1256;
|
||||
@@ -103,15 +100,15 @@ export class DashboardService {
|
||||
const logs = await this.logModel.findAll({
|
||||
order: [["created_at", "DESC"]],
|
||||
limit,
|
||||
attributes: ["id", "username", "action", "module", "ip_address", "created_at"]
|
||||
attributes: ["id", "action", "module", "ip", "created_at"]
|
||||
});
|
||||
|
||||
return logs.map((log) => ({
|
||||
id: log.id,
|
||||
user: log.username || "系统",
|
||||
user: String(log.userId || "系统"),
|
||||
action: log.action,
|
||||
module: log.module,
|
||||
ip: log.ipAddress,
|
||||
ip: log.ip,
|
||||
time: dayjs(log.created_at).format("MM-DD HH:mm")
|
||||
}));
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import { Provide, Inject } from "@midwayjs/core";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import SysDict from "../entity/sysDict.entity";
|
||||
import { Op } from "sequelize";
|
||||
|
||||
@Provide()
|
||||
export class DictService {
|
||||
@InjectRepository(SysDict)
|
||||
dictModel!: typeof SysDict;
|
||||
|
||||
async list(query: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
dictType?: string;
|
||||
keyword?: string;
|
||||
status?: number;
|
||||
}): Promise<any> {
|
||||
const page = query.page || 1;
|
||||
const pageSize = query.pageSize || 10;
|
||||
|
||||
const where: any = {};
|
||||
|
||||
if (query.dictType) {
|
||||
where.dictType = query.dictType;
|
||||
}
|
||||
|
||||
if (query.keyword) {
|
||||
where[Op.or] = [
|
||||
{ dictLabel: { [Op.like]: `%${query.keyword}%` } },
|
||||
{ dictValue: { [Op.like]: `%${query.keyword}%` } },
|
||||
];
|
||||
}
|
||||
|
||||
if (query.status !== undefined && query.status !== null) {
|
||||
where.status = query.status;
|
||||
}
|
||||
|
||||
const { count, rows } = await this.dictModel.findAndCountAll({
|
||||
where,
|
||||
order: [["sort_order", "ASC"], ["id", "ASC"]],
|
||||
limit: Number(pageSize),
|
||||
offset: (Number(page) - 1) * Number(pageSize),
|
||||
});
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "获取字典列表成功",
|
||||
data: {
|
||||
list: rows,
|
||||
total: count,
|
||||
page: Number(page),
|
||||
pageSize: Number(pageSize),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async types(): Promise<any> {
|
||||
const rows = await this.dictModel.findAll({
|
||||
attributes: ["dictType"],
|
||||
group: ["dictType"],
|
||||
order: [["dictType", "ASC"]],
|
||||
});
|
||||
|
||||
const types = rows.map(row => row.dictType);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "获取字典类型成功",
|
||||
data: types,
|
||||
};
|
||||
}
|
||||
|
||||
async create(data: any): Promise<any> {
|
||||
const { dictType, dictLabel, dictValue, sortOrder = 0, status = 1 } = data;
|
||||
|
||||
if (!dictType || !dictLabel || !dictValue) {
|
||||
return {
|
||||
code: 400,
|
||||
message: "字典类型、字典标签、字典键值不能为空",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
const dict = await this.dictModel.create({
|
||||
dict_type: dictType,
|
||||
dict_label: dictLabel,
|
||||
dict_value: dictValue,
|
||||
sort_order: sortOrder,
|
||||
status,
|
||||
});
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "创建字典成功",
|
||||
data: dict,
|
||||
};
|
||||
}
|
||||
|
||||
async update(id: string, data: any): Promise<any> {
|
||||
const dict = await this.dictModel.findByPk(Number(id));
|
||||
|
||||
if (!dict) {
|
||||
return {
|
||||
code: 404,
|
||||
message: "字典不存在",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
const updateData: any = {};
|
||||
if (data.dictType !== undefined) updateData.dict_type = data.dictType;
|
||||
if (data.dictLabel !== undefined) updateData.dict_label = data.dictLabel;
|
||||
if (data.dictValue !== undefined) updateData.dict_value = data.dictValue;
|
||||
if (data.sortOrder !== undefined) updateData.sort_order = data.sortOrder;
|
||||
if (data.status !== undefined) updateData.status = data.status;
|
||||
|
||||
await dict.update(updateData);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "更新字典成功",
|
||||
data: dict,
|
||||
};
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<any> {
|
||||
const dict = await this.dictModel.findByPk(Number(id));
|
||||
|
||||
if (!dict) {
|
||||
return {
|
||||
code: 404,
|
||||
message: "字典不存在",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
await dict.destroy();
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "删除字典成功",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Provide, Inject } from "@midwayjs/core";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import OperationLog from "../entity/operationLog.entity";
|
||||
import SysOperationLog from "../entity/sysOperationLog.entity";
|
||||
import { Op } from "sequelize";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
@Provide()
|
||||
export class LogService {
|
||||
@InjectRepository(OperationLog)
|
||||
logModel!: typeof OperationLog;
|
||||
@InjectRepository(SysOperationLog)
|
||||
logModel!: typeof SysOperationLog;
|
||||
|
||||
async list(query: {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
moduleName?: string;
|
||||
actionType?: string;
|
||||
userId?: string | number;
|
||||
module?: string;
|
||||
action?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
operator?: string;
|
||||
}): Promise<any> {
|
||||
const page = query.page || 1;
|
||||
const pageSize = query.pageSize || 10;
|
||||
@@ -67,19 +67,22 @@ export class LogService {
|
||||
limit: 10000,
|
||||
});
|
||||
|
||||
const headers = ["序号", "操作人", "操作类型", "操作模块", "请求方法", "请求URL", "IP地址", "状态码", "耗时(ms)", "操作时间"];
|
||||
const rows = logs.map((log: any, index: number) => [
|
||||
index + 1,
|
||||
log.username || "-",
|
||||
log.action || "-",
|
||||
log.module || "-",
|
||||
log.method || "-",
|
||||
(log.url || "").replace(/,/g, ","),
|
||||
log.ipAddress || "-",
|
||||
log.statusCode || "-",
|
||||
log.duration || 0,
|
||||
dayjs(log.created_at).format("YYYY-MM-DD HH:mm:ss"),
|
||||
]);
|
||||
const headers = ["序号", "操作人ID", "操作模块", "操作动作", "请求方法", "请求参数", "IP地址", "操作时间"];
|
||||
const rows = logs.map((log: any, index: number) => {
|
||||
const paramsText = (log.params || "").replace(/,/g, ",");
|
||||
const paramsCell = paramsText.length > 200 ? paramsText.substring(0, 200) + "..." : paramsText;
|
||||
|
||||
return [
|
||||
index + 1,
|
||||
log.userId || "-",
|
||||
log.module || "-",
|
||||
log.action || "-",
|
||||
log.method || "-",
|
||||
paramsCell,
|
||||
log.ip || "-",
|
||||
dayjs(log.created_at).format("YYYY-MM-DD HH:mm:ss"),
|
||||
];
|
||||
});
|
||||
|
||||
const csvContent = [headers.join(","), ...rows.map(row => row.join(","))].join("\n");
|
||||
|
||||
@@ -100,18 +103,18 @@ export class LogService {
|
||||
|
||||
private buildWhereClause(query: any): any {
|
||||
const where: any = {};
|
||||
const { operator, actionType, moduleName, startDate, endDate } = query;
|
||||
const { userId, module, action, startDate, endDate } = query;
|
||||
|
||||
if (operator) {
|
||||
where.username = { [Op.like]: `%${operator}%` };
|
||||
if (userId) {
|
||||
where.userId = userId;
|
||||
}
|
||||
|
||||
if (actionType) {
|
||||
where.action = { [Op.like]: `%${actionType}%` };
|
||||
if (module) {
|
||||
where.module = { [Op.like]: `%${module}%` };
|
||||
}
|
||||
|
||||
if (moduleName) {
|
||||
where.module = { [Op.like]: `%${moduleName}%` };
|
||||
if (action) {
|
||||
where.action = { [Op.like]: `%${action}%` };
|
||||
}
|
||||
|
||||
if (startDate || endDate) {
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
import { Provide } from "@midwayjs/core";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import SysMenu from "../entity/sysMenu.entity";
|
||||
|
||||
@Provide()
|
||||
export class MenuService {
|
||||
@InjectRepository(SysMenu)
|
||||
menuModel!: typeof SysMenu;
|
||||
|
||||
async getMenuTree(): Promise<any> {
|
||||
const menus = await this.menuModel.findAll({
|
||||
order: [["sort_order", "ASC"], ["id", "ASC"]],
|
||||
});
|
||||
|
||||
const tree = this.buildMenuTree(menus.map(menu => menu.toJSON()));
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "获取菜单树成功",
|
||||
data: tree,
|
||||
};
|
||||
}
|
||||
|
||||
async create(data: any): Promise<any> {
|
||||
const {
|
||||
parentId,
|
||||
menuName,
|
||||
menuPath,
|
||||
component,
|
||||
icon,
|
||||
sortOrder = 0,
|
||||
menuType = 2,
|
||||
permission,
|
||||
visible = 1,
|
||||
} = data;
|
||||
|
||||
if (!menuName) {
|
||||
return {
|
||||
code: 400,
|
||||
message: "菜单名称不能为空",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
const menu = await this.menuModel.create({
|
||||
parent_id: parentId,
|
||||
menu_name: menuName,
|
||||
menu_path: menuPath,
|
||||
component,
|
||||
icon,
|
||||
sort_order: sortOrder,
|
||||
menu_type: menuType,
|
||||
permission,
|
||||
visible,
|
||||
});
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "创建菜单成功",
|
||||
data: menu,
|
||||
};
|
||||
}
|
||||
|
||||
async update(id: string, data: any): Promise<any> {
|
||||
const menu = await this.menuModel.findByPk(Number(id));
|
||||
|
||||
if (!menu) {
|
||||
return {
|
||||
code: 404,
|
||||
message: "菜单不存在",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
const updateData: any = {};
|
||||
if (data.parentId !== undefined) updateData.parent_id = data.parentId;
|
||||
if (data.menuName !== undefined) updateData.menu_name = data.menuName;
|
||||
if (data.menuPath !== undefined) updateData.menu_path = data.menuPath;
|
||||
if (data.component !== undefined) updateData.component = data.component;
|
||||
if (data.icon !== undefined) updateData.icon = data.icon;
|
||||
if (data.sortOrder !== undefined) updateData.sort_order = data.sortOrder;
|
||||
if (data.menuType !== undefined) updateData.menu_type = data.menuType;
|
||||
if (data.permission !== undefined) updateData.permission = data.permission;
|
||||
if (data.visible !== undefined) updateData.visible = data.visible;
|
||||
|
||||
await menu.update(updateData);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "更新菜单成功",
|
||||
data: menu,
|
||||
};
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<any> {
|
||||
const menu = await this.menuModel.findByPk(Number(id));
|
||||
|
||||
if (!menu) {
|
||||
return {
|
||||
code: 404,
|
||||
message: "菜单不存在",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
const childCount = await this.menuModel.count({
|
||||
where: { parentId: Number(id) },
|
||||
});
|
||||
|
||||
if (childCount > 0) {
|
||||
return {
|
||||
code: 400,
|
||||
message: "存在子菜单,无法删除",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
await menu.destroy();
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "删除菜单成功",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
private buildMenuTree(menus: any[]): any[] {
|
||||
const map = new Map<number, any>();
|
||||
const tree: any[] = [];
|
||||
|
||||
for (const menu of menus) {
|
||||
map.set(menu.id, {
|
||||
id: menu.id,
|
||||
parentId: menu.parentId,
|
||||
menuName: menu.menuName,
|
||||
menuPath: menu.menuPath,
|
||||
component: menu.component,
|
||||
icon: menu.icon,
|
||||
sortOrder: menu.sortOrder,
|
||||
menuType: menu.menuType,
|
||||
permission: menu.permission,
|
||||
visible: menu.visible,
|
||||
children: [],
|
||||
});
|
||||
}
|
||||
|
||||
for (const menu of menus) {
|
||||
const node = map.get(menu.id)!;
|
||||
if (menu.parentId && map.has(menu.parentId)) {
|
||||
map.get(menu.parentId)!.children.push(node);
|
||||
} else {
|
||||
tree.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
@@ -1,110 +1,111 @@
|
||||
import { Provide, Inject } from "@midwayjs/core";
|
||||
import { Provide } from "@midwayjs/core";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import User from "../entity/user.entity";
|
||||
import UserRole from "../entity/userRole.entity";
|
||||
import Role from "../entity/role.entity";
|
||||
import RolePermission from "../entity/rolePermission.entity";
|
||||
import Permission from "../entity/permission.entity";
|
||||
import SysUserRole from "../entity/sysUserRole.entity";
|
||||
import SysRoleMenu from "../entity/sysRoleMenu.entity";
|
||||
import SysMenu from "../entity/sysMenu.entity";
|
||||
|
||||
@Provide()
|
||||
export class PermissionService {
|
||||
@InjectRepository(User)
|
||||
userModel!: typeof User;
|
||||
@InjectRepository(SysUserRole)
|
||||
userRoleModel!: typeof SysUserRole;
|
||||
|
||||
@InjectRepository(UserRole)
|
||||
userRoleModel!: typeof UserRole;
|
||||
@InjectRepository(SysRoleMenu)
|
||||
roleMenuModel!: typeof SysRoleMenu;
|
||||
|
||||
@InjectRepository(Role)
|
||||
roleModel!: typeof Role;
|
||||
@InjectRepository(SysMenu)
|
||||
menuModel!: typeof SysMenu;
|
||||
|
||||
@InjectRepository(RolePermission)
|
||||
rolePermissionModel!: typeof RolePermission;
|
||||
|
||||
@InjectRepository(Permission)
|
||||
permissionModel!: typeof Permission;
|
||||
|
||||
async checkUserPermission(userId: number, permissionCode: string): Promise<boolean> {
|
||||
/**
|
||||
* 获取用户可见菜单树(用户角色 → 角色菜单 → 可见菜单,按 sort_order/id 升序去重)
|
||||
*/
|
||||
async getUserMenus(userId: number): Promise<any[]> {
|
||||
const userRoles = await this.userRoleModel.findAll({
|
||||
where: { userId },
|
||||
include: [this.roleModel],
|
||||
where: { userId }
|
||||
});
|
||||
|
||||
const roleIds = userRoles.map(ur => ur.roleId);
|
||||
if (!roleIds.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const roleMenus = await this.roleMenuModel.findAll({
|
||||
where: { roleId: roleIds },
|
||||
include: [
|
||||
{
|
||||
model: this.menuModel,
|
||||
where: { visible: 1 }
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const menuMap = new Map<number, any>();
|
||||
for (const rm of roleMenus) {
|
||||
const menu = rm.menu;
|
||||
if (menu && !menuMap.has(menu.id)) {
|
||||
menuMap.set(menu.id, this.toMenuNode(menu));
|
||||
}
|
||||
}
|
||||
|
||||
const menus = Array.from(menuMap.values());
|
||||
menus.sort((a, b) => a.sortOrder - b.sortOrder || a.id - b.id);
|
||||
|
||||
return this.buildMenuTree(menus);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查用户角色关联的菜单中是否存在指定权限标识的菜单
|
||||
*/
|
||||
async checkUserPermission(userId: number, permissionCode: string): Promise<boolean> {
|
||||
const userRoles = await this.userRoleModel.findAll({
|
||||
where: { userId }
|
||||
});
|
||||
|
||||
const roleIds = userRoles.map(ur => ur.roleId);
|
||||
if (!roleIds.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rolePermissions = await this.rolePermissionModel.findAll({
|
||||
const roleMenus = await this.roleMenuModel.findAll({
|
||||
where: { roleId: roleIds },
|
||||
include: [
|
||||
{
|
||||
model: this.permissionModel,
|
||||
where: { code: permissionCode },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return rolePermissions.some(rp => rp.permission?.code === permissionCode);
|
||||
}
|
||||
|
||||
async getUserPermissions(userId: number): Promise<string[]> {
|
||||
const permissions = await this.getUserPermissionTree(userId);
|
||||
return this.flattenPermissions(permissions);
|
||||
}
|
||||
|
||||
async getUserPermissionTree(userId: number): Promise<any[]> {
|
||||
const userRoles = await this.userRoleModel.findAll({
|
||||
where: { userId },
|
||||
include: [
|
||||
{
|
||||
model: this.roleModel,
|
||||
include: [
|
||||
{
|
||||
model: this.rolePermissionModel,
|
||||
include: [this.permissionModel],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const allPermissions = new Map<number, any>();
|
||||
|
||||
for (const ur of userRoles) {
|
||||
const role: any = ur.role;
|
||||
if (role && role.rolePermissions) {
|
||||
for (const rp of role.rolePermissions) {
|
||||
if (rp.permission && !allPermissions.has(rp.permission.id)) {
|
||||
allPermissions.set(rp.permission.id, rp.permission.toJSON());
|
||||
}
|
||||
model: this.menuModel,
|
||||
where: { permission: permissionCode }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.buildPermissionTree(Array.from(allPermissions.values()));
|
||||
}
|
||||
|
||||
async getAllPermissions(): Promise<any[]> {
|
||||
const permissions = await this.permissionModel.findAll({
|
||||
order: [["sort_order", "ASC"]],
|
||||
]
|
||||
});
|
||||
|
||||
return this.buildPermissionTree(permissions.map(p => p.toJSON()));
|
||||
return roleMenus.some(rm => rm.menu?.permission === permissionCode);
|
||||
}
|
||||
|
||||
private buildPermissionTree(permissions: any[]): any[] {
|
||||
private toMenuNode(menu: SysMenu): any {
|
||||
return {
|
||||
id: menu.id,
|
||||
parentId: menu.parentId,
|
||||
menuName: menu.menuName,
|
||||
menuPath: menu.menuPath,
|
||||
component: menu.component,
|
||||
icon: menu.icon,
|
||||
sortOrder: menu.sortOrder,
|
||||
menuType: menu.menuType,
|
||||
permission: menu.permission,
|
||||
visible: menu.visible,
|
||||
children: []
|
||||
};
|
||||
}
|
||||
|
||||
private buildMenuTree(menus: any[]): any[] {
|
||||
const map = new Map<number, any>();
|
||||
const tree: any[] = [];
|
||||
|
||||
for (const perm of permissions) {
|
||||
map.set(perm.id, { ...perm, children: [] });
|
||||
for (const menu of menus) {
|
||||
map.set(menu.id, { ...menu, children: [] });
|
||||
}
|
||||
|
||||
for (const perm of permissions) {
|
||||
const node = map.get(perm.id)!;
|
||||
if (perm.parentId && map.has(perm.parentId)) {
|
||||
map.get(perm.parentId)!.children.push(node);
|
||||
for (const menu of menus) {
|
||||
const node = map.get(menu.id)!;
|
||||
if (menu.parentId && map.has(menu.parentId)) {
|
||||
map.get(menu.parentId)!.children.push(node);
|
||||
} else {
|
||||
tree.push(node);
|
||||
}
|
||||
@@ -112,17 +113,4 @@ export class PermissionService {
|
||||
|
||||
return tree;
|
||||
}
|
||||
|
||||
private flattenPermissions(tree: any[]): string[] {
|
||||
const codes: string[] = [];
|
||||
|
||||
for (const node of tree) {
|
||||
codes.push(node.code);
|
||||
if (node.children?.length) {
|
||||
codes.push(...this.flattenPermissions(node.children));
|
||||
}
|
||||
}
|
||||
|
||||
return codes;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Provide, Inject } from "@midwayjs/core";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import Role from "../entity/role.entity";
|
||||
import UserRole from "../entity/userRole.entity";
|
||||
import RolePermission from "../entity/rolePermission.entity";
|
||||
import Permission from "../entity/permission.entity";
|
||||
import SysRole from "../entity/sysRole.entity";
|
||||
import SysUserRole from "../entity/sysUserRole.entity";
|
||||
import SysRoleMenu from "../entity/sysRoleMenu.entity";
|
||||
import SysMenu from "../entity/sysMenu.entity";
|
||||
import { Op } from "sequelize";
|
||||
|
||||
@Provide()
|
||||
export class RoleService {
|
||||
@InjectRepository(Role)
|
||||
roleModel!: typeof Role;
|
||||
@InjectRepository(SysRole)
|
||||
roleModel!: typeof SysRole;
|
||||
|
||||
@InjectRepository(UserRole)
|
||||
userRoleModel!: typeof UserRole;
|
||||
@InjectRepository(SysUserRole)
|
||||
userRoleModel!: typeof SysUserRole;
|
||||
|
||||
@InjectRepository(RolePermission)
|
||||
rolePermissionModel!: typeof RolePermission;
|
||||
@InjectRepository(SysRoleMenu)
|
||||
roleMenuModel!: typeof SysRoleMenu;
|
||||
|
||||
@InjectRepository(Permission)
|
||||
permissionModel!: typeof Permission;
|
||||
@InjectRepository(SysMenu)
|
||||
menuModel!: typeof SysMenu;
|
||||
|
||||
async list(query: {
|
||||
page?: number;
|
||||
@@ -32,20 +32,17 @@ export class RoleService {
|
||||
const whereClause: any = {};
|
||||
if (query.keyword) {
|
||||
whereClause[Op.or] = [
|
||||
{ name: { [Op.like]: `%${query.keyword}%` } },
|
||||
{ code: { [Op.like]: `%${query.keyword}%` } },
|
||||
{ roleCode: { [Op.like]: `%${query.keyword}%` } },
|
||||
{ roleName: { [Op.like]: `%${query.keyword}%` } },
|
||||
];
|
||||
}
|
||||
|
||||
const { count, rows } = await this.roleModel.findAndCountAll({
|
||||
where: whereClause,
|
||||
attributes: ["id", "roleCode", "roleName", "description", "sortOrder"],
|
||||
offset,
|
||||
limit: pageSize,
|
||||
order: [["sort_order", "ASC"], ["created_at", "DESC"]],
|
||||
include: [{
|
||||
model: this.rolePermissionModel,
|
||||
include: [this.permissionModel],
|
||||
}],
|
||||
});
|
||||
|
||||
for (const role of rows) {
|
||||
@@ -80,12 +77,7 @@ export class RoleService {
|
||||
}
|
||||
|
||||
async detail(id: string): Promise<any> {
|
||||
const role = await this.roleModel.findByPk(Number(id), {
|
||||
include: [{
|
||||
model: this.rolePermissionModel,
|
||||
include: [this.permissionModel],
|
||||
}],
|
||||
});
|
||||
const role = await this.roleModel.findByPk(Number(id));
|
||||
|
||||
if (!role) {
|
||||
return {
|
||||
@@ -108,10 +100,10 @@ export class RoleService {
|
||||
}
|
||||
|
||||
async create(data: any): Promise<any> {
|
||||
const { name, code, description, status = 1, sortOrder = 0 } = data;
|
||||
const { roleCode, roleName, description, sortOrder = 0 } = data;
|
||||
|
||||
const existingRole = await this.roleModel.findOne({
|
||||
where: { code },
|
||||
where: { roleCode },
|
||||
});
|
||||
|
||||
if (existingRole) {
|
||||
@@ -123,10 +115,9 @@ export class RoleService {
|
||||
}
|
||||
|
||||
const role = await this.roleModel.create({
|
||||
name,
|
||||
code,
|
||||
roleCode,
|
||||
roleName,
|
||||
description,
|
||||
status,
|
||||
sort_order: sortOrder,
|
||||
});
|
||||
|
||||
@@ -148,9 +139,9 @@ export class RoleService {
|
||||
};
|
||||
}
|
||||
|
||||
if (data.code && data.code !== role.code) {
|
||||
if (data.roleCode && data.roleCode !== role.roleCode) {
|
||||
const existingRole = await this.roleModel.findOne({
|
||||
where: { code: data.code },
|
||||
where: { roleCode: data.roleCode },
|
||||
});
|
||||
|
||||
if (existingRole) {
|
||||
@@ -163,9 +154,9 @@ export class RoleService {
|
||||
}
|
||||
|
||||
const updateData: any = {};
|
||||
if (data.name !== undefined) updateData.name = data.name;
|
||||
if (data.roleCode !== undefined) updateData.roleCode = data.roleCode;
|
||||
if (data.roleName !== undefined) updateData.roleName = data.roleName;
|
||||
if (data.description !== undefined) updateData.description = data.description;
|
||||
if (data.status !== undefined) updateData.status = data.status;
|
||||
if (data.sortOrder !== undefined) updateData.sort_order = data.sortOrder;
|
||||
|
||||
await role.update(updateData);
|
||||
@@ -200,7 +191,7 @@ export class RoleService {
|
||||
};
|
||||
}
|
||||
|
||||
await this.rolePermissionModel.destroy({
|
||||
await this.roleMenuModel.destroy({
|
||||
where: { roleId: Number(id) },
|
||||
});
|
||||
|
||||
@@ -213,7 +204,7 @@ export class RoleService {
|
||||
};
|
||||
}
|
||||
|
||||
async getPermissions(roleId: number): Promise<any> {
|
||||
async getMenus(roleId: number): Promise<any> {
|
||||
const role = await this.roleModel.findByPk(roleId);
|
||||
|
||||
if (!role) {
|
||||
@@ -224,28 +215,32 @@ export class RoleService {
|
||||
};
|
||||
}
|
||||
|
||||
const rolePermissions = await this.rolePermissionModel.findAll({
|
||||
const roleMenus = await this.roleMenuModel.findAll({
|
||||
where: { roleId },
|
||||
});
|
||||
const assignedPermissionIds = rolePermissions.map(rp => rp.permissionId);
|
||||
const assignedMenuIds = roleMenus.map(rm => rm.menuId);
|
||||
|
||||
const allPermissions = await this.permissionModel.findAll({
|
||||
const menus = await this.menuModel.findAll({
|
||||
where: { menuType: { [Op.in]: [1, 2] } },
|
||||
order: [["sort_order", "ASC"], ["id", "ASC"]],
|
||||
});
|
||||
|
||||
const permissionTree = this.buildPermissionTree(
|
||||
allPermissions.map(p => p.toJSON()),
|
||||
assignedPermissionIds
|
||||
const menuTree = this.buildMenuTree(
|
||||
menus.map(m => m.toJSON()),
|
||||
assignedMenuIds
|
||||
);
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "获取角色权限成功",
|
||||
data: permissionTree,
|
||||
message: "获取角色菜单成功",
|
||||
data: {
|
||||
menuTree,
|
||||
menuIds: assignedMenuIds,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async updatePermissions(roleId: number, permissionIds: number[]): Promise<any> {
|
||||
async updateMenus(roleId: number, menuIds: number[]): Promise<any> {
|
||||
const role = await this.roleModel.findByPk(roleId);
|
||||
|
||||
if (!role) {
|
||||
@@ -256,47 +251,57 @@ export class RoleService {
|
||||
};
|
||||
}
|
||||
|
||||
await this.rolePermissionModel.destroy({
|
||||
await this.roleMenuModel.destroy({
|
||||
where: { roleId },
|
||||
});
|
||||
|
||||
if (permissionIds && permissionIds.length > 0) {
|
||||
const rolePermissions = permissionIds.map(permissionId => ({
|
||||
if (menuIds && menuIds.length > 0) {
|
||||
const roleMenus = menuIds.map(menuId => ({
|
||||
roleId,
|
||||
permissionId,
|
||||
menuId,
|
||||
}));
|
||||
|
||||
await this.rolePermissionModel.bulkCreate(rolePermissions);
|
||||
await this.roleMenuModel.bulkCreate(roleMenus);
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "权限更新成功",
|
||||
message: "菜单更新成功",
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
|
||||
private buildPermissionTree(permissions: any[], checkedIds: number[]): any[] {
|
||||
const map = new Map<number, any>();
|
||||
private buildMenuTree(menus: any[], checkedIds: number[]): any[] {
|
||||
const nodeMap = new Map<number, any>();
|
||||
const menuIdSet = new Set(menus.map(m => m.id));
|
||||
const tree: any[] = [];
|
||||
|
||||
for (const perm of permissions) {
|
||||
map.set(perm.id, {
|
||||
...perm,
|
||||
for (const menu of menus) {
|
||||
nodeMap.set(menu.id, {
|
||||
...menu,
|
||||
children: [],
|
||||
checked: checkedIds.includes(perm.id),
|
||||
checked: checkedIds.includes(menu.id),
|
||||
});
|
||||
}
|
||||
|
||||
for (const perm of permissions) {
|
||||
const node = map.get(perm.id)!;
|
||||
if (perm.parent_id && map.has(perm.parent_id)) {
|
||||
map.get(perm.parent_id)!.children.push(node);
|
||||
for (const menu of menus) {
|
||||
const node = nodeMap.get(menu.id)!;
|
||||
const parent = menu.parentId ? nodeMap.get(menu.parentId) : undefined;
|
||||
|
||||
// 仅当父节点为一级节点时挂为其子节点,保证输出两级树;
|
||||
// 父节点不在列表或父节点本身是二级节点时,提升为一级节点
|
||||
if (parent && !menuIdSet.has(parent.parentId)) {
|
||||
parent.children.push(node);
|
||||
} else {
|
||||
tree.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
// 一级节点已在查询时按 sort_order 排序,此处对子节点排序
|
||||
for (const node of tree) {
|
||||
node.children.sort((a: any, b: any) => a.sortOrder - b.sortOrder);
|
||||
}
|
||||
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import { Provide } from '@midwayjs/core';
|
||||
|
||||
@Provide()
|
||||
export class SystemService {
|
||||
private settingsCache: Map<string, any> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.initDefaultSettings();
|
||||
}
|
||||
|
||||
private initDefaultSettings() {
|
||||
this.settingsCache.set('basic', {
|
||||
siteName: '智能物联网设备管理系统',
|
||||
logo: '',
|
||||
sessionTimeout: 120,
|
||||
});
|
||||
|
||||
this.settingsCache.set('security', {
|
||||
passwordMinLength: 6,
|
||||
maxLoginAttempts: 5,
|
||||
lockoutDuration: 30,
|
||||
ipWhitelist: [] as string[],
|
||||
});
|
||||
|
||||
this.settingsCache.set('notification', {
|
||||
enableEmail: false,
|
||||
emailSmtpConfig: null as any,
|
||||
});
|
||||
}
|
||||
|
||||
async getSettings(): Promise<any> {
|
||||
return {
|
||||
code: 200,
|
||||
message: '获取系统设置成功',
|
||||
data: {
|
||||
basic: this.settingsCache.get('basic'),
|
||||
security: this.settingsCache.get('security'),
|
||||
notification: this.settingsCache.get('notification'),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async updateBasicSettings(data: any): Promise<any> {
|
||||
const current = this.settingsCache.get('basic') || {};
|
||||
this.settingsCache.set('basic', { ...current, ...data });
|
||||
return {
|
||||
code: 200,
|
||||
message: '基本设置保存成功',
|
||||
};
|
||||
}
|
||||
|
||||
async updateSecuritySettings(data: any): Promise<any> {
|
||||
const current = this.settingsCache.get('security') || {};
|
||||
this.settingsCache.set('security', { ...current, ...data });
|
||||
return {
|
||||
code: 200,
|
||||
message: '安全设置保存成功',
|
||||
};
|
||||
}
|
||||
|
||||
async updateNotificationSettings(data: any): Promise<any> {
|
||||
const current = this.settingsCache.get('notification') || {};
|
||||
this.settingsCache.set('notification', { ...current, ...data });
|
||||
return {
|
||||
code: 200,
|
||||
message: '通知设置保存成功',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
import { Provide, Inject } from "@midwayjs/core";
|
||||
import { InjectRepository } from "@midwayjs/sequelize";
|
||||
import { Repository } from "sequelize-typescript";
|
||||
import User from "../entity/user.entity";
|
||||
import UserRole from "../entity/userRole.entity";
|
||||
import Role from "../entity/role.entity";
|
||||
import SysUser from "../entity/sysUser.entity";
|
||||
import SysUserRole from "../entity/sysUserRole.entity";
|
||||
import SysRole from "../entity/sysRole.entity";
|
||||
import { Op } from "sequelize";
|
||||
import { hashPassword } from "../util/crypto";
|
||||
|
||||
@Provide()
|
||||
export class UserService {
|
||||
@InjectRepository(User)
|
||||
userModel!: Repository<User>;
|
||||
@InjectRepository(SysUser)
|
||||
userModel!: Repository<SysUser>;
|
||||
|
||||
@InjectRepository(UserRole)
|
||||
userRoleModel!: Repository<UserRole>;
|
||||
@InjectRepository(SysUserRole)
|
||||
userRoleModel!: Repository<SysUserRole>;
|
||||
|
||||
@InjectRepository(Role)
|
||||
roleModel!: Repository<Role>;
|
||||
@InjectRepository(SysRole)
|
||||
roleModel!: Repository<SysRole>;
|
||||
|
||||
async list(query: { page?: any; pageSize?: any; keyword?: string; status?: number; roleId?: number }): Promise<any> {
|
||||
const page = Number(query.page) || 1;
|
||||
@@ -24,7 +24,7 @@ export class UserService {
|
||||
const where: any = {};
|
||||
|
||||
if (query.keyword) {
|
||||
where[Op.or] = [{ username: { [Op.like]: `%${query.keyword}%` } }, { nickname: { [Op.like]: `%${query.keyword}%` } }, { phone: { [Op.like]: `%${query.keyword}%` } }];
|
||||
where[Op.or] = [{ username: { [Op.like]: `%${query.keyword}%` } }, { realName: { [Op.like]: `%${query.keyword}%` } }, { phone: { [Op.like]: `%${query.keyword}%` } }];
|
||||
}
|
||||
|
||||
if (query.status !== undefined) {
|
||||
@@ -33,11 +33,11 @@ export class UserService {
|
||||
|
||||
const includeOptions: any = [
|
||||
{
|
||||
model: UserRole,
|
||||
model: SysUserRole,
|
||||
required: !!query.roleId,
|
||||
include: [
|
||||
{
|
||||
model: Role,
|
||||
model: SysRole,
|
||||
where: query.roleId ? { id: query.roleId } : {},
|
||||
required: !!query.roleId
|
||||
}
|
||||
@@ -57,7 +57,8 @@ export class UserService {
|
||||
|
||||
const list = rows.map((user) => {
|
||||
const u = user.toJSON();
|
||||
delete u.password;
|
||||
delete u.passwordHash;
|
||||
delete u.password_hash;
|
||||
return u;
|
||||
});
|
||||
|
||||
@@ -77,8 +78,8 @@ export class UserService {
|
||||
const user = await this.userModel.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: UserRole,
|
||||
include: [Role]
|
||||
model: SysUserRole,
|
||||
include: [SysRole]
|
||||
}
|
||||
]
|
||||
});
|
||||
@@ -92,7 +93,8 @@ export class UserService {
|
||||
}
|
||||
|
||||
const userData = user.toJSON();
|
||||
delete userData.password;
|
||||
delete userData.passwordHash;
|
||||
delete userData.password_hash;
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
@@ -102,7 +104,7 @@ export class UserService {
|
||||
}
|
||||
|
||||
async create(data: any): Promise<any> {
|
||||
const { username, password, nickname, email, phone, roleIds, status = 1 } = data;
|
||||
const { username, password, realName, email, phone, roleIds, status = 1 } = data;
|
||||
|
||||
const existingUser = await this.userModel.findOne({
|
||||
where: { username }
|
||||
@@ -120,8 +122,8 @@ export class UserService {
|
||||
|
||||
const user = await this.userModel.create({
|
||||
username,
|
||||
password: hashedPassword,
|
||||
nickname,
|
||||
passwordHash: hashedPassword,
|
||||
realName,
|
||||
email,
|
||||
phone,
|
||||
status
|
||||
@@ -134,14 +136,15 @@ export class UserService {
|
||||
const createdUser = await this.userModel.findByPk(user.id, {
|
||||
include: [
|
||||
{
|
||||
model: UserRole,
|
||||
include: [Role]
|
||||
model: SysUserRole,
|
||||
include: [SysRole]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const userData = createdUser!.toJSON();
|
||||
delete userData.password;
|
||||
delete userData.passwordHash;
|
||||
delete userData.password_hash;
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
@@ -163,14 +166,13 @@ export class UserService {
|
||||
|
||||
const updateData: any = {};
|
||||
|
||||
if (data.nickname !== undefined) updateData.nickname = data.nickname;
|
||||
if (data.realName !== undefined) updateData.realName = data.realName;
|
||||
if (data.email !== undefined) updateData.email = data.email;
|
||||
if (data.phone !== undefined) updateData.phone = data.phone;
|
||||
if (data.avatar !== undefined) updateData.avatar = data.avatar;
|
||||
if (data.status !== undefined) updateData.status = data.status;
|
||||
|
||||
if (data.password) {
|
||||
updateData.password = await hashPassword(data.password);
|
||||
updateData.passwordHash = await hashPassword(data.password);
|
||||
}
|
||||
|
||||
await user.update(updateData);
|
||||
@@ -182,14 +184,15 @@ export class UserService {
|
||||
const updatedUser = await this.userModel.findByPk(id, {
|
||||
include: [
|
||||
{
|
||||
model: UserRole,
|
||||
include: [Role]
|
||||
model: SysUserRole,
|
||||
include: [SysRole]
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const userData = updatedUser!.toJSON();
|
||||
delete userData.password;
|
||||
delete userData.passwordHash;
|
||||
delete userData.password_hash;
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
@@ -198,7 +201,7 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<any> {
|
||||
async delete(id: string, currentUserId?: number): Promise<any> {
|
||||
const user = await this.userModel.findByPk(id);
|
||||
|
||||
if (!user) {
|
||||
@@ -209,6 +212,22 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
if (Number(id) === 1) {
|
||||
return {
|
||||
code: 400,
|
||||
message: "内置管理员账号不允许删除",
|
||||
data: null
|
||||
};
|
||||
}
|
||||
|
||||
if (currentUserId && Number(id) === Number(currentUserId)) {
|
||||
return {
|
||||
code: 400,
|
||||
message: "不能删除当前登录账号",
|
||||
data: null
|
||||
};
|
||||
}
|
||||
|
||||
await this.userRoleModel.destroy({
|
||||
where: { userId: id }
|
||||
});
|
||||
@@ -222,7 +241,7 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
async batchDelete(ids: number[]): Promise<any> {
|
||||
async batchDelete(ids: number[], currentUserId?: number): Promise<any> {
|
||||
if (!ids || ids.length === 0) {
|
||||
return {
|
||||
code: 400,
|
||||
@@ -231,6 +250,22 @@ export class UserService {
|
||||
};
|
||||
}
|
||||
|
||||
if (ids.includes(1)) {
|
||||
return {
|
||||
code: 400,
|
||||
message: "内置管理员账号不允许删除",
|
||||
data: null
|
||||
};
|
||||
}
|
||||
|
||||
if (currentUserId && ids.includes(Number(currentUserId))) {
|
||||
return {
|
||||
code: 400,
|
||||
message: "不能删除当前登录账号",
|
||||
data: null
|
||||
};
|
||||
}
|
||||
|
||||
await this.userRoleModel.destroy({
|
||||
where: { userId: ids }
|
||||
});
|
||||
@@ -260,7 +295,8 @@ export class UserService {
|
||||
await user.update({ status });
|
||||
|
||||
const userData = user.toJSON();
|
||||
delete userData.password;
|
||||
delete userData.passwordHash;
|
||||
delete userData.password_hash;
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
@@ -271,18 +307,17 @@ export class UserService {
|
||||
|
||||
async getRoleList(): Promise<any> {
|
||||
const roles = await this.roleModel.findAll({
|
||||
where: { status: 1 },
|
||||
attributes: ["id", "roleCode", "roleName"],
|
||||
order: [
|
||||
["sort_order", "ASC"],
|
||||
["created_at", "DESC"]
|
||||
],
|
||||
raw: true
|
||||
]
|
||||
});
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
message: "获取角色列表成功",
|
||||
data: roles
|
||||
data: roles.map((role) => role.toJSON())
|
||||
};
|
||||
}
|
||||
|
||||
@@ -298,4 +333,4 @@ export class UserService {
|
||||
|
||||
await this.userRoleModel.bulkCreate(userRoles);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user