Files
Scoreboard/devdocs/05-接口与实时协议.md
T

6.0 KiB
Raw Blame History

05 · 接口与实时协议

所属:运动会积分榜系统 · 开发文档集 版本:v0.1(讨论稿) · 2026-09-09 上游:README · 相关:04-数据模型、06-大屏端设计


第一部分 · 实时同步协议

1. 通道

  • Socket.IO,路径 /socket.io,与 HTTP 同端口。
  • 房间:display(所有大屏)、admin(所有管理端)、admin:<userId>(定向通知,如「你的草稿已被发布」)。
  • 传输:优先 WebSocket,失败自动降级 polling(Socket.IO 内建)。

2. 版本号 + 快照/增量(保证断线不错乱)

服务端维护单调递增 version(等于 change_log.id 最大值)

客户端连接
  └─ emit "hello" { role, lastVersion }
       ├─ lastVersion 为空 或 太旧(增量超阈值)→ "snapshot" { version, data }
       └─ 可增量 → "catchup" { fromVersion, changes:[...], version }
服务端有变更
  └─ emit "change" { version, changes:[...] }
客户端应用后
  └─ emit "ack" { version }        // 服务端据此识别慢客户端
服务端每 10s
  └─ emit "heartbeat" { version, serverTime }
       客户端发现 version 落后 → emit "resync"

3. 事件清单

事件 方向 载荷
hello C→S { role, lastVersion?, displayKey? }
snapshot S→C { version, settings, teams, events, standings, records, latestResults }
catchup S→C { fromVersion, version, changes[] }
change S→C { version, changes[] }
resync C→S {}
ack C→S { version }
heartbeat S→C { version, serverTime }
display:command S→C { action: 'goto'|'lock'|'unlock'|'reload'|'announce', payload }

changes[] 元素形如:

{ "entity": "result", "id": 42, "action": "upsert", "data": { } }

action 取值:upsert / void / replace(replace 用于整体替换的列表,如积分榜)。

4. 断线与降级

场景 行为
大屏断网 继续显示 localStorage 最后一次快照,右上角显示「⚠ 连接中断 · 数据可能不是最新」
大屏重连 按 lastVersion 增量补发;补不上就整包快照
管理端断网 顶部横幅提示,禁止提交写操作(避免双写)
服务端重启 客户端自动重连,版本号从数据库恢复(不重置)
多管理端同时改同一项目 服务端事务串行化;后提交者收到冲突提示,前端刷新最新数据

5. 幂等与顺序

  • 客户端按 version 去重:重复或乱序的变更直接丢弃。
  • 服务端写操作带 requestId,重复提交返回首次结果。

第二部分 · REST API

6. 约定

  • 前缀 /api,JSON,UTF-8。
  • 认证:HttpOnly Cookie 会话 + CSRF Token(管理端);大屏端免认证只读。
  • 统一响应:{ ok: true, data } / { ok: false, error: { code, message, detail? } }。
  • 分页:?page=&size=,返回 { items, total, page, size }。

7. 接口清单(初稿)

认证

方法 路径 说明
POST /api/auth/login 登录
POST /api/auth/logout 登出
GET /api/auth/me 当前用户

基础配置

方法 路径 说明
GET/PUT /api/settings 赛事配置
GET/POST/PUT/DELETE /api/teams[/:id] 单位 CRUD
GET/POST/PUT/DELETE /api/sports[/:id] 大项 CRUD
GET/POST/PUT/DELETE /api/events[/:id] 小项 CRUD
GET/POST/PUT /api/scoring-profiles[/:id] 积分模板
GET/POST/PUT /api/athletes[/:id] 运动员
POST /api/entries/import 导入报名表

成绩(核心)

方法 路径 说明
GET /api/events/:id/entries 该项目全部报名项 + 现有成绩
PUT /api/results/draft 批量保存草稿(一次一个项目的整组)
POST /api/results/publish 发布(触发积分重算与推送)
POST /api/results/unpublish 撤销发布
POST /api/results/:id/void 作废单条成绩
GET /api/results?eventId=&state= 查询

积分与排名

方法 路径 说明
GET /api/standings 总积分榜(含奖牌数)
GET /api/standings/teams/:id/breakdown 某单位积分明细(可解释每一分)
GET /api/standings/athletes 个人积分榜
GET/POST /api/adjustments[/:id/void] 人工加减分

纪录

方法 路径 说明
GET /api/records 纪录榜
POST /api/record-breaks 登记破纪录
POST /api/record-breaks/:id/approve 确认加分

大屏控制与导出

方法 路径 说明
POST /api/display/command 切页 / 锁定 / 刷新 / 公告
GET /api/export/standings.xlsx 导出总积分榜
GET /api/export/breakdown.xlsx 导出单位积分明细
GET /api/export/results.xlsx 导出成绩册
GET /api/audit-logs 审计日志
POST /api/backup 立即备份
GET /api/health 健康检查(供运维/守护脚本)

8. 错误码

code 含义
AUTH_REQUIRED / AUTH_FAILED / FORBIDDEN 认证与权限
VALIDATION_FAILED 入参不合法(附字段级 detail)
CONFLICT 并发修改冲突
STATE_INVALID 状态不允许该操作(如未发布就想撤销发布)
RATE_LIMITED 触发限流
INTERNAL 服务端异常

附录 · 实时事件样例

// 服务端 → 大屏
{
  "event": "change",
  "data": {
    "version": 128,
    "changes": [
      {
        "entity": "result",
        "id": 512,
        "action": "upsert",
        "data": { "eventId": 7, "entryId": 88, "rank": 1, "resultText": "11.58", "state": "published" }
      },
      {
        "entity": "standings",
        "action": "replace",
        "data": [ { "teamId": 1, "totalPoints": 186, "gold": 5, "rank": 1 } ]
      }
    ]
  }
}