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

✨ feat(crud): add user tracking functionality for CRUD operations

- add userTracking option to CrudOptions for enabling user tracking
- modify create and update methods to accept userId parameter
- implement setUserFields method to automatically set createdBy and updatedBy fields
- pass current user ID from request context to service layer
- allow customization of tracking field names through UserTrackingOptions interface
yourname 8 месяцев назад
Родитель
Сommit
163b8cffd6
2 измененных файлов с 45 добавлено и 9 удалено
  1. 6 4
      src/server/utils/generic-crud.routes.ts
  2. 39 5
      src/server/utils/generic-crud.service.ts

+ 6 - 4
src/server/utils/generic-crud.routes.ts

@@ -13,13 +13,13 @@ export function createCrudRoutes<
   GetSchema extends z.ZodSchema = z.ZodSchema,
   ListSchema extends z.ZodSchema = z.ZodSchema
 >(options: CrudOptions<T, CreateSchema, UpdateSchema, GetSchema, ListSchema>) {
-  const { entity, createSchema, updateSchema, getSchema, listSchema, searchFields, relations, middleware = [] } = options;
+  const { entity, createSchema, updateSchema, getSchema, listSchema, searchFields, relations, middleware = [], userTracking } = options;
   
   // 创建CRUD服务实例
   // 抽象类不能直接实例化,需要创建具体实现类
   class ConcreteCrudService extends GenericCrudService<T> {
     constructor() {
-      super(AppDataSource, entity);
+      super(AppDataSource, entity, { userTracking });
     }
   }
   const crudService = new ConcreteCrudService();
@@ -269,7 +269,8 @@ export function createCrudRoutes<
     .openapi(createRouteDef, async (c: any) => {
       try {
         const data = c.req.valid('json');
-        const result = await crudService.create(data);
+        const user = c.get('user');
+        const result = await crudService.create(data, user?.id);
         return c.json(result, 201);
       } catch (error) {
         if (error instanceof z.ZodError) {
@@ -305,7 +306,8 @@ export function createCrudRoutes<
       try {
         const { id } = c.req.valid('param');
         const data = c.req.valid('json');
-        const result = await crudService.update(id, data);
+        const user = c.get('user');
+        const result = await crudService.update(id, data, user?.id);
         
         if (!result) {
           return c.json({ code: 404, message: '资源不存在' }, 404);

+ 39 - 5
src/server/utils/generic-crud.service.ts

@@ -3,12 +3,17 @@ import { z } from '@hono/zod-openapi';
 
 export abstract class GenericCrudService<T extends ObjectLiteral> {
   protected repository: Repository<T>;
+  private userTrackingOptions?: UserTrackingOptions;
 
   constructor(
     protected dataSource: DataSource,
-    protected entity: new () => T
+    protected entity: new () => T,
+    options?: {
+      userTracking?: UserTrackingOptions;
+    }
   ) {
     this.repository = this.dataSource.getRepository(entity);
+    this.userTrackingOptions = options?.userTracking;
   }
 
   /**
@@ -120,19 +125,42 @@ export abstract class GenericCrudService<T extends ObjectLiteral> {
     });
   }
 
+  /**
+   * 设置用户跟踪字段
+   */
+  private setUserFields(data: any, userId?: string | number, isCreate: boolean = true): void {
+    if (!this.userTrackingOptions || !userId) {
+      return;
+    }
+
+    const { createdByField = 'createdBy', updatedByField = 'updatedBy' } = this.userTrackingOptions;
+
+    if (isCreate && createdByField) {
+      data[createdByField] = userId;
+    }
+
+    if (updatedByField) {
+      data[updatedByField] = userId;
+    }
+  }
+
   /**
    * 创建实体
    */
-  async create(data: DeepPartial<T>): Promise<T> {
-    const entity = this.repository.create(data as DeepPartial<T>);
+  async create(data: DeepPartial<T>, userId?: string | number): Promise<T> {
+    const entityData = { ...data };
+    this.setUserFields(entityData, userId, true);
+    const entity = this.repository.create(entityData as DeepPartial<T>);
     return this.repository.save(entity);
   }
 
   /**
    * 更新实体
    */
-  async update(id: number, data: Partial<T>): Promise<T | null> {
-    await this.repository.update(id, data);
+  async update(id: number, data: Partial<T>, userId?: string | number): Promise<T | null> {
+    const updateData = { ...data };
+    this.setUserFields(updateData, userId, false);
+    await this.repository.update(id, updateData);
     return this.getById(id);
   }
 
@@ -152,6 +180,11 @@ export abstract class GenericCrudService<T extends ObjectLiteral> {
   }
 }
 
+export interface UserTrackingOptions {
+  createdByField?: string;
+  updatedByField?: string;
+}
+
 export type CrudOptions<
   T extends ObjectLiteral,
   CreateSchema extends z.ZodSchema = z.ZodSchema,
@@ -167,4 +200,5 @@ export type CrudOptions<
   searchFields?: string[];
   relations?: string[];
   middleware?: any[];
+  userTracking?: UserTrackingOptions;
 };