Skip to content

API 设计规范

一、RESTful API 设计

1. 资源命名

┌─────────────────────────────────────────────────────────────────┐
│                   RESTful 资源命名规范                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   ✅ 正确                             ❌ 错误                    │
│   ─────────────────────────────────────────────────────────    │
│   GET /users                          GET /getUsers             │
│   GET /users/123                      GET /user?id=123          │
│   POST /users                         POST /createUser          │
│   PUT /users/123                      POST /updateUser          │
│   DELETE /users/123                   GET /deleteUser?id=123    │
│                                                                 │
│   GET /users/123/orders               GET /getUserOrders        │
│   POST /users/123/orders              POST /createOrderForUser  │
│                                                                 │
│   规则:                                                         │
│   1. 使用名词复数 (/users 而非 /user)                           │
│   2. 使用小写和连字符 (/order-items 而非 /orderItems)           │
│   3. 不在 URL 中使用动词 (用 HTTP 方法表示动作)                  │
│   4. 资源层级不超过 3 层                                        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2. HTTP 方法语义

┌─────────────────────────────────────────────────────────────────┐
│                    HTTP 方法使用规范                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   方法      │ 语义     │ 幂等 │ 安全 │ 示例                     │
│   ─────────┼──────────┼──────┼──────┼─────────────────────     │
│   GET      │ 查询     │  是  │  是  │ GET /users/123           │
│   POST     │ 创建     │  否  │  否  │ POST /users              │
│   PUT      │ 全量更新 │  是  │  否  │ PUT /users/123           │
│   PATCH    │ 部分更新 │  是  │  否  │ PATCH /users/123         │
│   DELETE   │ 删除     │  是  │  否  │ DELETE /users/123        │
│                                                                 │
│   幂等: 多次请求效果与一次相同                                   │
│   安全: 不修改资源状态                                          │
│                                                                 │
│   特殊操作的处理:                                                │
│   ─────────────────────────────────────────────────────────    │
│   操作              │ RESTful 方式                              │
│   ─────────────────┼────────────────────────────────────────   │
│   登录              │ POST /sessions (创建会话资源)             │
│   登出              │ DELETE /sessions/current                 │
│   批量删除          │ DELETE /users?ids=1,2,3                  │
│   复杂搜索          │ POST /users/search                       │
│   状态变更          │ PATCH /orders/123 {"status": "paid"}     │
│   批量操作          │ POST /orders/batch                       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

3. 状态码使用

┌─────────────────────────────────────────────────────────────────┐
│                    HTTP 状态码规范                               │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   2xx 成功                                                      │
│   ─────────────────────────────────────────────────────────    │
│   200 OK              │ 通用成功                                │
│   201 Created         │ 创建成功,返回新资源                     │
│   202 Accepted        │ 请求已接受,异步处理中                   │
│   204 No Content      │ 成功,无返回内容 (DELETE)                │
│                                                                 │
│   4xx 客户端错误                                                 │
│   ─────────────────────────────────────────────────────────    │
│   400 Bad Request     │ 请求参数错误                            │
│   401 Unauthorized    │ 未认证 (需要登录)                        │
│   403 Forbidden       │ 无权限 (已登录但权限不足)                 │
│   404 Not Found       │ 资源不存在                              │
│   409 Conflict        │ 资源冲突 (如重复创建)                    │
│   422 Unprocessable   │ 参数验证失败                            │
│   429 Too Many Reqs   │ 请求频率限制                            │
│                                                                 │
│   5xx 服务端错误                                                 │
│   ─────────────────────────────────────────────────────────    │
│   500 Internal Error  │ 服务器内部错误                          │
│   502 Bad Gateway     │ 网关错误 (上游服务故障)                  │
│   503 Unavailable     │ 服务不可用 (过载或维护)                  │
│   504 Gateway Timeout │ 网关超时                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

4. 请求响应规范

json
// 成功响应
{
    "code": 0,
    "message": "success",
    "data": {
        "id": "123",
        "name": "张三",
        "email": "[email protected]",
        "created_at": "2024-01-15T10:30:00Z"
    }
}

// 列表响应 (带分页)
{
    "code": 0,
    "message": "success",
    "data": {
        "items": [
            {"id": "1", "name": "用户1"},
            {"id": "2", "name": "用户2"}
        ],
        "pagination": {
            "page": 1,
            "page_size": 20,
            "total": 100,
            "total_pages": 5
        }
    }
}

