输入关键词开始搜索

接口设计文档

06 — 云融(YunRong)· 接口设计文档 (API)

前置文档03-通信协议详细设计文档04-数据库设计文档05-模块详细设计文档 设计思路00-设计思路文档

版本:v0.1 状态:已发布 最后更新:2025-01


1. 接口体系总览

1.1 双通道架构

┌─────────────────────────────────────────────────────┐
│                    客户端                            │
│                                                      │
│  ┌──────────────────┐    ┌────────────────────────┐  │
│  │   REST API        │    │   WebSocket API        │  │
│  │   (HTTPS)         │    │   (WSS)                │  │
│  │                   │    │                        │  │
│  │ • 认证/登录        │    │ • 实时消息收发          │  │
│  │ • 文件上传/下载     │    │ • 通知推送             │  │
│  │ • 联系人/组织架构   │    │ • 心跳/状态同步        │  │
│  │ • 任务/审批        │    │ • 增量同步             │  │
│  │ • 数据统计         │    │                        │  │
│  └────────┬─────────┘    └───────────┬────────────┘  │
│           │                          │               │
└───────────┼──────────────────────────┼───────────────┘
            │                          │
      ┌─────┴──────┐            ┌──────┴──────┐
      │  API Server │            │ WS Server   │
      │  (REST)     │            │ (WebSocket) │
      └─────┬──────┘            └──────┬──────┘
            │                          │
      ┌─────┴──────────────────────────┴──────┐
      │            PostgreSQL                  │
      └───────────────────────────────────────┘

1.2 使用场景分工

场景通道理由
登录/注册REST一次性操作,无需长连接
获取联系人列表REST请求-响应模式,适合 HTTP
发送消息WebSocket实时性要求,双向推送
接收消息WebSocket服务端主动推送,HTTP 轮询太浪费
文件上传REST (multipart)大块数据传输,HTTP Range 更成熟
文件下载REST (Range)断点续传依赖 HTTP Range
搜索消息REST计算密集型,服务端处理
心跳WebSocket Ping/Pong保活长连接
增量同步WebSocket和消息推送共用通道

1.3 基础约定

约定说明
Base URLhttps://{server}/api/v1
内容类型请求/响应均为 application/json(文件上传除外)
字符编码UTF-8
日期时间ISO 8601 格式:2025-01-15T10:30:00.000Z 或 Unix 毫秒时间戳
分页游标分页(见 §2)
认证HTTP Header: Authorization: Bearer <access_token>
版本URL 路径前缀 /v1/

2. 通用约定

2.1 请求格式

METHOD /api/v1/resource HTTP/1.1
Host: server.example.com
Authorization: Bearer eyJhbGciOi...
Content-Type: application/json
Accept: application/json

2.2 成功响应格式

{
  "code": 0,
  "message": "success",
  "data": { ... },
  "meta": {
    "timestamp": 1704067200000,
    "request_id": "req_uuid_v4"
  }
}
字段类型说明
codeint0 表示成功
messagestring人类可读的响应消息
dataobject/array/null响应数据
meta.timestampuint64服务端时间戳
meta.request_idstring请求追踪 ID(用于日志关联)

2.3 错误响应格式

{
  "code": 401002,
  "message": "Token 已过期,请重新登录",
  "data": null,
  "meta": {
    "timestamp": 1704067200000,
    "request_id": "req_uuid_v4"
  }
}

2.4 游标分页

// 请求
GET /api/v1/messages?conv_id=2001&before=1704067200000&limit=50

// 响应
{
  "code": 0,
  "message": "success",
  "data": {
    "items": [ ... ],
    "pagination": {
      "has_more": true,
      "next_cursor": "1704060000000",
      "limit": 50
    }
  }
}
参数说明
before游标:返回此时间戳之前的记录(首次请求可不传,返回最新)
after游标:返回此时间戳之后的记录(向上翻页用)
limit每页条数(默认 50,最大 200)
next_cursor下一页的游标值(即本页最后一条的时间戳)

3. REST API 端点清单

3.1 认证模块 /api/v1/auth

3.1.1 登录

POST /api/v1/auth/login
Content-Type: application/json

Request:
{
  "username": "zhangsan",
  "password": "hashed_password",
  "device_id": "uuid-device-001",
  "platform": "windows",
  "client_version": "0.1.0"
}

