07-openapi.md 6.7 KB

Hono OpenAPI规范

常见不规范问题

  1. 路径参数问题:

    • ❌ 使用冒号定义路径参数: /:id
    • ✅ 必须使用花括号: /{id}
  2. 参数Schema缺失:

    • ❌ 未定义params Schema
    • ✅ 必须定义并添加OpenAPI元数据
  3. 参数获取方式:

    • ❌ 使用c.req.param()
    • ✅ 必须使用c.req.valid('param')
  4. URL参数类型转换:

    • ❌ 直接使用z.number()验证URL查询参数
    • ✅ 必须使用z.coerce.number()自动转换字符串参数, zod v4必须要加
    • OpenAPI元数据:

      • ❌ 路径参数缺少OpenAPI描述
      • ✅ 必须包含example和description
    • api响应:

      • ❌ 200响应码缺少
      • ✅ 200也必须写,c.json(result, 200)
    • 认证中间件:

      • ❌ security: [{ Bearer: [] }],
      • ✅ middleware: [authMiddleware],
    • 子路由路径:

      • ❌ path: '/users',
      • ✅ path: '/',
      • ❌ path: '/users/{id}',
      • ✅ path: '/{id}',

    核心规范

    1. 路由定义

    2. 查询参数处理

    • URL参数类型:
      • URL查询参数总是以字符串形式传递
      • 必须正确处理字符串到其他类型的转换
    • 数字参数处理:

      // 错误方式 - 直接使用z.number()
      z.number().int().positive() // 无法处理字符串参数
      
      // 正确方式 - 使用z.coerce.number()
      z.coerce.number().int().positive() // 自动转换字符串参数
      
    • 布尔参数处理:

      // 错误方式 - 直接使用z.boolean()
      z.boolean() // 无法处理字符串参数
      
      // 正确方式 - 使用z.coerce.boolean()
      z.coerce.boolean() // 自动转换字符串参数
      
    • 路径参数:

      • 必须使用花括号 {} 定义 (例: /{id})
      • 必须定义 params Schema 并添加 OpenAPI 元数据:

        const GetParams = z.object({
        id: z.string().openapi({
        param: { name: 'id', in: 'path' },
        example: '1',
        description: '资源ID'
        })
        });
        
      • 路由定义中必须包含 params 定义:

        request: { params: GetParams }
        
      • 必须使用 c.req.valid('param') 获取路径参数

    • 请求定义:

      request: {
      body: {
        content: {
          'application/json': { schema: YourZodSchema }
        }
      }
      }
      
    • 响应定义:

      responses: {
      200: {
        description: '成功响应描述',
        content: { 'application/json': { schema: SuccessSchema } }
      },
      400: {
        description: '客户端错误',
        content: { 'application/json': { schema: ErrorSchema } }
      },
      500: {
        description: '服务器错误',
        content: { 'application/json': { schema: ErrorSchema } }
      }
      }
      

      列表响应定义示例

      // 列表响应Schema, 响应时,data应统一用实体中定义的schema
      import { RackInfoSchema } from '@/server/modules/racks/rack-info.entity';
      const RackListResponse = z.object({
      data: z.array(RackInfoSchema),
      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: 'post',
      path: '/',
      middleware: [authMiddleware],
      request: {
        body: {
          content: { 'application/json': { schema: CreateSchema } }
        }
      },
      responses: {
        200: { ... },
        400: { ... },
        500: { ... }
      }
      });
      

    2. 错误处理

    • 错误响应必须使用统一格式: { code: number, message: string }
    • 必须与OpenAPI定义完全一致
    • 处理示例:

      try {
      // 业务逻辑
      } catch (error) {
      return c.json({ code: 500, message: '操作失败' }, 500);
      }
      

    3. dataSource引入

    • 示例:

      import { AppDataSource } from '@/server/data-source';
      

    4. service初始化

    • 示例:

      import { WorkspaceService } from '@/server/modules/workspaces/workspace.service';
      const workspaceService = new WorkspaceService(AppDataSource);
      
      ### 5. 用户context获取
      - 示例:
      

      typescript

    const user = c.get('user');

      - 注意: 确保 `c.get('user')` 已经在 `authMiddleware` 中设置
    
    ### 6. AuthContext引用
    - 示例:
    

    typescript import { AuthContext } from '@/server/types/context';

    
    ### 7. createRoute, OpenAPIHono 引入
    - 示例:
    

    typescript import { createRoute, OpenAPIHono } from '@hono/zod-openapi';

    
    ### 8. ErrorSchema 引入
    - 示例:
    

    typescript import { ErrorSchema } from '@/server/utils/errorHandler';

    
    ## 进阶规范
    ### 1. 路由聚合
    当多个相关路由需要组合时:
    1. **文件结构**:
       - 拆分为独立文件 (`create.ts`, `list.ts` 等)
       - 创建 `index.ts` 聚合所有子路由
    
    

    src/server/api/ ├── [resource]/ # 资源路由目录 │ ├── [id]/ # 带ID的子路由 │ │ ├── get.ts # 获取单条 │ │ ├── put.ts # 更新单条
    │ │ └── delete.ts # 删除单条 │ ├── get.ts # 列表查询 │ ├── post.ts # 创建资源 │ └── index.ts # 聚合导出

    
    2. **实现**:
    

    typescript

    import listRoute from './get';
    import createRackRoute from './post';
    import getByIdRoute from './[id]/get';
    import updateRoute from './[id]/put';
    import deleteRoute from './[id]/delete';
    import { OpenAPIHono } from '@hono/zod-openapi';
    
    const app = new OpenAPIHono()
      .route('/', listRoute)
      .route('/', createRackRoute)
      .route('/', getByIdRoute)
      .route('/', updateRoute)
      .route('/', deleteRoute)
    
    export default app;
    
    
    3. **优势**:
       - 保持模块化
       - 简化维护
       - 统一API入口
    
    ## 路由文件代码结构规范
      +imports: 依赖导入
      +serviceInit: 服务初始化
      +paramsSchema: 路径参数定义
      +responseSchema: 响应定义
      +errorSchema: 错误定义
      +routeDef: 路由定义
      +app: 路由实例
    
    ## src/server/api.ts 统一引入
    

    ts import authRoute from '@/server/api/auth/index' const routes = api.route('/api/v1/auth', authRoute)

    
    ## 完整示例
    

    typescript // 路由实例 const app = new OpenAPIHono().openapi(routeDef, async (c) => { try {

    // 业务逻辑
    return c.json(result, 200);
    

    } catch (error) {

    return c.json({ 
      code: 500, 
      message: error instanceOf Error ? error.message : '操作失败' 
    }, 500);
    

    } });

    export default app; ```