// 错误响应
{
    "code": 40001,
    "message": "参数验证失败",
    "data": null,
    "errors": [
        {"field": "email", "message": "邮箱格式不正确"},
        {"field": "password", "message": "密码长度至少8位"}
    ],
    "request_id": "req_abc123"
}
go
// Go 响应结构体
type Response struct {
    Code      int         `json:"code"`
    Message   string      `json:"message"`
    Data      interface{} `json:"data,omitempty"`
    Errors    []Error     `json:"errors,omitempty"`
    RequestID string      `json:"request_id,omitempty"`
}

type Error struct {
    Field   string `json:"field"`
    Message string `json:"message"`
}

type Pagination struct {
    Page       int `json:"page"`
    PageSize   int `json:"page_size"`
    Total      int `json:"total"`
    TotalPages int `json:"total_pages"`
}

// 响应工具函数
func Success(c *gin.Context, data interface{}) {
    c.JSON(http.StatusOK, Response{
        Code:      0,
        Message:   "success",
        Data:      data,
        RequestID: c.GetString("request_id"),
    })
}

func Error(c *gin.Context, code int, message string) {
    c.JSON(getHTTPStatus(code), Response{
        Code:      code,
        Message:   message,
        RequestID: c.GetString("request_id"),
    })
}

二、API 版本管理

版本策略

┌─────────────────────────────────────────────────────────────────┐
│                    API 版本管理策略                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   方式 1: URL 路径版本 (推荐)                                    │
│   ─────────────────────────────────────────────────────────    │
│   /api/v1/users                                                 │
│   /api/v2/users                                                 │
│                                                                 │
│   优点: 直观、易于调试、CDN友好                                  │
│   缺点: URL 变化、可能维护多版本                                 │
│                                                                 │
│                                                                 │
│   方式 2: Header 版本                                            │
│   ─────────────────────────────────────────────────────────    │
│   GET /api/users                                                │
│   Accept: application/vnd.api+json; version=2                   │
│                                                                 │
│   优点: URL 稳定                                                │
│   缺点: 不直观、调试困难                                        │
│                                                                 │
│                                                                 │
│   方式 3: 查询参数版本                                          │
│   ─────────────────────────────────────────────────────────    │
│   /api/users?version=2                                          │
│                                                                 │
│   优点: 灵活                                                    │
│   缺点: 与业务参数混淆                                          │
│                                                                 │
│                                                                 │
│   版本演进策略:                                                  │
│   ─────────────────────────────────────────────────────────    │
│   1. 新功能: 添加新字段 (向后兼容)                               │
│   2. 小改动: 使用特性开关控制                                   │
│   3. 大改动: 发布新版本 (v2)                                    │
│   4. 废弃旧版本: 通知 → 警告 → 下线                             │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

向后兼容

go
// 向后兼容的字段添加
type UserV1 struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

type UserV2 struct {
    ID        string     `json:"id"`
    Name      string     `json:"name"`
    Email     string     `json:"email"`
    Phone     string     `json:"phone,omitempty"`     // 新增可选字段
    CreatedAt *time.Time `json:"created_at,omitempty"` // 新增可选字段
}

// 废弃字段标记
type User struct {
    ID       string `json:"id"`
    Name     string `json:"name"`
    Username string `json:"username,omitempty"` // Deprecated: use name instead
    Email    string `json:"email"`
}

// 版本路由
func SetupRoutes(r *gin.Engine) {
    v1 := r.Group("/api/v1")
    {
        v1.GET("/users", v1GetUsers)
        v1.POST("/users", v1CreateUser)
    }

    v2 := r.Group("/api/v2")
    {
        v2.GET("/users", v2GetUsers)  // 新版本接口
        v2.POST("/users", v2CreateUser)
    }
}

三、gRPC API 设计

1. Proto 定义规范

protobuf
// user.proto
syntax = "proto3";

package user.v1;

option go_package = "github.com/example/api/user/v1";

import "google/protobuf/timestamp.proto";
import "google/protobuf/empty.proto";

// 用户服务
service UserService {
    // 获取用户
    rpc GetUser(GetUserRequest) returns (GetUserResponse);

    // 创建用户
    rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);

    // 更新用户
    rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);

    // 删除用户
    rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);

    // 用户列表
    rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);

    // 流式获取用户变更
    rpc WatchUsers(WatchUsersRequest) returns (stream UserEvent);
}

// 用户实体
message User {
    string id = 1;
    string name = 2;
    string email = 3;
    UserStatus status = 4;
    google.protobuf.Timestamp created_at = 5;
    google.protobuf.Timestamp updated_at = 6;
}

