修复
This commit is contained in:
1 parent
d16dbc69b4
commit
fc8c3ee3ad
2 files changed
+370
-113
No files matched your search
+272
-18
@@ -21,6 +21,7 @@ import (
|
||||
"server/models"
|
||||
"server/pkg/identity"
|
||||
"server/pkg/jwtutil"
|
||||
authsvc "server/services/auth"
|
||||
"server/services/gueststore"
|
||||
"server/services/wechatmini"
|
||||
|
||||
@@ -83,12 +84,11 @@ func (c *GuestController) MiniLogin() {
|
||||
return
|
||||
}
|
||||
|
||||
// openid 仅作溯源,拿不到不阻断游客签发
|
||||
// openid 仅作溯源与身份识别(已绑定过正式身份时按 openid 认人),拿不到不阻断游客签发
|
||||
var openID, unionID string
|
||||
if req.Code != "" {
|
||||
if session, serr := wechatmini.Code2Session(cfg, req.Code); serr == nil && session != nil {
|
||||
openID, unionID = session.OpenID, session.UnionID
|
||||
}
|
||||
session := guestMiniSession(cfg, req.Code)
|
||||
if session != nil {
|
||||
openID, unionID = session.OpenID, session.UnionID
|
||||
}
|
||||
|
||||
if strings.TrimSpace(req.PhoneCode) == "" {
|
||||
@@ -114,6 +114,12 @@ func (c *GuestController) MiniLogin() {
|
||||
return
|
||||
}
|
||||
|
||||
// 手机号已注册云泽账号(或该微信已绑定正式身份):直接签发正式令牌按登录处理。
|
||||
// (与 /auth/wechat/miniapp/login 同一套识别与签发逻辑)
|
||||
if c.tryUserLogin(mobile, session) {
|
||||
return
|
||||
}
|
||||
|
||||
guest, err := gueststore.Upsert(mobile, openID, unionID)
|
||||
if err != nil {
|
||||
logs.Error("[guest] upsert 失败: %v", err)
|
||||
@@ -143,6 +149,258 @@ func (c *GuestController) MiniLogin() {
|
||||
})
|
||||
}
|
||||
|
||||
// identityByPhone 按微信已验证手机号定位认证中心身份。
|
||||
// 租户账号手机号可能落在三处,逐层兜底(先查认证中心,查不到再回退业务表):
|
||||
// 1. yz_auth_identity.mobile/account/email(FindIdentityByAccount)
|
||||
// 2. yz_auth_tenant_user.phone → identity_id
|
||||
// 3. yz_system_tenant_user.phone → uid(全量迁移后 uid == identity_id)
|
||||
func identityByPhone(mobile string) *models.AuthIdentity {
|
||||
if ai, err := authsvc.FindIdentityByAccount(mobile); err == nil && ai != nil {
|
||||
return ai
|
||||
}
|
||||
var identityID uint64
|
||||
var tu models.AuthTenantUser
|
||||
if err := models.Orm.QueryTable(new(models.AuthTenantUser)).
|
||||
Filter("phone", mobile).Filter("delete_time__isnull", true).
|
||||
OrderBy("-is_default", "id").One(&tu); err == nil {
|
||||
identityID = tu.IdentityID
|
||||
} else {
|
||||
var su models.SystemTenantUser
|
||||
if err2 := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("phone", mobile).Filter("delete_time__isnull", true).
|
||||
OrderBy("-is_default", "id").One(&su); err2 == nil {
|
||||
identityID = su.Uid
|
||||
}
|
||||
}
|
||||
if identityID == 0 {
|
||||
return nil
|
||||
}
|
||||
ai := &models.AuthIdentity{}
|
||||
if err := models.Orm.QueryTable(new(models.AuthIdentity)).
|
||||
Filter("id", identityID).Filter("delete_time__isnull", true).One(ai); err != nil {
|
||||
return nil
|
||||
}
|
||||
return ai
|
||||
}
|
||||
|
||||
// phoneOfIdentity 取身份手机号:identity.mobile 为空时回退租户绑定/业务用户表。
|
||||
func phoneOfIdentity(identityID uint64) string {
|
||||
var tus []models.AuthTenantUser
|
||||
if _, err := models.Orm.QueryTable(new(models.AuthTenantUser)).
|
||||
Filter("identity_id", identityID).Filter("delete_time__isnull", true).
|
||||
OrderBy("-is_default", "id").All(&tus); err == nil {
|
||||
for _, tu := range tus {
|
||||
if tu.Phone != nil {
|
||||
if p := strings.TrimSpace(*tu.Phone); p != "" {
|
||||
return p
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var sus []models.SystemTenantUser
|
||||
if _, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("uid", identityID).Filter("delete_time__isnull", true).
|
||||
OrderBy("-is_default", "id").All(&sus); err == nil {
|
||||
for _, su := range sus {
|
||||
if su.Phone != nil {
|
||||
if p := strings.TrimSpace(*su.Phone); p != "" {
|
||||
return p
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// guestMiniSession 拿 wx.login code 换 openid/unionid;失败不阻断(返回 nil)。
|
||||
func guestMiniSession(cfg *wechatmini.Config, code string) *wechatmini.Session {
|
||||
if strings.TrimSpace(code) == "" {
|
||||
return nil
|
||||
}
|
||||
session, err := wechatmini.Code2Session(cfg, code)
|
||||
if err != nil {
|
||||
logs.Warn("[guest] code 换取 openid 失败: %v", err)
|
||||
return nil
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
// identityByWechat 按 openid/unionid 查已绑定的正式身份(登录页静默 bind 的落点)。
|
||||
// 与小程序一键登录 resolveIdentity 同源:手机号查不到时的第二级认人方式。
|
||||
func identityByWechat(openID, unionID string) *models.AuthIdentity {
|
||||
var bind models.AuthIdentityThird
|
||||
qs := models.Orm.QueryTable(new(models.AuthIdentityThird)).Filter("provider", "wechat_mini")
|
||||
found := false
|
||||
if openID != "" {
|
||||
if err := qs.Filter("open_id", openID).One(&bind); err == nil {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found && unionID != "" {
|
||||
if err := models.Orm.QueryTable(new(models.AuthIdentityThird)).
|
||||
Filter("provider", "wechat_mini").Filter("union_id", unionID).One(&bind); err == nil {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
ai := &models.AuthIdentity{}
|
||||
if err := models.Orm.QueryTable(new(models.AuthIdentity)).
|
||||
Filter("id", bind.IdentityID).Filter("delete_time__isnull", true).One(ai); err != nil {
|
||||
return nil
|
||||
}
|
||||
return ai
|
||||
}
|
||||
|
||||
// bindWechatMini 把当前小程序 openid 绑到该身份。手机号已经微信验证,
|
||||
// 属于强证据,允许覆盖旧绑定(与 controllers/auth syncBinding 的 verified 分支一致)。
|
||||
func bindWechatMini(session *wechatmini.Session, identityID uint64) {
|
||||
var bind models.AuthIdentityThird
|
||||
qs := models.Orm.QueryTable(new(models.AuthIdentityThird)).Filter("provider", "wechat_mini")
|
||||
if err := qs.Filter("open_id", session.OpenID).One(&bind); err == nil {
|
||||
if bind.IdentityID == identityID {
|
||||
return
|
||||
}
|
||||
_, _ = models.Orm.QueryTable(new(models.AuthIdentityThird)).Filter("id", bind.ID).Delete()
|
||||
}
|
||||
item := &models.AuthIdentityThird{IdentityID: identityID, Provider: "wechat_mini", OpenID: session.OpenID}
|
||||
if session.UnionID != "" {
|
||||
uid := session.UnionID
|
||||
item.UnionID = &uid
|
||||
}
|
||||
_, _ = models.Orm.Insert(item)
|
||||
}
|
||||
|
||||
// tryUserLogin 一键授权时的正式身份识别:手机号命中启用账号且该账号有企业,
|
||||
// 则签发正式令牌并返回 type=user(对齐小程序一键登录:单企业直接进入,
|
||||
// 多企业返回待选令牌由企业选择接口完成切换)。已处理返回 true。
|
||||
// 未命中 / 账号无企业 / 签发失败 → 返回 false,回落到游客签发流程。
|
||||
func (c *GuestController) tryUserLogin(mobile string, session *wechatmini.Session) bool {
|
||||
var authIdentity *models.AuthIdentity
|
||||
if session != nil {
|
||||
authIdentity = identityByWechat(session.OpenID, session.UnionID)
|
||||
}
|
||||
if authIdentity == nil {
|
||||
authIdentity = identityByPhone(mobile)
|
||||
}
|
||||
if authIdentity == nil {
|
||||
return false
|
||||
}
|
||||
// 手机号已通过微信验证:把 openid 补绑/改绑到该身份(下次无手机号也能认人)
|
||||
if session != nil && session.OpenID != "" {
|
||||
bindWechatMini(session, authIdentity.ID)
|
||||
}
|
||||
if authIdentity.Status != models.AuthIdentityStatusEnabled {
|
||||
c.gReply(403, "账号已被禁用或锁定,无法使用", nil)
|
||||
return true
|
||||
}
|
||||
tenants, lerr := authsvc.ListTenantOptions(authIdentity.ID)
|
||||
if lerr != nil || len(tenants) == 0 {
|
||||
return false // 账号未加入任何企业:按游客模式继续使用
|
||||
}
|
||||
|
||||
needChoose := len(tenants) > 1
|
||||
tid := tenants[0].Tid
|
||||
accessTTL := 0
|
||||
if needChoose {
|
||||
tid = authsvc.PendingTenantID
|
||||
accessTTL = 600 // 待选状态令牌只够用户完成企业选择
|
||||
}
|
||||
|
||||
username := "微信用户"
|
||||
if authIdentity.Nickname != nil && strings.TrimSpace(*authIdentity.Nickname) != "" {
|
||||
username = strings.TrimSpace(*authIdentity.Nickname)
|
||||
} else if authIdentity.Mobile != nil && *authIdentity.Mobile != "" {
|
||||
username = *authIdentity.Mobile
|
||||
}
|
||||
|
||||
sess, serr := authsvc.CreateSession(authsvc.SessionInfo{
|
||||
IdentityID: authIdentity.ID,
|
||||
Tid: tid,
|
||||
ClientID: "yz-miniapp",
|
||||
IP: c.Ctx.Input.IP(),
|
||||
UserAgent: c.Ctx.Request.UserAgent(),
|
||||
LoginType: "wechat_mini",
|
||||
Amr: "wechat_mini",
|
||||
})
|
||||
if serr != nil {
|
||||
logs.Error("[guest] 正式登录创建会话失败: %v", serr)
|
||||
return false
|
||||
}
|
||||
tokens, terr := authsvc.IssueTokens(authsvc.TokenIssue{
|
||||
IdentityID: authIdentity.ID,
|
||||
Tid: tid,
|
||||
ClientID: "yz-miniapp",
|
||||
Sid: sess.Sid,
|
||||
Username: username,
|
||||
UserType: "app",
|
||||
Amr: "wechat_mini",
|
||||
AccessTTL: accessTTL,
|
||||
})
|
||||
if terr != nil {
|
||||
logs.Error("[guest] 正式登录签发令牌失败: %v", terr)
|
||||
return false
|
||||
}
|
||||
|
||||
identityIDCopy := authIdentity.ID
|
||||
tidCopy := tid
|
||||
amr := "wechat_mini"
|
||||
_, _ = models.Orm.Insert(&models.AuthLoginLog{
|
||||
Tid: &tidCopy,
|
||||
IdentityID: &identityIDCopy,
|
||||
UserName: username,
|
||||
ClientID: "yz-miniapp",
|
||||
LoginType: "wechat_mini",
|
||||
Amr: &amr,
|
||||
Status: 1,
|
||||
Message: "小程序手机号一键登录成功(免登录入口识别为正式账号)",
|
||||
IP: c.Ctx.Input.IP(),
|
||||
UserAgent: c.Ctx.Request.UserAgent(),
|
||||
})
|
||||
|
||||
c.gReply(200, "success", map[string]interface{}{
|
||||
"type": "user",
|
||||
"tokens": tokens,
|
||||
"tenants": tenants,
|
||||
"need_choose_tenant": needChoose,
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
// findGuestForIdentity 找回当前身份对应的未合并游客记录:
|
||||
// 优先按手机号,查不到再按该身份已绑定的微信 openid/unionid
|
||||
// (微信验证号可能与账号注册号不一致,openid 是另一条可靠对应关系)。
|
||||
func findGuestForIdentity(identityID uint64, mobile string) (models.GuestUser, bool) {
|
||||
var guest models.GuestUser
|
||||
if mobile != "" {
|
||||
err := models.Orm.QueryTable(new(models.GuestUser)).
|
||||
Filter("phone", mobile).Filter("merged_uid", 0).One(&guest)
|
||||
if err == nil {
|
||||
return guest, true
|
||||
}
|
||||
}
|
||||
var binds []models.AuthIdentityThird
|
||||
if _, err := models.Orm.QueryTable(new(models.AuthIdentityThird)).
|
||||
Filter("identity_id", identityID).Filter("provider", "wechat_mini").All(&binds); err == nil {
|
||||
for _, b := range binds {
|
||||
if b.OpenID != "" {
|
||||
if err := models.Orm.QueryTable(new(models.GuestUser)).
|
||||
Filter("openid", b.OpenID).Filter("merged_uid", 0).One(&guest); err == nil {
|
||||
return guest, true
|
||||
}
|
||||
}
|
||||
if b.UnionID != nil && *b.UnionID != "" {
|
||||
if err := models.Orm.QueryTable(new(models.GuestUser)).
|
||||
Filter("unionid", *b.UnionID).Filter("merged_uid", 0).One(&guest); err == nil {
|
||||
return guest, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return guest, false
|
||||
}
|
||||
|
||||
// Merge POST /api/guest/merge(携带正式令牌)
|
||||
// 按当前身份手机号找回游客记录,把 yz_guest_data 各模块数据并入正式账号。
|
||||
func (c *GuestController) Merge() {
|
||||
@@ -161,24 +419,20 @@ func (c *GuestController) Merge() {
|
||||
c.gReply(500, "查询身份失败", nil)
|
||||
return
|
||||
}
|
||||
if ai.Mobile == nil || strings.TrimSpace(*ai.Mobile) == "" {
|
||||
c.gReply(200, "无可合并数据", map[string]interface{}{"merged": false})
|
||||
return
|
||||
// 回查手机号用于定位游客记录:identity.mobile 为空时回退租户绑定/业务用户表
|
||||
var mobile string
|
||||
if ai.Mobile != nil {
|
||||
mobile = strings.TrimSpace(*ai.Mobile)
|
||||
}
|
||||
if mobile == "" {
|
||||
mobile = phoneOfIdentity(ai.ID)
|
||||
}
|
||||
|
||||
var guest models.GuestUser
|
||||
err = models.Orm.QueryTable(new(models.GuestUser)).
|
||||
Filter("phone", strings.TrimSpace(*ai.Mobile)).
|
||||
Filter("merged_uid", 0).
|
||||
One(&guest)
|
||||
if err == orm.ErrNoRows {
|
||||
guest, found := findGuestForIdentity(ai.ID, mobile)
|
||||
if !found {
|
||||
c.gReply(200, "无可合并数据", map[string]interface{}{"merged": false})
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
c.gReply(500, "查询游客记录失败", nil)
|
||||
return
|
||||
}
|
||||
|
||||
counts, merr := mergeGuestToUser(&guest, uint64(claims.UserID), claims.TenantId, claims.Username)
|
||||
if merr != nil {
|
||||
|
||||
Reference in new issue
Block a user