2
0

11-custom-crud.md 12 KB

自定义复杂CRUD开发流程规范

适用场景

适用于包含复杂业务逻辑的实体,需要手动实现服务方法和路由处理的场景,如:

  • 复杂业务规则
  • 多表关联操作
  • 特殊权限控制
  • 非标准数据处理

开发流程

1. 创建实体

  • 位置: src/server/modules/[模块名]/[实体名].entity.ts
  • 定义实体类和Zod Schema
  • 示例:

     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: '示例名称' })
       // 其他更新字段...
     });
    

2. 注册实体到数据源

  • src/server/data-source.ts中添加实体导入和注册:

     // 实体类导入
     import { YourEntity } from "./modules/[模块名]/[实体名].entity"
         
     export const AppDataSource = new DataSource({
       // ...其他配置
       entities: [
         User, Role, YourEntity // 添加新实体到数组
       ],
       // ...其他配置
     });
    

3. 创建自定义Service

  • 位置: 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> {
         // 实现复杂业务逻辑
         // 可能包含事务、多表操作等
       }
     }
    

4. 创建自定义API路由

  • 目录结构:

     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;
    

5. 注册路由

  • src/server/api.ts中添加路由注册:

     import yourEntityRoutes from '@/server/api/your-entity/index';
         
     // 注册路由
     api.route('/api/v1/your-entities', yourEntityRoutes);
    

6. 创建客户端API

  • 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'];
    

7. 前端调用

- 在页面组件(如`Users.tsx`)中:
  - 使用`InferResponseType`提取响应类型
  - 使用`InferRequestType`提取请求类型
  - 示例:
    ```typescript
    type EntityResponse = InferResponseType<typeof entityClient.$get, 200>;
    type CreateRequest = InferRequestType<typeof entityClient.$post>['json'];
    ```

8. 注册管理后台路由和菜单

- **注册路由**:在`src/client/admin/routes.tsx`中添加路由配置

- **注册菜单**:在`src/client/admin/menu.tsx`中添加菜单配置