🔌 RESTful / GraphQL API 设计规范与标准模板

基于 OpenClaw + Claude Code 的端到端研发自动化系统 · API 接口协议设计 Agent 输出标准

版本:v1.0 | 更新日期:2026 年 3 月 13 日 | 遵循 OpenAPI 3.1 & GraphQL 2026 Spec

📑 目录导航

API 设计风格选择指南

1.1 RESTful vs GraphQL 对比

RESTful API

  • 适用场景:资源导向型应用、CRUD 操作、简单查询
  • 优势:
    • 标准化程度高,生态成熟
    • HTTP 语义清晰,缓存友好
    • 工具链完善(Swagger/Postman)
    • 学习成本低,易于理解
  • 劣势:
    • 过度获取/获取不足问题
    • 多次请求解决复杂查询
    • 版本迭代需维护多套接口
  • 推荐用例:用户管理、商品 CRUD、文件上传下载

GraphQL API

  • 适用场景:复杂数据关系、多端适配、实时订阅
  • 优势:
    • 按需获取数据,减少网络传输
    • 单次请求获取关联数据
    • 强类型 Schema,自文档化
    • 向后兼容,无需版本管理
  • 劣势:
    • 缓存策略复杂
    • 查询复杂度控制难
    • 学习曲线较陡
  • 推荐用例:社交网络信息流、仪表盘数据聚合、移动端适配

1.2 选型决策树

快速决策指南:
  1. 是否需要精确控制返回字段? → 是:GraphQL / 否:REST
  2. 是否有复杂嵌套查询需求? → 是:GraphQL / 否:REST
  3. 是否需要实时订阅推送? → 是:GraphQL Subscriptions
  4. 团队是否熟悉 GraphQL? → 否:优先 REST
  5. 是否需要强类型约束? → 是:GraphQL

1.3 混合架构方案

在实际项目中,可采用REST + GraphQL 混合架构

RESTful API 设计规范

2.1 URL 命名规范

2.1.1 基本规则

# ✅ 正确示例 GET /api/v1/users # 获取用户列表 GET /api/v1/users/{id} # 获取单个用户 POST /api/v1/users # 创建用户 PUT /api/v1/users/{id} # 全量更新用户 PATCH /api/v1/users/{id} # 部分更新用户 DELETE /api/v1/users/{id} # 删除用户 # 资源嵌套 GET /api/v1/users/{userId}/orders # 获取用户的订单 GET /api/v1/users/{userId}/orders/{orderId} # 获取用户的特定订单 # ❌ 错误示例 GET /api/v1/getUsers # 动词出现在 URL 中 POST /api/v1/createUser # 使用动词而非名词 GET /api/v1/user-list # 使用连字符而非复数

2.1.2 命名约定

规则 说明 示例
使用名词复数 资源集合使用复数形式 /users, /orders, /products
小写字母 URL 全部使用小写 /api/v1/users
连字符分隔 多单词资源用连字符 /user-profiles, /order-items
避免深层嵌套 嵌套不超过 2 层 /users/{id}/orders ✅

2.2 HTTP 方法使用规范

方法 语义 幂等性 典型场景
GET 获取资源 查询列表、详情
POST 创建资源 新建用户、提交订单
PUT 替换资源 全量更新用户信息
PATCH 部分更新 修改邮箱、状态
DELETE 删除资源 删除用户、取消订单

2.3 查询参数规范

# 分页 GET /api/v1/users?page=1&size=20 GET /api/v1/users?offset=0&limit=20 # 排序 GET /api/v1/users?sort=created_at desc GET /api/v1/users?sort=name asc,email desc # 过滤 GET /api/v1/users?status=active&role=admin GET /api/v1/products?price_min=100&price_max=500 # 字段选择 GET /api/v1/users?fields=id,name,email # 搜索 GET /api/v1/users?q=john&search_fields=name,email

2.4 请求头规范

