YD-WEB/COMPETITION-DEPLOY.md
ltx 84b7b0f893 feat(website): add competition results and service deployment
- Add the event results page with schedule, rankings, Battle bracket, athletes, records, bilingual labels, and navigation entry.
- Add the read/write competition data service, a complete Excel example, empty public state, and validation smoke tests.
- Handle Furion statusCode/data responses from the production API and retain static fallback for the public page.
- Document Docker sidecar deployment beside test_eventapi, persistent storage, private admin access, Nginx routing, and OSS asset rewriting.

Verification:
- node server/competition-api.smoke.mjs
- node scripts/competition-response-test.mjs
- node scripts/battle-layout-test.mjs
- npm run build -- --base=https://assets.example.test/static/test/
- npm run rewrite:oss -- https://assets.example.test/static/test/
- Production /api/competition/state returned HTTP 200 after the sidecar started.

Deployment note: publish the updated YD-WEB static build before public results can consume the unwrapped API response. The event remains unpublished until an administrator saves and publishes data.
2026-10-20 13:38:23 +08:00

57 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 官网赛事成绩接入
官网入口为 `/#/sports/competition` 的第 4 项「赛事成绩」,独立页面为 `/#/sports/results`。`YD_Event.Web` 的「官网赛事信息」页面负责维护与发布。原需求文档的系统 B(现场评分控制台)不在此接入链路中。
## 运行接口
接口使用 Node.js 18+,没有额外依赖。生产环境必须设置以下环境变量后运行 `npm run serve:competition-api`:
| 变量 | 用途 |
|---|---|
| `COMPETITION_DATA_PATH` | 持久化 JSON 文件路径;默认 `server/data/competition-state.json`。生产环境应放在持久化磁盘并备份 |
| `COMPETITION_PORT` | 监听端口,默认 `8787` |
| `COMPETITION_HOST` | 监听地址,默认 `127.0.0.1`;分机部署时设为赛事服务的私网 IP,并用防火墙限制 `8787` 端口只允许后台服务器访问 |
| `COMPETITION_ADMIN_ALLOWED_IPS` | 允许直接访问内部管理接口的客户端 IP,逗号分隔;分机部署时填写 `YD_Event.Admin` 服务器的私网 IP。服务默认还允许本机环回地址 |
| `COMPETITION_ALLOWED_ORIGINS` | 仅在浏览器直接访问赛事服务时需要;当前正式链路由 YD_Event.Admin 转发,官网浏览器不直接连接赛事服务 |
官网通过 `https://jitsi-web.yuedongsports.com/api/competition/state` 读取已发布数据。这个匿名只读接口由 YD_Event.Admin 提供,并通过私网读取赛事服务的 `/api/competition/state`;未发布时赛事服务只返回空态。管理页仍通过需要登录的 `/api/website-competition` 读取草稿及保存,不需要管理密钥。官网每 10 秒重新读取一次公开数据。
启动赛事服务时设置 `COMPETITION_HOST=<赛事服务私网 IP>` 和 `COMPETITION_ADMIN_ALLOWED_IPS=<后台服务器私网 IP>`;在 YD_Event.Admin 设置 `CompetitionApi__AdminUrl=http://<赛事服务私网 IP>:8787/api/competition/admin`。防火墙只允许后台服务器访问赛事服务的 `8787` 端口。先从后台服务器执行 `curl -i http://<赛事服务私网 IP>:8787/api/competition/ping`,确认服务返回 `200`。`jitsi-web.yuedongsports.com` 沿用现有 `/api/` 到 YD_Event.Admin 的代理,无需将公开请求直接代理到赛事服务。
若运维已应用旧版赛事 Nginx 片段,应改用 [`deploy/nginx/jitsi-competition-api.conf`](deploy/nginx/jitsi-competition-api.conf):只屏蔽公网 `/api/competition/admin`,让公开 `/api/competition/state` 走现有后台代理。修改后执行 `nginx -t` 再重载 Nginx。
部署后从官网域名验证公开接口及 CORS 响应头:
```bash
curl -i -H 'Origin: https://www.yuedongsports.com' https://jitsi-web.yuedongsports.com/api/competition/state
curl -i https://jitsi-web.yuedongsports.com/api/competition/admin
```
第一条请求应返回 `200` 和允许 `https://www.yuedongsports.com` 的 `Access-Control-Allow-Origin`;第二条公网管理路径应返回 `404`。若公开接口仍返回 `404`,先确认 YD_Event.Admin 已部署匿名只读控制器,且旧版 Nginx 赛事代理已移除。若返回 `502`,检查内网赛事服务、`CompetitionApi__AdminUrl` 和防火墙。内部管理接口只接受 `COMPETITION_ADMIN_ALLOWED_IPS` 中的来源地址;公网不可直连赛事服务的 `8787` 端口。
2026-10-20 排查发现:此前线上 `/api/competition/state` 返回后台生成的 `502`,原因是 `test_eventapi` 容器未配置 `CompetitionApi__AdminUrl`,同机没有监听 `8787` 的赛事数据服务。部署共享网络的赛事容器后,公开接口已恢复 `200`。当前后台响应由 Furion 包装为 `statusCode/data`,官网读取代码会解包其中的赛事 JSON;官网更新构建后才会使用这项解析修复。
若赛事服务与 `test_eventapi` 部署在同一台 Docker 主机,可按 [`deploy/competition-api/README.md`](deploy/competition-api/README.md) 启动独立的 Node 容器并共享后台容器的网络命名空间。两个容器因此共用回环地址,后台现有默认 `127.0.0.1:8787` 可用,且 `8787` 不需要对公网开放。后台容器被 Jenkins 重建时必须同步重建该赛事服务容器。
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | `jitsi-web.yuedongsports.com/api/competition/state` | YD_Event.Admin 的匿名只读接口;只返回已发布数据 |
| GET/PUT | `jitsi-web.yuedongsports.com/api/website-competition` | 后台登录后读取草稿、保存或发布 |
| GET/PUT | `<赛事服务私网 IP>:8787/api/competition/admin` | 仅供 YD_Event.Admin 内部调用;按来源 IP 放行 |
官网生产构建默认从 `https://jitsi-web.yuedongsports.com/api/competition/state` 读取,每 10 秒刷新一次,不再请求 `https://www.yuedongsports.com/api/competition/state`。如需覆盖,在构建官网时设置 `VITE_COMPETITION_API_URL` 为完整接口基地址。开发时官网 Vite 会把 `/api/competition` 代理到 `http://localhost:8787`,可用 `COMPETITION_API_TARGET` 覆盖。
YD_Event.Web 的「官网赛事信息」路由隐藏于菜单,未登录访问会跳转登录页;管理接口要求已有后台登录态。正式环境访问基地址为 `https://jitsi-web.yuedongsports.com/`。分机部署时将 `CompetitionApi__AdminUrl` 设为 `http://<赛事服务私网 IP>:8787/api/competition/admin`。后台管理和公开只读接口均由 YD_Event.Admin 通过私网转发到赛事服务。
升级两端服务后,旧的 `COMPETITION_ADMIN_TOKEN`、`CompetitionApi__AdminToken` 和 `CompetitionApi__AdminTokenFile` 可从部署配置中移除;它们不再被本功能读取。上线前由运维确认后台私网 IP 白名单、防火墙入站规则及公网管理路径屏蔽均已生效,再验证无密钥的后台保存流程。
## 上传格式
管理页提供包含填写说明和示例行的 Excel 模板。一次上传 `.xlsx` 文件会将赛程、全能成绩、挑战成绩、Battle、选手和纪录载入草稿,点击「保存官网赛事信息」才写入接口。页面也可下载当前数据的 Excel 文件。
Excel 工作表依次为「填写说明」「赛程」「全能成绩」「挑战成绩」「Battle场次」「选手」「纪录」。六张数据表的表头必须保留;删除示例行后可填写正式数据。全能成绩的名次使用官方成绩册数据,前台不重算名次。挑战成绩的「榜单」区分决赛和资格赛。
缺失的图片回退到默认头像。照片字段应使用浏览器可访问的 HTTPS 地址或官网同源路径;上传图片文件本身由现有图片存储服务负责。仓库内的 `public/competition-state.json` 是静态只读空态,不会存放管理后台的未发布数据。
运行接口验证:`node server/competition-api.smoke.mjs`。