批量优化

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

+109 -24
View File
@@ -12,6 +12,7 @@ import (
"server/pkg/jwtutil"
"server/pkg/passwordutil"
"server/services"
authsvc "server/services/auth"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
@@ -56,11 +57,18 @@ type backendTenantUserPayload struct {
IsDefault *int8 `json:"is_default"`
Status *int8 `json:"status"`
Remark *string `json:"remark"`
// IdentityID + VerifyPassword:绑定已有认证身份时使用。
// 归并键(手机/邮箱/账号)命中已有身份时,接口返回 409 要求验证;
// 前端补上 identity_id 与该账号的登录密码(verify_password)重新提交,
// 验证通过只建企业绑定,不新建身份、不覆盖原密码。
IdentityID uint64 `json:"identity_id"`
VerifyPassword string `json:"verify_password"`
}
type backendChangePasswordPayload struct {
ID uint64 `json:"id"`
Password string `json:"password"`
By string `json:"by"`
}
func formatBackendBirth(birth *string) *string {
@@ -187,7 +195,12 @@ func (c *BackendAdminUserController) parseTenantUserPayload() (backendTenantUser
// yz_system_tenant_user.uid 仍是老 uid(老表不在 uid 全量迁移范围内),
// 因此必须先换算再按 uid 查;先按主键 id 查会把 identity_id 命中的是
// 主键恰好相同的**别人**的记录(个人中心显示他人资料就是这么来的)。
func findBackendTenantUser(idOrUid uint64, jwtTid uint64) (*models.SystemTenantUser, error) {
//
// byID=true 表示调用方明确传入的是 yz_system_tenant_user 主键
// (用户管理页的行数据,列表接口返回的 id 就是主键),
// 此时只按主键精确查询,禁止 identity_id/uid 换算——否则主键值
// 恰好等于别人老 uid 时会查到**别人**的记录(编辑李增炎却回显李志强)。
func findBackendTenantUser(idOrUid uint64, jwtTid uint64, byID bool) (*models.SystemTenantUser, error) {
if idOrUid == 0 {
return nil, errors.New("用户ID不能为空")
}
@@ -201,6 +214,14 @@ func findBackendTenantUser(idOrUid uint64, jwtTid uint64) (*models.SystemTenantU
return qs
}
if byID {
var row models.SystemTenantUser
if err := base().Filter("id", idOrUid).One(&row); err != nil {
return nil, err
}
return &row, nil
}
// 1) 当作 identity_id:换算成本租户老 uid 后匹配
var row models.SystemTenantUser
if uid := services.ResolveTenantUID(jwtTid, idOrUid); uid > 0 {
@@ -323,14 +344,18 @@ func (c *BackendAdminUserController) GetTenantUsers() {
}
// GetUserInfo 获取后台租户用户详情
// GET /backend/getUserInfo/:id
// GET /backend/getUserInfo/:id[?by=id]
// by=id:前端明确传主键(用户管理页),只按主键查询;
// 缺省时兼容 identity_id / 老 uid(个人中心等场景)。
func (c *BackendAdminUserController) GetUserInfo() {
jwtUid, jwtTid := c.getJWTUidTid()
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
byID := c.GetString("by") == "id"
if id == 0 && !byID {
id = jwtUid
}
@@ -340,7 +365,7 @@ func (c *BackendAdminUserController) GetUserInfo() {
return
}
u, err := findBackendTenantUser(id, jwtTid)
u, err := findBackendTenantUser(id, jwtTid, byID)
if err != nil || u == nil {
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "后台用户信息不存在"}
_ = c.ServeJSON()
@@ -389,16 +414,75 @@ func (c *BackendAdminUserController) AddUser() {
account := strings.TrimSpace(*p.Account)
p.Account = &account
hashed, err := passwordutil.Hash(strings.TrimSpace(*p.Password))
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()}
_ = c.ServeJSON()
return
}
p.Password = &hashed
// ── 统一认证:一人一套账号密码,跨企业只建绑定 ─────────────────────────
// 分支①:前端携带 identity_id(409 验证后的二次提交)——
// 验证该身份的登录密码,通过后只建企业绑定,不新建身份、不覆盖原密码。
if p.IdentityID > 0 {
if strings.TrimSpace(p.VerifyPassword) == "" {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请输入该账号在原企业的登录密码以验证身份"}
_ = c.ServeJSON()
return
}
identity, err := authsvc.GetIdentityByID(p.IdentityID)
if err != nil || identity == nil {
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "认证身份不存在"}
_ = c.ServeJSON()
return
}
if !authsvc.VerifyIdentityPassword(identity, strings.TrimSpace(p.VerifyPassword)) {
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "密码不正确,无法确认是同一账号"}
_ = c.ServeJSON()
return
}
// uid 统一为 identity_id,与已全量迁移的业务表口径一致
p.Uid = identity.ID
// 老表镜像身份层现有密码哈希(登录仍以认证中心为准)
if identity.PasswordHash != nil && *identity.PasswordHash != "" {
h := *identity.PasswordHash
p.Password = &h
} else {
// 身份层无密码(历史数据),仅镜像到老表,不影响统一认证
hashed, hErr := passwordutil.Hash(strings.TrimSpace(*p.Password))
if hErr != nil {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": hErr.Error()}
_ = c.ServeJSON()
return
}
p.Password = &hashed
}
} else if p.Uid == 0 {
// 分支②:归并键(手机 > 邮箱 > 账号)命中已有身份 → 不覆盖,
// 返回 409 让前端提示「该账号已在哪些企业存在」,
// 用户输入原企业登录密码验证本人后,携 identity_id 重新提交(走分支①)。
if existed, hit := authsvc.LocateIdentityByKey(account, strOrEmpty(p.Phone), strOrEmpty(p.Email)); hit {
hints := make([]map[string]interface{}, 0)
if tenants, err := authsvc.ListTenantOptions(existed.ID); err == nil {
for _, t := range tenants {
hints = append(hints, map[string]interface{}{"tid": t.Tid, "tenant_name": t.TenantName})
}
}
c.Data["json"] = map[string]interface{}{
"code": 409,
"msg": "该账号已在其他企业存在,请输入原企业登录密码验证身份后绑定本企业",
"data": map[string]interface{}{
"need_verify": true,
"identity_id": existed.ID,
"tenants": hints,
},
}
_ = c.ServeJSON()
return
}
// 分支③:全新身份 —— password 必填,先建认证身份再拿 identity_id 当 uid
hashed, err := passwordutil.Hash(strings.TrimSpace(*p.Password))
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": err.Error()}
_ = c.ServeJSON()
return
}
p.Password = &hashed
if p.Uid == 0 {
// 统一走 uid:优先用认证中心 identity_id,与已全量迁移的业务表口径一致
uid, err := ensureTenantUIDForNewUser(
p.Tid,
strOrEmpty(p.Account),
@@ -478,10 +562,11 @@ func (c *BackendAdminUserController) AddUser() {
}
}
// 双写:同步到统一认证中心(读回最新记录,确保 group_id 已落库)
// 双写:同步到统一认证中心(读回最新记录,确保 group_id 已落库);
// 身份若已存在,密码以身份层为准,不同步(避免覆盖)
var created models.SystemTenantUser
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).One(&created); e == nil {
syncTenantUserToAuth(&created)
syncTenantUserToAuth(&created, false)
}
c.Data["json"] = map[string]interface{}{
@@ -511,7 +596,7 @@ func (c *BackendAdminUserController) EditUser() {
_, jwtTid := c.getJWTUidTid()
row, err := findBackendTenantUser(id, jwtTid)
row, err := findBackendTenantUser(id, jwtTid, c.GetString("by") == "id")
if err != nil || row == nil {
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"}
_ = c.ServeJSON()
@@ -606,10 +691,10 @@ func (c *BackendAdminUserController) EditUser() {
_ = services.SetDefaultTenant(row.Uid, row.Tid)
}
// 双写:同步到统一认证中心
// 双写:同步到统一认证中心(编辑不改密码,不同步密码字段)
var edited models.SystemTenantUser
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", row.ID).One(&edited); e == nil {
syncTenantUserToAuth(&edited)
syncTenantUserToAuth(&edited, false)
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
@@ -620,8 +705,8 @@ func (c *BackendAdminUserController) EditUser() {
// DELETE /backend/deleteUser/:id
//
// 用户管理里的账号属于租户层面的登录凭证(yz_system_tenant_user),与员工
//(yz_backend_employee)、人事档案(yz_backend_employee_file)、通讯录
//(yz_backend_erp_contact)是三份不同用途的数据:账号只用于登录系统,员工与档案属于企业数据。
// (yz_backend_employee)、人事档案(yz_backend_employee_file)、通讯录
// (yz_backend_erp_contact)是三份不同用途的数据:账号只用于登录系统,员工与档案属于企业数据。
// 因此这里只解除登录绑定,不做任何级联操作——删除账号不会影响员工档案和通讯录记录。
// 员工侧的离职/禁用走 deleteEmployee,档案侧的离职走 employeefile/delete。
func (c *BackendAdminUserController) DeleteUser() {
@@ -636,7 +721,7 @@ func (c *BackendAdminUserController) DeleteUser() {
_, jwtTid := c.getJWTUidTid()
row, err := findBackendTenantUser(id, jwtTid)
row, err := findBackendTenantUser(id, jwtTid, c.GetString("by") == "id")
if err != nil || row == nil {
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"}
_ = c.ServeJSON()
@@ -682,7 +767,7 @@ func (c *BackendAdminUserController) ChangePassword() {
_, jwtTid := c.getJWTUidTid()
row, err := findBackendTenantUser(p.ID, jwtTid)
row, err := findBackendTenantUser(p.ID, jwtTid, p.By == "id")
if err != nil || row == nil {
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "用户不存在"}
_ = c.ServeJSON()
@@ -708,10 +793,10 @@ func (c *BackendAdminUserController) ChangePassword() {
return
}
// 双写:同步新密码到统一认证中心
// 双写:同步新密码到统一认证中心(改密场景,显式允许更新身份层密码)
var updated models.SystemTenantUser
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", row.ID).One(&updated); e == nil {
syncTenantUserToAuth(&updated)
syncTenantUserToAuth(&updated, true)
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"}