实体创建流程分为两种模式,根据业务复杂度选择合适的实现方式:
GenericCrudService和createCrudRoutes快速生成基础CRUD接口| 类型 | 适用场景 | 技术选型 |
|---|---|---|
| 标准通用CRUD | 简单数据管理、无复杂业务逻辑、基础CRUD操作 | GenericCrudService + createCrudRoutes |
| 自定义复杂CRUD | 复杂业务规则、多表关联操作、特殊权限控制、非标准数据处理 | 自定义Service + 手动路由实现 |
src/server/modules/[模块名]/[实体名].entity.tsuser.entity.ts在src/server/data-source.ts中添加实体导入和注册:
// 实体类导入
import { YourEntity } from "./modules/[模块名]/[实体名].entity"
export const AppDataSource = new DataSource({
// ...其他配置
entities: [
User, Role, YourEntity // 添加新实体到数组
],
// ...其他配置
});
src/server/modules/[模块名]/[实体名].service.tsGenericCrudService通过构造函数注入DataSource
import { GenericCrudService } from '@/server/utils/generic-crud.service';
import { DataSource } from 'typeorm';
import { YourEntity } from './your-entity.entity';
export class YourEntityService extends GenericCrudService<YourEntity> {
constructor(dataSource: DataSource) {
super(dataSource, YourEntity);
}
}
使用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'], // 可选,指定搜索字段
middleware: [authMiddleware] // 可选,添加中间件
});
export default yourEntityRoutes;
在src/server/api.ts中添加路由注册:
import yourEntityRoutes from '@/server/api/your-entity/index';
// 注册路由
api.route('/api/v1/your-entities', yourEntityRoutes);
在src/client/api.ts中添加客户端定义:
import { hc } from 'hono/client';
import { YourEntityRoutes } from '@/server/api';
export const yourEntityClient = hc<YourEntityRoutes>('/api/v1', {
fetch: axiosFetch,
}).api.v1['your-entities'];
在页面组件(如pages_users.tsx)中:
InferResponseType提取响应类型InferRequestType提取请求类型示例:
type EntityResponse = InferResponseType<typeof entityClient.$get, 200>;
type CreateRequest = InferRequestType<typeof entityClient.$post>['json'];
- **注册路由**:在`src/client/admin/routes.tsx`中添加路由配置:
```typescript
import YourEntityList from './pages/YourEntityList';
import YourEntityDetail from './pages/YourEntityDetail';
export const routes = [
// ...其他路由
{
path: '/your-entities',
element: <YourEntityList />
},
{
path: '/your-entities/:id',
element: <YourEntityDetail />
}
];
```
- **注册菜单**:在`src/client/admin/menu.tsx`中添加菜单配置:
```typescript
import { TableOutlined } from '@ant-design/icons';
export const menuItems = [
// ...其他菜单项
{
key: 'your-entities',
icon: <TableOutlined />,
label: '实体管理',
path: '/your-entities'
}
];
```
当实体需要复杂业务逻辑或非标准CRUD操作时,采用以下完整流程:
src/server/modules/[模块名]/[实体名].entity.ts示例:
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
import { z } from '@hono/zod-openapi';
@Entity('your_entity')
export class YourEntity {
@PrimaryGeneratedColumn({ unsigned: true })
id!: number;
@Column({ name: 'name', type: 'varchar', length: 255 })
name!: string;
// 其他业务字段...
}
// Zod Schema定义
export const YourEntitySchema = z.object({
id: z.number().int().positive().openapi({ description: '实体ID' }),
name: z.string().max(255).openapi({ description: '名称', example: '示例名称' })
// 其他字段Schema...
});
export const CreateYourEntityDto = z.object({
name: z.string().max(255).openapi({ description: '名称', example: '示例名称' })
// 其他创建字段...
});
export const UpdateYourEntityDto = z.object({
name: z.string().max(255).optional().openapi({ description: '名称', example: '示例名称' })
// 其他更新字段...
});
在src/server/data-source.ts中添加实体导入和注册:
// 实体类导入
import { YourEntity } from "./modules/[模块名]/[实体名].entity"
export const AppDataSource = new DataSource({
// ...其他配置
entities: [
User, Role, YourEntity // 添加新实体到数组
],
// ...其他配置
});
src/server/modules/[模块名]/[实体名].service.ts示例:
import { DataSource, Repository } from 'typeorm';
import { YourEntity } from './your-entity.entity';
import { CreateYourEntityDto, UpdateYourEntityDto } from './your-entity.entity';
import { AppError } from '@/server/utils/errorHandler';
export class YourEntityService {
private repository: Repository<YourEntity>;
constructor(dataSource: DataSource) {
this.repository = dataSource.getRepository(YourEntity);
}
/**
* 获取实体列表(带复杂过滤条件)
*/
async findAll(filters: any): Promise<[YourEntity[], number]> {
const query = this.repository.createQueryBuilder('entity');
// 添加复杂业务过滤逻辑
if (filters.status) {
query.andWhere('entity.status = :status', { status: filters.status });
}
// 多表关联查询示例
if (filters.includeRelated) {
query.leftJoinAndSelect('entity.relatedEntity', 'related');
}
const [items, total] = await query.getManyAndCount();
return [items, total];
}
/**
* 根据ID获取单个实体
*/
async findById(id: number): Promise<YourEntity> {
const entity = await this.repository.findOneBy({ id });
if (!entity) {
throw new AppError('实体不存在', 404);
}
return entity;
}
/**
* 创建实体(带业务规则验证)
*/
async create(data: CreateYourEntityDto): Promise<YourEntity> {
// 业务规则验证示例
const existing = await this.repository.findOneBy({ name: data.name });
if (existing) {
throw new AppError('名称已存在', 400);
}
const entity = this.repository.create(data);
return this.repository.save(entity);
}
/**
* 更新实体(带业务逻辑处理)
*/
async update(id: number, data: UpdateYourEntityDto): Promise<YourEntity> {
const entity = await this.findById(id);
// 业务逻辑处理示例
if (data.name && data.name !== entity.name) {
// 记录名称变更日志等业务操作
}
Object.assign(entity, data);
return this.repository.save(entity);
}
/**
* 删除实体(带权限检查)
*/
async delete(id: number, userId: number): Promise<boolean> {
const entity = await this.findById(id);
// 权限检查示例
if (entity.createdBy !== userId) {
throw new AppError('没有删除权限', 403);
}
await this.repository.remove(entity);
return true;
}
/**
* 自定义业务方法示例
*/
async customBusinessOperation(params: any): Promise<any> {
// 实现复杂业务逻辑
// 可能包含事务、多表操作等
}
}
目录结构:
src/server/api/[实体名]/
├── get.ts # 列表查询
├── post.ts # 创建实体
├── [id]/
│ ├── get.ts # 获取单个实体
│ ├── put.ts # 更新实体
│ └── delete.ts # 删除实体
└── index.ts # 路由聚合
列表查询路由示例 (get.ts):
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { z } from 'zod';
import { YourEntitySchema } from '@/server/modules/your-module/your-entity.entity';
import { ErrorSchema } from '@/server/utils/errorHandler';
import { AppDataSource } from '@/server/data-source';
import { YourEntityService } from '@/server/modules/your-module/your-entity.service';
import { AuthContext } from '@/server/types/context';
import { authMiddleware } from '@/server/middleware/auth.middleware';
// 查询参数Schema
const ListQuery = z.object({
page: z.coerce.number().int().positive().default(1).openapi({
description: '页码',
example: 1
}),
pageSize: z.coerce.number().int().positive().default(10).openapi({
description: '每页条数',
example: 10
}),
status: z.coerce.number().optional().openapi({
description: '状态过滤',
example: 1
})
});
// 响应Schema
const ListResponse = z.object({
data: z.array(YourEntitySchema),
pagination: z.object({
total: z.number().openapi({ example: 100, description: '总记录数' }),
current: z.number().openapi({ example: 1, description: '当前页码' }),
pageSize: z.number().openapi({ example: 10, description: '每页数量' })
})
});
// 路由定义
const routeDef = createRoute({
method: 'get',
path: '/',
middleware: [authMiddleware],
request: {
query: ListQuery
},
responses: {
200: {
description: '成功获取实体列表',
content: { 'application/json': { schema: ListResponse } }
},
400: {
description: '请求参数错误',
content: { 'application/json': { schema: ErrorSchema } }
},
500: {
description: '服务器错误',
content: { 'application/json': { schema: ErrorSchema } }
}
}
});
// 路由实现
const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
try {
const query = c.req.valid('query');
const service = new YourEntityService(AppDataSource);
const [data, total] = await service.findAll({
status: query.status,
// 其他过滤条件
});
return c.json({
data,
pagination: {
total,
current: query.page,
pageSize: query.pageSize
}
}, 200);
} catch (error) {
const { code = 500, message = '获取列表失败' } = error as Error & { code?: number };
return c.json({ code, message }, code);
}
});
export default app;
创建实体路由示例 (post.ts):
import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
import { CreateYourEntityDto, YourEntitySchema } from '@/server/modules/your-module/your-entity.entity';
import { ErrorSchema } from '@/server/utils/errorHandler';
import { AppDataSource } from '@/server/data-source';
import { YourEntityService } from '@/server/modules/your-module/your-entity.service';
import { AuthContext } from '@/server/types/context';
import { authMiddleware } from '@/server/middleware/auth.middleware';
// 路由定义
const routeDef = createRoute({
method: 'post',
path: '/',
middleware: [authMiddleware],
request: {
body: {
content: {
'application/json': { schema: CreateYourEntityDto }
}
}
},
responses: {
200: {
description: '成功创建实体',
content: { 'application/json': { schema: YourEntitySchema } }
},
400: {
description: '请求参数错误',
content: { 'application/json': { schema: ErrorSchema } }
},
500: {
description: '服务器错误',
content: { 'application/json': { schema: ErrorSchema } }
}
}
});
// 路由实现
const app = new OpenAPIHono<AuthContext>().openapi(routeDef, async (c) => {
try {
const data = await c.req.json();
const service = new YourEntityService(AppDataSource);
const result = await service.create(data);
return c.json(result, 200);
} catch (error) {
const { code = 500, message = '创建实体失败' } = error as Error & { code?: number };
return c.json({ code, message }, code);
}
});
export default app;
路由聚合示例 (index.ts):
import { OpenAPIHono } from '@hono/zod-openapi';
import listRoute from './get';
import createRoute from './post';
import getByIdRoute from './[id]/get';
import updateRoute from './[id]/put';
import deleteRoute from './[id]/delete';
const app = new OpenAPIHono()
.route('/', listRoute)
.route('/', createRoute)
.route('/', getByIdRoute)
.route('/', updateRoute)
.route('/', deleteRoute);
export default app;
在src/server/api.ts中添加路由注册:
import yourEntityRoutes from '@/server/api/your-entity/index';
// 注册路由
api.route('/api/v1/your-entities', yourEntityRoutes);
在src/client/api.ts中添加客户端定义:
import { hc } from 'hono/client';
import { YourEntityRoutes } from '@/server/api';
export const yourEntityClient = hc<YourEntityRoutes>('/api/v1', {
fetch: axiosFetch,
}).api.v1['your-entities'];
- 在页面组件(如`pages_users.tsx`)中:
- 使用`InferResponseType`提取响应类型
- 使用`InferRequestType`提取请求类型
- 示例:
```typescript
type EntityResponse = InferResponseType<typeof entityClient.$get, 200>;
type CreateRequest = InferRequestType<typeof entityClient.$post>['json'];
```
- **注册路由**:在`src/client/admin/routes.tsx`中添加路由配置:
```typescript
import YourEntityList from './pages/YourEntityList';
import YourEntityDetail from './pages/YourEntityDetail';
export const routes = [
// ...其他路由
{
path: '/your-entities',
element: <YourEntityList />
},
{
path: '/your-entities/:id',
element: <YourEntityDetail />
}
];
```
- **注册菜单**:在`src/client/admin/menu.tsx`中添加菜单配置:
```typescript
import { TableOutlined } from '@ant-design/icons';
export const menuItems = [
// ...其他菜单项
{
key: 'your-entities',
icon: <TableOutlined />,
label: '实体管理',
path: '/your-entities'
}
];
```