# 通用CRUD实现规范 ## 概述 通用CRUD模块提供标准化的增删改查功能实现,通过泛型机制支持快速创建实体的RESTful API接口。本规范定义了使用通用CRUD服务和路由的标准流程和最佳实践。 ## 1. 通用CRUD服务 (GenericCrudService) ### 1.1 基础用法 通用CRUD服务提供基础的数据操作方法,所有实体服务类应继承此类: ```typescript import { GenericCrudService } from '@/server/utils/generic-crud.service'; import { DataSource } from 'typeorm'; import { YourEntity } from '@/server/modules/your-module/your-entity.entity'; export class YourEntityService extends GenericCrudService { constructor(dataSource: DataSource) { super(dataSource, YourEntity); } // 可以重写或扩展基础方法 async customMethod() { // 自定义业务逻辑 } } ``` ### 1.2 核心方法 | 方法 | 描述 | 参数 | 返回值 | |------|------|------|--------| | `getList` | 获取分页列表 | `page`, `pageSize`, `keyword`, `searchFields`, `where`, `relations`, `order` | `[T[], number]` | | `getById` | 根据ID获取单个实体 | `id: number`, `relations?: string[]` | `T \| null` | | `create` | 创建实体 | `data: DeepPartial` | `T` | | `update` | 更新实体 | `id: number`, `data: Partial` | `T \| null` | | `delete` | 删除实体 | `id: number` | `boolean` | | `createQueryBuilder` | 创建查询构建器 | `alias?: string` | `QueryBuilder` | ### 1.3 构造函数注入 必须通过构造函数注入`DataSource`,禁止直接使用全局实例: ```typescript // 正确示例 constructor(private dataSource: DataSource) { super(dataSource, YourEntity); } // 错误示例 constructor() { super(AppDataSource, YourEntity); // 禁止使用全局AppDataSource } ``` ## 2. 通用CRUD路由 (createCrudRoutes) ### 2.1 基础用法 通过`createCrudRoutes`函数快速创建标准CRUD路由: ```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, searchFields: ['name', 'description'], // 可选,指定搜索字段 relations: ['relatedEntity'], // 可选,指定关联查询关系(支持嵌套关联,如 ['relatedEntity.client']) middleware: [authMiddleware] // 可选,添加中间件 }); export default yourEntityRoutes; ``` ### 2.2 配置选项 (CrudOptions) | 参数 | 类型 | 描述 | 是否必需 | |------|------|------|----------| | `entity` | `new () => T` | 实体类构造函数 | 是 | | `createSchema` | `z.ZodSchema` | 创建实体的Zod验证 schema | 是 | | `updateSchema` | `z.ZodSchema` | 更新实体的Zod验证 schema | 是 | | `getSchema` | `z.ZodSchema` | 获取单个实体的响应 schema | 是 | | `listSchema` | `z.ZodSchema` | 获取列表的响应 schema | 是 | | `searchFields` | `string[]` | 搜索字段数组,用于关键词搜索 | 否 | | `relations` | `string[]` | 关联查询配置,指定需要关联查询的关系 | 否 | | `middleware` | `any[]` | 应用于所有CRUD路由的中间件数组 | 否 | ### 2.3 生成的路由 调用`createCrudRoutes`会自动生成以下标准RESTful路由: | 方法 | 路径 | 描述 | |------|------|------| | GET | `/` | 获取实体列表(支持分页、搜索、排序、关联查询) | | POST | `/` | 创建新实体 | | GET | `/{id}` | 获取单个实体详情(支持关联查询) | | PUT | `/{id}` | 更新实体 | | DELETE | `/{id}` | 删除实体 | ### 2.4 路由注册 生成的路由需要在API入口文件中注册: ```typescript // src/server/api.ts import yourEntityRoutes from '@/server/api/your-entity/index'; // 注册路由 api.route('/api/v1/your-entities', yourEntityRoutes); ``` ## 3. 实体类要求 使用通用CRUD模块的实体类必须满足以下要求: 1. 必须包含主键`id`字段,类型为数字 2. 必须定义配套的Zod schema: - 实体完整schema(用于响应) - 创建DTO schema(用于创建请求验证) - 更新DTO schema(用于更新请求验证) 示例: ```typescript // your-entity.entity.ts 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 { @PrimaryGeneratedColumn({ unsigned: true }) id!: number; @Column({ name: 'name', type: 'varchar', length: 255 }) name!: string; @ManyToOne(() => RelatedEntity, related => related.yourEntities) relatedEntity!: RelatedEntity; // 其他字段... } // Zod schemas 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... }); ``` ## 4. 高级用法 ### 4.1 自定义中间件 可以为CRUD路由添加自定义中间件,如认证和权限控制: ```typescript import { authMiddleware } from '@/server/middleware/auth.middleware'; import { permissionMiddleware } from '@/server/middleware/permission.middleware'; const yourEntityRoutes = createCrudRoutes({ // ...其他配置 middleware: [ authMiddleware, permissionMiddleware(['your_entity:read', 'your_entity:write']) ] }); ``` ### 4.2 扩展路由 生成基础CRUD路由后,可以添加自定义路由: ```typescript import { OpenAPIHono } from '@hono/zod-openapi'; const app = new OpenAPIHono() .route('/', yourEntityRoutes) .get('/custom-action', (c) => { // 自定义路由处理逻辑 return c.json({ message: '自定义操作' }); }); export default app; ``` ### 4.3 重写服务方法 当通用CRUD服务的默认实现不满足需求时,可以重写相应方法: ```typescript export class YourEntityService extends GenericCrudService { // ...构造函数 async getList( page: number = 1, pageSize: number = 10, keyword?: string, searchFields?: string[], where: Partial = {}, relations: string[] = [], order: { [P in keyof YourEntity]?: 'ASC' | 'DESC' } = {} ): Promise<[YourEntity[], number]> { // 添加自定义过滤条件 where.isDeleted = 0; // 例如:默认过滤已删除数据 return super.getList(page, pageSize, keyword, searchFields, where, relations, order); } } ``` ## 5. 错误处理 通用CRUD模块已内置标准错误处理机制: 1. 参数验证错误:返回400状态码和验证错误详情 2. 资源不存在:返回404状态码 3. 服务器错误:返回500状态码和错误消息 所有错误响应格式统一为: ```json { "code": 错误代码, "message": "错误描述", "errors": 可选的详细错误信息 } ``` ## 6. 最佳实践 1. **单一职责**:通用CRUD仅处理标准数据操作,复杂业务逻辑应在具体服务类中实现 2. **命名规范**: - 服务类:`[EntityName]Service` - 路由文件:遵循RESTful资源命名规范,使用复数形式 3. **权限控制**:始终为CRUD路由添加适当的认证和授权中间件 4. **数据验证**:确保Zod schema包含完整的验证规则和OpenAPI元数据 5. **搜索优化**:合理设置`searchFields`,避免在大表的文本字段上进行模糊搜索 6. **分页处理**:所有列表接口必须支持分页,避免返回大量数据 7. **关联查询**:使用`relations`配置时,避免过度关联导致性能问题 8. **事务管理**:复杂操作应使用事务确保数据一致性