Browse Source

Merge branch 'starter' of 139-template-116/d8d-vite-starter into starter

18617351030 4 months ago
parent
commit
b69ef8aba2

+ 15 - 5
.roo/rules/12-generic-crud.md

@@ -32,7 +32,7 @@ export class YourEntityService extends GenericCrudService<YourEntity> {
 | 方法 | 描述 | 参数 | 返回值 |
 |------|------|------|--------|
 | `getList` | 获取分页列表 | `page`, `pageSize`, `keyword`, `searchFields`, `where`, `relations`, `order` | `[T[], number]` |
-| `getById` | 根据ID获取单个实体 | `id: number` | `T \| null` |
+| `getById` | 根据ID获取单个实体 | `id: number`, `relations?: string[]` | `T \| null` |
 | `create` | 创建实体 | `data: DeepPartial<T>` | `T` |
 | `update` | 更新实体 | `id: number`, `data: Partial<T>` | `T \| null` |
 | `delete` | 删除实体 | `id: number` | `boolean` |
@@ -73,6 +73,7 @@ const yourEntityRoutes = createCrudRoutes({
   getSchema: YourEntitySchema,
   listSchema: YourEntitySchema,
   searchFields: ['name', 'description'], // 可选,指定搜索字段
+  relations: ['relatedEntity'], // 可选,指定关联查询关系(支持嵌套关联,如 ['relatedEntity.client'])
   middleware: [authMiddleware] // 可选,添加中间件
 });
 
@@ -89,6 +90,7 @@ export default yourEntityRoutes;
 | `getSchema` | `z.ZodSchema` | 获取单个实体的响应 schema | 是 |
 | `listSchema` | `z.ZodSchema` | 获取列表的响应 schema | 是 |
 | `searchFields` | `string[]` | 搜索字段数组,用于关键词搜索 | 否 |
+| `relations` | `string[]` | 关联查询配置,指定需要关联查询的关系 | 否 |
 | `middleware` | `any[]` | 应用于所有CRUD路由的中间件数组 | 否 |
 
 ### 2.3 生成的路由
@@ -97,9 +99,9 @@ export default yourEntityRoutes;
 
 | 方法 | 路径 | 描述 |
 |------|------|------|
-| GET | `/` | 获取实体列表(支持分页、搜索、排序) |
+| GET | `/` | 获取实体列表(支持分页、搜索、排序、关联查询) |
 | POST | `/` | 创建新实体 |
-| GET | `/{id}` | 获取单个实体详情 |
+| GET | `/{id}` | 获取单个实体详情(支持关联查询) |
 | PUT | `/{id}` | 更新实体 |
 | DELETE | `/{id}` | 删除实体 |
 
@@ -129,8 +131,9 @@ api.route('/api/v1/your-entities', yourEntityRoutes);
 
 ```typescript
 // your-entity.entity.ts
-import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from 'typeorm';
 import { z } from '@hono/zod-openapi';
+import { RelatedEntity } from './related-entity.entity';
 
 @Entity('your_entity')
 export class YourEntity {
@@ -140,6 +143,9 @@ export class YourEntity {
   @Column({ name: 'name', type: 'varchar', length: 255 })
   name!: string;
   
+  @ManyToOne(() => RelatedEntity, related => related.yourEntities)
+  relatedEntity!: RelatedEntity;
+  
   // 其他字段...
 }
 
@@ -147,16 +153,19 @@ export class YourEntity {
 export const YourEntitySchema = z.object({
   id: z.number().int().positive().openapi({ description: '实体ID' }),
   name: z.string().max(255).openapi({ description: '名称', example: '示例名称' }),
+  relatedEntity: RelatedEntitySchema, // 关联实体schema
   // 其他字段schema...
 });
 
 export const CreateYourEntityDto = z.object({
   name: z.string().max(255).openapi({ description: '名称', example: '示例名称' }),
+  relatedEntityId: z.number().int().positive().openapi({ description: '关联实体ID', example: 1 }),
   // 其他创建字段schema...
 });
 
 export const UpdateYourEntityDto = z.object({
   name: z.string().max(255).optional().openapi({ description: '名称', example: '示例名称' }),
+  relatedEntityId: z.number().int().positive().optional().openapi({ description: '关联实体ID', example: 1 }),
   // 其他更新字段schema...
 });
 ```
@@ -249,4 +258,5 @@ export class YourEntityService extends GenericCrudService<YourEntity> {
 4. **数据验证**:确保Zod schema包含完整的验证规则和OpenAPI元数据
 5. **搜索优化**:合理设置`searchFields`,避免在大表的文本字段上进行模糊搜索
 6. **分页处理**:所有列表接口必须支持分页,避免返回大量数据
-7. **事务管理**:复杂操作应使用事务确保数据一致性
+7. **关联查询**:使用`relations`配置时,避免过度关联导致性能问题
+8. **事务管理**:复杂操作应使用事务确保数据一致性

+ 207 - 0
.roo/rules/14-crud-filtering.md

@@ -0,0 +1,207 @@
+# 通用CRUD筛选规范
+
+## 概述
+
+本规范定义了通用CRUD模块的增强筛选功能,支持多种查询模式和灵活的筛选条件。
+
+## 新增功能
+
+### 1. 筛选参数支持
+- **参数名**: `filters`
+- **类型**: JSON字符串
+- **位置**: 查询参数
+- **可选**: 是
+
+### 2. 支持的筛选操作
+
+| 操作类型 | 语法格式 | 示例 | 说明 |
+|----------|----------|------|------|
+| 精确匹配 | `"字段名": 值` | `{"status": 1}` | 等于指定值 |
+| 模糊匹配 | `"字段名": "%值%"` | `{"name": "%张%"}` | LIKE查询,包含指定值 |
+| 大于 | `"字段名": {"gt": 值}` | `{"age": {"gt": 18}}` | 大于指定值 |
+| 大于等于 | `"字段名": {"gte": 值}` | `{"age": {"gte": 18}}` | 大于等于指定值 |
+| 小于 | `"字段名": {"lt": 值}` | `{"score": {"lt": 60}}` | 小于指定值 |
+| 小于等于 | `"字段名": {"lte": 值}` | `{"score": {"lte": 60}}` | 小于等于指定值 |
+| 范围查询 | `"字段名": {"between": [最小值, 最大值]}` | `{"createdAt": {"between": ["2024-01-01", "2024-12-31"]}}` | 在指定范围内 |
+| IN查询 | `"字段名": [值1, 值2, ...]` | `{"status": [1, 2, 3]}` | 值在指定数组中 |
+| NULL匹配 | `"字段名": null` | `{"deletedAt": null}` | 字段为NULL |
+
+### 3. 组合查询
+支持多个条件同时筛选,所有条件为AND关系:
+
+```json
+{
+  "status": 1,
+  "name": "%张%",
+  "createdAt": {"gte": "2024-01-01"}
+}
+```
+
+## API使用规范
+
+### 请求格式
+```http
+GET /api/v1/entities?filters={"status":1,"name":"%张%","createdAt":{"gte":"2024-01-01"}}
+```
+
+### 响应格式
+```json
+{
+  "data": [...],
+  "pagination": {
+    "total": 100,
+    "current": 1,
+    "pageSize": 10
+  }
+}
+```
+
+### 错误处理
+- 格式错误:返回400状态码,message为"筛选条件格式错误"
+- 字段不存在:安全忽略,不影响查询
+- 类型不匹配:安全处理,返回空结果
+
+## 前端集成规范
+
+### React Hook示例
+```typescript
+const useEntityList = () => {
+  const [data, setData] = useState([]);
+  
+  const fetchData = async (filters = {}) => {
+    const response = await client.$get({
+      query: {
+        page: 1,
+        pageSize: 10,
+        filters: JSON.stringify(filters)
+      }
+    });
+    // 处理响应...
+  };
+  
+  return { data, fetchData };
+};
+```
+
+### ProTable集成
+```typescript
+const columns = [
+  {
+    title: '状态',
+    dataIndex: 'status',
+    valueType: 'select',
+    valueEnum: { 0: '禁用', 1: '启用' },
+  },
+  {
+    title: '创建时间',
+    dataIndex: 'createdAt',
+    valueType: 'dateRange',
+  },
+];
+
+const handleRequest = async (params) => {
+  const filters = {};
+  
+  if (params.status !== undefined) filters.status = params.status;
+  if (params.createdAt?.length === 2) {
+    filters.createdAt = { between: params.createdAt };
+  }
+  
+  return client.$get({
+    query: {
+      page: params.current,
+      pageSize: params.pageSize,
+      keyword: params.keyword,
+      filters: JSON.stringify(filters)
+    }
+  });
+};
+```
+
+## 后端实现规范
+
+### GenericCrudService使用
+```typescript
+// 直接使用(无需修改)
+const service = new GenericCrudService(dataSource, UserEntity);
+const [data, total] = await service.getList(
+  page, pageSize, keyword, searchFields, where, relations, order, filters
+);
+
+// 自定义实现
+export class UserService extends GenericCrudService<User> {
+  async getList(
+    page: number = 1,
+    pageSize: number = 10,
+    keyword?: string,
+    searchFields?: string[],
+    where?: Partial<User>,
+    relations: string[] = [],
+    order: any = {},
+    filters?: any
+  ) {
+    // 添加业务特定的筛选
+    const customFilters = {
+      ...filters,
+      isDeleted: 0
+    };
+    
+    return super.getList(page, pageSize, keyword, searchFields, where, relations, order, customFilters);
+  }
+}
+```
+
+## 最佳实践
+
+### 1. 性能优化
+- 为常用筛选字段添加数据库索引
+- 避免对大字段进行模糊查询
+- 合理使用分页参数
+
+### 2. 安全考虑
+- 避免传递敏感字段到filters
+- 验证用户权限相关的筛选条件
+- 限制查询结果集大小
+
+### 3. 类型安全
+```typescript
+// 推荐:定义筛选类型
+interface UserFilters {
+  status?: number;
+  name?: string;
+  age?: { gte?: number; lte?: number };
+  createdAt?: { between?: [string, string] };
+}
+
+// 使用时
+const filters: UserFilters = { status: 1 };
+```
+
+### 4. 错误处理
+```typescript
+try {
+  const filters = JSON.parse(query.filters);
+  // 验证filters结构...
+} catch (error) {
+  return { code: 400, message: '筛选条件格式错误' };
+}
+```
+
+## 常见问题
+
+### Q: 如何忽略大小写的模糊匹配?
+A: 使用标准SQL LIKE语法,数据库层面处理大小写
+
+### Q: 是否支持OR条件?
+A: 当前版本仅支持AND关系,OR条件需要自定义实现
+
+### Q: 如何处理空字符串?
+A: 空字符串会被自动忽略,不会加入筛选条件
+
+### Q: 是否支持嵌套对象筛选?
+A: 支持,使用点表示法:`{"related.field": "value"}`
+
+## 版本兼容
+- 完全向后兼容,不影响现有API调用
+- 新增参数为可选,不传递时保持原有行为
+- 错误时返回标准化错误响应

+ 185 - 0
.roo/rules/15-user-tracking.md

@@ -0,0 +1,185 @@
+# 通用CRUD操作人ID字段配置规范
+
+## 概述
+
+本规范定义了如何在通用CRUD模块中自动处理操作人ID字段,确保创建和更新操作时自动记录操作用户信息。
+
+## 新增功能
+
+通用CRUD模块已支持自动注入操作人ID字段,包含以下功能:
+
+1. **创建时自动记录创建人ID**
+2. **更新时自动记录更新人ID**
+3. **灵活配置字段名称**
+4. **向后兼容现有实现**
+
+## 配置方式
+
+### 1. 在CrudOptions中配置用户跟踪
+
+```typescript
+import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
+import { YourEntity } from '@/server/modules/your-module/your-entity.entity';
+import { YourEntitySchema, CreateYourEntityDto, UpdateYourEntityDto } from '@/server/modules/your-module/your-entity.entity';
+import { authMiddleware } from '@/server/middleware/auth.middleware';
+
+const yourEntityRoutes = createCrudRoutes({
+  entity: YourEntity,
+  createSchema: CreateYourEntityDto,
+  updateSchema: UpdateYourEntityDto,
+  getSchema: YourEntitySchema,
+  listSchema: YourEntitySchema,
+  middleware: [authMiddleware],
+  // 用户跟踪配置
+  userTracking: {
+    createdByField: 'createdBy',    // 创建人ID字段名,默认 'createdBy'
+    updatedByField: 'updatedBy'     // 更新人ID字段名,默认 'updatedBy'
+  }
+});
+```
+
+### 2. 实体类定义要求
+
+实体必须包含与配置对应的字段:
+
+```typescript
+@Entity('your_entities')
+export class YourEntity {
+  @PrimaryGeneratedColumn({ unsigned: true })
+  id!: number;
+
+  // 业务字段...
+  
+  @Column({ name: 'created_by', type: 'int', nullable: true, comment: '创建用户ID' })
+  createdBy?: number;
+  
+  @Column({ name: 'updated_by', type: 'int', nullable: true, comment: '更新用户ID' })
+  updatedBy?: number;
+  
+  @CreateDateColumn({ name: 'created_at' })
+  createdAt!: Date;
+  
+  @UpdateDateColumn({ name: 'updated_at' })
+  updatedAt!: Date;
+}
+```
+
+### 3. 字段命名约定
+
+| 配置字段名 | 推荐数据库字段名 | 类型 | 说明 |
+|------------|------------------|------|------|
+| createdByField | created_by | int/string | 创建人用户ID |
+| updatedByField | updated_by | int/string | 更新人用户ID |
+
+## 配置示例
+
+### 示例1:使用默认字段名
+
+```typescript
+const routes = createCrudRoutes({
+  entity: MyEntity,
+  createSchema: CreateMyEntityDto,
+  updateSchema: UpdateMyEntityDto,
+  getSchema: MyEntitySchema,
+  listSchema: MyEntitySchema,
+  userTracking: {} // 使用默认字段名 createdBy 和 updatedBy
+});
+```
+
+### 示例2:自定义字段名
+
+```typescript
+const routes = createCrudRoutes({
+  entity: MyEntity,
+  createSchema: CreateMyEntityDto,
+  updateSchema: UpdateMyEntityDto,
+  getSchema: MyEntitySchema,
+  listSchema: MyEntitySchema,
+  userTracking: {
+    createdByField: 'created_user_id',
+    updatedByField: 'modified_user_id'
+  }
+});
+```
+
+### 示例3:只记录创建人
+
+```typescript
+const routes = createCrudRoutes({
+  entity: MyEntity,
+  createSchema: CreateMyEntityDto,
+  updateSchema: UpdateMyEntityDto,
+  getSchema: MyEntitySchema,
+  listSchema: MyEntitySchema,
+  userTracking: {
+    createdByField: 'created_by',
+    updatedByField: undefined // 不记录更新人
+  }
+});
+```
+
+### 示例4:兼容现有字段名
+
+```typescript
+const routes = createCrudRoutes({
+  entity: Linkman,
+  createSchema: CreateLinkmanDto,
+  updateSchema: UpdateLinkmanDto,
+  getSchema: LinkmanSchema,
+  listSchema: LinkmanSchema,
+  userTracking: {
+    createdByField: 'createdUserId', // 匹配现有字段
+    updatedByField: 'updatedUserId'  // 匹配现有字段
+  }
+});
+```
+
+## 工作原理
+
+1. **创建操作**:
+   - 当用户创建新记录时,系统自动从认证上下文中提取用户ID
+   - 将用户ID注入到配置的创建人字段中
+   - 如果未配置createdByField,则跳过此步骤
+
+2. **更新操作**:
+   - 当用户更新记录时,系统自动从认证上下文中提取用户ID
+   - 将用户ID注入到配置的更新人字段中
+   - 如果未配置updatedByField,则跳过此步骤
+
+3. **向后兼容**:
+   - 未配置userTracking时,保持原有行为不变
+   - 所有现有代码无需修改即可继续运行
+
+## 注意事项
+
+1. **认证要求**:必须在路由中添加`authMiddleware`才能获取用户信息
+2. **字段类型**:用户ID字段应支持存储用户ID的数据类型(通常为int或varchar)
+3. **数据库字段**:确保实体类中定义的数据库字段名与配置一致
+4. **空值处理**:如果用户未登录,相关字段将保持为null
+
+## 最佳实践
+
+1. **统一命名**:在项目中统一使用相同的字段命名约定
+2. **索引优化**:为操作人ID字段添加数据库索引以提高查询性能
+3. **关联查询**:可以通过relations配置加载操作人详情
+4. **审计功能**:结合时间戳字段实现完整的操作审计
+
+## 常见问题
+
+### Q: 如何支持字符串类型的用户ID?
+A: 实体类字段类型设置为varchar即可,系统会自动处理字符串类型
+
+### Q: 可以配置不同的用户ID字段吗?
+A: 可以,通过createdByField和updatedByField分别配置
+
+### Q: 会影响现有实体吗?
+A: 不会,只有配置了userTracking的实体才会启用此功能
+
+### Q: 如何获取操作人详情?
+A: 在relations中配置用户关联关系即可:
+```typescript
+const routes = createCrudRoutes({
+  entity: YourEntity,
+  relations: ['createdByUser', 'updatedByUser'],
+  // ...其他配置
+});

+ 2 - 1
package.json

@@ -41,7 +41,8 @@
     "react-router-dom": "^7.6.1",
     "react-toastify": "^11.0.5",
     "reflect-metadata": "^0.2.2",
-    "typeorm": "^0.3.24"
+    "typeorm": "^0.3.24",
+    "uuid": "^11.1.0"
   },
   "devDependencies": {
     "@types/debug": "^4.1.12",

+ 3 - 0
pnpm-lock.yaml

@@ -110,6 +110,9 @@ importers:
       typeorm:
         specifier: ^0.3.24
         version: 0.3.24(babel-plugin-macros@3.1.0)(ioredis@5.6.1)(mysql2@3.14.1)(reflect-metadata@0.2.2)
+      uuid:
+        specifier: ^11.1.0
+        version: 11.1.0
     devDependencies:
       '@types/debug':
         specifier: ^4.1.12

+ 1 - 1
src/server/data-source.ts

@@ -14,7 +14,7 @@ export const AppDataSource = new DataSource({
   password: process.env.DB_PASSWORD || "",
   database: process.env.DB_DATABASE || "d8dai",
   entities: [
-    User, Role
+    User, Role, 
   ],
   migrations: [],
   synchronize: process.env.DB_SYNCHRONIZE !== "false",

+ 37 - 26
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, 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();
@@ -53,6 +53,11 @@ export function createCrudRoutes<
         sortOrder: z.enum(['ASC', 'DESC']).optional().default('DESC').openapi({
           example: 'DESC',
           description: '排序方向'
+        }),
+        // 增强的筛选参数
+        filters: z.string().optional().openapi({
+          example: '{"status": 1, "createdAt": {"gte": "2024-01-01", "lte": "2024-12-31"}}',
+          description: '筛选条件(JSON字符串),支持精确匹配、范围查询、IN查询等'
         })
       })
     },
@@ -215,16 +220,25 @@ export function createCrudRoutes<
   const routes = app
     .openapi(listRoute, async (c) => {
       try {
-        const { page, pageSize, keyword, sortBy, sortOrder } = c.req.valid('query');
+        const query = c.req.valid('query') as any;
+        const { page, pageSize, keyword, sortBy, sortOrder, filters } = query;
         
         // 构建排序对象
-        // 使用Record和类型断言解决泛型索引写入问题
-        const order: Partial<Record<keyof T, 'ASC' | 'DESC'>> = {};
+        const order: any = {};
         if (sortBy) {
-          (order as Record<string, 'ASC' | 'DESC'>)[sortBy] = sortOrder || 'DESC';
+          order[sortBy] = sortOrder || 'DESC';
         } else {
-          // 默认按id降序排序
-          (order as Record<string, 'ASC' | 'DESC'>)['id'] = 'DESC';
+          order['id'] = 'DESC';
+        }
+        
+        // 解析筛选条件
+        let parsedFilters: any = undefined;
+        if (filters) {
+          try {
+            parsedFilters = JSON.parse(filters);
+          } catch (e) {
+            return c.json({ code: 400, message: '筛选条件格式错误' }, 400);
+          }
         }
         
         const [data, total] = await crudService.getList(
@@ -232,13 +246,14 @@ export function createCrudRoutes<
           pageSize,
           keyword,
           searchFields,
-          undefined, // where条件
-          [], // relations
-          order
+          undefined,
+          relations || [],
+          order,
+          parsedFilters
         );
         
         return c.json({
-          data: data as any[],
+          data,
           pagination: { total, current: page, pageSize }
         }, 200);
       } catch (error) {
@@ -251,10 +266,11 @@ export function createCrudRoutes<
         }, 500);
       }
     })
-    .openapi(createRouteDef, async (c) => {
+    .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) {
@@ -266,10 +282,10 @@ export function createCrudRoutes<
         }, 500);
       }
     })
-    .openapi(getRouteDef, async (c) => {
+    .openapi(getRouteDef, async (c: any) => {
       try {
         const { id } = c.req.valid('param');
-        const result = await crudService.getById(id);
+        const result = await crudService.getById(id, relations || []);
         
         if (!result) {
           return c.json({ code: 404, message: '资源不存在' }, 404);
@@ -286,11 +302,12 @@ export function createCrudRoutes<
         }, 500);
       }
     })
-    .openapi(updateRouteDef, async (c) => {
+    .openapi(updateRouteDef, async (c: any) => {
       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);
@@ -298,9 +315,6 @@ export function createCrudRoutes<
         
         return c.json(result, 200);
       } catch (error) {
-        if (error instanceof z.ZodError) {
-          return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
-        }
         if (error instanceof z.ZodError) {
           return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
         }
@@ -310,7 +324,7 @@ export function createCrudRoutes<
         }, 500);
       }
     })
-    .openapi(deleteRouteDef, async (c) => {
+    .openapi(deleteRouteDef, async (c: any) => {
       try {
         const { id } = c.req.valid('param');
         const success = await crudService.delete(id);
@@ -319,11 +333,8 @@ export function createCrudRoutes<
           return c.json({ code: 404, message: '资源不存在' }, 404);
         }
         
-        return c.body(null, 204) as unknown as Response;
+        return c.body(null, 204);
       } catch (error) {
-        if (error instanceof z.ZodError) {
-          return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
-        }
         if (error instanceof z.ZodError) {
           return c.json({ code: 400, message: '参数验证失败', errors: error.errors }, 400);
         }

+ 112 - 24
src/server/utils/generic-crud.service.ts

@@ -3,20 +3,22 @@ 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;
   }
 
   /**
    * 获取分页列表
    */
-  /**
-   * 获取分页列表,支持高级查询
-   */
   async getList(
     page: number = 1,
     pageSize: number = 10,
@@ -24,25 +26,35 @@ export abstract class GenericCrudService<T extends ObjectLiteral> {
     searchFields?: string[],
     where?: Partial<T>,
     relations: string[] = [],
-    order: { [P in keyof T]?: 'ASC' | 'DESC' } = {}
+    order: { [P in keyof T]?: 'ASC' | 'DESC' } = {},
+    filters?: {
+      [key: string]: any;
+    }
   ): Promise<[T[], number]> {
     const skip = (page - 1) * pageSize;
     const query = this.repository.createQueryBuilder('entity');
-    
-    // 关联查询
+
+    // 添加关联关系(支持嵌套关联,如 ['contract.client'])
     if (relations.length > 0) {
-      relations.forEach(relation => {
-        query.leftJoinAndSelect(`entity.${relation}`, relation);
+      relations.forEach((relation, relationIndex) => {
+        const parts = relation.split('.');
+        let currentAlias = 'entity';
+        
+        parts.forEach((part, index) => {
+          const newAlias = index === 0 ? part : `${currentAlias}_${relationIndex}`;
+          query.leftJoinAndSelect(`${currentAlias}.${part}`, newAlias);
+          currentAlias = newAlias;
+        });
       });
     }
-    
+
     // 关键词搜索
     if (keyword && searchFields && searchFields.length > 0) {
       query.andWhere(searchFields.map(field => `entity.${field} LIKE :keyword`).join(' OR '), {
         keyword: `%${keyword}%`
       });
     }
-    
+
     // 条件查询
     if (where) {
       Object.entries(where).forEach(([key, value]) => {
@@ -51,42 +63,104 @@ export abstract class GenericCrudService<T extends ObjectLiteral> {
         }
       });
     }
-    
+
+    // 扩展筛选条件
+    if (filters) {
+      Object.entries(filters).forEach(([key, value]) => {
+        if (value !== undefined && value !== null && value !== '') {
+          const fieldName = key.startsWith('_') ? key.substring(1) : key;
+          
+          // 支持不同类型的筛选
+          if (Array.isArray(value)) {
+            // 数组类型:IN查询
+            if (value.length > 0) {
+              query.andWhere(`entity.${fieldName} IN (:...${key})`, { [key]: value });
+            }
+          } else if (typeof value === 'string' && value.includes('%')) {
+            // 模糊匹配
+            query.andWhere(`entity.${fieldName} LIKE :${key}`, { [key]: value });
+          } else if (typeof value === 'object' && value !== null) {
+            // 范围查询
+            if ('gte' in value) {
+              query.andWhere(`entity.${fieldName} >= :${key}_gte`, { [`${key}_gte`]: value.gte });
+            }
+            if ('gt' in value) {
+              query.andWhere(`entity.${fieldName} > :${key}_gt`, { [`${key}_gt`]: value.gt });
+            }
+            if ('lte' in value) {
+              query.andWhere(`entity.${fieldName} <= :${key}_lte`, { [`${key}_lte`]: value.lte });
+            }
+            if ('lt' in value) {
+              query.andWhere(`entity.${fieldName} < :${key}_lt`, { [`${key}_lt`]: value.lt });
+            }
+            if ('between' in value && Array.isArray(value.between) && value.between.length === 2) {
+              query.andWhere(`entity.${fieldName} BETWEEN :${key}_start AND :${key}_end`, {
+                [`${key}_start`]: value.between[0],
+                [`${key}_end`]: value.between[1]
+              });
+            }
+          } else {
+            // 精确匹配
+            query.andWhere(`entity.${fieldName} = :${key}`, { [key]: value });
+          }
+        }
+      });
+    }
+
     // 排序
     Object.entries(order).forEach(([key, direction]) => {
       query.orderBy(`entity.${key}`, direction);
     });
-    
+
     return query.skip(skip).take(pageSize).getManyAndCount();
   }
 
   /**
-   * 高级查询方法
+   * 根据ID获取单个实体
    */
-  createQueryBuilder(alias: string = 'entity') {
-    return this.repository.createQueryBuilder(alias);
+  async getById(id: number, relations: string[] = []): Promise<T | null> {
+    return this.repository.findOne({
+      where: { id } as any,
+      relations
+    });
   }
 
   /**
-   * 根据ID获取单个实体
+   * 设置用户跟踪字段
    */
-  async getById(id: number): Promise<T | null> {
-    return this.repository.findOneBy({ id } as any);
+  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);
   }
 
@@ -97,6 +171,18 @@ export abstract class GenericCrudService<T extends ObjectLiteral> {
     const result = await this.repository.delete(id);
     return result.affected === 1;
   }
+
+  /**
+   * 高级查询方法
+   */
+  createQueryBuilder(alias: string = 'entity') {
+    return this.repository.createQueryBuilder(alias);
+  }
+}
+
+export interface UserTrackingOptions {
+  createdByField?: string;
+  updatedByField?: string;
 }
 
 export type CrudOptions<
@@ -112,5 +198,7 @@ export type CrudOptions<
   getSchema: GetSchema;
   listSchema: ListSchema;
   searchFields?: string[];
+  relations?: string[];
   middleware?: any[];
+  userTracking?: UserTrackingOptions;
 };