enum UserStatus {
    USER_STATUS_UNSPECIFIED = 0;
    USER_STATUS_ACTIVE = 1;
    USER_STATUS_INACTIVE = 2;
    USER_STATUS_BANNED = 3;
}

// 请求消息
message GetUserRequest {
    string id = 1;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
    string password = 3;
}

message UpdateUserRequest {
    string id = 1;
    optional string name = 2;
    optional string email = 3;
}

message DeleteUserRequest {
    string id = 1;
}

message ListUsersRequest {
    int32 page = 1;
    int32 page_size = 2;
    string filter = 3;  // 过滤条件
    string order_by = 4;  // 排序字段
}

// 响应消息
message GetUserResponse {
    User user = 1;
}

message CreateUserResponse {
    User user = 1;
}

message UpdateUserResponse {
    User user = 1;
}

message ListUsersResponse {
    repeated User users = 1;
    int32 total = 2;
    int32 page = 3;
    int32 page_size = 4;
}

// 事件
message UserEvent {
    EventType type = 1;
    User user = 2;
    google.protobuf.Timestamp timestamp = 3;
}

enum EventType {
    EVENT_TYPE_UNSPECIFIED = 0;
    EVENT_TYPE_CREATED = 1;
    EVENT_TYPE_UPDATED = 2;
    EVENT_TYPE_DELETED = 3;
}

2. 错误处理

protobuf
// errors.proto
import "google/rpc/status.proto";

// 使用 google.rpc.Status 标准错误格式
// 错误码使用 google.rpc.Code

// 自定义错误详情
message ErrorDetail {
    string code = 1;       // 业务错误码
    string message = 2;    // 错误消息
    string field = 3;      // 错误字段
}
go
// Go 错误处理
import (
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
)

func (s *UserServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    user, err := s.repo.GetByID(req.Id)
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            return nil, status.Error(codes.NotFound, "user not found")
        }
        return nil, status.Error(codes.Internal, "internal error")
    }

    return &pb.GetUserResponse{User: user}, nil
}

// 带详情的错误
func (s *UserServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
    // 验证
    if req.Name == "" {
        st := status.New(codes.InvalidArgument, "validation failed")
        st, _ = st.WithDetails(&pb.ErrorDetail{
            Code:    "INVALID_NAME",
            Message: "name is required",
            Field:   "name",
        })
        return nil, st.Err()
    }

    // ...
}

四、API 安全

1. 认证方式

┌─────────────────────────────────────────────────────────────────┐
│                    API 认证方式对比                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   1. API Key                                                    │
│   ─────────────────────────────────────────────────────────    │
│   Authorization: ApiKey abc123                                  │
│   X-API-Key: abc123                                            │
│                                                                 │
│   适用: 服务间调用、第三方集成                                   │
│   优点: 简单                                                    │
│   缺点: 泄露风险、无法撤销单次                                   │
│                                                                 │
│   2. JWT (JSON Web Token)                                       │
│   ─────────────────────────────────────────────────────────    │
│   Authorization: Bearer eyJhbGciOiJIUzI1NiIs...                │
│                                                                 │
│   适用: 用户认证、前后端分离                                     │
│   优点: 无状态、包含用户信息                                     │
│   缺点: 无法主动失效、payload 可被解码                           │
│                                                                 │
│   3. OAuth 2.0                                                  │
│   ─────────────────────────────────────────────────────────    │
│   Authorization: Bearer access_token                            │
│                                                                 │
│   适用: 第三方授权、开放平台                                     │
│   优点: 标准化、细粒度授权                                       │
│   缺点: 复杂度高                                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

2. JWT 实现

go
import (
    "github.com/golang-jwt/jwt/v5"
)

type Claims struct {
    UserID   string   `json:"user_id"`
    Username string   `json:"username"`
    Roles    []string `json:"roles"`
    jwt.RegisteredClaims
}

type JWTService struct {
    secretKey     []byte
    accessExpiry  time.Duration
    refreshExpiry time.Duration
}

func (s *JWTService) GenerateToken(user *User) (string, string, error) {
    // Access Token (短期)
    accessClaims := Claims{
        UserID:   user.ID,
        Username: user.Username,
        Roles:    user.Roles,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.accessExpiry)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "my-service",
        },
    }
    accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
    accessStr, err := accessToken.SignedString(s.secretKey)
    if err != nil {
        return "", "", err
    }

    // Refresh Token (长期)
    refreshClaims := jwt.RegisteredClaims{
        ExpiresAt: jwt.NewNumericDate(time.Now().Add(s.refreshExpiry)),
        IssuedAt:  jwt.NewNumericDate(time.Now()),
        Subject:   user.ID,
    }
    refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
    refreshStr, err := refreshToken.SignedString(s.secretKey)
    if err != nil {
        return "", "", err
    }

    return accessStr, refreshStr, nil
}

