Files
yunzerwebsiteallinone/go/services/auth/sync.go
T
2026-09-19 21:44:04 +08:00

220 lines
5.7 KiB
Go

package auth
import (
"strings"
"github.com/google/uuid"
"server/models"
"server/pkg/passwordutil"
)
// SyncTenantUserInput 老表 → 认证中心同步入参
type SyncTenantUserInput struct {
Tid uint64
Account string // 企业内账号
Name string
Phone string
Email string
PasswordHash string // 已哈希的密码;为空表示不修改密码
GroupID uint64
OrgID uint64
Status int8
IsDefault int8
}
// SyncTenantUser 把租户用户同步到统一认证中心(幂等,可重复调用)。
//
// 归并规则与迁移脚本一致:手机号 > 邮箱 > 企业内账号。
// - 身份不存在 → 创建;已存在 → 更新手机/邮箱/昵称/状态
// - 绑定不存在 → 创建;已存在 → 更新账号/姓名/部门/角色/状态
//
// 注意:密码属于身份层(一人一份),因此在任一企业修改密码,
// 该用户在其他企业的登录密码会同步变化——这是统一认证的预期行为。
func SyncTenantUser(in SyncTenantUserInput) error {
identity, err := findOrCreateIdentity(in)
if err != nil {
return err
}
return upsertTenantBinding(identity, in)
}
// RemoveTenantUser 删除该用户在指定企业的绑定(保留身份本身,
// 因为该身份可能还绑定着其他企业)。
func RemoveTenantUser(tid uint64, account, phone, email string) error {
identity := findIdentityByKey(account, phone, email)
if identity == nil {
return nil // 认证中心无此身份,无需处理
}
_, err := models.Orm.QueryTable(new(models.AuthTenantUser)).
Filter("tid", tid).
Filter("identity_id", identity.ID).
Delete()
return err
}
// findOrCreateIdentity 按归并键查找身份,不存在则创建
func findOrCreateIdentity(in SyncTenantUserInput) (*models.AuthIdentity, error) {
if m := findIdentityByKey(in.Account, in.Phone, in.Email); m != nil {
// 补全手机/邮箱/昵称;密码仅在显式传入时更新
update := map[string]interface{}{}
if in.Phone != "" {
update["mobile"] = in.Phone
}
if in.Email != "" {
update["email"] = in.Email
}
if in.Name != "" {
update["nickname"] = in.Name
}
if in.Status > 0 {
update["status"] = in.Status
}
if in.PasswordHash != "" {
update["password_hash"] = in.PasswordHash
update["password_algo"] = passwordutil.AlgoOf(in.PasswordHash)
}
if len(update) > 0 {
_, _ = models.Orm.QueryTable(new(models.AuthIdentity)).
Filter("id", m.ID).
Update(update)
}
// 重新读取,返回最新值
_ = models.Orm.QueryTable(new(models.AuthIdentity)).Filter("id", m.ID).One(m)
return m, nil
}
item := &models.AuthIdentity{
UnionID: strings.ReplaceAll(uuid.NewString(), "-", ""),
PasswordAlgo: passwordutil.AlgoLegacy,
Status: in.Status,
}
if in.Status == 0 {
item.Status = models.AuthIdentityStatusEnabled
}
if in.Phone != "" {
item.Mobile = &in.Phone
}
if in.Email != "" {
item.Email = &in.Email
}
if in.Name != "" {
item.Nickname = &in.Name
}
if in.PasswordHash != "" {
item.PasswordHash = &in.PasswordHash
item.PasswordAlgo = passwordutil.AlgoOf(in.PasswordHash)
}
id, err := models.Orm.Insert(item)
if err != nil {
return nil, err
}
item.ID = uint64(id)
return item, nil
}
// findIdentityByKey 按 手机 > 邮箱 > 账号 查找身份
func findIdentityByKey(account, phone, email string) *models.AuthIdentity {
base := models.Orm.QueryTable(new(models.AuthIdentity)).Filter("delete_time__isnull", true)
if v := strings.TrimSpace(phone); v != "" {
m := &models.AuthIdentity{}
if err := base.Filter("mobile", v).One(m); err == nil {
return m
}
}
if v := strings.TrimSpace(email); v != "" {
m := &models.AuthIdentity{}
if err := base.Filter("email", v).One(m); err == nil {
return m
}
}
// 企业内账号:先找绑定关系再回查身份
if v := strings.TrimSpace(account); v != "" {
var bind models.AuthTenantUser
if err := models.Orm.QueryTable(new(models.AuthTenantUser)).
Filter("account", v).
Filter("delete_time__isnull", true).
OrderBy("-is_default", "id").
One(&bind); err == nil {
m := &models.AuthIdentity{}
if err := models.Orm.QueryTable(new(models.AuthIdentity)).
Filter("id", bind.IdentityID).One(m); err == nil {
return m
}
}
}
return nil
}
// upsertTenantBinding 创建或更新身份-企业绑定
func upsertTenantBinding(identity *models.AuthIdentity, in SyncTenantUserInput) error {
var existed models.AuthTenantUser
err := models.Orm.QueryTable(new(models.AuthTenantUser)).
Filter("tid", in.Tid).
Filter("identity_id", identity.ID).
One(&existed)
if err != nil {
// 新建绑定
item := &models.AuthTenantUser{
Tid: in.Tid,
IdentityID: identity.ID,
GroupID: in.GroupID,
OrgID: in.OrgID,
Status: in.Status,
IsDefault: in.IsDefault,
}
if in.Status == 0 {
item.Status = 1
}
if in.Account != "" {
item.Account = &in.Account
}
if in.Name != "" {
item.Name = &in.Name
}
if in.Phone != "" {
item.Phone = &in.Phone
}
if in.Email != "" {
item.Email = &in.Email
}
_, err := models.Orm.Insert(item)
return err
}
update := map[string]interface{}{}
if in.Account != "" {
update["account"] = in.Account
}
if in.Name != "" {
update["name"] = in.Name
}
if in.Phone != "" {
update["phone"] = in.Phone
}
if in.Email != "" {
update["email"] = in.Email
}
if in.GroupID > 0 {
update["group_id"] = in.GroupID
}
if in.OrgID > 0 {
update["org_id"] = in.OrgID
}
if in.Status > 0 {
update["status"] = in.Status
}
if in.IsDefault >= 0 {
update["is_default"] = in.IsDefault
}
if len(update) == 0 {
return nil
}
_, err = models.Orm.QueryTable(new(models.AuthTenantUser)).
Filter("id", existed.ID).
Update(update)
return err
}