通用CRUD模块提供标准化的增删改查功能实现,通过泛型机制支持快速创建实体的RESTful API接口。本规范定义了使用通用CRUD服务和路由的标准流程和最佳实践。
通用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() {
// 自定义业务逻辑
}
}
| 方法 | 描述 | 参数 | 返回值 |
|---|---|---|---|
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> |
必须通过构造函数注入DataSource,禁止直接使用全局实例:
// 正确示例
constructor(private dataSource: DataSource) {
super(dataSource, YourEntity);
}
// 错误示例
constructor() {
super(AppDataSource, YourEntity); // 禁止使用全局AppDataSource
}
通过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;
| 参数 | 类型 | 描述 | 是否必需 |
|---|---|---|---|
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路由的中间件数组 | 否 |
relationFields |
RelationFieldOptions |
多对多关联字段配置,支持通过ID数组操作关联关系 | 否 |
调用createCrudRoutes会自动生成以下标准RESTful路由:
| 方法 | 路径 | 描述 |
|---|---|---|
| GET | / |
获取实体列表(支持分页、搜索、排序、关联查询) |
| POST | / |
创建新实体 |
| GET | /{id} |
获取单个实体详情(支持关联查询) |
| PUT | /{id} |
更新实体 |
| DELETE | /{id} |
删除实体 |
生成的路由需要在API入口文件中注册:
// src/server/api.ts
import yourEntityRoutes from '@/server/api/your-entity/index';
// 注册路由
api.route('/api/v1/your-entities', yourEntityRoutes);
使用通用CRUD模块的实体类必须满足以下要求:
id字段,类型为数字示例:
// 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...
});
新增 relationFields 配置选项,用于处理多对多关联字段:
import { createCrudRoutes } from '@/server/utils/generic-crud.routes';
import { PolicyNews, PolicyNewsSchema, CreatePolicyNewsDto, UpdatePolicyNewsDto } from '@/server/modules/silver-users/policy-news.entity';
import { File } from '@/server/modules/files/file.entity';
import { authMiddleware } from '@/server/middleware/auth.middleware';
const policyNewsRoutes = createCrudRoutes({
entity: PolicyNews,
createSchema: CreatePolicyNewsDto,
updateSchema: UpdatePolicyNewsDto,
getSchema: PolicyNewsSchema,
listSchema: PolicyNewsSchema,
relations: ['files'],
middleware: [authMiddleware],
relationFields: {
fileIds: {
relationName: 'files', // 实体中的关联属性名
targetEntity: File, // 关联的目标实体类
joinTableName: 'policy_news_files' // 可选:中间表名
}
}
});
interface RelationFieldOptions {
[fieldName: string]: {
relationName: string; // 实体中的关联属性名
targetEntity: new () => any; // 关联的目标实体类
joinTableName?: string; // 中间表名(可选)
};
}
实体定义:
@Entity('policy_news')
export class PolicyNews {
@PrimaryGeneratedColumn({ unsigned: true })
id!: number;
@Column({ name: 'news_title', type: 'varchar', length: 255 })
newsTitle!: string;
// 关联文件的多对多关系
@ManyToMany(() => File)
@JoinTable({
name: 'policy_news_files',
joinColumn: { name: 'policy_news_id', referencedColumnName: 'id' },
inverseJoinColumn: { name: 'file_id', referencedColumnName: 'id' }
})
files?: File[];
}
请求格式:
// 创建政策资讯
POST /api/v1/policy-news
{
"newsTitle": "新政策解读",
"fileIds": [1, 2, 3] // 关联文件ID数组
}
// 更新政策资讯
PUT /api/v1/policy-news/1
{
"newsTitle": "更新后的政策解读",
"fileIds": [2, 4, 5] // 更新后的文件ID数组
}
[] 表示清除所有关联可以为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'])
]
});
生成基础CRUD路由后,可以添加自定义路由:
import { OpenAPIHono } from '@hono/zod-openapi';
const app = new OpenAPIHono()
.route('/', yourEntityRoutes)
.get('/custom-action', (c) => {
// 自定义路由处理逻辑
return c.json({ message: '自定义操作' });
});
export default app;
当通用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);
}
}
通用CRUD模块已内置标准错误处理机制:
所有错误响应格式统一为:
{
"code": 错误代码,
"message": "错误描述",
"errors": 可选的详细错误信息
}
[EntityName]ServicesearchFields,避免在大表的文本字段上进行模糊搜索relations配置时,避免过度关联导致性能问题