|
|
@@ -0,0 +1,272 @@
|
|
|
+# 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()自动转换字符串参数
|
|
|
+
|
|
|
+5. **OpenAPI元数据**:
|
|
|
+ - ❌ 路径参数缺少OpenAPI描述
|
|
|
+ - ✅ 必须包含example和description
|
|
|
+
|
|
|
+6. **api响应**:
|
|
|
+ - ❌ 200响应码缺少
|
|
|
+ - ✅ 200也必须写,c.json(result, 200)
|
|
|
+
|
|
|
+7. **认证中间件**:
|
|
|
+ - ❌ security: [{ Bearer: [] }],
|
|
|
+ - ✅ middleware: [authMiddleware],
|
|
|
+
|
|
|
+8. **子路由路径**:
|
|
|
+ - ❌ path: '/users',
|
|
|
+ - ✅ path: '/',
|
|
|
+ - ❌ path: '/users/{id}',
|
|
|
+ - ✅ path: '/{id}',
|
|
|
+
|
|
|
+## 核心规范
|
|
|
+### 1. 路由定义
|
|
|
+
|
|
|
+### 2. 查询参数处理
|
|
|
+- **URL参数类型**:
|
|
|
+ - URL查询参数总是以字符串形式传递
|
|
|
+ - 必须正确处理字符串到其他类型的转换
|
|
|
+- **数字参数处理**:
|
|
|
+ ```typescript
|
|
|
+ // 错误方式 - 直接使用z.number()
|
|
|
+ z.number().int().positive() // 无法处理字符串参数
|
|
|
+
|
|
|
+ // 正确方式 - 使用z.coerce.number()
|
|
|
+ z.coerce.number().int().positive() // 自动转换字符串参数
|
|
|
+ ```
|
|
|
+- **布尔参数处理**:
|
|
|
+ ```typescript
|
|
|
+ // 错误方式 - 直接使用z.boolean()
|
|
|
+ z.boolean() // 无法处理字符串参数
|
|
|
+
|
|
|
+ // 正确方式 - 使用z.coerce.boolean()
|
|
|
+ z.coerce.boolean() // 自动转换字符串参数
|
|
|
+ ```
|
|
|
+- **路径参数**:
|
|
|
+ - 必须使用花括号 `{}` 定义 (例: `/{id}`)
|
|
|
+ - 必须定义 params Schema 并添加 OpenAPI 元数据:
|
|
|
+ ```typescript
|
|
|
+ const GetParams = z.object({
|
|
|
+ id: z.string().openapi({
|
|
|
+ param: { name: 'id', in: 'path' },
|
|
|
+ example: '1',
|
|
|
+ description: '资源ID'
|
|
|
+ })
|
|
|
+ });
|
|
|
+ ```
|
|
|
+ - 路由定义中必须包含 params 定义:
|
|
|
+ ```typescript
|
|
|
+ request: { params: GetParams }
|
|
|
+ ```
|
|
|
+ - 必须使用 `c.req.valid('param')` 获取路径参数
|
|
|
+
|
|
|
+- **请求定义**:
|
|
|
+ ```typescript
|
|
|
+ request: {
|
|
|
+ body: {
|
|
|
+ content: {
|
|
|
+ 'application/json': { schema: YourZodSchema }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+- **响应定义**:
|
|
|
+ ```typescript
|
|
|
+ responses: {
|
|
|
+ 200: {
|
|
|
+ description: '成功响应描述',
|
|
|
+ content: { 'application/json': { schema: SuccessSchema } }
|
|
|
+ },
|
|
|
+ 400: {
|
|
|
+ description: '客户端错误',
|
|
|
+ content: { 'application/json': { schema: ErrorSchema } }
|
|
|
+ },
|
|
|
+ 500: {
|
|
|
+ description: '服务器错误',
|
|
|
+ content: { 'application/json': { schema: ErrorSchema } }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ```
|
|
|
+ 列表响应定义示例
|
|
|
+ ```typescript
|
|
|
+ // 列表响应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: '每页数量'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ });
|
|
|
+ ```
|
|
|
+
|
|
|
+- **路由示例**:
|
|
|
+ ```typescript
|
|
|
+ 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定义完全一致
|
|
|
+- 处理示例:
|
|
|
+ ```typescript
|
|
|
+ try {
|
|
|
+ // 业务逻辑
|
|
|
+ } catch (error) {
|
|
|
+ return c.json({ code: 500, message: '操作失败' }, 500);
|
|
|
+ }
|
|
|
+ ```
|
|
|
+
|
|
|
+### 3. dataSource引入
|
|
|
+- 示例:
|
|
|
+ ```typescript
|
|
|
+ import { AppDataSource } from '@/server/data-source';
|
|
|
+ ```
|
|
|
+
|
|
|
+### 4. service初始化
|
|
|
+- 示例:
|
|
|
+ ```typescript
|
|
|
+ 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<AuthContext>().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;
|
|
|
+```
|