Просмотр исходного кода

♻️ refactor(users): 重构用户路由为通用CRUD模式

- 使用createCrudRoutes重构用户路由,替代手动路由注册
- 添加权限控制配置,包括create/read/update/delete权限
- 集成认证中间件和用户追踪功能

✨ feat(users): 增强用户实体功能

- 添加createdBy和updatedBy字段用于追踪创建和更新用户
- 定义CreateUserDto和UpdateUserDto数据传输对象
- 扩展UserSchema,包含新添加的追踪字段
yourname 7 месяцев назад
Родитель
Сommit
d536197381
2 измененных файлов с 107 добавлено и 14 удалено
  1. 24 13
      src/server/api/users/index.ts
  2. 83 1
      src/server/modules/users/user.entity.ts

+ 24 - 13
src/server/api/users/index.ts

@@ -1,15 +1,26 @@
-import { OpenAPIHono } from '@hono/zod-openapi';
-import listUsersRoute from './get';
-import createUserRoute from './post';
-import getUserByIdRoute from './[id]/get';
-import updateUserRoute from './[id]/put';
-import deleteUserRoute from './[id]/delete';
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { UserEntity, UserSchema, CreateUserDto, UpdateUserDto } from '@/server/modules/users/user.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
 
-const app = new OpenAPIHono()
-  .route('/', listUsersRoute)
-  .route('/', createUserRoute)
-  .route('/', getUserByIdRoute)
-  .route('/', updateUserRoute)
-  .route('/', deleteUserRoute);
+// 使用权限配置的示例
+const userRoutes = createCrudRoutes({
+  entity: UserEntity,
+  createSchema: CreateUserDto,
+  updateSchema: UpdateUserDto,
+  getSchema: UserSchema,
+  listSchema: UserSchema,
+  searchFields: ['username', 'email', 'phone'],
+  middleware: [authMiddleware],
+  permissions: {
+    create: ['system:user:create'],
+    read: ['system:user:view:all'],
+    update: ['system:user:update'],
+    delete: ['system:user:delete']
+  },
+  userTracking: {
+    createdByField: 'createdBy',
+    updatedByField: 'updatedBy'
+  }
+});
 
-export default app;
+export default userRoutes;

+ 83 - 1
src/server/modules/users/user.entity.ts

@@ -56,6 +56,12 @@ export class UserEntity {
   @UpdateDateColumn({ name: 'updated_at', type: 'timestamp' })
   updatedAt!: Date;
 
+  @Column({ name: 'created_by', type: 'int', nullable: true, comment: '创建用户ID' })
+  createdBy?: number;
+
+  @Column({ name: 'updated_by', type: 'int', nullable: true, comment: '更新用户ID' })
+  updatedBy?: number;
+
   constructor(partial?: Partial<UserEntity>) {
     Object.assign(this, partial);
   }
@@ -108,5 +114,81 @@ export const UserSchema = z.object({
   defaultDepartmentId: z.number().int().positive().nullable().openapi({ description: '默认部门ID', example: 1 }),
   dataScopeType: z.enum([DataScopeType.PERSONAL, DataScopeType.DEPARTMENT, DataScopeType.SUB_DEPARTMENT, DataScopeType.COMPANY, DataScopeType.CUSTOM]).default(DataScopeType.PERSONAL).openapi({ description: '数据范围类型', example: DataScopeType.PERSONAL }),
   createdAt: z.date().openapi({ description: '创建时间' }),
-  updatedAt: z.date().openapi({ description: '更新时间' })
+  updatedAt: z.date().openapi({ description: '更新时间' }),
+  createdBy: z.number().int().positive().nullable().openapi({ description: '创建用户ID', example: 1 }),
+  updatedBy: z.number().int().positive().nullable().openapi({ description: '更新用户ID', example: 1 })
+});
+
+export const CreateUserDto = z.object({
+  username: z.string().min(3).max(255).openapi({
+    example: 'admin',
+    description: '用户名,3-255个字符'
+  }),
+  password: z.string().min(6).max(255).openapi({
+    example: 'password123',
+    description: '密码,最少6位'
+  }),
+  phone: z.string().max(255).nullable().openapi({
+    example: '13800138000',
+    description: '手机号'
+  }),
+  email: z.string().email().max(255).nullable().openapi({
+    example: 'user@example.com',
+    description: '邮箱'
+  }),
+  nickname: z.string().max(255).nullable().openapi({
+    example: '昵称',
+    description: '用户昵称'
+  }),
+  name: z.string().max(255).nullable().openapi({
+    example: '张三',
+    description: '真实姓名'
+  }),
+  avatar: z.string().max(255).nullable().openapi({
+    example: 'https://example.com/avatar.jpg',
+    description: '用户头像'
+  }),
+  isDisabled: z.coerce.number().int().min(0).max(1).default(DisabledStatus.ENABLED).openapi({
+    example: DisabledStatus.ENABLED,
+    description: '是否禁用(0:启用,1:禁用)'
+  }),
+  defaultDepartmentId: z.coerce.number().int().positive().nullable().openapi({ description: '默认部门ID', example: 1 }),
+  dataScopeType: z.enum([DataScopeType.PERSONAL, DataScopeType.DEPARTMENT, DataScopeType.SUB_DEPARTMENT, DataScopeType.COMPANY, DataScopeType.CUSTOM]).default(DataScopeType.PERSONAL).openapi({ description: '数据范围类型', example: DataScopeType.PERSONAL })
+});
+
+export const UpdateUserDto = z.object({
+  username: z.string().min(3).max(255).optional().openapi({
+    example: 'admin',
+    description: '用户名,3-255个字符'
+  }),
+  password: z.string().min(6).max(255).optional().openapi({
+    example: 'password123',
+    description: '密码,最少6位'
+  }),
+  phone: z.string().max(255).nullable().optional().openapi({
+    example: '13800138000',
+    description: '手机号'
+  }),
+  email: z.string().email().max(255).nullable().optional().openapi({
+    example: 'user@example.com',
+    description: '邮箱'
+  }),
+  nickname: z.string().max(255).nullable().optional().openapi({
+    example: '昵称',
+    description: '用户昵称'
+  }),
+  name: z.string().max(255).nullable().optional().openapi({
+    example: '张三',
+    description: '真实姓名'
+  }),
+  avatar: z.string().max(255).nullable().optional().openapi({
+    example: 'https://example.com/avatar.jpg',
+    description: '用户头像'
+  }),
+  isDisabled: z.coerce.number().int().min(0).max(1).optional().openapi({
+    example: DisabledStatus.ENABLED,
+    description: '是否禁用(0:启用,1:禁用)'
+  }),
+  defaultDepartmentId: z.coerce.number().int().positive().nullable().optional().openapi({ description: '默认部门ID', example: 1 }),
+  dataScopeType: z.enum([DataScopeType.PERSONAL, DataScopeType.DEPARTMENT, DataScopeType.SUB_DEPARTMENT, DataScopeType.COMPANY, DataScopeType.CUSTOM]).optional().openapi({ description: '数据范围类型', example: DataScopeType.PERSONAL })
 });