Переглянути джерело

📝 docs(rules): update entity creation process documentation

- add process overview with two implementation modes: standard generic CRUD and custom complex CRUD
- add scenario selection table for different CRUD types
- expand standard generic CRUD process with code examples for service and routes
- add detailed custom complex CRUD development process with comprehensive code examples
- include entity definition, service implementation, route creation, route registration, client API and frontend usage examples
- update directory structure examples for API routes
yourname 5 місяців тому
батько
коміт
4493e6b003
1 змінених файлів з 466 додано та 21 видалено
  1. 466 21
      .roo/rules/11-entity-creation.md

+ 466 - 21
.roo/rules/11-entity-creation.md

@@ -1,37 +1,82 @@
 # 新实体创建流程规范
 
-## 完整开发流程
+## 流程概述
 
-1. **创建实体**
+实体创建流程分为两种模式,根据业务复杂度选择合适的实现方式:
+
+- **标准通用CRUD**:适用于简单数据模型,使用`GenericCrudService`和`createCrudRoutes`快速生成基础CRUD接口
+- **自定义复杂CRUD**:适用于包含复杂业务逻辑的实体,需要手动实现服务方法和路由处理
+
+## 适用场景选择
+
+| 类型 | 适用场景 | 技术选型 |
+|------|----------|----------|
+| 标准通用CRUD | 简单数据管理、无复杂业务逻辑、基础CRUD操作 | `GenericCrudService` + `createCrudRoutes` |
+| 自定义复杂CRUD | 复杂业务规则、多表关联操作、特殊权限控制、非标准数据处理 | 自定义Service + 手动路由实现 |
+
+## 标准通用CRUD开发流程
+
+### 1. **创建实体**
    - 位置: `src/server/modules/[模块名]/[实体名].entity.ts`
    - 参考已有实体文件如`user.entity.ts`
    - 注意: 必须包含Zod Schema定义
 
-2. **创建Service**
+### 2. **创建Service**
    - 位置: `src/server/modules/[模块名]/[实体名].service.ts`
+   - 继承`GenericCrudService`
    - 通过构造函数注入DataSource
-   - 使用实体Schema进行输入输出验证
+   ```typescript
+   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);
+     }
+   }
+   ```
 
-3. **创建API路由**
-   - 目录结构:
-     ```
-     src/server/api/[实体名]/
-     ├── get.ts       # 列表
-     ├── post.ts      # 创建
-     ├── [id]/
-     │   ├── get.ts   # 详情
-     │   ├── put.ts   # 更新
-     │   └── delete.ts # 删除
-     └── index.ts     # 路由聚合
+### 3. **创建API路由**
+   - 使用`createCrudRoutes`快速生成CRUD路由:
+     ```typescript
+     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;
      ```
-   - 必须使用实体Schema作为请求/响应Schema
-   - 参考`users`模块的实现
 
-4. **注册路由**
-   - 在`src/server/api.ts`中添加路由注册
+### 4. **注册路由**
+   - 在`src/server/api.ts`中添加路由注册:
+     ```typescript
+     import yourEntityRoutes from '@/server/api/your-entity/index';
+     
+     // 注册路由
+     api.route('/api/v1/your-entities', yourEntityRoutes);
+     ```
 
-5. **创建客户端API**
-   - 在`src/client/api.ts`中添加客户端定义
+### 5. **创建客户端API**
+   - 在`src/client/api.ts`中添加客户端定义:
+     ```typescript
+     import { hc } from 'hono/client';
+     import { YourEntityRoutes } from '@/server/api';
+     
+     export const yourEntityClient = hc<YourEntityRoutes>('/api/v1', {
+       fetch: axiosFetch,
+     }).your-entities;
+     ```
 
 6. **前端调用**
    - 在页面组件(如`pages_users.tsx`)中:
@@ -43,6 +88,406 @@
        type CreateRequest = InferRequestType<typeof entityClient.$post>['json'];
        ```
 
