feat(server): web 用户中心后端(2/3) — 订阅 URL 端点

- GET /v1/me/subscription:返回 {url}=<SUB_BASE 或请求 host>/sub/<token>,
  首次访问按需生成 users.sub_token。
- POST /v1/me/subscription/reset:轮换 sub_token,返回新 url。
- GET /sub/{token}(公开,无 JWT):按 token 查用户 dp_uuid,取首个活跃节点,
  复用 httpapi.BuildClientConfig 渲染 sing-box 客户端配置返回(仅在配置了节点存储时挂载)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wangjia
2026-06-17 08:38:03 +08:00
parent 009dbb8d07
commit 8047d17b48
2 changed files with 204 additions and 1 deletions
+14 -1
View File
@@ -258,8 +258,19 @@ func mountV1(r chi.Router, sqlDB *sql.DB, rdb *redis.Client, nodeSvc *nodes.Serv
// ── Nodes + Connect ───────────────────────────────────────────────────────
var nodeAPI *httpapi.NodeAPI
var nodeStore nodes.NodeStore
if nodeSvc != nil {
nodeAPI = httpapi.NewNodeAPI(nodeSvc.Store(), nodeSvc.Hub(), os.Getenv("NODE_DERIVE_KEY"))
nodeStore = nodeSvc.Store()
nodeAPI = httpapi.NewNodeAPI(nodeStore, nodeSvc.Hub(), os.Getenv("NODE_DERIVE_KEY"))
}
// ── Subscription URL (web user-center 订阅导入) ───────────────────────────
subAPI := httpapi.NewSubscriptionAPI(sqlDB, nodeStore, os.Getenv("NODE_DERIVE_KEY"), os.Getenv("SUB_BASE"))
// Public subscription endpoint (no JWT): clients fetch their sing-box config
// from the per-user URL. Only available when a node store is configured.
if nodeStore != nil {
r.Get("/sub/{token}", subAPI.ServeSub)
}
// ─── Mount under /v1 ─────────────────────────────────────────────────────
@@ -286,6 +297,8 @@ func mountV1(r chi.Router, sqlDB *sql.DB, rdb *redis.Client, nodeSvc *nodes.Serv
devicesHandler.RegisterRoutes(me)
// Web 用户中心调用 /v1/me/redeemapp 仍用 /v1/redeem。两者同处理器。
me.Post("/redeem", redeemHandler.ServeHTTP)
me.Get("/subscription", subAPI.GetSubscription)
me.Post("/subscription/reset", subAPI.ResetSubscription)
})
protected.Post("/redeem", redeemHandler.ServeHTTP)
protected.Get("/usage", usageHandler.ServeHTTP)