func (s *JWTService) ValidateToken(tokenStr string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(token *jwt.Token) (interface{}, error) {
        return s.secretKey, nil
    })
    if err != nil {
        return nil, err
    }

    claims, ok := token.Claims.(*Claims)
    if !ok || !token.Valid {
        return nil, errors.New("invalid token")
    }

    return claims, nil
}

// 中间件
func AuthMiddleware(jwtService *JWTService) gin.HandlerFunc {
    return func(c *gin.Context) {
        authHeader := c.GetHeader("Authorization")
        if authHeader == "" {
            c.JSON(401, gin.H{"error": "missing authorization header"})
            c.Abort()
            return
        }

        tokenStr := strings.TrimPrefix(authHeader, "Bearer ")
        claims, err := jwtService.ValidateToken(tokenStr)
        if err != nil {
            c.JSON(401, gin.H{"error": "invalid token"})
            c.Abort()
            return
        }

        c.Set("user_id", claims.UserID)
        c.Set("roles", claims.Roles)
        c.Next()
    }
}

3. API 限流

go
// 令牌桶限流
import "golang.org/x/time/rate"

type RateLimiter struct {
    limiters sync.Map
    rate     rate.Limit
    burst    int
}

func NewRateLimiter(r rate.Limit, burst int) *RateLimiter {
    return &RateLimiter{
        rate:  r,
        burst: burst,
    }
}

func (r *RateLimiter) GetLimiter(key string) *rate.Limiter {
    limiter, exists := r.limiters.Load(key)
    if !exists {
        limiter = rate.NewLimiter(r.rate, r.burst)
        r.limiters.Store(key, limiter)
    }
    return limiter.(*rate.Limiter)
}

// 限流中间件
func RateLimitMiddleware(limiter *RateLimiter) gin.HandlerFunc {
    return func(c *gin.Context) {
        // 按用户 ID 或 IP 限流
        key := c.GetString("user_id")
        if key == "" {
            key = c.ClientIP()
        }

        if !limiter.GetLimiter(key).Allow() {
            c.JSON(429, gin.H{
                "error": "rate limit exceeded",
                "retry_after": 1,
            })
            c.Abort()
            return
        }

        c.Next()
    }
}

五、API 文档

OpenAPI/Swagger

yaml
# openapi.yaml
openapi: 3.0.3
info:
  title: User Service API
  description: 用户服务 API 文档
  version: 1.0.0
  contact:
    name: API Support
    email: [email protected]

servers:
  - url: https://api.example.com/v1
    description: 生产环境
  - url: https://api-staging.example.com/v1
    description: 测试环境

paths:
  /users:
    get:
      summary: 获取用户列表
      tags:
        - Users
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: page_size
          in: query
          schema:
            type: integer
            default: 20
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserListResponse'

    post:
      summary: 创建用户
      tags:
        - Users
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateUserRequest'
      responses:
        '201':
          description: 创建成功
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UserResponse'
        '400':
          $ref: '#/components/responses/BadRequest'

components:
  schemas:
    User:
      type: object
      properties:
        id:
          type: string
          example: "123"
        name:
          type: string
          example: "张三"
        email:
          type: string
          format: email
          example: "[email protected]"
        created_at:
          type: string
          format: date-time

    CreateUserRequest:
      type: object
      required:
        - name
        - email
        - password
      properties:
        name:
          type: string
          minLength: 2
          maxLength: 50
        email:
          type: string
          format: email
        password:
          type: string
          minLength: 8

  responses:
    BadRequest:
      description: 请求参数错误
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

六、检查清单

API 设计检查

  • [ ] URL 是否使用名词复数?
  • [ ] HTTP 方法是否正确使用?
  • [ ] 状态码是否规范?
  • [ ] 响应格式是否统一?

安全检查

  • [ ] 是否有认证机制?
  • [ ] 是否有限流保护?
  • [ ] 敏感数据是否加密传输?
  • [ ] 是否防止常见攻击?

文档检查

  • [ ] 是否有完整的 API 文档?
  • [ ] 是否包含请求/响应示例?
  • [ ] 错误码是否有说明?
  • [ ] 是否有版本变更记录?

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布