批量优化

This commit is contained in:
hero920103 committed 2026-10-08 15:30:37 +08:00
1 parent 438145cae7
commit c925b198d1
50 files changed
+5716 -291

No files matched your search

+190
View File
@@ -0,0 +1,190 @@
package wechatmp
import (
"errors"
"strings"
"time"
"server/models"
)
// =============================================================
// 注册「账号注册」验证码:网页发起 → 展示带参二维码(scene)→
// 用户微信扫码关注 → 服务号被动回复 6 位验证码 → 网页填码核销。
// 复用 yz_platform_wechat_mp_verify_code 会话表(bind_type="register"),
// 扫码事件回复走通用 handleScanScene,无需新增事件处理。
// =============================================================
// WechatBindTypeRegister 注册验证码会话类型(yz_platform_wechat_mp_verify_code.bind_type)
const WechatBindTypeRegister = "register"
// StartRegisterVerify 创建注册验证码会话并生成带参二维码。
// 带参二维码生成失败(未认证公众号 48001 等)时回退:qrURL 为空、
// notice 返回引导(用户在公众号内发送「注册」获取验证码),scene 仍有效可轮询。
func StartRegisterVerify(ttl time.Duration) (scene, qrURL, notice string, expireAt time.Time, err error) {
if ttl <= 0 {
ttl = DefaultVerifyTTL
}
cfg, err := LoadEnabledConfig()
if err != nil {
return "", "", "", time.Time{}, err
}
scene = randomScene()
code := randomCode6()
now := time.Now()
expireAt = now.Add(ttl)
row := &models.WechatMpVerifyCode{
Scene: scene,
Code: code,
Status: models.WechatVerifyStatusWaiting,
BindType: WechatBindTypeRegister,
ExpireAt: &expireAt,
CreateTime: now,
UpdateTime: &now,
}
if _, err = models.Orm.Insert(row); err != nil {
return "", "", "", time.Time{}, err
}
qrURL, err = CreateQRCode(cfg, scene, int(ttl.Seconds())+120)
if err != nil {
// 不吞真实错误:与扫码登录 StartScanLogin 一致,把微信返回的
// errcode 与排查建议(IP 白名单 / 48001 未认证等)透出,便于定位;
// 同时保留「公众号内发送注册」兜底路径,会话仍有效可轮询核销。
notice := "请在公众号内直接发送「注册」获取验证码"
var apiErr *APIError
if errors.As(err, &apiErr) {
notice = "生成带参二维码失败(" + apiErr.Error() + ")。" + notice
if hint := apiErr.Explain(); hint != "" {
notice += ":" + hint
}
} else {
notice = "生成带参二维码失败:" + err.Error() + "。" + notice
}
return scene, "", notice, expireAt, nil
}
return scene, qrURL, "", expireAt, nil
}
// GetRegisterVerifyStatus 查询注册会话扫码状态(网页轮询展示「已扫码」)
func GetRegisterVerifyStatus(scene string) (scanned bool, expireAt time.Time, err error) {
row, err := GetVerifyCode(scene)
if err != nil {
return false, time.Time{}, err
}
if row.ExpireAt != nil {
expireAt = *row.ExpireAt
}
if row.BindType != WechatBindTypeRegister {
return false, expireAt, ErrSceneInvalid
}
return row.Status == models.WechatVerifyStatusScanned, expireAt, nil
}
// ConfirmRegisterVerify 核销注册验证码:校验会话归属与状态,返回扫码人 openid。
// 要求会话已被扫码(openid 已写入)、验证码匹配,核销后一次性失效。
func ConfirmRegisterVerify(scene, code string) (openid string, err error) {
row, err := GetVerifyCode(scene)
if err != nil {
return "", err
}
if row.BindType != WechatBindTypeRegister {
return "", ErrSceneInvalid
}
switch row.Status {
case models.WechatVerifyStatusWaiting:
return "", ErrSceneNotScanned
case models.WechatVerifyStatusUsed:
return "", ErrCodeUsed
case models.WechatVerifyStatusExpired:
return "", ErrCodeExpired
}
if strings.TrimSpace(code) != row.Code {
return "", ErrCodeNotPending
}
now := time.Now()
if _, uerr := models.Orm.QueryTable(new(models.WechatMpVerifyCode)).
Filter("id", row.ID).
Filter("status", models.WechatVerifyStatusScanned).
Update(map[string]interface{}{"status": models.WechatVerifyStatusUsed, "update_time": now}); uerr != nil {
return "", uerr
}
return row.OpenID, nil
}
// CreateRegisterOpenIDVerify 公众号内发送「注册」时创建的注册会话:
// openid 已知(消息发送者),网页端按验证码核销(无带参二维码的兜底路径)。
func CreateRegisterOpenIDVerify(openid string, ttl time.Duration) (*models.WechatMpVerifyCode, error) {
openid = strings.TrimSpace(openid)
if openid == "" {
return nil, errors.New("openid 为空")
}
if ttl <= 0 {
ttl = DefaultVerifyTTL
}
now := time.Now()
expireAt := now.Add(ttl)
row := &models.WechatMpVerifyCode{
Scene: randomScene(),
Code: randomCode6(),
OpenID: openid,
Status: models.WechatVerifyStatusScanned, // 微信号已知,等同「已扫码待核销」
BindType: WechatBindTypeRegister,
ExpireAt: &expireAt,
CreateTime: now,
UpdateTime: &now,
}
if _, err := models.Orm.Insert(row); err != nil {
return nil, err
}
return row, nil
}
// LatestPendingRegisterVerifyByOpenID 该微信号最近一条未核销的注册会话(重发验证码时复用)
func LatestPendingRegisterVerifyByOpenID(openid string) (*models.WechatMpVerifyCode, error) {
var row models.WechatMpVerifyCode
err := models.Orm.QueryTable(new(models.WechatMpVerifyCode)).
Filter("openid", openid).
Filter("bind_type", WechatBindTypeRegister).
Filter("status", models.WechatVerifyStatusScanned).
Filter("expire_at__gt", time.Now()).
OrderBy("-id").
One(&row)
if err != nil {
return nil, err
}
return &row, nil
}
// ConfirmRegisterVerifyByCode 无二维码兜底:按验证码认领「公众号发起」的注册会话。
// 只认领 bind_type=register、openid 非空、已扫码待核销的最近会话,CAS 核销防止重复使用。
func ConfirmRegisterVerifyByCode(code string) (string, error) {
code = strings.TrimSpace(code)
if code == "" {
return "", ErrCodeNotPending
}
var row models.WechatMpVerifyCode
err := models.Orm.QueryTable(new(models.WechatMpVerifyCode)).
Filter("code", code).
Filter("bind_type", WechatBindTypeRegister).
Filter("openid__ne", "").
Filter("status", models.WechatVerifyStatusScanned).
Filter("expire_at__gt", time.Now()).
OrderBy("-id").
One(&row)
if err != nil {
return "", ErrCodeNotPending
}
now := time.Now()
affected, uerr := models.Orm.QueryTable(new(models.WechatMpVerifyCode)).
Filter("id", row.ID).
Filter("status", models.WechatVerifyStatusScanned).
Update(map[string]interface{}{"status": models.WechatVerifyStatusUsed, "update_time": now})
if uerr != nil {
return "", uerr
}
if affected == 0 {
return "", ErrCodeUsed
}
return row.OpenID, nil
}