fix: 应用 xhigh 代码评审的跨端修复
ci / server (push) Failing after 13s
ci / design-tokens (push) Failing after 11s

来自 xhigh code review 的正确性/健壮性修复,覆盖全部五端:
- server:鉴权 fail-closed、计量交叉校验与配额扣穿处理、WS 网关并发与关闭顺序、
  billing 行锁、redis Lua 过期与设备槽刷新、config 解析
- desktop:会话 epoch 防串话、WS 重连与 401 处理、api 客户端复用、统一 usePoll 轮询
- android:握手时序、请求头封装、账户状态派生、按需重组
- ios:finalize 宽限、串行采集、错误文案服务端优先、删除死代码 CommitController

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-13 11:50:08 +08:00
parent 50b49f3cbe
commit b5ab92a57e
42 changed files with 2125 additions and 469 deletions
+44 -21
View File
@@ -4,6 +4,7 @@ package httpapi
import (
"context"
"log/slog"
"net/http"
"github.com/gin-gonic/gin"
@@ -29,47 +30,65 @@ type Deps struct {
Quota *quota.Manager
}
// pickProvider 按配置选择 ASR Providergummy 在 6B 实现 + DASHSCOPE_API_KEY 就绪后接入)。
func pickProvider(cfg config.Config) asr.Provider {
// pickProvider 按配置选择 ASR Provider17E)。ASR_PROVIDER=gummy 且 DASHSCOPE_API_KEY
// 就绪时装配真实 gummy;缺 key 则告警并降级 mock。返回值 kind 为装配结果("gummy"/"mock")。
func pickProvider(cfg config.Config) (asr.Provider, string) {
switch cfg.ASRProvider {
case "gummy":
if cfg.DashScopeAPIKey != "" {
return asr.NewGummy(cfg.DashScopeAPIKey), "gummy"
}
slog.Warn("ASR_PROVIDER=gummy but DASHSCOPE_API_KEY empty, falling back to mock")
return asr.NewMock(), "mock"
default:
return asr.NewMock()
return asr.NewMock(), "mock"
}
}
// pickWechat 微信凭证未配置(#2A 申请中)时使用 mock。
func pickWechat(cfg config.Config) auth.WechatClient {
// pickWechat 微信凭证未配置(#2A 申请中)时使用 mock。返回 kind="mock"(真实实现就绪前恒为 mock)。
func pickWechat(cfg config.Config) (auth.WechatClient, string) {
if cfg.WechatWebAppID == "" {
return auth.MockWechat{}
return auth.MockWechat{}, "mock"
}
return auth.MockWechat{} // TODO(5C-真实): 开放平台实现,凭证就绪后替换
return auth.MockWechat{}, "mock" // TODO(5C-真实): 开放平台实现,凭证就绪后替换
}
// pickPay 商户号未配置(#2B 申请中)时使用 mock。
func pickPay(cfg config.Config) billing.PayClient {
// pickPay 商户号未配置(#2B 申请中)时使用 mock。返回 kind="mock"。
// ⚠️⚠️ 上线前必须替换为真实 PayClientMockPay 不验签,/v1/pay/notify 等于公开充值接口。
func pickPay(cfg config.Config) (billing.PayClient, string) {
if cfg.WxPayMchID == "" {
return billing.MockPay{}
return billing.MockPay{}, "mock"
}
return billing.MockPay{} // TODO(7D-真实): wechatpay-go 实现,商户号就绪后替换
// TODO(7D-真实)【上线阻断项】: wechatpay-go 实现 + APIv3 验签,商户号就绪后替换,否则充值接口可被伪造。
return billing.MockPay{}, "mock"
}
// pickStorage OSS 未配置时图片落本地盘。
func pickStorage(cfg config.Config) feedback.Storage {
// pickStorage OSS 未配置时图片落本地盘。返回 kind="local"OSS 就绪后改为 "oss")。
func pickStorage(cfg config.Config) (feedback.Storage, string) {
if cfg.OSSBucket == "" {
return feedback.LocalStorage{Dir: "var/uploads"}
return feedback.LocalStorage{Dir: "var/uploads"}, "local"
}
return feedback.LocalStorage{Dir: "var/uploads"} // TODO(8C-真实): OSS 实现
return feedback.LocalStorage{Dir: "var/uploads"}, "local" // TODO(8C-真实): OSS 实现
}
func Register(r *gin.Engine, d *Deps) {
r.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"ok": true}) })
authH := &auth.Handlers{DB: d.DB, RDB: d.RDB, JWT: d.JWT, Wechat: pickWechat(d.Cfg)}
billH := &billing.Handlers{DB: d.DB, Pay: pickPay(d.Cfg), Quota: d.Quota}
userH := &user.Handlers{DB: d.DB, Quota: d.Quota}
fbH := &feedback.Handlers{DB: d.DB, RDB: d.RDB, Storage: pickStorage(d.Cfg)}
provider, providerKind := pickProvider(d.Cfg)
wechat, wechatKind := pickWechat(d.Cfg)
pay, payKind := pickPay(d.Cfg)
storage, storageKind := pickStorage(d.Cfg)
// 启动时打印各外部依赖的装配结果(real/mock),便于部署核对(17E)。
slog.Info("dependency assembly",
"asr_provider", providerKind, "wechat", wechatKind, "pay", payKind, "storage", storageKind)
authH := &auth.Handlers{DB: d.DB, RDB: d.RDB, JWT: d.JWT, Wechat: wechat}
billH := &billing.Handlers{DB: d.DB, Pay: pay, Quota: d.Quota}
userH := &user.Handlers{DB: d.DB, Quota: d.Quota, AppVersions: d.Cfg.AppLatest}
fbH := &feedback.Handlers{DB: d.DB, RDB: d.RDB, Storage: storage}
telH := telemetry.New(d.DB, d.RDB)
gwH := &gateway.Handler{Provider: pickProvider(d.Cfg), Quota: d.Quota, RDB: d.RDB}
gwH := &gateway.Handler{Provider: provider, Quota: d.Quota, RDB: d.RDB}
// 后台任务
bg := context.Background()
@@ -85,8 +104,12 @@ func Register(r *gin.Engine, d *Deps) {
v1.GET("/auth/wechat/callback", authH.QrCallback)
v1.POST("/auth/wechat", authH.MobileLogin)
v1.GET("/packs", billH.Packs)
// ⚠️ 部署前必须切换到真实 PayClient(验签)!当前 MockPay 不验签,
// /v1/pay/notify 等于一个无鉴权的公开充值接口(任意人可伪造支付成功回调充值)。
// 上线前务必让 pickPay 返回带 APIv3 验签的真实实现(见 pickPay 的 TODO(7D-真实))。
v1.POST("/pay/notify", billH.Notify)
v1.POST("/metrics/batch", telH.Batch)
// metrics/batch 可匿名上报;OptionalMiddleware 在带合法 JWT 时注入 user_id 以关联用户(17F)。
v1.POST("/metrics/batch", d.JWT.OptionalMiddleware(), telH.Batch)
v1.GET("/app/latest", userH.AppLatest)
// 需登录