Response 200:
{
  "code": 0,
  "data": {
    "user_id": 1001,
    "display_name": "张三",
    "avatar_url": "https://server/avatars/1001.webp",
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 7200,
    "ws_url": "wss://server/ws?token=eyJhbGciOi..."
  }
}

Errors:
  401001 — 用户名或密码错误
  401002 — 账号已被禁用
  401003 — 设备未授权

3.1.2 Token 刷新

POST /api/v1/auth/refresh
Authorization: Bearer <expired_access_token>

Request:
{
  "refresh_token": "eyJhbGciOi..."
}

Response 200:
{
  "code": 0,
  "data": {
    "access_token": "eyJhbGciOi...",
    "refresh_token": "eyJhbGciOi...",
    "expires_in": 7200
  }
}

Errors:
  401010 — Refresh Token 已过期
  401011 — Refresh Token 无效

3.1.3 登出

POST /api/v1/auth/logout
Authorization: Bearer <access_token>

Request: {}

Response 200:
{
  "code": 0,
  "data": { "logged_out": true }
}

3.2 联系人模块 /api/v1/contacts

3.2.1 获取联系人列表

GET /api/v1/contacts?department_id=0&limit=500

Response 200:
{
  "code": 0,
  "data": {
    "items": [
      {
        "id": 1002,
        "name": "李四",
        "avatar_url": "https://server/avatars/1002.webp",
        "department_id": 10,
        "department_name": "研发部",
        "title": "高级工程师",
        "email": "lisi@example.com",
        "phone": "13800138000",
        "status": 1,
        "pinyin": "lisi"
      }
    ],
    "total": 2000
  }
}

3.2.2 搜索联系人

GET /api/v1/contacts/search?q=李&limit=20

Response 200:
{
  "code": 0,
  "data": {
    "items": [ ... ]
  }
}

3.2.3 获取组织架构树

GET /api/v1/departments?parent_id=0&depth=2

Response 200:
{
  "code": 0,
  "data": {
    "items": [
      {
        "id": 1,
        "name": "总公司",
        "parent_id": 0,
        "member_count": 1500,
        "children": [
          {
            "id": 10,
            "name": "研发部",
            "parent_id": 1,
            "member_count": 80
          }
        ]
      }
    ]
  }
}

3.3 会话模块 /api/v1/conversations

3.3.1 获取会话列表

GET /api/v1/conversations?limit=50

Response 200:
{
  "code": 0,
  "data": {
    "items": [
      {
        "id": 2001,
        "type": 0,
        "title": "李四",
        "avatar_url": "https://server/avatars/1002.webp",
        "last_message": {
          "preview": "好的,明天开会讨论",
          "timestamp": 1704067200000,
          "sender_id": 1002
        },
        "unread_count": 3,
        "is_pinned": false,
        "is_muted": false,
        "members": [1001, 1002]
      }
    ]
  }
}

3.3.2 创建会话

POST /api/v1/conversations
Content-Type: application/json

Request:
{
  "type": 1,
  "title": "项目讨论组",
  "member_ids": [1002, 1003, 1004]
}

Response 201:
{
  "code": 0,
  "data": {
    "id": 2005,
    "type": 1,
    "title": "项目讨论组",
    "created_at": 1704067200000
  }
}

3.3.3 获取消息历史

GET /api/v1/conversations/2001/messages?before=1704067200000&limit=50

Response 200:
{
  "code": 0,
  "data": {
    "items": [
      {
        "msg_id": "msg_abc123",
        "sender_id": 1002,
        "sender_name": "李四",
        "content_type": 0,
        "content_body": { "type": "text", "text": "你好" },
        "quote_msg_id": null,
        "timestamp": 1704067200000
      }
    ],
    "pagination": {
      "has_more": true,
      "next_cursor": "1704060000000"
    }
  }
}

3.3.4 搜索消息

GET /api/v1/conversations/2001/messages/search?q=报告&limit=20

// 或全局搜索
GET /api/v1/messages/search?q=报告&conv_id=2001&limit=20

3.4 任务模块 /api/v1/tasks

3.4.1 获取任务列表

GET /api/v1/tasks?status=0&priority=2&limit=50

