主题
10 · 实战:一份生产级电商 API 完整文档
你已经学完了 9 章基础知识,这一章把所有知识揉成一份真实可用、能直接抄的 openapi.yaml——商品 + 购物车 + 订单 + 用户 + 鉴权 + 全套 components 复用,配套 docker-compose 一键起 UI + Mock + Editor。
1. 业务场景
┌─────────────────────────────┐
│ 电商前后端示例 │
└─────────────────────────────┘
GET /products ← 商品列表(首页 / 分类)
GET /products/{sku} ← 商品详情
POST /products ← 上架商品(管理员)
│
GET /cart ← 我的购物车
POST /cart/items ← 加购
PUT /cart/items/{sku} ← 改数量
DEL /cart/items/{sku} ← 移除
│
POST /orders ← 下单(购物车 → 订单)
GET /orders ← 我的订单
GET /orders/{id} ← 订单详情
POST /orders/{id}:cancel ← 取消订单
│
POST /auth/login ← 登录拿 token
POST /auth/register ← 注册
GET /me ← 当前用户信息功能清单:
- ✅ 完整 CRUD + 子资源
- ✅ JWT 鉴权(含全局 + 单接口豁免)
- ✅ 完整
components.schemas复用 - ✅ 通用
responses(Unauthorized / NotFound / ValidationError) - ✅ 分页 / 排序 / 搜索通用参数
- ✅ 多 example,调试体验好
- ✅ 图片上传(multipart/form-data)
- ✅ 自定义动作(
POST /orders/{id}:cancel)
2. 文件目录结构
deploy/
├── docker-compose.yml ← 一键起 UI + Editor + Mock
├── openapi.yaml ← 主文件(本章重点)
├── README.md
└── postman/ ← 自动生成的测试集合3. 完整 openapi.yaml
全文见
10_real_case/code/openapi.yaml(800+ 行)。 下面摘出关键设计点讲解。
3.1 全局头部 + 鉴权
yaml
openapi: 3.0.3
info:
title: 电商示例 API
version: 1.0.0
description: |
基于 OpenAPI 3.0 的电商 API 完整示例。
## 鉴权
所有接口默认要 Bearer Token;标注 `🔓` 的接口免鉴权。
## 限流
每用户每秒最多 10 次请求;超过返回 `429`。
## 联系方式
📧 dev@example.com
contact: { name: 团队, email: dev@example.com }
license: { name: MIT }
servers:
- url: http://localhost:3000/v1
description: 本地
- url: https://api.shop.example.com/v1
description: 生产
tags:
- { name: auth, description: '🔐 鉴权 / 登录' }
- { name: user, description: '👤 用户' }
- { name: product, description: '🛍 商品' }
- { name: cart, description: '🛒 购物车' }
- { name: order, description: '📦 订单' }
security:
- bearerAuth: []3.2 鉴权接口(免鉴权 = security: [])
yaml
paths:
/auth/login:
post:
tags: [auth]
operationId: login
summary: 登录
security: [] # 🔓 公开
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/LoginRequest' }
responses:
'200':
description: 登录成功
content:
application/json:
schema: { $ref: '#/components/schemas/LoginResponse' }
'401':
$ref: '#/components/responses/Unauthorized'
'400':
$ref: '#/components/responses/ValidationError'3.3 商品列表(带分页 / 搜索 / 多 example)
yaml
paths:
/products:
get:
tags: [product]
operationId: listProducts
summary: 商品列表
security: []
parameters:
- $ref: '#/components/parameters/PageQuery'
- $ref: '#/components/parameters/SizeQuery'
- name: category
in: query
schema: { type: string }
example: phone
- name: q
in: query
schema: { type: string }
description: 搜索关键词
example: iPhone
responses:
'200':
description: 商品列表
content:
application/json:
schema: { $ref: '#/components/schemas/ProductListResponse' }
examples:
empty:
summary: 空结果
value: { items: [], meta: { page: 1, size: 20, total: 0 } }
normal:
summary: 有数据
value:
items:
- sku: P-001
name: iPhone 15
price: 5999
meta: { page: 1, size: 20, total: 100 }3.4 上传商品图(multipart)
yaml
paths:
/products/{sku}/image:
post:
tags: [product]
operationId: uploadProductImage
summary: 上传商品主图(管理员)
parameters:
- $ref: '#/components/parameters/SkuPath'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: 图片文件(PNG / JPG,<= 2MB)
required: [file]
responses:
'200':
description: 上传成功
content:
application/json:
schema:
type: object
properties:
url: { type: string, format: uri }3.5 自定义动作 :cancel
yaml
paths:
/orders/{id}:cancel:
post:
tags: [order]
operationId: cancelOrder
summary: 取消订单
parameters:
- $ref: '#/components/parameters/OrderIdPath'
responses:
'200':
description: 取消成功
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'409':
description: 状态冲突(已发货/已完成不能取消)
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }3.6 完整的 components 复用
yaml
components:
parameters:
PageQuery:
name: page
in: query
schema: { type: integer, default: 1, minimum: 1 }
SizeQuery:
name: size
in: query
schema: { type: integer, default: 20, minimum: 1, maximum: 100 }
SkuPath:
name: sku
in: path
required: true
schema: { type: string, pattern: '^P-\d{3,}$' }
example: P-001
OrderIdPath:
name: id
in: path
required: true
schema: { type: string, format: uuid }
schemas:
Product:
type: object
required: [sku, name, price]
properties:
sku: { type: string, example: P-001 }
name: { type: string, example: iPhone 15 }
price: { type: number, format: double, example: 5999 }
stock: { type: integer, example: 100 }
category: { type: string, example: phone }
imageUrl: { type: string, format: uri }
createdAt: { type: string, format: date-time, readOnly: true }
ProductListResponse:
type: object
properties:
items:
type: array
items: { $ref: '#/components/schemas/Product' }
meta: { $ref: '#/components/schemas/PageMeta' }
PageMeta:
type: object
properties:
page: { type: integer }
size: { type: integer }
total: { type: integer }
Error:
type: object
required: [code, message]
properties:
code: { type: integer, example: 400 }
message: { type: string, example: '参数错误' }
traceId: { type: string }
responses:
Unauthorized:
description: 未登录或 token 过期
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { code: 401, message: 'Token 已过期' }
NotFound:
description: 资源不存在
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
ValidationError:
description: 参数校验失败
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT整份 yaml 看下来你会发现:90% 是
components和responses,paths里几乎全是$ref——这就是工程级 yaml 的样子。
4. 一键起服务(docker-compose)
yaml
# docker-compose.yml
version: '3.8'
services:
ui:
image: swaggerapi/swagger-ui
ports: ["8080:8080"]
environment:
SWAGGER_JSON: /spec/openapi.yaml
volumes:
- ./openapi.yaml:/spec/openapi.yaml:ro
editor:
image: swaggerapi/swagger-editor
ports: ["8081:8080"]
volumes:
- ./openapi.yaml:/tmp/openapi.yaml:ro
environment:
SWAGGER_FILE: /tmp/openapi.yaml
mock:
image: stoplight/prism:4
ports: ["4010:4010"]
command: mock -h 0.0.0.0 /spec/openapi.yaml
volumes:
- ./openapi.yaml:/spec/openapi.yaml:robash
docker compose up -d打开:
http://localhost:8080→ Swagger UI 看文档http://localhost:8081→ Swagger Editor 改 yamlhttp://localhost:4010/products→ Prism Mock 自动返回符合 schema 的假数据
5. 给前端生成 SDK
bash
docker run --rm -v $PWD:/local \
openapitools/openapi-generator-cli generate \
-i /local/openapi.yaml \
-g typescript-axios \
-o /local/sdk-ts \
--additional-properties=npmName=@shop/api-sdk,npmVersion=1.0.0前端业务代码:
typescript
import { ProductApi, CartApi, OrderApi, Configuration } from '@shop/api-sdk';
const cfg = new Configuration({
basePath: 'http://localhost:3000/v1',
accessToken: () => localStorage.getItem('token') || '',
});
const productApi = new ProductApi(cfg);
const orderApi = new OrderApi(cfg);
// 100% 类型安全
const { data } = await productApi.listProducts(1, 20, 'phone', 'iPhone');
data.items.forEach(p => console.log(p.name, p.price));
const order = await orderApi.createOrder({
items: [{ sku: 'P-001', quantity: 2 }]
});6. 给后端生成接口骨架
bash
docker run --rm -v $PWD:/local \
openapitools/openapi-generator-cli generate \
-i /local/openapi.yaml \
-g spring \
-o /local/server \
--additional-properties=interfaceOnly=true,useSpringBoot3=true后端只要 implements 这些接口即可——所有 @RestController、@RequestMapping、入参校验、文档注解全部生成好。
7. CI 全套自动化
yaml
# .github/workflows/api.yml
name: API
on:
push:
paths: ['openapi.yaml']
pull_request:
paths: ['openapi.yaml']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint with Spectral
run: npx -y @stoplight/spectral-cli lint openapi.yaml
- name: Diff (breaking changes)
run: |
git fetch origin main
npx -y openapi-diff origin/main:openapi.yaml openapi.yaml --fail-on-incompatible
publish:
needs: validate
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Generate TS SDK
run: |
npx -y @openapitools/openapi-generator-cli generate \
-i openapi.yaml -g typescript-axios -o sdk-ts \
--additional-properties=npmName=@shop/api-sdk,npmVersion=$(date +%Y.%m.%d.%H%M)
- name: Publish npm
run: cd sdk-ts && npm publish --access public
env: { NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} }
- name: Build static docs
run: npx -y @redocly/cli build-docs openapi.yaml -o public/api.html
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public效果:
- 改了 yaml → push → 自动 lint + breaking 检测
- 合并到 main → 自动发 npm SDK + 部署到 GitHub Pages 文档站
8. 工程经验总结
✅ openapi.yaml 跟业务代码同 repo,CI 严格 lint
✅ 所有数据模型走 components.schemas,paths 里全 $ref
✅ 通用响应(401/404/422/默认 Error)抽到 components.responses
✅ 通用参数(PageQuery、SizeQuery)抽到 components.parameters
✅ 给每个 operation 写 operationId(决定生成的方法名)
✅ Mock + UI + Editor 三件套一份 docker-compose 启
✅ CI 自动生成 SDK 发 npm,自动构建文档站
❌ 别在 yaml 里写真 token / API key(提交即泄漏)
❌ 别让一份 yaml 超过 5000 行 — 该拆就拆
❌ 别只写文档,要在 CI 里跑 schemathesis 模糊测试,确保后端真的符合9. 代码资源
10_real_case/code/openapi.yaml· 完整可用的 800+ 行 yaml10_real_case/code/docker-compose.yml· 一键起 UI + Editor + Mock10_real_case/code/README.md· 启动说明
→ 进 10_real_case/code/ 目录直接抄。
💻 示例代码
yaml
version: '3.8'
services:
ui:
image: swaggerapi/swagger-ui
container_name: shop-swagger-ui
ports: ["8080:8080"]
environment:
SWAGGER_JSON: /spec/openapi.yaml
DOC_EXPANSION: list
DEEP_LINKING: "true"
DISPLAY_REQUEST_DURATION: "true"
volumes:
- ./openapi.yaml:/spec/openapi.yaml:ro
editor:
image: swaggerapi/swagger-editor
container_name: shop-swagger-editor
ports: ["8081:8080"]
volumes:
- ./openapi.yaml:/tmp/openapi.yaml:ro
environment:
SWAGGER_FILE: /tmp/openapi.yaml
mock:
image: stoplight/prism:4
container_name: shop-mock
ports: ["4010:4010"]
command: mock -h 0.0.0.0 /spec/openapi.yaml
volumes:
- ./openapi.yaml:/spec/openapi.yaml:ro
redoc:
image: redocly/redoc
container_name: shop-redoc
ports: ["8082:80"]
environment:
SPEC_URL: openapi.yaml
volumes:
- ./openapi.yaml:/usr/share/nginx/html/openapi.yaml:royaml
openapi: 3.0.3
info:
title: 电商示例 API
version: 1.0.0
description: |
基于 OpenAPI 3.0 的电商 API 完整示例。
## 鉴权
所有接口默认要 Bearer Token;标注 `🔓` 的接口免鉴权(公开接口 / 登录注册)。
## 限流
每用户每秒最多 10 次请求;超过返回 `429 Too Many Requests`。
## 错误格式
```json
{ "code": 400, "message": "...", "traceId": "abc-123" }
```
contact:
name: 团队联系人
email: dev@example.com
license:
name: MIT
servers:
- url: http://localhost:3000/v1
description: 本地
- url: https://staging.shop.example.com/v1
description: 测试
- url: https://api.shop.example.com/v1
description: 生产
tags:
- { name: auth, description: '🔐 鉴权 / 登录注册' }
- { name: user, description: '👤 用户' }
- { name: product, description: '🛍 商品' }
- { name: cart, description: '🛒 购物车' }
- { name: order, description: '📦 订单' }
security:
- bearerAuth: []
paths:
# ========================== AUTH ==========================
/auth/register:
post:
tags: [auth]
operationId: register
summary: 注册
security: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/RegisterRequest' }
responses:
'201':
description: 注册成功
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'400':
$ref: '#/components/responses/ValidationError'
'409':
description: 邮箱已被注册
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
/auth/login:
post:
tags: [auth]
operationId: login
summary: 登录
security: []
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/LoginRequest' }
examples:
normal:
summary: 普通用户
value: { email: alice@x.com, password: 's3cret' }
admin:
summary: 管理员
value: { email: admin@x.com, password: 'admin123' }
responses:
'200':
description: 登录成功
content:
application/json:
schema: { $ref: '#/components/schemas/LoginResponse' }
'401':
$ref: '#/components/responses/Unauthorized'
# ========================== ME ==========================
/me:
get:
tags: [user]
operationId: getMe
summary: 当前用户信息
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/User' }
'401':
$ref: '#/components/responses/Unauthorized'
# ========================== PRODUCTS ==========================
/products:
get:
tags: [product]
operationId: listProducts
summary: 商品列表
security: []
parameters:
- $ref: '#/components/parameters/PageQuery'
- $ref: '#/components/parameters/SizeQuery'
- name: category
in: query
schema: { type: string }
example: phone
- name: q
in: query
description: 搜索关键词
schema: { type: string }
example: iPhone
- name: sort
in: query
schema:
type: string
enum: [price_asc, price_desc, newest]
default: newest
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/ProductListResponse' }
examples:
empty:
summary: 空结果
value: { items: [], meta: { page: 1, size: 20, total: 0 } }
normal:
summary: 有数据
value:
items:
- { sku: P-001, name: iPhone 15, price: 5999, stock: 100, category: phone }
- { sku: P-002, name: AirPods Pro, price: 1899, stock: 50, category: audio }
meta: { page: 1, size: 20, total: 100 }
post:
tags: [product]
operationId: createProduct
summary: 上架商品(管理员)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ProductCreate' }
responses:
'201':
description: 创建成功
content:
application/json:
schema: { $ref: '#/components/schemas/Product' }
'400':
$ref: '#/components/responses/ValidationError'
'403':
$ref: '#/components/responses/Forbidden'
/products/{sku}:
parameters:
- $ref: '#/components/parameters/SkuPath'
get:
tags: [product]
operationId: getProduct
summary: 商品详情
security: []
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Product' }
'404':
$ref: '#/components/responses/NotFound'
put:
tags: [product]
operationId: updateProduct
summary: 修改商品(管理员)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/ProductUpdate' }
responses:
'200':
description: 更新成功
content:
application/json:
schema: { $ref: '#/components/schemas/Product' }
'404':
$ref: '#/components/responses/NotFound'
delete:
tags: [product]
operationId: deleteProduct
summary: 下架商品(管理员)
responses:
'204':
description: 删除成功
'404':
$ref: '#/components/responses/NotFound'
/products/{sku}/image:
post:
tags: [product]
operationId: uploadProductImage
summary: 上传商品主图
parameters:
- $ref: '#/components/parameters/SkuPath'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
properties:
file:
type: string
format: binary
description: 图片文件(PNG / JPG,<= 2MB)
required: [file]
responses:
'200':
description: 上传成功
content:
application/json:
schema:
type: object
properties:
url:
type: string
format: uri
example: https://cdn.example.com/products/P-001.png
# ========================== CART ==========================
/cart:
get:
tags: [cart]
operationId: getCart
summary: 获取购物车
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Cart' }
delete:
tags: [cart]
operationId: clearCart
summary: 清空购物车
responses:
'204':
description: 已清空
/cart/items:
post:
tags: [cart]
operationId: addCartItem
summary: 加入购物车
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/CartItemCreate' }
responses:
'200':
description: 已加入
content:
application/json:
schema: { $ref: '#/components/schemas/Cart' }
'404':
$ref: '#/components/responses/NotFound'
/cart/items/{sku}:
parameters:
- $ref: '#/components/parameters/SkuPath'
put:
tags: [cart]
operationId: updateCartItem
summary: 改数量
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [quantity]
properties:
quantity: { type: integer, minimum: 1, maximum: 999 }
responses:
'200':
description: 已更新
content:
application/json:
schema: { $ref: '#/components/schemas/Cart' }
delete:
tags: [cart]
operationId: removeCartItem
summary: 移除商品
responses:
'204':
description: 已移除
# ========================== ORDERS ==========================
/orders:
get:
tags: [order]
operationId: listOrders
summary: 我的订单
parameters:
- $ref: '#/components/parameters/PageQuery'
- $ref: '#/components/parameters/SizeQuery'
- name: status
in: query
schema:
type: string
enum: [pending, paid, shipped, completed, cancelled]
responses:
'200':
description: 订单列表
content:
application/json:
schema: { $ref: '#/components/schemas/OrderListResponse' }
post:
tags: [order]
operationId: createOrder
summary: 创建订单(购物车 → 订单)
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/OrderCreate' }
responses:
'201':
description: 订单已创建
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'400':
$ref: '#/components/responses/ValidationError'
'402':
description: 余额不足 / 支付失败
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
/orders/{id}:
parameters:
- $ref: '#/components/parameters/OrderIdPath'
get:
tags: [order]
operationId: getOrder
summary: 订单详情
responses:
'200':
description: OK
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'404':
$ref: '#/components/responses/NotFound'
/orders/{id}:cancel:
post:
tags: [order]
operationId: cancelOrder
summary: 取消订单
parameters:
- $ref: '#/components/parameters/OrderIdPath'
responses:
'200':
description: 取消成功
content:
application/json:
schema: { $ref: '#/components/schemas/Order' }
'409':
description: 状态冲突(已发货/已完成不能取消)
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
# ========================== COMPONENTS ==========================
components:
parameters:
PageQuery:
name: page
in: query
description: 页码
schema: { type: integer, default: 1, minimum: 1 }
SizeQuery:
name: size
in: query
description: 每页条数
schema: { type: integer, default: 20, minimum: 1, maximum: 100 }
SkuPath:
name: sku
in: path
required: true
description: 商品 SKU
schema:
type: string
pattern: '^P-\d{3,}$'
example: P-001
OrderIdPath:
name: id
in: path
required: true
description: 订单 ID
schema: { type: string, format: uuid }
example: 550e8400-e29b-41d4-a716-446655440000
schemas:
User:
type: object
required: [id, email, name]
properties:
id: { type: integer, readOnly: true, example: 1 }
email: { type: string, format: email, example: alice@x.com }
name: { type: string, example: Alice }
role:
type: string
enum: [user, admin]
default: user
avatarUrl: { type: string, format: uri, nullable: true }
createdAt: { type: string, format: date-time, readOnly: true }
RegisterRequest:
type: object
required: [email, password, name]
properties:
email: { type: string, format: email }
password: { type: string, format: password, minLength: 6, maxLength: 50 }
name: { type: string, maxLength: 50 }
LoginRequest:
type: object
required: [email, password]
properties:
email: { type: string, format: email }
password: { type: string, format: password }
LoginResponse:
type: object
required: [token, user]
properties:
token: { type: string, description: JWT, example: 'eyJhbGciOi...' }
expiresIn: { type: integer, description: 秒, example: 7200 }
user: { $ref: '#/components/schemas/User' }
Product:
type: object
required: [sku, name, price, stock]
properties:
sku: { type: string, example: P-001 }
name: { type: string, example: iPhone 15 }
description: { type: string, example: 全新 A17 芯片 }
price: { type: number, format: double, minimum: 0, example: 5999 }
stock: { type: integer, minimum: 0, example: 100 }
category: { type: string, example: phone }
imageUrl: { type: string, format: uri, nullable: true }
createdAt: { type: string, format: date-time, readOnly: true }
ProductCreate:
type: object
required: [sku, name, price, stock]
properties:
sku: { type: string, pattern: '^P-\d{3,}$' }
name: { type: string, maxLength: 100 }
description: { type: string, maxLength: 1000 }
price: { type: number, format: double, minimum: 0 }
stock: { type: integer, minimum: 0 }
category: { type: string }
ProductUpdate:
type: object
properties:
name: { type: string, maxLength: 100 }
description: { type: string, maxLength: 1000 }
price: { type: number, format: double, minimum: 0 }
stock: { type: integer, minimum: 0 }
category: { type: string }
ProductListResponse:
type: object
properties:
items:
type: array
items: { $ref: '#/components/schemas/Product' }
meta: { $ref: '#/components/schemas/PageMeta' }
PageMeta:
type: object
properties:
page: { type: integer }
size: { type: integer }
total: { type: integer }
Cart:
type: object
properties:
items:
type: array
items: { $ref: '#/components/schemas/CartItem' }
totalQuantity: { type: integer, readOnly: true }
totalPrice: { type: number, format: double, readOnly: true }
CartItem:
type: object
required: [sku, quantity]
properties:
sku: { type: string }
name: { type: string, readOnly: true }
price: { type: number, format: double, readOnly: true }
quantity: { type: integer, minimum: 1, maximum: 999 }
imageUrl: { type: string, readOnly: true }
CartItemCreate:
type: object
required: [sku, quantity]
properties:
sku: { type: string }
quantity: { type: integer, minimum: 1, maximum: 999, default: 1 }
Order:
type: object
required: [id, userId, items, totalPrice, status, createdAt]
properties:
id: { type: string, format: uuid, readOnly: true }
userId: { type: integer, readOnly: true }
items:
type: array
items: { $ref: '#/components/schemas/OrderItem' }
totalPrice: { type: number, format: double, readOnly: true }
status:
type: string
enum: [pending, paid, shipped, completed, cancelled]
readOnly: true
shippingAddress: { $ref: '#/components/schemas/Address' }
createdAt: { type: string, format: date-time, readOnly: true }
OrderItem:
type: object
properties:
sku: { type: string }
name: { type: string }
price: { type: number, format: double }
quantity: { type: integer }
OrderCreate:
type: object
required: [items, shippingAddress]
properties:
items:
type: array
minItems: 1
items:
type: object
required: [sku, quantity]
properties:
sku: { type: string }
quantity: { type: integer, minimum: 1 }
shippingAddress: { $ref: '#/components/schemas/Address' }
couponCode: { type: string, nullable: true }
Address:
type: object
required: [recipient, phone, country, province, city, detail]
properties:
recipient: { type: string, example: 张三 }
phone: { type: string, example: 13800138000 }
country: { type: string, example: 中国 }
province: { type: string, example: 广东 }
city: { type: string, example: 深圳 }
detail: { type: string, example: 南山区科技园 }
zipCode: { type: string, nullable: true }
OrderListResponse:
type: object
properties:
items:
type: array
items: { $ref: '#/components/schemas/Order' }
meta: { $ref: '#/components/schemas/PageMeta' }
Error:
type: object
required: [code, message]
properties:
code: { type: integer, example: 400 }
message: { type: string, example: 参数错误 }
traceId: { type: string, example: trace-abc-123 }
details:
type: object
additionalProperties: true
responses:
Unauthorized:
description: 未登录或 token 已过期
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { code: 401, message: 'Token 已过期', traceId: 'trace-xxx' }
Forbidden:
description: 已登录但无权限
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { code: 403, message: '需要管理员权限' }
NotFound:
description: 资源不存在
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example: { code: 404, message: '资源不存在' }
ValidationError:
description: 参数校验失败
content:
application/json:
schema: { $ref: '#/components/schemas/Error' }
example:
code: 400
message: 请求参数错误
details: { email: '必须是合法邮箱' }
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWTmarkdown
# 10 章 · 综合实战代码
一份生产级的电商 API 完整 OpenAPI 文档 + 一键启动的开发环境。
## 文件清单code/ ├── openapi.yaml ← 800 行完整文档(认真读完一次能学很多) ├── docker-compose.yml ← 一键起 4 个服务 └── README.md ← 本文
## 快速开始
### 1. 启动全套服务
```bash
docker compose up -d四个服务一键就绪:
| 服务 | 地址 | 用途 |
|---|---|---|
| Swagger UI | http://localhost:8080 | 在线浏览 API 文档 |
| Swagger Editor | http://localhost:8081 | 在浏览器编辑 yaml |
| Prism Mock | http://localhost:4010 | Mock API 服务 |
| Redoc | http://localhost:8082 | 漂亮的发布版文档 |
2. 调用 Mock API 试试
bash
# 商品列表(公开接口)
curl http://localhost:4010/v1/products
# 登录拿 token
curl -X POST http://localhost:4010/v1/auth/login \
-H 'content-type: application/json' \
-d '{"email":"alice@x.com","password":"s3cret"}'
# 拿 token 调需要鉴权的接口
curl http://localhost:4010/v1/me \
-H 'authorization: Bearer fake-token-for-mock'
# Prism 会自动按 schema 校验请求体
curl -X POST http://localhost:4010/v1/auth/login \
-H 'content-type: application/json' \
-d '{"email":"not-an-email"}'
# → 返回 422,提示 email 格式错误3. 给前端生成 SDK
bash
docker run --rm -v $PWD:/local \
openapitools/openapi-generator-cli generate \
-i /local/openapi.yaml \
-g typescript-axios \
-o /local/sdk-ts4. 给后端生成接口骨架
bash
# Spring Boot
docker run --rm -v $PWD:/local \
openapitools/openapi-generator-cli generate \
-i /local/openapi.yaml \
-g spring \
-o /local/server-spring \
--additional-properties=interfaceOnly=true,useSpringBoot3=true
# Node.js Express
docker run --rm -v $PWD:/local \
openapitools/openapi-generator-cli generate \
-i /local/openapi.yaml \
-g nodejs-express-server \
-o /local/server-node5. 静态文档发布
bash
npx -y @redocly/cli build-docs openapi.yaml -o public/api.html
# public/api.html 是单文件,扔到任何静态服务器都能用学习建议
- 先用 Swagger UI 浏览全部接口(http://localhost:8080),有个全局印象
- 打开 openapi.yaml,对照看每个接口对应哪段 yaml
- 试着改 yaml 加字段 —— 比如给
Product加一个tags: array - 用 codegen 生成自己感兴趣语言的 SDK,看下生成的代码长啥样
- 接入到自己的项目,把 yaml 当真理之源
工程检查清单
- [ ] 用
npx @stoplight/spectral-cli lint openapi.yaml检查 yaml 质量 - [ ] CI 里跑 openapi-diff,PR 自动检测 breaking change
- [ ] 给每个 operation 起好 operationId,让生成的方法名漂亮
- [ ] 用 components 把模型 / 参数 / 响应都抽出来复用
- [ ] 自动化部署 Redoc 到内部 wiki / GitHub Pages
- [ ] 配合 schemathesis 跑模糊测试,确保后端真的符合 yaml