2
0

12-generic-crud.md 8.4 KB

通用CRUD实现规范

概述

通用CRUD模块提供标准化的增删改查功能实现,通过泛型机制支持快速创建实体的RESTful API接口。本规范定义了使用通用CRUD服务和路由的标准流程和最佳实践。

1. 通用CRUD服务 (GenericCrudService)

1.1 基础用法

通用CRUD服务提供基础的数据操作方法,所有实体服务类应继承此类:

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<YourEntity> {
  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> T
update 更新实体 id: number, data: Partial<T> T \| null
delete 删除实体 id: number boolean
createQueryBuilder 创建查询构建器 alias?: string QueryBuilder<T>

1.3 构造函数注入

必须通过构造函数注入DataSource,禁止直接使用全局实例:

// 正确示例
constructor(private dataSource: DataSource) {
  super(dataSource, YourEntity);
}

// 错误示例
constructor() {
  super(AppDataSource, YourEntity); // 禁止使用全局AppDataSource
}

2. 通用CRUD路由 (createCrudRoutes)

2.1 基础用法

通过createCrudRoutes函数快速创建标准CRUD路由:

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入口文件中注册:

// 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(用于更新请求验证)

示例:

// 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路由添加自定义中间件,如认证和权限控制:

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路由后,可以添加自定义路由:

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服务的默认实现不满足需求时,可以重写相应方法:

export class YourEntityService extends GenericCrudService<YourEntity> {
  // ...构造函数
  
  async getList(
    page: number = 1,
    pageSize: number = 10,
    keyword?: string,
    searchFields?: string[],
    where: Partial<YourEntity> = {},
    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状态码和错误消息

所有错误响应格式统一为:

{
  "code": 错误代码,
  "message": "错误描述",
  "errors": 可选的详细错误信息
}

6. 最佳实践

  1. 单一职责:通用CRUD仅处理标准数据操作,复杂业务逻辑应在具体服务类中实现
  2. 命名规范
    • 服务类:[EntityName]Service
    • 路由文件:遵循RESTful资源命名规范,使用复数形式
  3. 权限控制:始终为CRUD路由添加适当的认证和授权中间件
  4. 数据验证:确保Zod schema包含完整的验证规则和OpenAPI元数据
  5. 搜索优化:合理设置searchFields,避免在大表的文本字段上进行模糊搜索
  6. 分页处理:所有列表接口必须支持分页,避免返回大量数据
  7. 关联查询:使用relations配置时,避免过度关联导致性能问题
  8. 事务管理:复杂操作应使用事务确保数据一致性