# 通用 CRUD 规范 ## 版本信息 | 版本 | 日期 | 描述 | 作者 | |------|------|------|------| | 1.0 | 2025-10-15 | 创建通用 CRUD 规范文档 | Winston | ## 概述 通用 CRUD(Create, Read, Update, Delete)系统是本项目的核心基础设施,提供类型安全、可扩展的数据操作服务。基于 TypeORM 和 Hono 框架构建,支持自动生成 OpenAPI 文档、关联查询、用户跟踪等高级功能。 ## 设计原则 ### 核心原则 - **类型安全**: 基于 TypeScript 的完整类型支持 - **可扩展性**: 支持自定义扩展和业务逻辑注入 - **一致性**: 统一的 API 设计和错误处理 - **性能优化**: 支持分页、缓存、关联查询优化 - **安全性**: 内置用户跟踪和权限控制 ### 架构模式 - **服务层**: `GenericCrudService` 提供核心 CRUD 操作 - **路由层**: `createCrudRoutes` 自动生成 RESTful API 路由 - **实体层**: TypeORM 实体定义数据模型 - **验证层**: Zod schema 提供输入验证 ## 核心组件 ### 1. GenericCrudService **位置**: `src/server/utils/generic-crud.service.ts` **核心功能**: - 分页列表查询(支持搜索、排序、复杂筛选) - 单个实体查询(支持关联关系) - 创建、更新、删除操作 - 用户跟踪(创建人、更新人) - 关联字段处理 **主要方法**: ```typescript // 分页查询 async getList( page: number = 1, pageSize: number = 10, keyword?: string, searchFields?: string[], where?: Partial, relations: string[] = [], order: { [P in keyof T]?: 'ASC' | 'DESC' } = {}, filters?: { [key: string]: any } ): Promise<[T[], number]> // 根据ID查询 async getById(id: number, relations: string[] = []): Promise // 创建实体 async create(data: DeepPartial, userId?: string | number): Promise // 更新实体 async update(id: number, data: Partial, userId?: string | number): Promise // 删除实体 async delete(id: number): Promise ``` ### 2. ConcreteCrudService **位置**: `src/server/utils/concrete-crud.service.ts` **用途**: 简化 `GenericCrudService` 的实例化,自动注入数据源。 **使用示例**: ```typescript const userService = new ConcreteCrudService(User, { userTracking: { createdByField: 'createdBy', updatedByField: 'updatedBy' } }); ``` ### 3. createCrudRoutes **位置**: `src/server/utils/generic-crud.routes.ts` **功能**: 自动生成完整的 CRUD 路由,包括: - `GET /` - 分页列表 - `GET /{id}` - 单个实体详情 - `POST /` - 创建实体 - `PUT /{id}` - 更新实体 - `DELETE /{id}` - 删除实体 **支持特性**: - 自动 OpenAPI 文档生成 - 输入验证(Zod schema) - 错误处理统一 - 中间件支持 - 只读模式 ## 使用指南 ### 1. 创建实体类 ```typescript // user.entity.ts import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id: number; @Column({ unique: true }) username: string; @Column({ nullable: true }) email: string | null; @CreateDateColumn() createdAt: Date; @UpdateDateColumn() updatedAt: Date; } ``` ### 2. 创建 Zod Schema ```typescript // user.schema.ts import { z } from '@hono/zod-openapi'; export const UserCreateSchema = z.object({ username: z.string().min(3).max(50), email: z.string().email().optional().nullable(), }); export const UserUpdateSchema = UserCreateSchema.partial(); export const UserGetSchema = z.object({ id: z.number(), username: z.string(), email: z.string().email().nullable(), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), }); export const UserListSchema = UserGetSchema; ``` ### 3. 注册 CRUD 路由 ```typescript // api/users/index.ts import { createCrudRoutes } from '../../../utils/generic-crud.routes'; import { User } from '../../../modules/users/user.entity'; import { UserCreateSchema, UserUpdateSchema, UserGetSchema, UserListSchema } from '../../../modules/users/user.schema'; export const userRoutes = createCrudRoutes({ entity: User, createSchema: UserCreateSchema, updateSchema: UserUpdateSchema, getSchema: UserGetSchema, listSchema: UserListSchema, searchFields: ['username', 'email'], relations: ['roles'], middleware: [authMiddleware], userTracking: { createdByField: 'createdBy', updatedByField: 'updatedBy' } }); ``` ### 4. 在 API 中注册路由 ```typescript // api/index.ts import { userRoutes } from './users'; const app = new OpenAPIHono(); app.route('/users', userRoutes); ``` ## 高级功能 ### 关联字段处理 支持多对多、一对多等关联关系的自动处理: ```typescript const roleRoutes = createCrudRoutes({ entity: Role, // ... schemas relationFields: { permissions: { relationName: 'permissions', targetEntity: Permission } } }); ``` ### 复杂筛选 支持多种筛选方式: ```typescript // 精确匹配 filters: '{"status": 1}' // 范围查询 filters: '{"createdAt": {"gte": "2024-01-01", "lte": "2024-12-31"}}' // IN 查询 filters: '{"status": [1, 2, 3]}' // 模糊匹配 filters: '{"name": "%keyword%"}' ``` ### 关联字段搜索 支持嵌套关联字段的搜索: ```typescript // 搜索用户关联的角色名称 searchFields: ['username', 'roles.name'] // 搜索嵌套关联 searchFields: ['contract.client.name'] ``` ## 最佳实践 ### 1. 实体设计 - 所有实体必须继承 `ObjectLiteral` - 使用 TypeORM 装饰器定义字段 - 包含 `createdAt` 和 `updatedAt` 时间戳 - 考虑软删除需求 ### 2. Schema 设计 - 创建和更新使用不同的 schema - 响应 schema 应包含完整字段 - 使用 `.optional()` 和 `.nullable()` 明确字段可选性 ### 3. 性能优化 - 合理设置分页大小(默认10条) - 为常用查询字段添加索引 - 使用关联查询时注意 N+1 问题 - 考虑缓存策略 ### 4. 安全性 - 使用用户跟踪记录操作人 - 实现适当的权限控制 - 验证所有输入数据 - 避免敏感信息暴露 ## 扩展和自定义 ### 自定义业务逻辑 ```typescript class CustomUserService extends ConcreteCrudService { async createWithValidation(data: UserCreateDto): Promise { // 自定义验证逻辑 await this.validateUniqueUsername(data.username); // 调用父类方法 return super.create(data); } private async validateUniqueUsername(username: string): Promise { const existing = await this.repository.findOne({ where: { username } }); if (existing) { throw new Error('用户名已存在'); } } } ``` ### 自定义路由 ```typescript const customRoutes = new OpenAPIHono(); // 使用通用 CRUD 路由 customRoutes.route('/', createCrudRoutes(crudOptions)); // 添加自定义路由 customRoutes.post('/bulk-create', async (c) => { // 自定义批量创建逻辑 }); ``` ## 常见问题解答 ### Q: 如何处理复杂的业务逻辑? A: 继承 `ConcreteCrudService` 并添加自定义方法,或创建独立的业务服务类。 ### Q: 如何实现软删除? A: 在实体中添加 `deletedAt` 字段,重写 `delete` 方法实现软删除逻辑。 ### Q: 如何优化关联查询性能? A: 使用 `relations` 参数指定需要的关联关系,避免不必要的关联加载。 ### Q: 如何处理文件上传等非标准操作? A: 创建独立的文件管理服务,或扩展 CRUD 服务添加文件处理逻辑。 ## 相关文档 - [主架构文档](../architecture.md) - [API 设计规范](./api-design-integration.md) - [数据模型规范](./data-model-schema-changes.md) - [测试策略](./testing-strategy.md) --- **文档状态**: 正式版 **下次评审**: 2025-11-15