# 必需头 Content-Type: application/json Authorization: Bearer <token> # 可选头 Accept: application/json Accept-Language: zh-CN X-Request-ID: abc123xyz # 链路追踪 X-Client-Version: 2.1.0 # 客户端版本 If-None-Match: "etag-value" # 缓存验证

GraphQL API 设计规范

3.1 Schema 设计原则

3.1.1 类型定义规范

type User { # 唯一标识符 id: ID! # 基本信息 username: String! email: String! displayName: String # 时间戳 createdAt: DateTime! updatedAt: DateTime! # 关联关系 posts(limit: Int = 10, offset: Int = 0): [Post!]! followers(limit: Int = 20): [User!]! # 计算字段 fullName: String! @deprecated(reason: "Use displayName instead") }

3.1.2 查询设计

type Query { # 单资源查询 user(id: ID!): User # 列表查询(带分页) users( filter: UserFilter pagination: PaginationInput sort: UserSortInput ): UserConnection! # 聚合查询 userStats(userId: ID!): UserStats! }

3.1.3 变更操作

type Mutation { # 创建资源 createUser(input: CreateUserInput!): CreateUserPayload! # 更新资源 updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload! # 删除资源 deleteUser(id: ID!): DeleteUserPayload! } input CreateUserInput { username: String! email: String! password: String! displayName: String } type CreateUserPayload { user: User errors: [Error!] }

3.2 分页规范

# Cursor 分页(推荐) type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! } type UserEdge { node: User! cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } # 查询示例 query { users(first: 20, after: "cursor123") { edges { node { id name } cursor } pageInfo { hasNextPage endCursor } } }

3.3 错误处理

type Error { code: String! message: String! field: String path: [String!] } # Union 类型处理多种结果 union UserResult = User | Error type Query { user(id: ID!): UserResult! }

3.4 安全最佳实践

必须实施的安全措施:
  • 禁用内省查询(生产环境)
  • 设置查询复杂度限制(最大深度、字段数)
  • 实现查询超时机制
  • 对输入参数进行严格验证
  • 实施速率限制(Rate Limiting)

统一响应格式与错误处理

4.1 RESTful 统一响应格式

# 成功响应(2xx) { "code": 200, "message": "success", "data": { "id": "usr_123abc", "username": "john_doe", "email": "john@example.com" }, "meta": { "timestamp": "2026-03-13T10:30:00Z", "trace_id": "req_abc123xyz", "version": "v1" } } # 列表响应 { "code": 200, "message": "success", "data": { "items": [...], "pagination": { "page": 1, "size": 20, "total": 156, "totalPages": 8 } }, "meta": { ... } }

4.2 错误响应格式

# 客户端错误(4xx) { "code": 400, "message": "Validation failed", "errors": [ { "field": "email", "code": "INVALID_FORMAT", "message": "Invalid email format" }, { "field": "password", "code": "TOO_SHORT", "message": "Password must be at least 8 characters" } ], "meta": { "timestamp": "2026-03-13T10:30:00Z", "trace_id": "req_xyz789" } } # 服务端错误(5xx) { "code": 500, "message": "Internal server error", "error_id": "ERR_DB_CONNECTION", "meta": { "timestamp": "2026-03-13T10:30:00Z", "trace_id": "req_err456" } }

4.3 错误码字典

错误码范围 类型 示例
200-299 成功 200 OK, 201 Created, 204 No Content
400-499 客户端错误 400 参数错误,401 未授权,403 禁止访问,404 不存在
500-599 服务端错误 500 内部错误,502 网关错误,503 服务不可用
1000-1999 通用业务错误 1001 参数校验失败,1002 资源不存在
2000-2999 用户相关错误 2001 用户不存在,2002 密码错误,2003 账号已锁定
3000-3999 订单相关错误 3001 库存不足,3002 订单已取消

4.4 GraphQL 错误格式

{ "data": null, "errors": [ { "message": "User not found", "locations": [{"line": 2, "column": 3}], "path": ["user"], "extensions": { "code": "NOT_FOUND", "field": "id", "timestamp": "2026-03-13T10:30:00Z" } } ] }

认证授权与安全规范

5.1 认证机制

5.1.1 JWT Token 认证

# 登录获取 Token POST /api/v1/auth/login Content-Type: application/json { "username": "john_doe", "password": "SecurePass123!" } # 响应 { "code": 200, "data": { "access_token": "eyJhbGciOiJIUzI1NiIs...", "refresh_token": "eyJhbGciOiJIUzI1NiIs...", "expires_in": 3600, "token_type": "Bearer" } } # 使用 Token GET /api/v1/users/me Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

5.1.2 OAuth 2.0 第三方登录

# 微信 OAuth 流程 1. 重定向到微信授权页 GET https://open.weixin.qq.com/connect/qrconnect? appid=APPID& redirect_uri=REDIRECT_URI& response_type=code& scope=snsapi_login& state=STATE 2. 使用 code 换取 access_token POST https://api.weixin.qq.com/sns/oauth2/access_token 3. 获取用户信息 GET https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN

5.2 权限控制

5.2.1 RBAC 角色权限模型

# 角色定义 roles: - admin: permissions: ["*:*"] - manager: permissions: ["users:read", "users:write", "orders:read"] - user: permissions: ["profile:read", "profile:write"] # 权限注解示例(Python FastAPI) @router.get("/users") @require_permission("users:read") async def list_users(current_user: User = Depends(get_current_user)): ...

5.3 安全措施

必须实施的安全防护:
  • HTTPS 强制:所有 API 必须使用 HTTPS
  • 速率限制:基于 IP 和用户维度的限流
  • CORS 配置:严格限制允许的源
  • 输入验证:对所有输入参数进行严格校验
  • SQL 注入防护:使用参数化查询
  • XSS 防护:输出编码和 CSP 头
  • 敏感数据加密:密码、身份证号等加密存储

5.4 安全头配置

# 响应头安全配置 Strict-Transport-Security: max-age=31536000; includeSubDomains X-Content-Type-Options: nosniff X-Frame-Options: DENY X-XSS-Protection: 1; mode=block Content-Security-Policy: default-src 'self' Referrer-Policy: strict-origin-when-cross-origin

RESTful OpenAPI 标准模板

6.1 OpenAPI 3.1 完整模板

openapi: "3.1.0" info: title: "用户管理系统 API" description: | 提供用户注册、登录、信息管理等功能 ## 认证方式 使用 JWT Bearer Token 进行认证 ## 错误码说明 - 1001: 参数校验失败 - 2001: 用户不存在 - 2002: 密码错误 version: "1.0.0" contact: name: "API Support" email: "support@example.com" license: name: "MIT" url: "https://opensource.org/licenses/MIT" servers: - url: "https://api.example.com/v1" description: "生产环境" - url: "https://staging-api.example.com/v1" description: "预发布环境" - url: "http://localhost:8000/v1" description: "本地开发环境" tags: - name: "认证" description: "用户认证相关接口" - name: "用户" description: "用户管理相关接口" paths: /auth/login: post: tags: ["认证"] summary: "用户登录" operationId: "loginUser" requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/LoginRequest" responses: "200": description: "登录成功" content: application/json: schema: $ref: "#/components/schemas/LoginResponse" "401": description: "认证失败" content: application/json: schema: $ref: "#/components/schemas/Error" /users: get: tags: ["用户"] summary: "获取用户列表" operationId: "listUsers" security: - bearerAuth: [] parameters: - name: "page" in: "query" schema: type: "integer" default: 1 - name: "size" in: "query" schema: type: "integer" default: 20 maximum: 100 responses: "200": description: "成功" content: application/json: schema: $ref: "#/components/schemas/UserList" components: securitySchemes: bearerAuth: type: "http" scheme: "bearer" bearerFormat: "JWT" schemas: LoginRequest: type: "object" required: ["username", "password"] properties: username: type: "string" example: "john_doe" password: type: "string" format: "password" example: "SecurePass123!" LoginResponse: type: "object" properties: code: type: "integer" example: 200 data: type: "object" properties: access_token: type: "string" refresh_token: type: "string" expires_in: type: "integer" example: 3600 User: type: "object" required: ["id", "username", "email"] properties: id: type: "string" format: "uuid" username: type: "string" email: type: "string" format: "email" created_at: type: "string" format: "date-time" Error: type: "object" required: ["code", "message"] properties: code: type: "integer" message: type: "string" errors: type: "array" items: $ref: "#/components/schemas/ValidationError"
模板使用说明:
  • 将模板保存为 openapi.yaml
  • 使用 Swagger UI 渲染:swagger-ui/openapi.yaml
  • 生成客户端代码:openapi-generator generate -i openapi.yaml -g typescript-axios
  • 生成服务端桩代码:openapi-generator generate -i openapi.yaml -g python-fastapi

GraphQL Schema 标准模板

7.1 完整 Schema 模板

# schema.graphql # 标量类型扩展 scalar DateTime scalar JSON # 枚举类型 enum UserRole { ADMIN MANAGER USER GUEST } enum UserStatus { ACTIVE INACTIVE SUSPENDED DELETED } # 对象类型 type User { id: ID! username: String! email: String! role: UserRole! status: UserStatus! profile: UserProfile posts(limit: Int = 10, offset: Int = 0): PostConnection! createdAt: DateTime! updatedAt: DateTime! } type UserProfile { displayName: String avatar: String bio: String location: String } type Post { id: ID! title: String! content: String! author: User! comments(limit: Int = 20): CommentConnection! createdAt: DateTime! } # 连接类型(分页) type UserConnection { edges: [UserEdge!]! pageInfo: PageInfo! totalCount: Int! } type UserEdge { node: User! cursor: String! } type PostConnection { edges: [PostEdge!]! pageInfo: PageInfo! totalCount: Int! } type PostEdge { node: Post! cursor: String! } type CommentConnection { edges: [CommentEdge!]! pageInfo: PageInfo! totalCount: Int! } type CommentEdge { node: Comment! cursor: String! } type PageInfo { hasNextPage: Boolean! hasPreviousPage: Boolean! startCursor: String endCursor: String } # 查询类型 type Query { # 单资源查询 user(id: ID!): User me: User # 列表查询 users( filter: UserFilter pagination: PaginationInput sort: UserSortInput ): UserConnection! post(id: ID!): Post posts(filter: PostFilter, pagination: PaginationInput): PostConnection! } # 变更类型 type Mutation { # 认证 login(username: String!, password: String!): AuthPayload! register(input: RegisterInput!): AuthPayload! refreshToken(refreshToken: String!): RefreshTokenPayload! # 用户操作 updateUser(id: ID!, input: UpdateUserInput!): UpdateUserPayload! deleteUser(id: ID!): DeleteUserPayload! # 文章操作 createPost(input: CreatePostInput!): CreatePostPayload! updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload! deletePost(id: ID!): DeletePostPayload! } # 订阅类型 type Subscription { userCreated: User! postPublished: Post! commentAdded(postId: ID!): Comment! } # 输入类型 input UserFilter { role: UserRole status: UserStatus search: String } input PaginationInput { first: Int after: String last: Int before: String } input UserSortInput { field: UserSortField! order: SortOrder! } enum UserSortField { CREATED_AT UPDATED_AT USERNAME EMAIL } enum SortOrder { ASC DESC } input RegisterInput { username: String! email: String! password: String! displayName: String } input UpdateUserInput { username: String email: String displayName: String bio: String location: String } input CreatePostInput { title: String! content: String! } input UpdatePostInput { title: String content: String } # Payload 类型 type AuthPayload { user: User accessToken: String! refreshToken: String! expiresIn: Int! errors: [Error!] } type RefreshTokenPayload { accessToken: String! expiresIn: Int! errors: [Error!] } type UpdateUserPayload { user: User errors: [Error!] } type DeleteUserPayload { success: Boolean! errors: [Error!] } type CreatePostPayload { post: Post errors: [Error!] } type UpdatePostPayload { post: Post errors: [Error!] } type DeletePostPayload { success: Boolean! errors: [Error!] } type Error { code: String! message: String! field: String path: [String!] }

7.2 Resolver 模板(Node.js)

# resolvers.js const resolvers = { Query: { user: async (parent, { id }, context) => { return context.db.user.findById(id); }, users: async (parent, { filter, pagination }, context) => { const users = await context.db.user.findMany({ where: filter, take: pagination?.first || 10, skip: pagination?.after ? 1 : 0, }); return { edges: users.map(user => ({ node: user, cursor: user.id })), pageInfo: { hasNextPage: users.length > pagination.first, hasPreviousPage: false, }, totalCount: await context.db.user.count(), }; }, }, Mutation: { createUser: async (parent, { input }, context) => { try { const user = await context.db.user.create({ data: input }); return { user, errors: [] }; } catch (error) { return { user: null, errors: [{ code: 'CREATE_FAILED', message: error.message }] }; } }, }, User: { posts: async (parent, { limit, offset }, context) => { const posts = await context.db.post.findByUser(parent.id, { limit, offset }); return { edges: posts.map(post => ({ node: post, cursor: post.id })), pageInfo: { hasNextPage: posts.length === limit }, totalCount: await context.db.post.countByUser(parent.id), }; }, }, };

API 版本管理策略

8.1 版本号命名规范

# Semantic Versioning (SemVer) 主版本号。次版本号。修订号 MAJOR.MINOR.PATCH # 示例 v1.0.0 # 初始稳定版本 v1.1.0 # 向后兼容的功能新增 v1.1.1 # 向后兼容的问题修复 v2.0.0 # 不兼容的 API 变更

8.2 版本控制方式

8.2.1 URL 路径版本(推荐)

GET https://api.example.com/v1/users GET https://api.example.com/v2/users

8.2.2 请求头版本

GET https://api.example.com/users Accept-Version: v2 # 或 Accept: application/vnd.example.v2+json

8.2.3 查询参数版本

GET https://api.example.com/users?version=2

8.3 弃用策略

弃用流程:
  1. 提前通知:至少提前 3 个月邮件/文档通知
  2. 响应头标记:Deprecation: true, Sunset: Sat, 31 Dec 2026 23:59:59 GMT
  3. 并行运行:新旧版本同时支持过渡期
  4. 监控使用:追踪旧版本调用量,主动联系重度用户
  5. 正式下线:过渡期后关闭旧版本

8.4 向后兼容准则

文档生成与测试规范

9.1 自动文档生成

9.1.1 Swagger UI 集成

# Python FastAPI 示例 from fastapi import FastAPI from fastapi.openapi.docs import get_swagger_ui_html app = FastAPI( title="用户管理系统", docs_url="/docs", redoc_url="/redoc", ) # 访问 http://localhost:8000/docs 查看交互式文档

9.1.2 GraphQL Playground

# Node.js Apollo Server const server = new ApolloServer({ typeDefs, resolvers, playground: { settings: { 'request.credentials': 'include', }, }, introspection: process.env.NODE_ENV !== 'production', });

9.2 API 测试规范

9.2.1 Postman 集合模板

{ "info": { "name": "用户管理系统 API", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "variable": [ { "key": "baseUrl", "value": "http://localhost:8000/v1" }, { "key": "token", "value": "" } ], "item": [ { "name": "认证", "item": [ { "name": "用户登录", "request": { "method": "POST", "header": [{"key": "Content-Type", "value": "application/json"}], "body": { "mode": "raw", "raw": "{\"username\":\"test\",\"password\":\"123456\"}" }, "url": {"raw": "{{baseUrl}}/auth/login"} }, "event": [ { "listen": "test", "script": { "exec": [ "var jsonData = pm.response.json();", "pm.collectionVariables.set('token', jsonData.data.access_token);" ] } } ] } ] } ] }

9.2.2 自动化测试脚本

# Python + Pytest 示例 import pytest import requests BASE_URL = "http://localhost:8000/v1" class TestUserAPI: def test_login_success(self): response = requests.post( f"{BASE_URL}/auth/login", json={"username": "test", "password": "123456"} ) assert response.status_code == 200 data = response.json() assert data["code"] == 200 assert "access_token" in data["data"] def test_get_user_list(self, auth_token): headers = {"Authorization": f"Bearer {auth_token}"} response = requests.get( f"{BASE_URL}/users", params={"page": 1, "size": 20}, headers=headers ) assert response.status_code == 200 data = response.json() assert "items" in data["data"] assert data["data"]["pagination"]["page"] == 1

9.3 性能测试

# Locust 压测脚本 from locust import HttpUser, task, between class APIUser(HttpUser): wait_time = between(1, 3) def on_start(self): # 登录获取 token response = self.client.post("/v1/auth/login", json={ "username": "test", "password": "123456" }) self.token = response.json()["data"]["access_token"] @task(3) def get_users(self): self.client.get( "/v1/users", headers={"Authorization": f"Bearer {self.token}"} ) @task(1) def create_user(self): self.client.post( "/v1/users", json={"username": "new_user", "email": "new@example.com"}, headers={"Authorization": f"Bearer {self.token}"} )

性能优化与最佳实践

10.1 RESTful 性能优化

10.1.1 缓存策略

# HTTP 缓存头 Cache-Control: public, max-age=3600 ETag: "abc123xyz" Last-Modified: Wed, 21 Oct 2025 07:28:00 GMT # 条件请求 If-None-Match: "abc123xyz" If-Modified-Since: Wed, 21 Oct 2025 07:28:00 GMT # 响应 304 Not Modified(无正文,节省带宽)

10.1.2 响应压缩

# 启用 Gzip/Brotli 压缩 Content-Encoding: gzip Content-Encoding: br # Brotli(更高效) # Nginx 配置 gzip on; gzip_types application/json text/plain application/javascript; gzip_min_length 1000;

10.1.3 数据库优化

10.2 GraphQL 性能优化

10.2.1 查询复杂度限制

# Apollo Server 复杂度限制 import { createComplexityRule } from 'graphql-validation-complexity'; const server = new ApolloServer({ validationRules: [createComplexityRule({ maximumComplexity: 1000, variables: {}, onComplete: (complexity) => { console.log('Query complexity:', complexity); }, })], });

10.2.2 DataLoader 批量加载

const DataLoader = require('dataloader'); # 批量加载用户 async function batchGetUsers(ids) { const users = await db.user.findMany({ where: { id: { in: ids } } }); return ids.map(id => users.find(u => u.id === id)); } const userLoader = new DataLoader(batchGetUsers); # Resolver 中使用 const resolvers = { Post: { author: async (post) => { return userLoader.load(post.authorId); # 自动批量化 } } };

10.2.3 查询深度限制

# 限制查询深度防止恶意嵌套 import depthLimit from 'graphql-depth-limit'; const server = new ApolloServer({ validationRules: [depthLimit(5)], # 最大深度 5 层 });

10.3 监控与告警

关键监控指标:
  • 延迟指标:P50/P90/P99 响应时间
  • 吞吐量:QPS(每秒请求数)
  • 错误率:4xx/5xx 错误占比
  • 饱和度:CPU/内存/连接池使用率
  • 业务指标:登录成功率、下单转化率

10.4 限流策略

# Redis + Lua 限流实现 local key = "rate_limit:" .. KEYS[1] local limit = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local current = redis.call("INCR", key) if current == 1 then redis.call("EXPIRE", key, window) end if current > limit then return 0 else return 1 end # 限流配置 - 普通用户:100 次/分钟 - VIP 用户:1000 次/分钟 - 企业用户:10000 次/分钟

10.5 文档维护清单

每次 API 变更必须更新:
  • ✅ OpenAPI/GraphQL Schema 定义文件
  • ✅ Swagger UI / GraphQL Playground
  • ✅ Postman 集合并同步到团队
  • ✅ SDK 代码并发布新版本
  • ✅ 变更日志(CHANGELOG.md)
  • ✅ 迁移指南(如为破坏性变更)