🔌 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 选型决策树
快速决策指南:
- 是否需要精确控制返回字段? → 是:GraphQL / 否:REST
- 是否有复杂嵌套查询需求? → 是:GraphQL / 否:REST
- 是否需要实时订阅推送? → 是:GraphQL Subscriptions
- 团队是否熟悉 GraphQL? → 否:优先 REST
- 是否需要强类型约束? → 是:GraphQL
1.3 混合架构方案
在实际项目中,可采用REST + GraphQL 混合架构:
- 使用 RESTful API 处理简单 CRUD 操作和文件上传
- 使用 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
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 分页规范
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 UserResult = User | Error
type Query {
user(id: ID!): UserResult!
}
3.4 安全最佳实践
必须实施的安全措施:
- 禁用内省查询(生产环境)
- 设置查询复杂度限制(最大深度、字段数)
- 实现查询超时机制
- 对输入参数进行严格验证
- 实施速率限制(Rate Limiting)
统一响应格式与错误处理
4.1 RESTful 统一响应格式
{
"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 错误响应格式
{
"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"
}
}
{
"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 认证
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"
}
}
GET /api/v1/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
5.1.2 OAuth 2.0 第三方登录
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"]
@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 模板
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
}
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)
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 版本号命名规范
主版本号。次版本号。修订号
MAJOR.MINOR.PATCH
v1.0.0
v1.1.0
v1.1.1
v2.0.0
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 弃用策略
弃用流程:
- 提前通知:至少提前 3 个月邮件/文档通知
- 响应头标记:
Deprecation: true, Sunset: Sat, 31 Dec 2026 23:59:59 GMT
- 并行运行:新旧版本同时支持过渡期
- 监控使用:追踪旧版本调用量,主动联系重度用户
- 正式下线:过渡期后关闭旧版本
8.4 向后兼容准则
- ✅ 允许:添加新字段、新接口、新枚举值
- ✅ 允许:可选参数增加默认值
- ❌ 禁止:删除或重命名现有字段
- ❌ 禁止:修改字段类型或语义
- ❌ 禁止:移除必填参数
- ❌ 禁止:改变错误码含义
文档生成与测试规范
9.1 自动文档生成
9.1.1 Swagger UI 集成
from fastapi import FastAPI
from fastapi.openapi.docs import get_swagger_ui_html
app = FastAPI(
title="用户管理系统",
docs_url="/docs",
redoc_url="/redoc",
)
9.1.2 GraphQL Playground
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 自动化测试脚本
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 性能测试
from locust import HttpUser, task, between
class APIUser(HttpUser):
wait_time = between(1, 3)
def on_start(self):
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 缓存策略
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
10.1.2 响应压缩
Content-Encoding: gzip
Content-Encoding: br
gzip on;
gzip_types application/json text/plain application/javascript;
gzip_min_length 1000;
10.1.3 数据库优化
- 为常用查询字段建立索引
- 使用 SELECT 指定字段,避免 SELECT *
- 实施分页,限制单次查询数据量
- 使用数据库连接池
- 热点数据缓存到 Redis
10.2 GraphQL 性能优化
10.2.1 查询复杂度限制
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);
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)],
});
10.3 监控与告警
关键监控指标:
- 延迟指标:P50/P90/P99 响应时间
- 吞吐量:QPS(每秒请求数)
- 错误率:4xx/5xx 错误占比
- 饱和度:CPU/内存/连接池使用率
- 业务指标:登录成功率、下单转化率
10.4 限流策略
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)
- ✅ 迁移指南(如为破坏性变更)