$npx -y skills add echoVic/boss-skill --skill api-development后端API开发方法论,包括RESTful/GraphQL设计、请求验证、错误处理和安全实现
| 1 | # 后端 API 开发方法论 |
| 2 | |
| 3 | ## API 契约管理 |
| 4 | |
| 5 | ### 契约来源 |
| 6 | |
| 7 | 实现 API 前,**必须**阅读 `architecture.md` §5(API 设计),获取: |
| 8 | - API 规范(RESTful/GraphQL) |
| 9 | - 接口列表(方法、路径、描述、认证要求) |
| 10 | - 请求/响应格式约定 |
| 11 | - 错误码规范 |
| 12 | |
| 13 | ### 契约遵守原则 |
| 14 | |
| 15 | 1. **严格实现**:API 端点的方法、路径、参数必须与 architecture.md §5 一致 |
| 16 | 2. **响应格式**:遵循统一的成功/错误响应结构 |
| 17 | 3. **偏差记录**:如需偏离契约,必须在输出报告中标注原因 |
| 18 | 4. **类型导出**:将请求/响应类型导出到共享文件,供前端引用 |
| 19 | |
| 20 | ## RESTful API 设计 |
| 21 | |
| 22 | ### 资源命名规范 |
| 23 | |
| 24 | | 操作 | HTTP 方法 | 路径 | 说明 | |
| 25 | |------|-----------|------|------| |
| 26 | | 列表 | GET | `/api/users` | 获取用户列表 | |
| 27 | | 详情 | GET | `/api/users/:id` | 获取单个用户 | |
| 28 | | 创建 | POST | `/api/users` | 创建新用户 | |
| 29 | | 更新 | PUT/PATCH | `/api/users/:id` | 更新用户 | |
| 30 | | 删除 | DELETE | `/api/users/:id` | 删除用户 | |
| 31 | |
| 32 | ### 统一响应格式 |
| 33 | |
| 34 | **成功响应**: |
| 35 | ```json |
| 36 | { |
| 37 | "success": true, |
| 38 | "data": { ... }, |
| 39 | "message": "Operation successful" |
| 40 | } |
| 41 | ``` |
| 42 | |
| 43 | **错误响应**: |
| 44 | ```json |
| 45 | { |
| 46 | "success": false, |
| 47 | "error": { |
| 48 | "code": "VALIDATION_ERROR", |
| 49 | "message": "Invalid input data", |
| 50 | "details": [ |
| 51 | { "field": "email", "message": "Invalid email format" } |
| 52 | ] |
| 53 | } |
| 54 | } |
| 55 | ``` |
| 56 | |
| 57 | ### 分页规范 |
| 58 | |
| 59 | **请求参数**: |
| 60 | ``` |
| 61 | GET /api/users?page=1&pageSize=20&sortBy=createdAt&order=desc |
| 62 | ``` |
| 63 | |
| 64 | **响应格式**: |
| 65 | ```json |
| 66 | { |
| 67 | "success": true, |
| 68 | "data": { |
| 69 | "items": [...], |
| 70 | "pagination": { |
| 71 | "page": 1, |
| 72 | "pageSize": 20, |
| 73 | "total": 100, |
| 74 | "totalPages": 5 |
| 75 | } |
| 76 | } |
| 77 | } |
| 78 | ``` |
| 79 | |
| 80 | ## 请求验证 |
| 81 | |
| 82 | ### 输入验证层级 |
| 83 | |
| 84 | 1. **路由层**:验证路径参数和查询参数 |
| 85 | 2. **中间件层**:验证请求体格式和必填字段 |
| 86 | 3. **Service 层**:验证业务规则 |
| 87 | |
| 88 | ### 验证示例 |
| 89 | |
| 90 | ```typescript |
| 91 | // 使用验证库(如 Zod、Joi、class-validator) |
| 92 | import { z } from 'zod'; |
| 93 | |
| 94 | const CreateUserSchema = z.object({ |
| 95 | name: z.string().min(1).max(100), |
| 96 | email: z.string().email(), |
| 97 | age: z.number().int().min(0).max(150).optional(), |
| 98 | }); |
| 99 | |
| 100 | // 在路由处理器中验证 |
| 101 | app.post('/api/users', async (req, res) => { |
| 102 | try { |
| 103 | const validatedData = CreateUserSchema.parse(req.body); |
| 104 | const user = await userService.create(validatedData); |
| 105 | res.json({ success: true, data: user }); |
| 106 | } catch (error) { |
| 107 | if (error instanceof z.ZodError) { |
| 108 | res.status(400).json({ |
| 109 | success: false, |
| 110 | error: { |
| 111 | code: 'VALIDATION_ERROR', |
| 112 | message: 'Invalid input data', |
| 113 | details: error.errors, |
| 114 | }, |
| 115 | }); |
| 116 | } |
| 117 | } |
| 118 | }); |
| 119 | ``` |
| 120 | |
| 121 | ### 常见验证规则 |
| 122 | |
| 123 | - **必填字段**:确保关键字段存在 |
| 124 | - **类型检查**:字符串、数字、布尔值、日期 |
| 125 | - **格式验证**:邮箱、URL、手机号、UUID |
| 126 | - **范围限制**:最小/最大长度、数值范围 |
| 127 | - **业务规则**:唯一性、外键存在性 |
| 128 | |
| 129 | ## 错误处理 |
| 130 | |
| 131 | ### 错误分类 |
| 132 | |
| 133 | | 错误类型 | HTTP 状态码 | 错误码 | 说明 | |
| 134 | |----------|-------------|--------|------| |
| 135 | | 验证错误 | 400 | VALIDATION_ERROR | 输入数据不合法 | |
| 136 | | 认证错误 | 401 | UNAUTHORIZED | 未登录或 Token 无效 | |
| 137 | | 权限错误 | 403 | FORBIDDEN | 无权限访问资源 | |
| 138 | | 资源不存在 | 404 | NOT_FOUND | 请求的资源不存在 | |
| 139 | | 冲突错误 | 409 | CONFLICT | 资源冲突(如重复创建) | |
| 140 | | 服务器错误 | 500 | INTERNAL_ERROR | 服务器内部错误 | |
| 141 | |
| 142 | ### 统一错误处理中间件 |
| 143 | |
| 144 | ```typescript |
| 145 | // errorHandler.ts |
| 146 | export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) { |
| 147 | console.error(err); |
| 148 | |
| 149 | if (err instanceof ValidationError) { |
| 150 | return res.status(400).json({ |
| 151 | success: false, |
| 152 | error: { |
| 153 | code: 'VALIDATION_ERROR', |
| 154 | message: err.message, |
| 155 | details: err.details, |
| 156 | }, |
| 157 | }); |
| 158 | } |
| 159 | |
| 160 | if (err instanceof NotFoundError) { |
| 161 | return res.status(404).json({ |
| 162 | success: false, |
| 163 | error: { |
| 164 | code: 'NOT_FOUND', |
| 165 | message: err.message, |
| 166 | }, |
| 167 | }); |
| 168 | } |
| 169 | |
| 170 | // 默认 500 错误 |
| 171 | res.status(500).json({ |
| 172 | success: false, |
| 173 | error: { |
| 174 | code: 'INTERNAL_ERROR', |
| 175 | message: 'An unexpected error occurred', |
| 176 | }, |
| 177 | }); |
| 178 | } |
| 179 | ``` |
| 180 | |
| 181 | ## 安全实现 |
| 182 | |
| 183 | ### 认证(Authentication) |
| 184 | |
| 185 | **JWT Token 示例**: |
| 186 | ```typescript |
| 187 | import jwt from 'jsonwebtoken'; |
| 188 | |
| 189 | // 生成 Token |
| 190 | export function generateToken(userId: string): string { |
| 191 | return jwt.sign({ userId }, process.env.JWT_SECRET!, { |
| 192 | expiresIn: '7d', |
| 193 | }); |
| 194 | } |
| 195 | |
| 196 | // 验证 Token 中间件 |
| 197 | export function authMiddleware(req: Request, res: Response, next: NextFunction) { |
| 198 | const token = req.headers.authorization?.replace('Bearer ', ''); |
| 199 | |
| 200 | if (!token) { |
| 201 | return res.status(401).json({ |
| 202 | success: false, |
| 203 | error: { code: 'UNAUTHORIZED', message: 'No token provided' }, |
| 204 | }); |
| 205 | } |
| 206 | |
| 207 | try { |
| 208 | const decoded = jwt.verify(token, process.env.JWT_SECRET!); |
| 209 | req.user = decoded; |
| 210 | next(); |
| 211 | } catch (error) { |
| 212 | res.status(401).json({ |
| 213 | success: false, |
| 214 | error: { code: 'UNAUTHORIZED', message: 'Invalid token' }, |
| 215 | }); |
| 216 | } |
| 217 | } |
| 218 | ``` |
| 219 | |
| 220 | ### 授权(Authorization) |
| 221 | |
| 222 | **基于角色的访问控制(RBAC)**: |
| 223 | ```typescript |
| 224 | export function requireRole(...roles: string[]) { |
| 225 | return (req: Request, res: Response, next: NextFunction) => { |
| 226 | if (!req.user || !roles.includes(req.user.role)) { |
| 227 | return res.status(403).json({ |
| 228 | success: false, |
| 229 | error: { code: 'FORBIDDEN', message: 'Insufficient permissions' }, |
| 230 | }); |
| 231 | } |
| 232 | next(); |
| 233 | }; |
| 234 | } |
| 235 | |
| 236 | // 使用 |
| 237 | app.delete('/api/users/:id', authMiddleware, requireRole('admin'), deleteUser); |
| 238 | ``` |
| 239 | |
| 240 | ### 输入消毒 |
| 241 | |
| 242 | - **SQL 注入防护**:使用参数化查询或 ORM |
| 243 | - **XSS 防护**:转义用户输入,使用 Content Security Policy |
| 244 | - **CSRF 防护**:使用 CSRF Token |
| 245 | - **文件上传**:验证文件类型和大小 |
| 246 | |
| 247 | ### 速率限制 |
| 248 | |
| 249 | ```typescript |
| 250 | import rateLimit from 'express-rate-limit'; |
| 251 | |
| 252 | const limiter = rateLimit({ |
| 253 | windo |