11-entity-creation.md 17 KB

新实体创建流程规范

流程概述

实体创建流程分为两种模式,根据业务复杂度选择合适的实现方式:

  • 标准通用CRUD:适用于简单数据模型,使用GenericCrudServicecreateCrudRoutes快速生成基础CRUD接口
  • 自定义复杂CRUD:适用于包含复杂业务逻辑的实体,需要手动实现服务方法和路由处理

适用场景选择

类型 适用场景 技术选型
标准通用CRUD 简单数据管理、无复杂业务逻辑、基础CRUD操作 GenericCrudService + createCrudRoutes
自定义复杂CRUD 复杂业务规则、多表关联操作、特殊权限控制、非标准数据处理 自定义Service + 手动路由实现

标准通用CRUD开发流程

1. 创建实体

  • 位置: src/server/modules/[模块名]/[实体名].entity.ts
  • 参考已有实体文件如user.entity.ts
  • 注意: 必须包含Zod Schema定义

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
  • 继承GenericCrudService
  • 通过构造函数注入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);
     }
    }
    

4. 创建API路由

  • 使用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;
    

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. 前端调用

  • 在页面组件(如pages_users.tsx)中:

    • 使用InferResponseType提取响应类型
    • 使用InferRequestType提取请求类型
    • 示例:

      type EntityResponse = InferResponseType<typeof entityClient.$get, 200>;
      type CreateRequest = InferRequestType<typeof entityClient.$post>['json'];
      

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

- **注册路由**:在`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开发流程

当实体需要复杂业务逻辑或非标准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. 前端调用

- 在页面组件(如`pages_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`中添加路由配置:
  ```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'
    }
  ];
  ```

注意事项

  1. 实体Schema必须在实体文件中定义,路由中直接引用,不要重复定义
  2. 前端表格/表单字段必须与实体定义保持一致
  3. 确保所有API调用都有正确的类型推断
  4. 参考现有模块实现保持风格一致