feat(auth): 微信扫码登录后端 —— 网页授权 + ticket 轮询
服务号「植趣 ZeeQ」已认证,走网页授权(snsapi_base,只拿 openid、用户无感), 不接管消息推送,副作用最小。 流程:PC 建 ticket → 二维码指向 /wx/mp?t= → 用户微信扫码 → 302 到微信授权页 → 回调 /api/v1/wx/mp/callback 用 code 换 openid → 找/建用户 → ticket 置 authorized → PC 轮询 /wx/mp/poll 拿到 authorized → 签发 JWT。ticket 一次性消费防重放。 - 配置(appid/secret/base_url)后台可改,secret AES 加密入库,与微信支付同一套 secrets; - ticket 存 Redis(短 TTL),无 Redis 时回退进程内内存(本地单实例可用,生产必须有 Redis); - User 加 wechat_openid。**部分唯一索引**(WHERE openid <> '')而非普通唯一: 存量邮箱用户该列是空串,普通唯一索引会让多个空串互撞、AutoMigrate 直接失败 —— 与之前 NULL 余额同类的坑,这次提前避开。 单测覆盖:授权 URL 拼接(含 #wechat_redirect 锚点必须在末尾)、secret 加密往返、 建号/查号、空 openid 不误命中存量用户。微信 API 调用依赖公网回调,本地测不了, 留待部署后真机扫码。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -47,11 +47,18 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob.
|
||||
r.GET("/metrics", gin.WrapH(promhttp.Handler()))
|
||||
r.GET("/healthz", h.Healthz)
|
||||
r.GET("/readyz", h.Readyz)
|
||||
// 微信扫码入口:用户在微信里打开二维码指向的这个地址,服务端 302 到微信授权页。
|
||||
// 顶级路径(非 /api)——二维码 URL 越短越好,且要落在「网页授权域名」根下。
|
||||
r.GET("/wx/mp", h.WxMPEntry)
|
||||
|
||||
api := r.Group("/api/v1")
|
||||
{
|
||||
// —— 公开:鉴权端点 / 健康 / 按 task_id 寻址的 SSE 与导出(EventSource/下载无法带 Bearer)——
|
||||
api.GET("/pricing", h.PublicPricing) // 公开定价(官网未登录也要能看价,故不挂鉴权)
|
||||
api.GET("/pricing", h.PublicPricing) // 公开定价(官网未登录也要能看价,故不挂鉴权)
|
||||
// 微信扫码登录(全公开:ticket 是唯一凭证;微信浏览器/PC 轮询都无鉴权头)
|
||||
api.POST("/wx/mp/ticket", h.WxMPTicket) // PC 建票,返回二维码 URL
|
||||
api.GET("/wx/mp/callback", h.WxMPCallback) // 微信授权回调
|
||||
api.GET("/wx/mp/poll", h.WxMPPoll) // PC 轮询登录态
|
||||
api.POST("/auth/register", h.Register) // 注册 + 签发 JWT
|
||||
api.POST("/auth/login", h.Login) // 登录 + 签发 JWT
|
||||
api.GET("/auth/me", h.Me) // 当前登录用户(无效令牌 → 401)
|
||||
@@ -148,6 +155,8 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob.
|
||||
admin.GET("/redeem-codes", h.AdminRedeemCodes)
|
||||
admin.GET("/packs", h.AdminPacks)
|
||||
admin.PUT("/packs", h.AdminSavePack)
|
||||
admin.GET("/wechat-mp", h.AdminGetWechatMP) // 微信扫码登录配置
|
||||
admin.PUT("/wechat-mp", h.AdminSaveWechatMP)
|
||||
admin.GET("/payment/wechat", h.AdminGetWechatPay)
|
||||
admin.PUT("/payment/wechat", h.AdminSaveWechatPay)
|
||||
admin.GET("/sub-plans", h.AdminSubPlans) // 订阅套餐(含下架)
|
||||
|
||||
Reference in New Issue
Block a user