import { createTRPCRouter, permissionRequiredProcedure } from '@/server/trpc' import { Permissions } from '@/constants/permissions' import { createUserSchema, updateUserSchema, changePasswordSchema } from '@/lib/schema/user' import { dataTableQueryParamsSchema } from '@/lib/schema/data-table' import { transformDataTableQueryParams } from '@/server/utils/data-table-helper' import bcrypt from 'bcryptjs' import { z } from 'zod' import { inferProcedureOutput, TRPCError } from '@trpc/server' import pLimit from 'p-limit' // 从环境变量获取并发限制,默认为16 const dbParallelLimit = pLimit(parseInt(process.env.DB_PARALLEL_LIMIT || '16', 10)) export const usersRouter = createTRPCRouter({ list: permissionRequiredProcedure(Permissions.USER_MANAGE) .input(dataTableQueryParamsSchema) .query(async ({ ctx, input }) => { const { where, orderBy, skip, take } = transformDataTableQueryParams(input, { model: 'User', columns: { id: { field: 'id', variant: 'text', sortable: true }, name: { field: 'name', variant: 'text', sortable: true }, status: { field: 'status', variant: 'select', sortable: true }, dept: { field: 'deptCode', variant: 'multiSelect', sortable: true }, roles: { field: 'roles.some.id', variant: 'select', transform: Number }, permissions: { field: 'roles.some.permissions.some.id', variant: 'select', transform: Number }, lastLoginAt: { field: 'lastLoginAt', sortable: true }, createdAt: { field: 'createdAt', sortable: true } }, }) const [data, total] = await Promise.all([ ctx.db.user.findMany({ where, orderBy: orderBy.some(item => 'id' in item) ? orderBy : [...orderBy, { id: 'asc' }], skip, take, include: { roles: { include: { permissions: true } }, dept: true }, }), ctx.db.user.count({ where }), ]) return { data, total } }), getRoles: permissionRequiredProcedure(Permissions.USER_MANAGE).query(({ ctx }) => ctx.db.role.findMany({ orderBy: { name: 'asc' } }) ), getPermissions: permissionRequiredProcedure(Permissions.USER_MANAGE).query(({ ctx }) => ctx.db.permission.findMany({ orderBy: { name: 'asc' } }) ), // 角色管理相关API getRolesWithStats: permissionRequiredProcedure(Permissions.USER_MANAGE).query(async ({ ctx }) => { const roles = await ctx.db.role.findMany({ include: { permissions: true, _count: { select: { users: true } } }, orderBy: { id: 'asc' } }) return roles.map((role) => ({ id: role.id, name: role.name, userCount: role._count.users, permissions: role.permissions })) }), createRole: permissionRequiredProcedure(Permissions.USER_MANAGE) .input(z.object({ name: z.string().min(1, '角色名称不能为空'), permissionIds: z.array(z.number()) })) .mutation(async ({ ctx, input }) => { const { name, permissionIds } = input // 检查角色名是否已存在 const existingRole = await ctx.db.role.findUnique({ where: { name } }) if (existingRole) throw new TRPCError({ code: 'BAD_REQUEST', message: '角色名称已存在' }) return ctx.db.role.create({ data: { name, permissions: { connect: permissionIds.map(id => ({ id })) } }, include: { permissions: true, _count: { select: { users: true } } } }) }), updateRole: permissionRequiredProcedure(Permissions.USER_MANAGE) .input(z.object({ id: z.number(), name: z.string().min(1, '角色名称不能为空'), permissionIds: z.array(z.number()) })) .mutation(async ({ ctx, input }) => { const { id, name, permissionIds } = input // 检查角色是否存在 const existingRole = await ctx.db.role.findUnique({ where: { id } }) if (!existingRole) throw new TRPCError({ code: 'NOT_FOUND', message: '角色不存在' }) // 检查角色名是否被其他角色占用 const roleWithSameName = await ctx.db.role.findUnique({ where: { name } }) if (roleWithSameName && roleWithSameName.id !== id) { throw new TRPCError({ code: 'BAD_REQUEST', message: '角色名称已被其他角色使用' }) } return ctx.db.role.update({ where: { id }, data: { name, permissions: { set: permissionIds.map(permissionId => ({ id: permissionId })) } }, include: { permissions: true, _count: { select: { users: true } } } }) }), deleteRole: permissionRequiredProcedure(Permissions.USER_MANAGE) .input(z.object({ id: z.number() })) .mutation(async ({ ctx, input }) => { const { id } = input // 检查角色是否存在 const existingRole = await ctx.db.role.findUnique({ where: { id }, include: { _count: { select: { users: true } } } }) if (!existingRole) throw new TRPCError({ code: 'NOT_FOUND', message: '角色不存在' }) // 检查是否有用户使用该角色 if (existingRole._count.users > 0) { throw new TRPCError({ code: 'BAD_REQUEST', message: '该角色还有用户在使用,无法删除' }) } return ctx.db.role.delete({ where: { id }, include: { permissions: true, _count: { select: { users: true } } } }) }), batchUpdateRole: permissionRequiredProcedure(Permissions.USER_MANAGE) .input(z.object({ roleId: z.number(), deptCodes: z.array(z.string()).optional(), action: z.enum(['grant', 'revoke']) })) .mutation(async ({ ctx, input }) => { const { roleId, deptCodes, action } = input // 检查角色是否存在 const role = await ctx.db.role.findUnique({ where: { id: roleId } }) if (!role) throw new TRPCError({ code: 'NOT_FOUND', message: '角色不存在' }) // 构建查询条件 const where: any = {} if (deptCodes && deptCodes.length > 0) { where.deptCode = { in: deptCodes } } // 获取符合条件的用户 const users = await ctx.db.user.findMany({ where, select: { id: true } }) // 分批处理,每批1000个用户 const batchSize = 1000 let processedCount = 0 for (let i = 0; i < users.length; i += batchSize) { const batch = users.slice(i, i + batchSize) await Promise.all( batch.map(user => dbParallelLimit(() => ctx.db.user.update({ where: { id: user.id }, data: { roles: action === 'grant' ? { connect: { id: roleId } } : { disconnect: { id: roleId } } } }) ) ) ) processedCount += batch.length } return { count: processedCount } }), create: permissionRequiredProcedure(Permissions.USER_MANAGE).input(createUserSchema).mutation(async ({ ctx, input }) => { const { id, name, status, deptCode, password, roleIds, isSuperAdmin } = input // 检查是否尝试创建超级管理员,只有超级管理员才能创建超级管理员 if (isSuperAdmin && !ctx.session?.user?.isSuperAdmin) { throw new TRPCError({ code: 'FORBIDDEN', message: '只有超级管理员才能创建超级管理员用户' }) } const existingUser = await ctx.db.user.findUnique({ where: { id } }) if (existingUser) throw new TRPCError({ code: 'BAD_REQUEST', message: '用户ID已存在' }) const hashedPassword = await bcrypt.hash(password, 12) return ctx.db.user.create({ data: { id, name: name?.trim() || '', status: status?.trim() || null, deptCode: deptCode?.trim() || null, password: hashedPassword, isSuperAdmin, roles: { connect: roleIds.map(id => ({ id })) } }, include: { roles: { include: { permissions: true } }, dept: true } }) }), update: permissionRequiredProcedure(Permissions.USER_MANAGE).input(updateUserSchema).mutation(async ({ ctx, input }) => { const { id, name, status, deptCode, password, roleIds, isSuperAdmin } = input // 检查用户是否存在 const existingUser = await ctx.db.user.findUnique({ where: { id } }) if (!existingUser) throw new TRPCError({ code: 'NOT_FOUND', message: '用户不存在' }) // 检查是否尝试修改 isSuperAdmin 字段,只有超级管理员才能操作 if (isSuperAdmin !== existingUser.isSuperAdmin && !ctx.session?.user?.isSuperAdmin) { throw new TRPCError({ code: 'FORBIDDEN', message: '只有超级管理员才能修改超级管理员权限' }) } // 准备更新数据 const updateData: any = { name: name?.trim() || '', status: status?.trim() || null, deptCode: deptCode?.trim() || null, isSuperAdmin, roles: { set: roleIds.map(roleId => ({ id: roleId })) } } // 如果提供了密码,则更新密码 if (password && password.trim()) { updateData.password = await bcrypt.hash(password, 12) } return ctx.db.user.update({ where: { id }, data: updateData, include: { roles: { include: { permissions: true } }, dept: true } }) }), getById: permissionRequiredProcedure(Permissions.USER_MANAGE).input(z.object({ id: z.string() })).query(async ({ ctx, input }) => { const user = await ctx.db.user.findUnique({ where: { id: input.id }, include: { roles: { include: { permissions: true } }, dept: true } }) if (!user) throw new TRPCError({ code: 'NOT_FOUND', message: '用户不存在' }) return user }), delete: permissionRequiredProcedure(Permissions.USER_MANAGE).input(z.object({ id: z.string() })).mutation(async ({ ctx, input }) => { const { id } = input // 检查用户是否存在 const existingUser = await ctx.db.user.findUnique({ where: { id } }) if (!existingUser) throw new TRPCError({ code: 'NOT_FOUND', message: '用户不存在' }) // 防止用户删除自己 if (ctx.session?.user?.id === id) { throw new TRPCError({ code: 'BAD_REQUEST', message: '不能删除自己的账户' }) } // 删除用户 return ctx.db.user.delete({ where: { id }, include: { roles: { include: { permissions: true } }, dept: true } }) }), changePassword: permissionRequiredProcedure('') .input(changePasswordSchema) .mutation(async ({ ctx, input }) => { const { oldPassword, newPassword } = input // 获取当前用户ID const userId = ctx.session?.user?.id if (!userId) { throw new TRPCError({ code: 'UNAUTHORIZED', message: '未登录' }) } // 获取用户信息 const user = await ctx.db.user.findUnique({ where: { id: userId } }) if (!user) { throw new TRPCError({ code: 'NOT_FOUND', message: '用户不存在' }) } // 验证旧密码 const isValidPassword = await bcrypt.compare(oldPassword, user.password) if (!isValidPassword) { throw new TRPCError({ code: 'BAD_REQUEST', message: '旧密码错误' }) } // 更新密码 const hashedPassword = await bcrypt.hash(newPassword, 12) await ctx.db.user.update({ where: { id: userId }, data: { password: hashedPassword } }) return { success: true } }), // 获取当前用户的完整资料 getCurrentUserProfile: permissionRequiredProcedure('') .query(async ({ ctx }) => { // 获取当前用户ID const userId = ctx.session?.user?.id if (!userId) { throw new TRPCError({ code: 'UNAUTHORIZED', message: '未登录' }) } // 获取用户完整信息 const user = await ctx.db.user.findUnique({ where: { id: userId }, include: { roles: { include: { permissions: true } }, dept: true } }) if (!user) { throw new TRPCError({ code: 'NOT_FOUND', message: '用户不存在' }) } return { id: user.id, name: user.name, status: user.status, deptCode: user.deptCode, deptName: user.dept?.name || null, deptFullName: user.dept?.fullName || null, isSuperAdmin: user.isSuperAdmin, lastLoginAt: user.lastLoginAt, createdAt: user.createdAt, roles: user.roles.map(role => ({ id: role.id, name: role.name, })), permissions: Array.from( new Set(user.roles.flatMap(role => role.permissions.map(p => ({ id: p.id, name: p.name, })) )) ).sort((a, b) => a.id - b.id), } }), // 获取当前用户可管理的院系列表和正在管理的院系 getManagedDepts: permissionRequiredProcedure('') .query(async ({ ctx }) => { const userId = ctx.session!.user.id // 获取用户当前信息 const currentUser = await ctx.db.user.findUnique({ where: { id: userId }, select: { currentManagedDept: true } }) let depts: Array<{ code: string; name: string; fullName: string }> // 超级管理员可以管理所有院系 if (ctx.session?.user?.isSuperAdmin) { depts = await ctx.db.dept.findMany({ orderBy: { code: 'asc' } }) } else { // 普通用户只能管理自己被授权的院系 const deptAdmins = await ctx.db.deptAdmin.findMany({ where: { uid: userId }, include: { dept: true }, orderBy: { deptCode: 'asc' } }) depts = deptAdmins.map(da => da.dept) } // 如果用户当前没有管理院系,但有可管理的院系,自动设置为第一个 let currentDept = currentUser?.currentManagedDept if (!currentDept && depts.length > 0) { currentDept = depts[0].code await ctx.db.user.update({ where: { id: userId }, data: { currentManagedDept: currentDept } }) } return { currentDept, depts } }), // 切换当前管理的院系 switchManagedDept: permissionRequiredProcedure('') .input(z.object({ deptCode: z.string().nullable() })) .mutation(async ({ ctx, input }) => { const { deptCode } = input // 如果要切换到某个院系,需要验证权限 if (deptCode) { // 超级管理员可以切换到任意院系 if (!ctx.session?.user?.isSuperAdmin) { // 普通用户需要验证是否有该院系的管理权限 const deptAdmin = await ctx.db.deptAdmin.findUnique({ where: { uidx_uid_dept_code: { uid: ctx.session!.user.id, deptCode: deptCode } } }) if (!deptAdmin) { throw new TRPCError({ code: 'FORBIDDEN', message: '您没有该院系的管理权限' }) } } // 验证院系是否存在 const dept = await ctx.db.dept.findUnique({ where: { code: deptCode } }) if (!dept) { throw new TRPCError({ code: 'NOT_FOUND', message: '院系不存在' }) } } // 更新用户的当前管理院系 await ctx.db.user.update({ where: { id: ctx.session!.user.id }, data: { currentManagedDept: deptCode } }) return { success: true, deptCode } }) }) export type UsersRouter = typeof usersRouter; export type User = inferProcedureOutput['data'][number]; export type UserProfile = inferProcedureOutput;