+## 自定义复杂CRUD开发流程
+
+当实体需要复杂业务逻辑或非标准CRUD操作时,采用以下完整流程:
+
+### 1. **创建实体**
+   - 位置: `src/server/modules/[模块名]/[实体名].entity.ts`
+   - 定义实体类和Zod Schema
+   - 示例:
+     ```typescript
+     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. **创建自定义Service**
+   - 位置: `src/server/modules/[模块名]/[实体名].service.ts`
+   - 实现自定义业务逻辑和数据访问
+   - 示例:
+     ```typescript
+     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> {
+         // 实现复杂业务逻辑
+         // 可能包含事务、多表操作等
+       }
+     }
+     ```
+
+### 3. **创建自定义API路由**
+   - 目录结构:
+     ```
+     src/server/api/[实体名]/
+     ├── get.ts       # 列表查询
+     ├── post.ts      # 创建实体
+     ├── [id]/
+     │   ├── get.ts   # 获取单个实体
+     │   ├── put.ts   # 更新实体
+     │   └── delete.ts # 删除实体
+     └── index.ts     # 路由聚合
+     ```
+   
+   - **列表查询路由示例** (get.ts):
+     ```typescript
+     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):
+     ```typescript
+     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):
+     ```typescript
+     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;
+     ```
+
+### 4. **注册路由**
+   - 在`src/server/api.ts`中添加路由注册:
+     ```typescript
+     import yourEntityRoutes from '@/server/api/your-entity/index';
+     
+     // 注册路由
+     api.route('/api/v1/your-entities', yourEntityRoutes);
+     ```
+
+### 5. **创建客户端API**
+   - 在`src/client/api.ts`中添加客户端定义:
+     ```typescript
+     import { hc } from 'hono/client';
+     import { YourEntityRoutes } from '@/server/api';
+     import { axiosFetch } from '@/client/utils/fetch';
+     
+     export const yourEntityClient = hc<YourEntityRoutes>('/api/v1', {
+       fetch: axiosFetch,
+     }).your_entities; // 注意: 路由路径中的连字符会转为下划线
+     
+     // 类型提取
+     import type { InferRequestType, InferResponseType } from 'hono/client';
+     
+     // 列表查询
+     export type YourEntityListQuery = InferRequestType<typeof yourEntityClient.$get>['query'];
+     export type YourEntityListResponse = InferResponseType<typeof yourEntityClient.$get, 200>;
+     
+     // 创建实体
+     export type CreateYourEntityRequest = InferRequestType<typeof yourEntityClient.$post>['json'];
+     export type CreateYourEntityResponse = InferResponseType<typeof yourEntityClient.$post, 200>;
+     
+     // 获取单个实体
+     export type GetYourEntityParams = InferRequestType<typeof yourEntityClient[':id']['$get']>['param'];
+     export type GetYourEntityResponse = InferResponseType<typeof yourEntityClient[':id']['$get'], 200>;
+     
+     // 更新实体
+     export type UpdateYourEntityParams = InferRequestType<typeof yourEntityClient[':id']['$put']>['param'];
+     export type UpdateYourEntityRequest = InferRequestType<typeof yourEntityClient[':id']['$put']>['json'];
+     export type UpdateYourEntityResponse = InferResponseType<typeof yourEntityClient[':id']['$put'], 200>;
+     
+     // 删除实体
+     export type DeleteYourEntityParams = InferRequestType<typeof yourEntityClient[':id']['$delete']>['param'];
+     export type DeleteYourEntityResponse = InferResponseType<typeof yourEntityClient[':id']['$delete'], 200>;
+     ```
+
+### 6. **前端调用示例**
+   - 在页面组件中使用客户端API:
+     ```typescript
+     import { yourEntityClient, type YourEntityListResponse } from '@/client/api';
+     import { useQuery, useMutation, useQueryClient } from 'react-query';
+     
+     // 获取列表数据
+     const fetchEntities = async () => {
+       const res = await yourEntityClient.$get({
+         query: { page: 1, pageSize: 10, status: 1 }
+       });
+       if (!res.ok) {
+         const error = await res.json();
+         throw new Error(error.message || '获取数据失败');
+       }
+       return res.json() as Promise<YourEntityListResponse>;
+     };
+     
+     // 使用React Query获取数据
+     export function useEntitiesData() {
+       return useQuery(['entities'], fetchEntities);
+     }
+     
+     // 创建实体
+     export function useCreateEntity() {
+       const queryClient = useQueryClient();
+       return useMutation(
+         (data) => yourEntityClient.$post({ json: data }),
+         {
+           onSuccess: () => {
+             queryClient.invalidateQueries(['entities']);
+           }
+         }
+       );
+     }
+     ```
+
 ## 注意事项
 
 1. 实体Schema必须在实体文件中定义,路由中直接引用,不要重复定义