refactor: 系统权限模块重构,迁移至PostgreSQL并对齐新设计

- 切换数据库为PostgreSQL,更新依赖与配置
- 重构权限体系,使用sys_user/sys_role/sys_menu等新实体替代旧模型
- 移除旧的用户角色/权限/系统设置相关代码与实体
- 新增字典、菜单管理模块与基础CRUD接口
- 优化鉴权逻辑,基于菜单权限标识进行接口校验
- 前端同步更新API、路由与权限控制逻辑
- 删除冗余的v-permission指令与相关代码
This commit is contained in:
andyesfly committed 2026-08-19 14:11:34 +08:00
1 parent 98c22f6afb
commit 0a8103f33d
67 files changed
+3263 -1924

No files matched your search

+27 -47
View File
@@ -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()];
}
}
+7 -10
View File
@@ -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) {
+145
View File
@@ -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,
};
}
}
+29 -26
View File
@@ -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) {
+158
View File
@@ -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;
}
}
+79 -91
View File
@@ -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;
}
}
+65 -60
View File
@@ -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;
}
}
-69
View File
@@ -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: '通知设置保存成功',
};
}
}
+71 -36
View File
@@ -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);
}
}
}