批量优化功能

This commit is contained in:
2026-09-30 00:57:36 +08:00
parent ca4220f92c
commit e3db229f41
43 changed files with 2959 additions and 207 deletions
+149
View File
@@ -1,14 +1,18 @@
package controllers
import (
"fmt"
"io"
"strconv"
"strings"
"time"
"server/models"
authsvc "server/services/auth"
"server/services/wechatmp"
beego "github.com/beego/beego/v2/server/web"
"github.com/beego/beego/v2/core/logs"
)
// WechatMpCallbackController 微信公众号服务器回调(微信服务器调用,无需平台鉴权)
@@ -100,6 +104,10 @@ func (c *WechatMpCallbackController) Callback() {
return
}
// 排查用:记录微信推送的消息/事件类型
logs.Info("[wechat-mp] 收到推送: type=%s event=%s from=%s key=%s",
msg.MsgType, msg.Event, msg.FromUserName, msg.EventKey)
reply := wechatmp.HandleInbound(cfg, msg)
if strings.TrimSpace(reply) == "" {
// 无需回复:返回空串,微信视为处理成功
@@ -122,3 +130,144 @@ func (c *WechatMpCallbackController) Callback() {
}
c.Ctx.Output.Body([]byte(plainReply))
}
// Login GET/POST /api/wechat/mp/login?token=xxx&client_id=yyy
// 公众号「确认登录」一次性令牌兑换登录态(无平台鉴权:令牌本身即凭证,一次性 + 短时效)。
// 已绑定身份:单企业直接签发令牌;多企业签发待选令牌并由前端展示企业选择。
// 同时认领等待中的 PC 扫码会话(存在时),PC 端轮询后自动登录。
func (c *WechatMpCallbackController) Login() {
token := strings.TrimSpace(c.GetString("token"))
clientID := strings.TrimSpace(c.GetString("client_id"))
if clientID == "" {
clientID = "yz-backend"
}
row, err := wechatmp.ConsumeLoginToken(token)
if err != nil {
c.Ctx.Output.SetStatus(400)
c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()}
_ = c.ServeJSON()
return
}
switch row.BindType {
case models.WechatBindTypeTenantUser:
// 认领 PC 扫码会话:确认链接携带 scene 时精确认领;「登录」文本路径无 scene,FIFO 兜底
if scene := strings.TrimSpace(c.GetString("scene")); scene != "" {
wechatmp.ConfirmScanLoginByToken(scene, token, row.BindType, row.BindID, row.BindTid)
} else {
wechatmp.ConfirmOldestPendingScanLogin(row.BindType, row.BindID, row.BindTid)
}
// 身份状态校验(禁用/锁定则拒绝登录)
var identity models.AuthIdentity
if err := models.Orm.QueryTable(new(models.AuthIdentity)).
Filter("id", row.BindID).One(&identity); err != nil {
c.failJSON(400, "账号不存在或已注销")
return
}
if identity.Status != models.AuthIdentityStatusEnabled {
c.failJSON(400, "账号已被禁用或锁定,无法登录")
return
}
username := "微信用户"
if identity.Nickname != nil && strings.TrimSpace(*identity.Nickname) != "" {
username = *identity.Nickname
}
// 企业选择:单企业直接登录;多企业签发待选令牌,由前端展示选择界面
tenants, lerr := authsvc.ListTenantOptions(row.BindID)
if lerr != nil || len(tenants) == 0 {
c.failJSON(400, "该账号未加入任何企业,无法登录")
return
}
tid := uint64(0)
needChoose := false
if len(tenants) == 1 {
tid = tenants[0].Tid
} else {
tid = authsvc.PendingTenantID
needChoose = true
}
tokens, terr := c.issueMpLoginTokens(row.BindID, tid, clientID, username, "公众号确认链接登录成功", needChoose)
if terr != nil {
c.failJSON(400, terr.Error())
return
}
data := map[string]interface{}{
"type": "backend",
"tokens": tokens,
"tenant": row.BindTid,
}
if needChoose {
data["need_choose_tenant"] = true
data["tenants"] = tenants
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "登录成功",
"data": data,
}
_ = c.ServeJSON()
default:
c.failJSON(400, "该微信绑定的账号类型暂不支持链接登录")
}
}
// issueMpLoginTokens 为绑定身份建会话并签发 UAC 令牌(含登录日志,失败不影响主流程)
// tid 为 authsvc.PendingTenantID 时签发待选企业用的短期令牌
func (c *WechatMpCallbackController) issueMpLoginTokens(identityID, tid uint64, clientID, username, logMsg string, needChoose bool) (*authsvc.TokenPair, error) {
accessTTL := 0
if needChoose {
accessTTL = 600 // 待选状态令牌只够用户完成企业选择
}
sess, serr := authsvc.CreateSession(authsvc.SessionInfo{
IdentityID: identityID,
Tid: tid,
ClientID: clientID,
IP: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Request.UserAgent(),
LoginType: "wechat_mp",
Amr: "wechat_mp",
})
if serr != nil {
return nil, fmt.Errorf("登录失败:%s", serr.Error())
}
tokens, terr := authsvc.IssueTokens(authsvc.TokenIssue{
IdentityID: identityID,
Tid: tid,
ClientID: clientID,
Sid: sess.Sid,
Username: username,
Amr: "wechat_mp",
AccessTTL: accessTTL,
})
if terr != nil {
return nil, fmt.Errorf("签发令牌失败:%s", terr.Error())
}
identityIDCopy := identityID
tidCopy := tid
amr := "wechat_mp"
_, _ = models.Orm.Insert(&models.AuthLoginLog{
Tid: &tidCopy,
IdentityID: &identityIDCopy,
UserName: username,
ClientID: clientID,
LoginType: "wechat_mp",
Amr: &amr,
Status: 1,
Message: logMsg,
IP: c.Ctx.Input.IP(),
UserAgent: c.Ctx.Request.UserAgent(),
})
return tokens, nil
}
func (c *WechatMpCallbackController) failJSON(bizCode int, msg string) {
c.Ctx.Output.SetStatus(400)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}