feat(auth): 微信扫码登录改为「带参二维码 + 关注/扫码事件」(登录即涨粉)
上一版做成了网页授权(OAuth 允许页),方向错了。改成用户要的流程: 扫码 → 弹公众号关注页 → 关注即登录,服务号顺带涨粉。 流程:PC 建票 → 后端用 access_token 调「带参数二维码」接口(scene=ticket) → 展示微信二维码图 → 用户扫码关注 → 微信推 subscribe/SCAN 事件到 /wx/mp/callback → 按 openid 找/建用户 → ticket 置 authorized → PC 轮询拿 JWT。 明文模式(消息加解密):回调只验签名 sha1(sort(token,ts,nonce)),不做 AES。 关键实现点: - access_token 缓存进 Redis(跨实例共享,避免重复拉取互相失效)+ 进程内锁双检; - 事件同时处理 subscribe(未关注,EventKey 带 qrscene_ 前缀)与 SCAN(已关注,不带); - 事件回调必须验签——否则任何人 POST 一个 openid 就能登录别人; - 回调无论如何回 "success",否则微信重试并给用户弹"公众号故障"; - User.wechat_openid 用部分唯一索引(WHERE <> ''),避开存量空串互撞。 配置(appid/secret/token)后台可改、secret AES 加密入库。管理端「运维 → 登录设置」 列出还需在公众平台做的事(服务器 URL / Token 一致 / 明文模式 / IP 白名单)。 本地验证(真流程,非 mock):验签回 echostr 与微信算法一致;模拟 subscribe 事件 → 建号 + 置票 → PC 轮询拿到 token+user → 库里确有该 openid 用户。真微信推真事件 留待部署后扫码。前端 web 登录页加「微信扫码/邮箱」双 tab,默认微信。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -47,17 +47,17 @@ 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)
|
||||
// 微信公众号消息推送回调(服务器配置 URL):GET 验签回 echostr,POST 收关注/扫码事件。
|
||||
// 顶级路径(非 /api),微信服务器直接访问,无鉴权头。
|
||||
r.GET("/wx/mp/callback", h.WxMPVerify)
|
||||
r.POST("/wx/mp/callback", h.WxMPEvent)
|
||||
|
||||
api := r.Group("/api/v1")
|
||||
{
|
||||
// —— 公开:鉴权端点 / 健康 / 按 task_id 寻址的 SSE 与导出(EventSource/下载无法带 Bearer)——
|
||||
api.GET("/pricing", h.PublicPricing) // 公开定价(官网未登录也要能看价,故不挂鉴权)
|
||||
// 微信扫码登录(全公开:ticket 是唯一凭证;微信浏览器/PC 轮询都无鉴权头)
|
||||
api.POST("/wx/mp/ticket", h.WxMPTicket) // PC 建票,返回二维码 URL
|
||||
api.GET("/wx/mp/callback", h.WxMPCallback) // 微信授权回调
|
||||
// 微信扫码登录(全公开:ticket 是唯一凭证;PC 建票与轮询都无鉴权头)
|
||||
api.POST("/wx/mp/ticket", h.WxMPTicket) // PC 建票 + 微信二维码
|
||||
api.GET("/wx/mp/poll", h.WxMPPoll) // PC 轮询登录态
|
||||
api.POST("/auth/register", h.Register) // 注册 + 签发 JWT
|
||||
api.POST("/auth/login", h.Login) // 登录 + 签发 JWT
|
||||
|
||||
Reference in New Issue
Block a user