Query 参数:
  status   — 0=待处理, 1=已读, 2=已处理, 3=已忽略
  priority — 0=低, 1=普通, 2=高
  type     — "approval" | "assignment" | "announcement"

Response 200:
{
  "code": 0,
  "data": {
    "items": [
      {
        "id": 5001,
        "notify_id": "n_12345",
        "task_type": "approval",
        "title": "请假审批",
        "body": "张三申请年假 3 天 (2025-01-20 ~ 2025-01-22)",
        "priority": 1,
        "status": 0,
        "from_user_id": 1001,
        "from_user_name": "张三",
        "action_url": "/tasks/5001/detail",
        "created_at": 1704067200000
      }
    ],
    "pagination": { "has_more": false }
  }
}

3.4.2 处理任务

POST /api/v1/tasks/5001/handle
Content-Type: application/json

Request:
{
  "action": "approve",
  "comment": "同意,注意交接工作"
}

// action: "approve" | "reject" | "complete" | "ignore"

Response 200:
{
  "code": 0,
  "data": {
    "id": 5001,
    "new_status": 2,
    "handled_at": 1704070000000
  }
}

3.5 文件模块 /api/v1/files

3.5.1 检查文件是否存在(秒传)

POST /api/v1/files/check
Content-Type: application/json

Request:
{
  "hash": "sha256:abc123def456...",
  "file_name": "报告.pdf",
  "file_size": 1048576
}

Response 200 (已存在 → 可秒传):
{
  "code": 0,
  "data": {
    "exists": true,
    "url": "https://server/files/doc_042.pdf"
  }
}

Response 200 (不存在 → 需要上传):
{
  "code": 0,
  "data": {
    "exists": false,
    "upload_id": "upload_uuid_xxx",
    "chunk_size": 1048576,
    "total_chunks": 1
  }
}

3.5.2 上传文件块

POST /api/v1/files/upload/{upload_id}/chunks/{chunk_index}
Content-Type: application/octet-stream
X-Chunk-Offset: 0
X-Chunk-Size: 1048576
X-Total-Chunks: 1
X-File-Hash: sha256:abc123def456...

Body: <binary data>

Response 200:
{
  "code": 0,
  "data": {
    "chunk_index": 0,
    "received": true
  }
}

3.5.3 完成上传

POST /api/v1/files/upload/{upload_id}/complete
Content-Type: application/json

Request:
{
  "file_hash": "sha256:abc123def456..."
}

Response 200:
{
  "code": 0,
  "data": {
    "url": "https://server/files/doc_042.pdf",
    "hash_verified": true,
    "file_size": 1048576
  }
}

Errors:
  9001 — 文件块不完整
  9003 — Hash 校验不匹配

3.5.4 下载文件(带 Range)

GET /api/v1/files/download?url=https://server/files/doc_042.pdf
Range: bytes=0-1048575

Response 206 Partial Content:
Content-Range: bytes 0-1048575/1048576
Content-Type: application/octet-stream

Body: <binary data>

3.6 统计报表模块 /api/v1/reports

3.6.1 消息统计

GET /api/v1/reports/message_stats?from=1704067200000&to=1704153600000&granularity=day

Query 参数:
  from        — 起始时间戳
  to          — 结束时间戳
  granularity — "hour" | "day" | "week" | "month"

Response 200:
{
  "code": 0,
  "data": {
    "total_sent": 1523,
    "total_received": 2105,
    "by_granularity": [
      { "timestamp": 1704067200000, "sent": 120, "received": 180 },
      { "timestamp": 1704153600000, "sent": 95,  "received": 140 }
    ],
    "by_conversation": [
      { "conv_id": 2001, "conv_name": "李四", "count": 300 },
      { "conv_id": 2003, "conv_name": "项目讨论组", "count": 850 }
    ]
  }
}

3.6.2 文件传输统计

GET /api/v1/reports/file_stats?from=1704067200000&to=1704153600000

Response 200:
{
  "code": 0,
  "data": {
    "total_uploads": 45,
    "total_downloads": 23,
    "total_bytes_up": 524288000,
    "total_bytes_down": 104857600
  }
}

3.6.3 导出报表

POST /api/v1/reports/export
Content-Type: application/json

Request:
{
  "type": "message_stats",
  "format": "csv",
  "from": 1704067200000,
  "to": 1704153600000
}

