引言
API文档是开发者体验的核心组成部分。手动维护文档不仅耗时,还容易与代码不同步。通过OpenAPI规范和自动化工具,我们可以实现文档与代码的同步,提升开发效率和API可用性。
OpenAPI 3.0规范
基础结构
openapi: 3.0.3
info:
title: 电商API
description: |
电商平台的RESTful API,提供用户管理、订单处理、
商品查询等功能。
## 认证
所有API请求需要包含Bearer Token。
## 速率限制
- 普通用户:100次/分钟
- VIP用户:500次/分钟
version: 2.1.0
contact:
name: API支持团队
email: api-support@example.com
url: https://developer.example.com
license:
name: Apache 2.0
url: https://www.apache.org/licenses/LICENSE-2.0.html
servers:
- url: https://api.example.com/v2
description: 生产环境
- url: https://staging-api.example.com/v2
description: 预发布环境
- url: http://localhost:3000/v2
description: 本地开发
tags:
- name: Users
description: 用户管理相关API
- name: Orders
description: 订单处理相关API
- name: Products
description: 商品查询相关API
路径与操作定义
paths:
/users:
get:
tags:
- Users
summary: 获取用户列表
description: 分页获取用户列表,支持按条件筛选
operationId: listUsers
parameters:
- name: page
in: query
description: 页码(从1开始)
required: false
schema:
type: integer
minimum: 1
default: 1
- name: limit
in: query
description: 每页数量
required: false
schema:
type: integer
minimum: 1
maximum: 100
default: 20
- name: status
in: query
description: 用户状态筛选
required: false
schema:
type: string
enum: [active, inactive, suspended]
responses:
'200':
description: 成功返回用户列表
content:
application/json:
schema:
type: object
properties:
data:
type: array
items:
$ref: '#/components/schemas/User'
pagination:
$ref: '#/components/schemas/Pagination'
example:
data:
- id: "usr_123"
username: "john_doe"
email: "john@example.com"
status: "active"
createdAt: "2026-01-15T10:30:00Z"
pagination:
page: 1
limit: 20
total: 150
totalPages: 8
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
post:
tags:
- Users
summary: 创建新用户
description: 创建一个新的用户账户
operationId: createUser
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateUserRequest'
example:
username: "jane_doe"
email: "jane@example.com"
password: "SecurePass123!"
role: "user"
responses:
'201':
description: 用户创建成功
content:
application/json:
schema:
$ref: '#/components/schemas/User'
headers:
Location:
description: 新创建用户的URL
schema:
type: string
example: "/users/usr_456"
'400':
$ref: '#/components/responses/BadRequest'
'409':
description: 用户名或邮箱已存在
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/users/{userId}:
get:
tags:
- Users
summary: 获取用户详情
operationId: getUser
parameters:
- name: userId
in: path
required: true
description: 用户ID
schema:
type: string
pattern: '^usr_[a-zA-Z0-9]+$'
example: usr_123
responses:
'200':
description: 成功返回用户详情
content:
application/json:
schema:
$ref: '#/components/schemas/UserDetail'
'404':
$ref: '#/components/responses/NotFound'
Schema定义
components:
schemas:
User:
type: object
required:
- id
- username
- email
- status
- createdAt
properties:
id:
type: string
description: 用户唯一标识
example: usr_123
username:
type: string
description: 用户名
minLength: 3
maxLength: 50
pattern: '^[a-zA-Z0-9_]+$'
example: john_doe
email:
type: string
format: email
description: 用户邮箱
example: john@example.com
status:
type: string
enum: [active, inactive, suspended]
description: 用户状态
example: active
createdAt:
type: string
format: date-time
description: 创建时间
example: "2026-01-15T10:30:00Z"
UserDetail:
allOf:
- $ref: '#/components/schemas/User'
- type: object
properties:
profile:
$ref: '#/components/schemas/UserProfile'
lastLoginAt:
type: string
format: date-time
nullable: true
UserProfile:
type: object
properties:
firstName:
type: string
example: John
lastName:
type: string
example: Doe
avatar:
type: string
format: uri
nullable: true
bio:
type: string
maxLength: 500
nullable: true
CreateUserRequest:
type: object
required:
- username
- email
- password
properties:
username:
type: string
minLength: 3
maxLength: 50
email:
type: string
format: email
password:
type: string
format: password
minLength: 8
description: |
密码要求:
- 至少8个字符
- 包含大小写字母
- 包含至少一个数字
- 包含至少一个特殊字符
role:
type: string
enum: [user, admin]
default: user
Pagination:
type: object
properties:
page:
type: integer
example: 1
limit:
type: integer
example: 20
total:
type: integer
example: 150
totalPages:
type: integer
example: 8
Error:
type: object
required:
- code
- message
properties:
code:
type: string
example: VALIDATION_ERROR
message:
type: string
example: "Invalid input data"
details:
type: array
items:
type: object
properties:
field:
type: string
message:
type: string
responses:
BadRequest:
description: 请求参数错误
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
Unauthorized:
description: 未授权访问
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: UNAUTHORIZED
message: "Invalid or expired token"
NotFound:
description: 资源不存在
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: NOT_FOUND
message: "Resource not found"
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
description: JWT认证Token
ApiKeyAuth:
type: apiKey
in: header
name: X-API-Key
security:
- BearerAuth: []
代码生成
从OpenAPI生成代码(OpenAPI Generator)
# 安装OpenAPI Generator
npm install -g @openapitools/openapi-generator-cli
# 生成Go服务端代码
openapi-generator-cli generate \
-i api-spec.yaml \
-g go-server \
-o ./generated/go-server \
--additional-properties=packageName=api
# 生成TypeScript客户端
openapi-generator-cli generate \
-i api-spec.yaml \
-g typescript-axios \
-o ./generated/ts-client
# 生成Python客户端
openapi-generator-cli generate \
-i api-spec.yaml \
-g python \
-o ./generated/python-client
从代码生成OpenAPI(Go示例)
// main.go
package main
import (
"github.com/gin-gonic/gin"
"github.com/swaggo/gin-swagger"
"github.com/swaggo/files"
_ "docs" // 自动生成的文档
)
// @title 电商API
// @version 2.1.0
// @description 电商平台的RESTful API
// @host api.example.com
// @BasePath /v2
// @securityDefinitions.apikey BearerAuth
// @in header
// @name Authorization
// GetUser godoc
// @Summary 获取用户详情
// @Description 根据用户ID获取用户详细信息
// @Tags Users
// @Accept json
// @Produce json
// @Param userId path string true "用户ID" example(usr_123)
// @Success 200 {object} User "成功返回用户详情"
// @Failure 404 {object} Error "用户不存在"
// @Failure 401 {object} Error "未授权"
// @Security BearerAuth
// @Router /users/{userId} [get]
func GetUser(c *gin.Context) {
userId := c.Param("userId")
user, err := userService.GetUser(userId)
if err != nil {
c.JSON(404, Error{Code: "NOT_FOUND", Message: "User not found"})
return
}
c.JSON(200, user)
}
// CreateUser godoc
// @Summary 创建新用户
// @Description 创建一个新的用户账户
// @Tags Users
// @Accept json
// @Produce json
// @Param request body CreateUserRequest true "创建用户请求"
// @Success 201 {object} User "用户创建成功"
// @Failure 400 {object} Error "请求参数错误"
// @Failure 409 {object} Error "用户名或邮箱已存在"
// @Router /users [post]
func CreateUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, Error{Code: "VALIDATION_ERROR", Message: err.Error()})
return
}
user, err := userService.CreateUser(req)
if err != nil {
c.JSON(409, Error{Code: "CONFLICT", Message: err.Error()})
return
}
c.JSON(201, user)
}
func main() {
r := gin.Default()
// Swagger文档路由
r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
v2 := r.Group("/v2")
{
users := v2.Group("/users")
{
users.GET("/:userId", GetUser)
users.POST("", CreateUser)
}
}
r.Run(":3000")
}
# 生成OpenAPI规范
swag init -g main.go -o ./docs
文档展示工具
Redoc(现代化文档UI)
<!DOCTYPE html>
<html>
<head>
<title>电商API文档</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
<style>
body { margin: 0; padding: 0; }
</style>
</head>
<body>
<redoc spec-url='./api-spec.yaml'></redoc>
<script src="https://cdn.redoc.ly/redoc/latest/bundles/redoc.standalone.js"></script>
</body>
</html>
Stoplight Elements
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>API Documentation</title>
<script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css">
</head>
<body>
<elements-api
apiDescriptionUrl="api-spec.yaml"
router="hash"
layout="sidebar"
/>
</body>
</html>
Mock服务搭建
Prism Mock Server
# 安装Prism
npm install -g @stoplight/prism-cli
# 启动Mock服务
prism mock api-spec.yaml
# Mock服务将在 http://localhost:4010 启动
# 根据OpenAPI规范自动生成响应
自定义Mock服务器(Node.js)
// mock-server.js
const express = require('express');
const { OpenAPIBackend } = require('openapi-backend');
const app = express();
app.use(express.json());
const api = new OpenAPIBackend({
definition: './api-spec.yaml',
handlers: {
listUsers: async (c, req, res) => {
const page = c.request.query.page || 1;
const limit = c.request.query.limit || 20;
// 生成Mock数据
const users = Array.from({ length: limit }, (_, i) => ({
id: `usr_${Math.random().toString(36).substr(2, 9)}`,
username: `user_${i + (page - 1) * limit}`,
email: `user${i}@example.com`,
status: ['active', 'inactive', 'suspended'][Math.floor(Math.random() * 3)],
createdAt: new Date().toISOString(),
}));
res.json({
data: users,
pagination: {
page,
limit,
total: 150,
totalPages: Math.ceil(150 / limit),
},
});
},
createUser: async (c, req, res) => {
const userData = c.request.requestBody;
const user = {
id: `usr_${Math.random().toString(36).substr(2, 9)}`,
...userData,
status: 'active',
createdAt: new Date().toISOString(),
};
res.status(201).json(user);
},
validationFail: async (c, req, res) => {
res.status(400).json({
code: 'VALIDATION_ERROR',
message: 'Request validation failed',
details: c.validation.errors,
});
},
notFound: async (c, req, res) => {
res.status(404).json({
code: 'NOT_FOUND',
message: 'Endpoint not found',
});
},
},
});
api.init();
app.use((req, res) => api.handleRequest(
{
method: req.method,
path: req.path,
query: req.query,
body: req.body,
headers: req.headers,
},
req,
res
));
app.listen(3000, () => {
console.log('Mock server running at http://localhost:3000');
});
文档驱动开发(Design-First)
# 工作流
1. 设计API规范(OpenAPI)
↓
2. 团队评审
↓
3. 生成Mock服务器
↓
4. 前端并行开发(基于Mock)
↓
5. 后端实现(基于规范)
↓
6. 集成测试
↓
7. 部署上线
API设计评审清单
## API设计规范检查
### 命名规范
- [ ] 资源名称使用复数形式(/users而非/user)
- [ ] URL使用小写和连字符
- [ ] 避免动词(使用HTTP方法表达操作)
- [ ] 保持一致的命名风格
### 请求设计
- [ ] 必填参数和可选参数明确标注
- [ ] 参数类型和格式正确定义
- [ ] 提供合理的默认值
- [ ] 包含参数验证规则
### 响应设计
- [ ] 所有状态码都有明确定义
- [ ] 成功响应包含完整示例
- [ ] 错误响应包含错误码和描述
- [ ] 分页参数标准化
### 安全性
- [ ] 敏感操作需要认证
- [ ] 权限控制明确定义
- [ ] 敏感数据标记为加密传输
### 文档质量
- [ ] 每个端点都有清晰的描述
- [ ] 包含实际可用的示例
- [ ] 业务规则在描述中说明
- [ ] 版本号正确标注
总结
API文档自动化核心价值:
- 单一数据源:OpenAPI规范作为唯一真相源
- 代码同步:文档与实现始终保持一致
- 开发效率:自动生成客户端SDK和服务端代码
- 协作增强:前后端可以并行开发
最佳实践:
- 采用Design-First方法,先设计后编码
- 使用代码注解保持文档同步
- 提供丰富的示例和错误场景
- 定期审查和更新API规范
- 集成到CI/CD流程中自动验证
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。