diff --git a/WEBSITE-COMPETITION-BACKEND.md b/WEBSITE-COMPETITION-BACKEND.md index 5b323cc..3d5de81 100644 --- a/WEBSITE-COMPETITION-BACKEND.md +++ b/WEBSITE-COMPETITION-BACKEND.md @@ -6,13 +6,13 @@ 正式环境访问基地址为 `https://jitsi-web.yuedongsports.com/`,管理接口完整地址为 `https://jitsi-web.yuedongsports.com/api/website-competition`。此地址是后台对外入口;`CompetitionApi:AdminUrl` 是后端转发到官网赛事服务的地址,需单独配置。 -`YD_Event.Admin` 在服务端把请求转发到官网赛事服务的 `/api/competition/admin`。服务间密钥只保存在服务器配置中,不发送给浏览器。官网赛事服务负责校验数据、持久化草稿或发布状态,并通知公开页面刷新。本仓库不新增赛事数据库表。 +`YD_Event.Admin` 在服务端把请求转发到官网赛事服务的内部 `/api/competition/admin`。两台服务器通过私网连接,不使用服务间管理密钥。官网赛事服务按来源 IP 放行内部管理请求,持久化草稿或发布状态,并通知公开页面刷新。本仓库不新增赛事数据库表。 ```text YD_Event.Web 管理页 → 当前域名 /api/website-competition(后台登录态) - → YD_Event.Admin(附加服务间 Bearer 密钥) - → 官网赛事服务 /api/competition/admin + → YD_Event.Admin(通过私网转发) + → 官网赛事服务 /api/competition/admin(来源 IP 白名单) → 持久化赛事 JSON / 官网公开接口 ``` @@ -41,17 +41,14 @@ YD_Event.Web 管理页 | 配置键 | 环境变量写法 | 说明 | | --- | --- | --- | -| `CompetitionApi:AdminUrl` | `CompetitionApi__AdminUrl` | 官网赛事服务管理接口完整 URL;默认 `http://127.0.0.1:8787/api/competition/admin` | -| `CompetitionApi:AdminToken` | `CompetitionApi__AdminToken` | 与官网赛事服务 `COMPETITION_ADMIN_TOKEN` 相同的服务间密钥 | -| `CompetitionApi:AdminTokenFile` | `CompetitionApi__AdminTokenFile` | 可选,密钥文件的绝对路径;未配置密钥值时读取 | -| `COMPETITION_ADMIN_TOKEN` | `COMPETITION_ADMIN_TOKEN` | 密钥值的兼容性回退配置 | +| `CompetitionApi:AdminUrl` | `CompetitionApi__AdminUrl` | 官网赛事服务内部管理接口完整 URL;本地默认 `http://127.0.0.1:8787/api/competition/admin` | -官网赛事服务要求 `COMPETITION_ADMIN_TOKEN` 至少 32 字符。不要将密钥写入前端环境变量、前端代码或公开仓库。若两个服务不在同一网络命名空间,必须配置可由 `YD_Event.Admin` 访问的 `CompetitionApi__AdminUrl`,不能使用默认的 `127.0.0.1`。 +正式环境两台服务器分机部署,必须将 `CompetitionApi__AdminUrl` 配置为 `http://<赛事服务私网 IP>:8787/api/competition/admin`,不能使用默认的 `127.0.0.1`。赛事服务应只在私网地址监听,并通过 `COMPETITION_ADMIN_ALLOWED_IPS` 放行 `YD_Event.Admin` 的私网 IP;防火墙只允许后台服务器连接该端口。公网 Nginx 必须屏蔽 `/api/competition/admin`,避免绕过后台登录认证直接修改赛事数据。 ## 部署步骤 -1. 启动官网赛事服务,设置持久化数据路径及 `COMPETITION_ADMIN_TOKEN`;其健康检查为 `GET /api/competition/ping`。 -2. 在 `YD_Event.Admin` 的运行环境配置管理接口 URL 与相同的服务间密钥,然后启动或重启后端。`Startup` 已注册专用 `WebsiteCompetition` HttpClient。 +1. 启动官网赛事服务,设置持久化数据路径、私网监听地址、后台服务器的来源 IP 白名单;其健康检查为 `GET /api/competition/ping`。 +2. 在 `YD_Event.Admin` 的运行环境配置内网管理接口 URL,然后启动或重启后端。`Startup` 已注册专用 `WebsiteCompetition` HttpClient。 3. 将后台站点同源的 `/api/website-competition` 路由转发到 `YD_Event.Admin`。不要把该路径直接暴露或转发到官网赛事服务管理接口。 4. 管理员登录 `YD_Event.Web` 后打开“官网赛事信息”,导入 `.xlsx`、核对草稿并保存;需要公开展示时再开启“公开发布”。 @@ -59,13 +56,13 @@ YD_Event.Web 管理页 | 状态 | 常见原因 | | --- | --- | -| `401/403` | 后台未登录或无权访问;若由上游返回,也可能是服务间密钥不一致 | +| `401/403` | 后台未登录或无权访问;若由上游返回 `403`,检查后台服务器私网 IP 是否在 `COMPETITION_ADMIN_ALLOWED_IPS` 中 | | `413` | `PUT` 请求体超过 8 MB | | `502` | 官网赛事服务无法连接 | -| `503` | 服务端密钥缺失、密钥文件不可读取,或上游 URL 无效 | +| `503` | 上游 URL 无效 | | `504` | 官网赛事服务超过 30 秒未响应 | -控制器不会在错误响应或日志中输出密钥。保存时上游的字段校验错误会原样返回,管理页会展示错误信息。 +保存时上游的字段校验错误会原样返回,管理页会展示错误信息。 ## 验证 diff --git a/YD_Event.Web.Entry/Controllers/WebsiteCompetitionController.cs b/YD_Event.Web.Entry/Controllers/WebsiteCompetitionController.cs index e0af363..d845c79 100644 --- a/YD_Event.Web.Entry/Controllers/WebsiteCompetitionController.cs +++ b/YD_Event.Web.Entry/Controllers/WebsiteCompetitionController.cs @@ -1,4 +1,3 @@ -using System.Net.Http.Headers; using System.Text; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -6,7 +5,7 @@ using Microsoft.AspNetCore.Mvc; namespace YD_Event.Web.Entry.Controllers; /// -/// 官网赛事信息管理。使用后台登录态访问,服务间密钥只保存在服务端。 +/// 官网赛事信息管理。使用后台登录态访问,将请求转发到内部赛事服务。 /// [ApiController] [Authorize] @@ -31,24 +30,6 @@ public sealed class WebsiteCompetitionController : ControllerBase private async Task ForwardAsync(HttpMethod method) { - var token = _configuration["CompetitionApi:AdminToken"] - ?? _configuration["COMPETITION_ADMIN_TOKEN"]; - var tokenFile = _configuration["CompetitionApi:AdminTokenFile"]; - if (string.IsNullOrWhiteSpace(token) && !string.IsNullOrWhiteSpace(tokenFile)) - { - try - { - token = (await System.IO.File.ReadAllTextAsync(tokenFile, HttpContext.RequestAborted)).Trim(); - } - catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) - { - return StatusCode(503, new { error = "官网赛事接口密钥文件不可读取" }); - } - } - - if (string.IsNullOrWhiteSpace(token)) - return StatusCode(503, new { error = "官网赛事接口未配置服务端密钥" }); - var adminUrl = _configuration["CompetitionApi:AdminUrl"] ?? "http://127.0.0.1:8787/api/competition/admin"; if (!Uri.TryCreate(adminUrl, UriKind.Absolute, out var target) @@ -56,7 +37,6 @@ public sealed class WebsiteCompetitionController : ControllerBase return StatusCode(503, new { error = "官网赛事接口地址无效" }); using var upstreamRequest = new HttpRequestMessage(method, target); - upstreamRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token); if (method == HttpMethod.Put) { using var reader = new StreamReader(Request.Body, Encoding.UTF8, leaveOpen: true);