From 3fc10fb86ff7e96665e28df02b2e9b377a7a828d Mon Sep 17 00:00:00 2001 From: AskaEth Date: Tue, 8 Sep 2026 21:24:36 +0800 Subject: [PATCH] =?UTF-8?q?docs(cors):=20X-API-Version=20=E6=98=8E?= =?UTF-8?q?=E7=A1=AE=E4=B8=BA=20advisory=20=E6=83=AF=E4=BE=8B=E5=A4=B4(E1)?= =?UTF-8?q?=20=E2=80=94=20=E6=B6=88=E9=99=A4=E7=89=88=E6=9C=AC=E5=8D=8F?= =?UTF-8?q?=E5=95=86=E5=81=87=E8=B1=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - middleware: CORS Allow-Headers 加 X-API-Version(跨域客户端不再预检失败) - api-reference §0.2 + api-client §4.1: 声明服务端不校验、不据此协商版本 - 后端/前端零行为改动(前端仍在发,现CORS放行) --- Docs/api-client.md | 2 +- Docs/api-reference.md | 4 ++-- internal/services/gateway/middleware.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Docs/api-client.md b/Docs/api-client.md index 3bab24c..13bd6a1 100644 --- a/Docs/api-client.md +++ b/Docs/api-client.md @@ -183,7 +183,7 @@ ### 4.1 一次性接入流程(三步走) 1. **拿令牌**:`POST /api/v1/auth/login` `{username,password}` → `{data:{token,expiresAt,role}}`(JWT,默认 24h,`role` ∈ `owner|admin|viewer`)。 -2. **声明版本**:后续每个请求头带上 `X-API-Version: 1` + `Authorization: Bearer `;WS 握手用 `ws://:/ws/chat?token=`(或 `Authorization: Bearer`)。**viewer 只能读会话/人格元数据;读写会话消息、管理写操作需 `owner`/`admin`。** +2. **声明版本(可选/advisory)**:可带 `X-API-Version: 1` + `Authorization: Bearer `;WS 握手用 `ws://:/ws/chat?token=`(或 `Authorization: Bearer`)。**`X-API-Version` 是**advisory**惯例头——服务端当前不校验、不据此协商版本,带不带行为不变**;`viewer` 只能读会话/人格元数据;读写会话消息、管理写操作需 `owner`/`admin`。 3. **带端标识**(可选但推荐):每条消息/请求可带 `deviceId`(见 §4.3),让她知道你从哪个端来。 ### 4.2 会话与消息用法(聊天) diff --git a/Docs/api-reference.md b/Docs/api-reference.md index b94a29e..1ed2d0c 100644 --- a/Docs/api-reference.md +++ b/Docs/api-reference.md @@ -15,7 +15,7 @@ - 成功:`{"data": }`(HTTP 200 / 201 / 202 / 204)。 - 失败:`{"error": {"code": , "message": }}`(HTTP 400/401/403/404/409/500/501 等)。 - **错误码**:业务错误带稳定字符串 `code`(如 `unauthorized` / `not_found` / `bad_request` / `chat_busy` / `not_implemented`),便于客户端分支;`message` 面向人读。 -- **CORS**:`Access-Control-Allow-Origin` 为配置的 `ALLOWED_ORIGINS` 白名单(`*` 时放开);`Allow-Methods: GET, POST, PUT, DELETE, OPTIONS`;`Allow-Headers: Authorization, Content-Type`;`Access-Control-Expose-Headers: X-Voice-Provider`(让跨域浏览器能读到该语音降级信号头,非跨域场景不受影响);`Max-Age: 600`。`OPTIONS` 预检直接回 `204`。同源/无 Origin 请求放行。**注意:CORS 只允许 `Authorization, Content-Type` 头——若客户端要额外发送 `X-API-Version`,需自行在网关侧放开或仅作为文档惯例(当前服务端**不校验**该头)。** +- **CORS**:`Access-Control-Allow-Origin` 为配置的 `ALLOWED_ORIGINS` 白名单(`*` 时放开);`Allow-Methods: GET, POST, PUT, DELETE, OPTIONS`;`Allow-Headers: Authorization, Content-Type, X-API-Version`;`Access-Control-Expose-Headers: X-Voice-Provider`(跨域浏览器可读语音降级信号头);`Max-Age: 600`。`OPTIONS` 预检直接回 `204`。同源/无 Origin 请求放行。**注意:`X-API-Version` 仅作**advisory**惯例头(见 §0.2)——CORS 已放行但它**不参与服务端版本协商**,服务端当前不校验、不据此改变行为。** - **请求日志**:每个请求由 `logging` 中间件记录一行(method/path/status/duration_ms),并计入可观测性指标。panic 由 `recover` 中间件兜底为 `500 {error:{code:internal,...}}`,不崩溃进程。 - **超时**:WS 连接 ping 周期 54s、pong 等待 60s、写超时 10s;读上限 4096 字节/帧。 @@ -34,7 +34,7 @@ ### 0.2 X-API-Version 头 -`Docs/api-client.md` 约定客户端每个请求带 `X-API-Version: 1`。**当前服务端不校验该头**(未在代码中读取),仅作为客户端契约惯例;如需服务端强校验,需另行实现。本文档在 curl 示例中**带上该头**以示惯例。 +`Docs/api-client.md` 建议客户端可带 `X-API-Version: 1`。**该头是 advisory 惯例头——服务端不校验、不据此协商版本,带不带行为完全不变**(`CORS Allow-Headers` 已放行它,跨域客户端发送不会预检失败)。若未来要真正版本化(`/api/v1` → 语义化协商),需另行实现服务端校验;在那之前客户端不应依赖它做任何行为分支。 ### 0.3 上传 / 媒体(重要确认) diff --git a/internal/services/gateway/middleware.go b/internal/services/gateway/middleware.go index fcc0fd7..c1577de 100644 --- a/internal/services/gateway/middleware.go +++ b/internal/services/gateway/middleware.go @@ -24,7 +24,7 @@ func (s *Server) cors(next http.Handler) http.Handler { } w.Header().Set("Vary", "Origin") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type") + w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, X-API-Version") w.Header().Set("Access-Control-Expose-Headers", voiceProviderHeader) w.Header().Set("Access-Control-Max-Age", "600") }