Response 200:
Content-Type: text/csv
Content-Disposition: attachment; filename="message_report_202501.csv"

Body: <CSV data>

支持的导出格式:csvjsonpdf(服务端生成)。

3.7 用户配置模块 /api/v1/preferences

3.7.1 获取配置

GET /api/v1/preferences

Response 200:
{
  "code": 0,
  "data": {
    "ui.theme": "dark",
    "ui.font_size": "14",
    "notify.sound": true,
    "notify.desktop": true,
    "file.download_dir": "/home/user/Downloads",
    "file.upload_limit_kbps": 0
  }
}

3.7.2 更新配置

PUT /api/v1/preferences
Content-Type: application/json

Request:
{
  "ui.theme": "light",
  "notify.sound": false
}

Response 200:
{
  "code": 0,
  "data": {
    "updated": ["ui.theme", "notify.sound"]
  }
}

4. 错误码完整目录

4.1 错误码结构

AABCCC
││└── 具体错误 (3 位)
│└── 子模块 (1 位)
└── 大模块 (2 位)
     01 = 认证
     02 = 联系人/组织
     03 = 会话/消息
     04 = 任务
     05 = 文件
     09 = 系统

4.2 完整清单

错误码HTTP 状态码含义
011001401用户名或密码错误
011002403账号已被禁用
011003403设备未授权
011010401Refresh Token 已过期
011011401Refresh Token 无效
012001401Access Token 过期(需刷新)
012002401Access Token 无效/格式错误
021001404联系人不存在
022001404部门不存在
031001404会话不存在
031002403无权限访问该会话
032001404消息不存在
032002400消息体超过大小限制(10KB)
041001404任务不存在
041002403无权处理该任务
041003400任务已被处理,不可重复操作
051001404上传任务不存在
051002400分块序号越界
052001400文件 Hash 校验失败
052002400文件大小超出限制(单文件 2GB)
091001400请求参数缺失或格式错误
091002429请求频率超限
091003500服务器内部错误
091004503服务暂时不可用

5. 认证流程

5.1 完整认证时序

Client                      REST Server           WS Server
  │                              │                     │
  │ 1. POST /auth/login          │                     │
  │─────────────────────────────►│                     │
  │                              │ 验证凭据              │
  │◄── access_token +            │                     │
  │    refresh_token + ws_url ──│                     │
  │                              │                     │
  │ 2. 存储 token (加密)          │                     │
  │                              │                     │
  │ 3. WSS connect(ws_url)       │                     │
  │──────────────────────────────────────────────────►│
  │                              │                     │
  │ 4. WebSocket auth 帧          │                     │
  │   { type:"auth", token } ────────────────────────►│
  │◄── { type:"auth_ok" } ──────────────────────────│
  │                              │                     │
  │  ........  2 小时后  ........ │                     │
  │                              │                     │
  │ 5. Token 即将过期(提前 5min) │                     │
  │ POST /auth/refresh           │                     │
  │─────────────────────────────►│                     │
  │◄── 新的 token pair ─────────│                     │
  │                              │                     │
  │ 6. 更新本地 token             │                     │
  │    (无缝切换,用户无感知)     │                     │

5.2 Token 安全策略

策略说明
Access Token短期(2h),JWT,Bearer Header 传输
Refresh Token长期(7d),不透明字符串,仅在 /auth/refresh 中使用
Token 存储加密存储于系统凭据管理器,不存 SQLite
撤回机制服务端维护黑名单,Refresh Token 被撤回后强制重新登录
多端支持每个 device_id 拥有独立的 token pair,互不影响

6. Mock Server 设计

6.1 架构

