瀏覽代碼

用户接口优化

wuyi 3 天之前
父節點
當前提交
7f2bf4a6ee
共有 4 個文件被更改,包括 173 次插入43 次删除
  1. 64 6
      src/controllers/user.controller.ts
  2. 2 1
      src/dto/user.dto.ts
  3. 12 8
      src/routes/user.routes.ts
  4. 95 28
      src/services/user.service.ts

+ 64 - 6
src/controllers/user.controller.ts

@@ -108,10 +108,25 @@ export class UserController {
           message: 'Unauthorized access'
         })
       }
-      const parentId = user.id
-      const { page, size } = query
 
-      const result = await this.userService.findAllChildUsers(parentId, page, size)
+      const { id, name, page, size } = query
+
+      let result
+
+      // ADMIN 可以查询所有用户
+      if (user.role === UserRole.ADMIN) {
+        result = await this.userService.findAllUsersWithFilter(page, size, id, name)
+      }
+      // MANAGER 只能查询自己下面的用户
+      else if (user.role === UserRole.MANAGER) {
+        const parentId = user.id
+        result = await this.userService.findAllChildUsers(parentId, page, size, undefined, name)
+      } else {
+        return reply.code(403).send({
+          error: 'Forbidden',
+          message: 'Access denied. Insufficient Permissions.'
+        })
+      }
 
       return reply.send(result)
     } catch (error) {
@@ -127,13 +142,29 @@ export class UserController {
   async createUser(request: FastifyRequest<{ Body: CreateUserBody }>, reply: FastifyReply) {
     try {
       const { password, name, role } = request.body
+      const currentUser = request.user
 
       const existingUser = await this.userService.findByName(name)
       if (existingUser) {
         return reply.code(400).send({ message: '用户名已存在' })
       }
 
-      const user = await this.userService.create(password, name, role, request.user.id)
+      // 根据当前用户角色决定创建用户的角色
+      let userRole: UserRole
+      if (currentUser.role === UserRole.ADMIN) {
+        // ADMIN 可以创建任意角色
+        userRole = role || UserRole.USER
+      } else if (currentUser.role === UserRole.MANAGER) {
+        // MANAGER 只能创建 USER 角色
+        userRole = UserRole.USER
+      } else {
+        return reply.code(403).send({
+          error: 'Forbidden',
+          message: 'Access denied. Insufficient Permissions.'
+        })
+      }
+
+      const user = await this.userService.create(password, name, userRole, currentUser.id)
 
       return reply.code(201).send({
         user: {
@@ -152,13 +183,17 @@ export class UserController {
   async updateUser(request: FastifyRequest<{ Body: UpdateUserBody }>, reply: FastifyReply) {
     try {
       const { id, name, password, role } = request.body
+      const currentUser = request.user
 
+      // 检查目标用户是否存在
+      let targetUser
       try {
-        await this.userService.findById(id)
+        targetUser = await this.userService.findById(id)
       } catch (error) {
         return reply.code(404).send({ message: '用户不存在' })
       }
 
+      // 检查用户名是否重复
       if (name) {
         const existingUser = await this.userService.findByName(name)
         if (existingUser && existingUser.id !== id) {
@@ -166,7 +201,30 @@ export class UserController {
         }
       }
 
-      const updatedUser = await this.userService.updateUser(id, { name, password, role })
+      let updateData: Partial<typeof targetUser>
+
+      if (currentUser.role === UserRole.ADMIN) {
+        // ADMIN 可以修改所有字段
+        updateData = { name, password, role }
+      } else if (currentUser.role === UserRole.MANAGER) {
+        // MANAGER 只能修改自己下面用户的 name 和 password
+        const isChildUser = await this.userService.isChildUser(currentUser.id, id)
+        if (!isChildUser) {
+          return reply.code(403).send({
+            error: 'Forbidden',
+            message: 'You can only update your child users.'
+          })
+        }
+        // MANAGER 只能修改 name 和 password,不能修改 role
+        updateData = { name, password }
+      } else {
+        return reply.code(403).send({
+          error: 'Forbidden',
+          message: 'Access denied. Insufficient Permissions.'
+        })
+      }
+
+      const updatedUser = await this.userService.updateUser(id, updateData)
 
       return reply.send({
         user: {

+ 2 - 1
src/dto/user.dto.ts

@@ -17,7 +17,8 @@ export interface ResetPasswordBody {
 }
 
 export interface ListUserQuery extends Pagination {
-  role?: UserRole | UserRole[]
+  id?: number
+  name?: string
 }
 
 export interface CreateUserBody {

+ 12 - 8
src/routes/user.routes.ts

@@ -1,22 +1,26 @@
 import { FastifyInstance } from 'fastify'
 import { UserController } from '../controllers/user.controller'
-import { authenticate, hasRole } from '../middlewares/auth.middleware'
+import { authenticate, hasAnyRole, hasRole } from '../middlewares/auth.middleware'
 import { ResetPasswordBody, ListUserQuery, CreateUserBody, UpdateUserBody } from '../dto/user.dto'
 import { UserRole } from '../entities/user.entity'
 export default async function userRoutes(fastify: FastifyInstance) {
   const userController = new UserController(fastify)
 
   fastify.post('/register', userController.register.bind(userController))
+
   fastify.post('/login', userController.login.bind(userController))
+
   fastify.get('/profile', { onRequest: [authenticate] }, userController.profile.bind(userController))
+
   fastify.post<{ Body: ResetPasswordBody }>(
     '/reset-password',
     { onRequest: [authenticate] },
     userController.resetPassword.bind(userController)
   )
+
   fastify.get<{ Querystring: ListUserQuery }>(
     '/',
-    { onRequest: [hasRole(UserRole.ADMIN)] },
+    { onRequest: [hasAnyRole(UserRole.ADMIN, UserRole.MANAGER)] },
     userController.list.bind(userController)
   )
 
@@ -26,24 +30,24 @@ export default async function userRoutes(fastify: FastifyInstance) {
     userController.getAllUsers.bind(userController)
   )
 
-  // 新增用户接口
+  // 新增用户
   fastify.post<{ Body: CreateUserBody }>(
     '/create',
-    { onRequest: [hasRole(UserRole.ADMIN)] },
+    { onRequest: [hasAnyRole(UserRole.ADMIN, UserRole.MANAGER)] },
     userController.createUser.bind(userController)
   )
 
-  // 修改用户接口
+  // 修改用户
   fastify.post<{ Body: UpdateUserBody }>(
     '/update',
-    { onRequest: [hasRole(UserRole.ADMIN)] },
+    { onRequest: [hasAnyRole(UserRole.ADMIN, UserRole.MANAGER)] },
     userController.updateUser.bind(userController)
   )
 
-  // 获取api子用户接口
+  // 获取api子用户
   fastify.get<{ Querystring: { parentId?: number } }>(
     '/children',
-    { onRequest: [hasRole(UserRole.ADMIN)] },
+    { onRequest: [hasAnyRole(UserRole.ADMIN, UserRole.MANAGER)] },
     userController.getChildApiUsers.bind(userController)
   )
 }

+ 95 - 28
src/services/user.service.ts

@@ -64,6 +64,55 @@ export class UserService {
     }
   }
 
+  async findAllUsersWithFilter(
+    page: number = 0,
+    size: number = 20,
+    id?: number,
+    name?: string
+  ): Promise<PaginationResponse<Partial<User>>> {
+    const queryBuilder = this.userRepository
+      .createQueryBuilder('user')
+      .select(['user.id', 'user.name', 'user.role', 'user.parentId', 'user.createdAt', 'user.updatedAt'])
+
+    // 按 id 筛选
+    if (id !== undefined) {
+      queryBuilder.andWhere('user.id = :id', { id })
+    }
+
+    // 按 name 精确匹配
+    if (name) {
+      queryBuilder.andWhere('user.name = :name', { name })
+    }
+
+    // 排序
+    queryBuilder.orderBy('user.id', 'ASC')
+
+    // 获取总数
+    const total = await queryBuilder.getCount()
+
+    // 分页
+    const users = await queryBuilder
+      .skip((Number(page) || 0) * (Number(size) || 20))
+      .take(Number(size) || 20)
+      .getMany()
+
+    return {
+      content: users.map(user => ({
+        id: user.id,
+        name: user.name,
+        role: user.role,
+        parentId: user.parentId,
+        createdAt: user.createdAt,
+        updatedAt: user.updatedAt
+      })),
+      metadata: {
+        total,
+        page: Number(page) || 0,
+        size: Number(size) || 20
+      }
+    }
+  }
+
   async findAllUsers(): Promise<Partial<User>[]> {
     return this.userRepository.find({
       select: ['id', 'name']
@@ -99,46 +148,64 @@ export class UserService {
     return result
   }
 
+  async isChildUser(parentId: number, targetUserId: number): Promise<boolean> {
+    // 如果目标用户就是自己,返回 true
+    if (parentId === targetUserId) {
+      return true
+    }
+
+    // 只有3层:ADMIN -> MANAGER -> USER,只需检查直接子用户
+    const targetUser = await this.userRepository.findOne({
+      select: ['id', 'parentId'],
+      where: { id: targetUserId }
+    })
+
+    return targetUser?.parentId === parentId
+  }
+
   async findAllChildUsers(
     parentId: number,
     page: number = 0,
-    size: number = 20
+    size: number = 20,
+    id?: number,
+    name?: string
   ): Promise<PaginationResponse<Partial<User>>> {
-    const result: Partial<User>[] = []
-
-    const currentUser = await this.userRepository.findOne({
-      select: ['id', 'name', 'role', 'parentId', 'createdAt', 'updatedAt'],
-      where: { id: parentId }
-    })
-
-    if (currentUser) {
-      result.push(currentUser)
+    // 查询自己以及直接子用户
+    const queryBuilder = this.userRepository
+      .createQueryBuilder('user')
+      .select(['user.id', 'user.name', 'user.role', 'user.parentId', 'user.createdAt', 'user.updatedAt'])
+      .where('(user.id = :parentId OR user.parentId = :parentId)', { parentId })
+
+    // 添加筛选条件
+    if (id !== undefined) {
+      queryBuilder.andWhere('user.id = :id', { id })
     }
 
-    const findChildren = async (pid: number) => {
-      const users = await this.userRepository.find({
-        select: ['id', 'name', 'role', 'parentId', 'createdAt', 'updatedAt'],
-        where: { parentId: pid }
-      })
-
-      for (const user of users) {
-        result.push(user)
-        await findChildren(user.id)
-      }
+    if (name) {
+      queryBuilder.andWhere('user.name = :name', { name })
     }
 
-    await findChildren(parentId)
+    // 排序
+    queryBuilder.orderBy('user.id', 'ASC')
 
-    result.sort((a, b) => (a.id || 0) - (b.id || 0))
+    // 获取总数
+    const total = await queryBuilder.getCount()
 
-    const total = result.length
-    const paginatedUsers = result.slice(
-      (Number(page) || 0) * (Number(size) || 20),
-      ((Number(page) || 0) + 1) * (Number(size) || 20)
-    )
+    // 分页
+    const users = await queryBuilder
+      .skip((Number(page) || 0) * (Number(size) || 20))
+      .take(Number(size) || 20)
+      .getMany()
 
     return {
-      content: paginatedUsers,
+      content: users.map(user => ({
+        id: user.id,
+        name: user.name,
+        role: user.role,
+        parentId: user.parentId,
+        createdAt: user.createdAt,
+        updatedAt: user.updatedAt
+      })),
       metadata: {
         total,
         page: Number(page) || 0,