优化小程序

This commit is contained in:
hero920103 committed 2026-10-31 01:21:55 +08:00
1 parent 2e2b837a20
commit 727319a6eb
24 files changed
+1509 -200

No files matched your search

+106 -21
View File
@@ -7,7 +7,9 @@ import (
"strings"
"time"
"server/models"
"server/services"
"server/services/wechatmini"
"server/services/wechatmp"
beego "github.com/beego/beego/v2/server/web"
@@ -101,16 +103,24 @@ func (c *AuthAccountController) SendRegisterCode() {
func (c *AuthAccountController) WechatStart() {
scene, qrURL, notice, expireAt, err := wechatmp.StartRegisterVerify(wechatmp.DefaultVerifyTTL)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
// 公众号未配置 / 未启用:仍回传「关注二维码」,用户可扫码进公众号发送「注册」取码
c.serveJSON(map[string]interface{}{
"code": 400,
"msg": "微信扫码核验暂不可用,请改用手机号验证码注册",
"data": map[string]interface{}{"follow_qrcode": wechatmp.FollowQrcodeURL()},
})
return
}
c.serveJSON(map[string]interface{}{
"code": 200,
"data": map[string]interface{}{
"scene": scene,
"qr_url": qrURL,
"notice": notice,
"expire_at": expireAt.Format(time.RFC3339),
"scene": scene,
"qr_url": qrURL,
// 带参二维码生成失败(IP 白名单 / 未认证 48001 等)时前端改展示关注二维码,
// 用户扫码关注后在公众号内发送「注册」照样能拿到验证码
"follow_qrcode": wechatmp.FollowQrcodeURL(),
"notice": notice,
"expire_at": expireAt.Format(time.RFC3339),
},
})
}
@@ -133,11 +143,54 @@ func (c *AuthAccountController) WechatStatus() {
})
}
// WechatMiniStart 账号注册:获取小程序码(扫码后在小程序内确认注册)。
// 小程序未配置 / 小程序码生成失败时返回 400 + reason=mini_unavailable,
// 前端据此回退公众号验证码或短信通道(详细错误只落服务端日志)。
// GET /auth/register/mini/start
func (c *AuthAccountController) WechatMiniStart() {
scene, qrURL, expireAt, err := wechatmini.StartRegisterVerify(wechatmini.DefaultRegisterTTL)
if err != nil {
c.serveJSON(map[string]interface{}{
"code": 400,
"msg": "微信扫码核验暂不可用,请改用手机号验证码注册",
"reason": "mini_unavailable",
})
return
}
c.serveJSON(map[string]interface{}{
"code": 200,
"data": map[string]interface{}{
"scene": scene,
"qr_url": qrURL,
"expire_at": expireAt.Format(time.RFC3339),
},
})
}
// WechatMiniStatus 账号注册:轮询小程序端确认状态(展示「已确认」)
// GET /auth/register/mini/status?scene=xxx
func (c *AuthAccountController) WechatMiniStatus() {
scene := strings.TrimSpace(c.GetString("scene"))
confirmed, expireAt, err := wechatmini.GetRegisterStatus(scene)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
c.serveJSON(map[string]interface{}{
"code": 200,
"data": map[string]interface{}{
"confirmed": confirmed,
"expire_at": expireAt.Format(time.RFC3339),
},
})
}
// RegisterVerify 第一步核验:验证码通过即创建统一身份,返回 register_token
// POST /auth/register/verify
//
// mode=phone { mode, phone, code, password, confirm }
// mode=account { mode, account, scene, code, password, confirm }
// mode=mini { mode, account, scene, password, confirm } // scene 为小程序码会话,已在小程序内确认
func (c *AuthAccountController) RegisterVerify() {
var req struct {
Mode string `json:"mode"`
@@ -153,22 +206,35 @@ func (c *AuthAccountController) RegisterVerify() {
}
openID := ""
if req.Mode == "account" {
unionID := ""
provider := ""
switch req.Mode {
case "account":
// 核销服务号验证码,得到扫码人 openid(注册完成后绑定微信登录):
// 有 scene 走二维码会话核销;无 scene(公众号发送「注册」)按验证码认领
// 先按 scene 核销二维码会话,未扫码(用户改走公众号内发送「注册」)时按验证码认领。
// 账号注册不依赖手机号,服务号无法按手机号关联,故不以「是否已扫码」为前置条件。
var err error
if strings.TrimSpace(req.Scene) != "" {
openID, err = wechatmp.ConfirmRegisterVerify(req.Scene, req.Code)
} else {
openID, err = wechatmp.ConfirmRegisterVerifyByCode(req.Code)
}
openID, err = wechatmp.ConfirmAccountRegisterVerify(req.Scene, req.Code)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
provider = models.IdPWechat
case "mini":
// 核销小程序注册会话:扫码人已在小程序内用 wx.login 确认,取 openid/unionid
oid, uid, _, cerr := wechatmini.ConsumeRegister(req.Scene)
if cerr != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": cerr.Error()})
return
}
openID = oid
unionID = uid
provider = models.IdPWechatMini
}
identity, token, err := services.VerifyAndCreateIdentity(services.RegisterVerifyInput{
// 身份推迟到第二步归属操作时创建(services.CompleteRegister),
// 第一步只签发待注册令牌,避免超时未完成留下占用账号的半成品身份
token, err := services.VerifyAndIssueRegisterToken(services.RegisterVerifyInput{
Mode: req.Mode,
Phone: req.Phone,
Account: req.Account,
@@ -176,6 +242,8 @@ func (c *AuthAccountController) RegisterVerify() {
Password: req.Password,
Confirm: req.Confirm,
OpenID: openID,
UnionID: unionID,
Provider: provider,
})
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
@@ -184,7 +252,7 @@ func (c *AuthAccountController) RegisterVerify() {
c.serveJSON(map[string]interface{}{
"code": 200,
"msg": "验证通过",
"data": map[string]interface{}{"register_token": token, "identity_id": identity.ID},
"data": map[string]interface{}{"register_token": token},
})
}
@@ -218,7 +286,14 @@ func (c *AuthAccountController) ApplyJoin() {
if !c.readJSON(&req) {
return
}
identityID, openID, err := services.PeekRegisterToken(req.Token)
// 第二步归属操作开始:此刻才真正创建身份(核验通过 ≠ 已建号)
if strings.TrimSpace(req.Token) == "" {
c.serveJSON(map[string]interface{}{
"code": 400, "msg": "注册会话已失效:未携带注册令牌,请返回第一步重新完成身份核验",
})
return
}
identityID, err := services.CompleteRegister(req.Token)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
@@ -229,7 +304,6 @@ func (c *AuthAccountController) ApplyJoin() {
return
}
services.ConsumeRegisterToken(req.Token)
services.BindRegisterWechat(identityID, openID)
c.serveJSON(map[string]interface{}{
"code": 200,
@@ -258,7 +332,14 @@ func (c *AuthAccountController) ApplyCreate() {
if !c.readJSON(&req) {
return
}
identityID, openID, err := services.PeekRegisterToken(req.Token)
// 第二步归属操作开始:此刻才真正创建身份(核验通过 ≠ 已建号)
if strings.TrimSpace(req.Token) == "" {
c.serveJSON(map[string]interface{}{
"code": 400, "msg": "注册会话已失效:未携带注册令牌,请返回第一步重新完成身份核验",
})
return
}
identityID, err := services.CompleteRegister(req.Token)
if err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
@@ -277,7 +358,6 @@ func (c *AuthAccountController) ApplyCreate() {
return
}
services.ConsumeRegisterToken(req.Token)
services.BindRegisterWechat(identityID, openID)
c.serveJSON(map[string]interface{}{
"code": 200,
@@ -298,13 +378,18 @@ func (c *AuthAccountController) FinishSkip() {
if !c.readJSON(&req) {
return
}
identityID, openID, err := services.PeekRegisterToken(req.Token)
if err != nil {
// 第二步归属操作开始:此刻才真正创建身份(核验通过 ≠ 已建号)
if strings.TrimSpace(req.Token) == "" {
c.serveJSON(map[string]interface{}{
"code": 400, "msg": "注册会话已失效:未携带注册令牌,请返回第一步重新完成身份核验",
})
return
}
if _, err := services.CompleteRegister(req.Token); err != nil {
c.serveJSON(map[string]interface{}{"code": 400, "msg": err.Error()})
return
}
services.ConsumeRegisterToken(req.Token)
services.BindRegisterWechat(identityID, openID)
c.serveJSON(map[string]interface{}{
"code": 200,
+39
View File
@@ -187,6 +187,45 @@ func (c *AuthWechatMiniController) Login() {
c.serveJSON(resp)
}
// RegisterConfirm 小程序端确认注册(网页注册页展示的小程序码扫码进入)
// POST /auth/register/mini/confirm
//
// { "scene": "小程序码携带的会话标识", "code": "wx.login的code", "phone_code": "可选", "appid": "可选,多小程序" }
//
// 无登录态:scene + code 即凭证,服务端用 code 换 openid 后标记该会话已确认,
// 网页端轮询到确认后继续填写账号密码;手机号快速验证为可选项(失败不阻断)。
func (c *AuthWechatMiniController) RegisterConfirm() {
var req struct {
Scene string `json:"scene"`
Code string `json:"code"`
PhoneCode string `json:"phone_code"`
AppID string `json:"appid"`
}
body := c.Ctx.Input.RequestBody
if len(body) == 0 {
body, _ = io.ReadAll(c.Ctx.Request.Body)
}
if err := json.Unmarshal(body, &req); err != nil {
c.fail("", 400, "参数错误")
return
}
req.Scene = strings.TrimSpace(req.Scene)
if req.Scene == "" {
c.fail("", 400, "缺少会话标识,请重新扫码")
return
}
if err := wechatmini.ConfirmRegister(req.Scene, req.Code, req.PhoneCode, req.AppID); err != nil {
msg := err.Error()
if hint, ok := wechatmini.ExplainError(err); ok {
msg = hint
}
logs.Warn("[wechat-mini] 注册确认失败 scene=%s: %v", req.Scene, err)
c.fail(MiniReasonWechatAPI, 400, msg)
return
}
c.serveJSON(map[string]interface{}{"code": 200, "msg": "确认成功,请回到电脑继续完成注册"})
}
// Bind 把当前小程序 openid 绑定到已登录身份
// POST /auth/wechat/miniapp/bind { "code": "wx.login的code" } Header: Bearer 令牌
//
+11 -1
View File
@@ -163,20 +163,30 @@ func (c *PlatformWechatMpController) TestConnection() {
c.jsonErr(400, 400, "请先保存 AppID 与 AppSecret")
return
}
// 出口 IP 一并返回:40164(IP 不在白名单)时管理员可直接复制去微信后台配置
outboundIP := wechatmp.OutboundIP()
token, err := wechatmp.GetAccessToken(cfg)
if err != nil {
msg := "连接失败:" + err.Error()
if hint, ok := wechatmp.ExplainError(err); ok {
msg += ";" + hint
}
if outboundIP != "" {
msg += ";本服务器出口 IP:" + outboundIP
}
c.jsonErr(400, 400, msg)
return
}
msg := "连接成功:AppID 与 AppSecret 校验通过"
if outboundIP != "" {
msg += "(本服务器出口 IP:" + outboundIP + ")"
}
c.ok(map[string]interface{}{
"access_token_suffix": wechatmp.MaskSecret(token),
"callback_url": wechatmp.CallbackURL(),
"verified": boolToInt8(cfg.Verified),
}, "连接成功:AppID 与 AppSecret 校验通过")
"outbound_ip": outboundIP,
}, msg)
}
// ============================ 绑定(当前平台用户) ============================