┌─────────────────────────────────────────────────────┐
│                 Mock Server (Qt Process)             │
│                                                      │
│  ┌────────────────────┐  ┌────────────────────────┐  │
│  │  HTTP Server        │  │  WebSocket Server      │  │
│  │  (QTcpServer)       │  │  (QWebSocketServer)    │  │
│  │  port: 18002        │  │  port: 18001           │  │
│  └─────────┬──────────┘  └───────────┬────────────┘  │
│            │                         │               │
│  ┌─────────┴─────────────────────────┴───────────┐   │
│  │              Route Dispatcher                  │   │
│  │  /auth/login     → AuthHandler                 │   │
│  │  /contacts       → ContactHandler              │   │
│  │  /conversations  → ConversationHandler         │   │
│  │  /files/*        → FileHandler                 │   │
│  │  /ws             → WsHandler                   │   │
│  └──────────────────────┬────────────────────────┘   │
│                         │                            │
│  ┌──────────────────────┴────────────────────────┐   │
│  │              Scenario Engine                   │   │
│  │  • 场景配置加载 (JSON)                          │   │
│  │  • 延迟模拟 (delay_ms)                         │   │
│  │  • 丢包模拟 (drop_rate)                        │   │
│  │  • 错误注入 (error_code)                       │   │
│  │  • 预设数据集 (contacts, messages, tasks)       │   │
│  └────────────────────────────────────────────────┘   │
│                                                      │
│  ┌────────────────────────────────────────────────┐   │
│  │              In-Memory Store                   │   │
│  │  • 用户表 / 会话表 / 消息表 / 任务表 / 文件表   │   │
│  │  • 所有数据存内存,重启清空                      │   │
│  └────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────┘

6.2 场景配置

{
  "scenario": "normal_full",
  "description": "正常使用完整场景",
  "config": {
    "network": {
      "base_delay_ms": 30,
      "jitter_ms": 15,
      "drop_rate": 0.0
    },
    "auth": {
      "valid_tokens": ["mock_token_admin", "mock_token_user1", "mock_token_user2"],
      "token_expiry_sec": 7200
    }
  },
  "dataset": {
    "users": [
      { "id": 1001, "name": "当前用户", "username": "me", "password": "123456" },
      { "id": 1002, "name": "李四", "username": "lisi" },
      { "id": 1003, "name": "王五", "username": "wangwu" }
    ],
    "departments": [
      { "id": 1, "name": "总公司", "parent_id": 0 },
      { "id": 10, "name": "研发部", "parent_id": 1 },
      { "id": 11, "name": "产品部", "parent_id": 1 }
    ],
    "conversations": [
      { "id": 2001, "type": 0, "members": [1001, 1002] },
      { "id": 2003, "type": 1, "title": "项目组", "members": [1001, 1002, 1003] }
    ],
    "messages": [
      { "msg_id": "m001", "conv_id": 2001, "sender_id": 1002, "content_type": 0,
        "content_body": { "type": "text", "text": "你好,明天的会议几点?" },
        "timestamp": 1704067000000 }
    ]
  }
}

6.3 Mock Server 处理示例

// mock/mock_server.cpp 核心路由

void MockServer::setupRoutes() {
    // ---- 认证 ----
    httpServer_.route("POST", "/api/v1/auth/login", [this](const Request& req) -> Response {
        auto body = json::parse(req.body);
        std::string username = body["username"];
        std::string password = body["password"];

        // 从 dataset 查找用户
        auto user = store_.findUser(username, password);
        if (!user) {
            return errorResponse(401, 11001, "用户名或密码错误");
        }

        return successResponse({
            {"user_id", user->id},
            {"display_name", user->name},
            {"access_token", "mock_token_" + username},
            {"refresh_token", "mock_refresh_" + username},
            {"expires_in", 7200},
            {"ws_url", "ws://localhost:18001/ws?token=mock_token_" + username}
        });
    });

    // ---- 联系人 ----
    httpServer_.route("GET", "/api/v1/contacts", [this](const Request& req) -> Response {
        if (!checkAuth(req)) return errorResponse(401, 12001, "Token 过期");

        auto contacts = store_.getAllContacts();
        return successResponse({
            {"items", contacts},
            {"total", contacts.size()}
        });
    });

    // ---- 消息历史 (游标分页) ----
    httpServer_.route("GET", "/api/v1/conversations/:id/messages",
        [this](const Request& req) -> Response {
        if (!checkAuth(req)) return errorResponse(401, 12001, "Token 过期");

        int64_t convId = std::stoll(req.param("id"));
        uint64_t before = req.query("before", UINT64_MAX);
        int limit = std::min(req.query("limit", 50), 200);

        auto [msgs, hasMore] = store_.getMessages(convId, before, limit);
        uint64_t nextCursor = msgs.empty() ? 0 : msgs.back()["timestamp"];

        return successResponse({
            {"items", msgs},
            {"pagination", {
                {"has_more", hasMore},
                {"next_cursor", std::to_string(nextCursor)},
                {"limit", limit}
            }}
        });
    });
}

6.4 错误注入场景

{
  "scenario": "error_injection",
  "description": "模拟各种错误场景",
  "config": {
    "network": {
      "base_delay_ms": 30
    },
    "injections": [
      {
        "path": "/api/v1/auth/login",
        "method": "POST",
        "condition": "body.password == 'wrong'",
        "response": { "code": 11001, "message": "用户名或密码错误" }
      },
      {
        "path": "/api/v1/files/upload/*",
        "condition": "chunk_index == 3",
        "action": "drop",
        "description": "模拟第 3 块上传丢包,测试断点续传"
      },
      {
        "path": "/api/v1/auth/refresh",
        "condition": "true",
        "response": { "code": 11010, "message": "Refresh Token 已过期" },
        "description": "模拟 Token 完全过期,测试重新登录流程"
      },
      {
        "ws": true,
        "condition": "after_connected_sec == 15",
        "action": "close_connection",
        "description": "连接 15 秒后强制断开,测试重连逻辑"
      }
    ]
  }
}

6.5 命令行接口

# 启动标准场景
./mock_server --scenario scenarios/normal_full.json

# 启动错误注入场景
./mock_server --scenario scenarios/error_injection.json -v

# 自定义端口
./mock_server --scenario scenarios/normal_full.json --http-port 9002 --ws-port 9001

# 热加载场景(运行时切换)
curl -X POST http://localhost:18002/__admin/scenario/load \
  -H "Content-Type: application/json" \
  -d '{"scenario": "error_injection"}'

管理接口(/__admin/ 前缀):

  • POST /__admin/scenario/load — 热加载场景
  • GET /__admin/scenario/current — 查看当前场景
  • POST /__admin/network/delay?ms=500 — 动态调整延迟
  • POST /__admin/network/drop?rate=0.1 — 动态调整丢包率
  • GET /__admin/stats — 查看请求统计

7. 客户端 HTTP Client 实现要点

// core/net/http_client.h
// HttpClient 的接口设计(已在 05 文档中定义,此处补充实现关键点)

class HttpClient : public IConnection {
public:
    // 通用 GET 请求
    void get(const std::string& path,
             const std::map<std::string, std::string>& query,
             std::function<void(int httpCode, const json& body)> callback);

    // 通用 POST 请求
    void post(const std::string& path,
              const json& body,
              std::function<void(int httpCode, const json& body)> callback);

    // 文件上传(分块)
    void uploadChunk(const std::string& uploadId, int chunkIndex,
                     const std::vector<uint8_t>& data, int totalChunks,
                     std::function<void(bool)> callback);

    // 文件下载(Range)
    void downloadRange(const std::string& url, uint64_t from, uint64_t to,
                       std::function<void(std::vector<uint8_t>)> callback);

private:
    QNetworkAccessManager* qnam_;  // Qt Network
    std::string baseUrl_;
    std::string authToken_;

    // 自动附加认证 Header
    QNetworkRequest buildRequest(const std::string& path);
};

// 认证 Header 注入
QNetworkRequest HttpClient::buildRequest(const std::string& path) {
    QUrl url(QString::fromStdString(baseUrl_ + path));
    QNetworkRequest req(url);
    req.setRawHeader("Authorization",
                     ("Bearer " + authToken_).c_str());
    req.setRawHeader("Accept", "application/json");
    req.setRawHeader("Content-Type", "application/json");
    return req;
}

附录 A — 接口与协议文档的分工

文档覆盖范围
03-通信协议WebSocket 帧格式(JSON 帧 + 二进制帧)、ACK/重传、心跳、TLS
06-API 设计(本文档)REST API 端点、Schema、错误码、认证流程、Mock Server

两篇文档共同构成完整的通信契约。


附录 B — HTTP 状态码使用

状态码使用场景
200成功
201创建成功(如创建会话)
206部分内容(文件 Range 下载)
400请求参数错误
401未认证
403无权限
404资源不存在
409冲突(如任务已被处理)
413请求体过大
429请求频率超限
500服务端内部错误

附录 C — 文档修订记录

版本日期作者变更说明
v0.12025-01初稿:7 个 API 模块,20+ 端点,完整错误码表,Mock Server 设计