803 lines
22 KiB
Go
803 lines
22 KiB
Go
package controllers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"strconv"
|
||
"strings"
|
||
|
||
"server/models"
|
||
"server/pkg/identity"
|
||
"server/pkg/passwordutil"
|
||
"server/services"
|
||
authsvc "server/services/auth"
|
||
|
||
"github.com/beego/beego/v2/client/orm"
|
||
beego "github.com/beego/beego/v2/server/web"
|
||
)
|
||
|
||
type BackendAdminUserController struct {
|
||
beego.Controller
|
||
}
|
||
|
||
type backendUserInfoDTO struct {
|
||
ID uint64 `json:"id"`
|
||
Tid uint64 `json:"tid"`
|
||
Uid uint64 `json:"uid"`
|
||
GroupID uint64 `json:"group_id"`
|
||
Account *string `json:"account"`
|
||
Name *string `json:"name"`
|
||
Phone *string `json:"phone"`
|
||
Email *string `json:"email"`
|
||
Avatar *string `json:"avatar"`
|
||
Sex uint8 `json:"sex"`
|
||
Birth *string `json:"birth"`
|
||
IsDefault int8 `json:"is_default"`
|
||
Status int8 `json:"status"`
|
||
Remark *string `json:"remark"`
|
||
CreateTime string `json:"create_time"`
|
||
UpdateTime *string `json:"update_time"`
|
||
TenantName string `json:"tenant_name"`
|
||
TenantCode string `json:"tenant_code"`
|
||
}
|
||
|
||
type backendTenantUserPayload struct {
|
||
Tid uint64 `json:"tid"`
|
||
Uid uint64 `json:"uid"`
|
||
GroupID *uint64 `json:"group_id"`
|
||
Account *string `json:"account"`
|
||
Name *string `json:"name"`
|
||
Phone *string `json:"phone"`
|
||
Email *string `json:"email"`
|
||
Sex *uint8 `json:"sex"`
|
||
Birth *string `json:"birth"`
|
||
Password *string `json:"password"`
|
||
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 {
|
||
if birth == nil {
|
||
return nil
|
||
}
|
||
|
||
s := strings.TrimSpace(*birth)
|
||
if s == "" {
|
||
return nil
|
||
}
|
||
|
||
if len(s) >= 10 {
|
||
date := s[:10]
|
||
return &date
|
||
}
|
||
|
||
return &s
|
||
}
|
||
|
||
func toBackendUserInfoDTO(u models.SystemTenantUser) backendUserInfoDTO {
|
||
var updateTime *string
|
||
if u.UpdateTime != nil {
|
||
s := u.UpdateTime.Format("2006-01-02 15:04:05")
|
||
updateTime = &s
|
||
}
|
||
|
||
tenantName := "未知租户"
|
||
tenantCode := ""
|
||
|
||
tenant, err := services.GetTenantByID(u.Tid)
|
||
if err == nil && tenant != nil {
|
||
tenantName = tenant.TenantName
|
||
tenantCode = tenant.TenantCode
|
||
}
|
||
|
||
// 头像取自认证中心身份(业务用户表 yz_system_tenant_user 不存头像)
|
||
var avatar *string
|
||
if a := services.GetIdentityAvatar(u.Uid); a != "" {
|
||
avatar = &a
|
||
}
|
||
|
||
return backendUserInfoDTO{
|
||
ID: u.ID,
|
||
Tid: u.Tid,
|
||
Uid: u.Uid,
|
||
GroupID: u.GroupID,
|
||
Account: u.Account,
|
||
Name: u.Name,
|
||
Phone: u.Phone,
|
||
Email: u.Email,
|
||
Avatar: avatar,
|
||
Sex: u.Sex,
|
||
Birth: formatBackendBirth(u.Birth),
|
||
IsDefault: u.IsDefault,
|
||
Status: u.Status,
|
||
Remark: u.Remark,
|
||
CreateTime: u.CreateTime.Format("2006-01-02 15:04:05"),
|
||
UpdateTime: updateTime,
|
||
TenantName: tenantName,
|
||
TenantCode: tenantCode,
|
||
}
|
||
}
|
||
|
||
// requireBackendIdentity 解析并校验 backend 端登录态;失败时已写好响应,调用方必须立即 return。
|
||
//
|
||
// 取代原 getJWTUidTid:那版从中间件注入的 ctx data 里取 uid/tid,
|
||
// 既不判端、取不到时又静默返回 0,而 0 会让「按企业过滤」退化成全局查询——
|
||
// 平台或小程序的令牌即可枚举、编辑别家企业的用户。
|
||
// 现在 uid/tid 只来自令牌,且必须是 backend 端且已绑定企业。
|
||
func (c *BackendAdminUserController) requireBackendIdentity() (uint64, uint64, bool) {
|
||
claims, err := identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndBackend)
|
||
if err != nil {
|
||
code := identity.Status(err)
|
||
c.Data["json"] = map[string]interface{}{"code": code, "msg": err.Error()}
|
||
_ = c.ServeJSON()
|
||
return 0, 0, false
|
||
}
|
||
if claims.TenantId <= 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 403, "msg": "登录态未绑定企业"}
|
||
_ = c.ServeJSON()
|
||
return 0, 0, false
|
||
}
|
||
return uint64(claims.UserID), uint64(claims.TenantId), true
|
||
}
|
||
|
||
func (c *BackendAdminUserController) parseTenantUserPayload() (backendTenantUserPayload, bool) {
|
||
var p backendTenantUserPayload
|
||
|
||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||
if err := json.Unmarshal(raw, &p); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
|
||
_ = c.ServeJSON()
|
||
return backendTenantUserPayload{}, false
|
||
}
|
||
|
||
return p, true
|
||
}
|
||
|
||
// findBackendTenantUser 按「用户在认证中心的 identity_id」或「记录主键 id」查租户用户。
|
||
//
|
||
// 注意查询顺序:令牌/前端传来的用户标识是认证中心 identity_id,而
|
||
// yz_system_tenant_user.uid 仍是老 uid(老表不在 uid 全量迁移范围内),
|
||
// 因此必须先换算再按 uid 查;先按主键 id 查会把 identity_id 命中的是
|
||
// 主键恰好相同的**别人**的记录(个人中心显示他人资料就是这么来的)。
|
||
//
|
||
// 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不能为空")
|
||
}
|
||
|
||
base := func() orm.QuerySeter {
|
||
qs := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
Filter("delete_time__isnull", true)
|
||
if jwtTid > 0 {
|
||
qs = qs.Filter("tid", jwtTid)
|
||
}
|
||
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 {
|
||
if err := base().Filter("uid", uid).One(&row); err == nil {
|
||
return &row, nil
|
||
}
|
||
}
|
||
|
||
// 2) 当作记录主键 id(用户管理页传的就是 row.id)
|
||
if err := base().Filter("id", idOrUid).One(&row); err == nil {
|
||
return &row, nil
|
||
}
|
||
|
||
// 3) 兜底:本来就是老 uid 的场景
|
||
err := base().Filter("uid", idOrUid).One(&row)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
return &row, nil
|
||
}
|
||
|
||
// GetAllUsers 获取当前租户后台用户列表
|
||
// GET /backend/getAllUsers
|
||
func (c *BackendAdminUserController) GetAllUsers() {
|
||
_, jwtTid, ok := c.requireBackendIdentity()
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
keyword := strings.TrimSpace(c.GetString("keyword"))
|
||
// 用户列表只按登录态隔离:?tid= 不再生效(此前不传 tid 会直接返回全部企业的用户)
|
||
tid := jwtTid
|
||
if tid == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 403, "msg": "登录态未绑定企业"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
cond := orm.NewCondition().And("delete_time__isnull", true).And("tid", tid)
|
||
|
||
if keyword != "" {
|
||
kwCond := orm.NewCondition().
|
||
Or("name__icontains", keyword).
|
||
Or("phone__icontains", keyword).
|
||
Or("email__icontains", keyword).
|
||
Or("account__icontains", keyword)
|
||
cond = cond.AndCond(kwCond)
|
||
}
|
||
|
||
var rows []models.SystemTenantUser
|
||
_, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
SetCond(cond).
|
||
OrderBy("-is_default", "-id").
|
||
All(&rows)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
list := make([]backendUserInfoDTO, 0, len(rows))
|
||
for _, row := range rows {
|
||
list = append(list, toBackendUserInfoDTO(row))
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": map[string]interface{}{
|
||
"list": list,
|
||
"total": len(list),
|
||
},
|
||
}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// GetTenantUsers 获取指定租户后台用户
|
||
// GET /backend/getTenantUsers/:tid
|
||
func (c *BackendAdminUserController) GetTenantUsers() {
|
||
tidStr := c.Ctx.Input.Param(":tid")
|
||
tid, _ := strconv.ParseUint(tidStr, 10, 64)
|
||
|
||
_, jwtTid, ok := c.requireBackendIdentity()
|
||
if !ok {
|
||
return
|
||
}
|
||
if jwtTid > 0 {
|
||
tid = jwtTid
|
||
}
|
||
|
||
if tid == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
var rows []models.SystemTenantUser
|
||
_, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
OrderBy("-is_default", "-id").
|
||
All(&rows)
|
||
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
list := make([]backendUserInfoDTO, 0, len(rows))
|
||
for _, row := range rows {
|
||
list = append(list, toBackendUserInfoDTO(row))
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": map[string]interface{}{
|
||
"list": list,
|
||
"total": len(list),
|
||
},
|
||
}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// GetUserInfo 获取后台租户用户详情
|
||
// GET /backend/getUserInfo/:id[?by=id]
|
||
// by=id:前端明确传主键(用户管理页),只按主键查询;
|
||
// 缺省时兼容 identity_id / 老 uid(个人中心等场景)。
|
||
func (c *BackendAdminUserController) GetUserInfo() {
|
||
jwtUid, jwtTid, ok := c.requireBackendIdentity()
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, _ := strconv.ParseUint(idStr, 10, 64)
|
||
|
||
byID := c.GetString("by") == "id"
|
||
|
||
if id == 0 && !byID {
|
||
id = jwtUid
|
||
}
|
||
|
||
if id == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
u, err := findBackendTenantUser(id, jwtTid, byID)
|
||
if err != nil || u == nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "后台用户信息不存在"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": toBackendUserInfoDTO(*u),
|
||
}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// AddUser 添加后台租户用户
|
||
// POST /backend/addUser
|
||
func (c *BackendAdminUserController) AddUser() {
|
||
p, ok := c.parseTenantUserPayload()
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
_, jwtTid, ok := c.requireBackendIdentity()
|
||
if !ok {
|
||
return
|
||
}
|
||
if jwtTid > 0 {
|
||
p.Tid = jwtTid
|
||
}
|
||
|
||
if p.Tid == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "tid 不能为空"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if p.Account == nil || strings.TrimSpace(*p.Account) == "" {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "account 不能为空"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if p.Password == nil || strings.TrimSpace(*p.Password) == "" {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
account := strings.TrimSpace(*p.Account)
|
||
p.Account = &account
|
||
|
||
// ── 统一认证:一人一套账号密码,跨企业只建绑定 ─────────────────────────
|
||
// 分支①:前端携带 identity_id(409 验证后的二次提交)——
|
||
// 验证该身份的登录密码,通过后只建企业绑定,不新建身份、不覆盖原密码。
|
||
if p.IdentityID > 0 {
|
||
if strings.TrimSpace(p.VerifyPassword) == "" {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "请输入该账号在原企业的登录密码以验证身份"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
ident, err := authsvc.GetIdentityByID(p.IdentityID)
|
||
if err != nil || ident == nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "认证身份不存在"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
if !authsvc.VerifyIdentityPassword(ident, strings.TrimSpace(p.VerifyPassword)) {
|
||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "密码不正确,无法确认是同一账号"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
// uid 统一为 identity_id,与已全量迁移的业务表口径一致
|
||
p.Uid = ident.ID
|
||
// 老表镜像身份层现有密码哈希(登录仍以认证中心为准)
|
||
if ident.PasswordHash != nil && *ident.PasswordHash != "" {
|
||
h := *ident.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
|
||
|
||
uid, err := ensureTenantUIDForNewUser(
|
||
p.Tid,
|
||
strOrEmpty(p.Account),
|
||
strOrEmpty(p.Name),
|
||
strOrEmpty(p.Phone),
|
||
strOrEmpty(p.Email),
|
||
strOrEmpty(p.Password),
|
||
)
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "生成用户ID失败"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
p.Uid = uid
|
||
}
|
||
|
||
isDefault := int8(0)
|
||
if p.IsDefault != nil {
|
||
isDefault = *p.IsDefault
|
||
}
|
||
|
||
status := int8(1)
|
||
if p.Status != nil {
|
||
status = *p.Status
|
||
}
|
||
|
||
// 用户数配额校验:达到上限后需要平台端为租户增购用户数才能继续添加
|
||
if check, err := services.CheckTenantUserQuota(p.Tid, p.Uid); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "用户数校验失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
} else if !check.Allowed {
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 400,
|
||
"msg": fmt.Sprintf("当前租户用户数已达上限(%d/%d),请联系平台管理员增购用户数", check.Used, check.Quota),
|
||
}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
id, err := services.BindTenantUser(
|
||
p.Tid,
|
||
p.Uid,
|
||
p.Account,
|
||
p.Name,
|
||
p.Phone,
|
||
p.Email,
|
||
p.Sex,
|
||
p.Birth,
|
||
p.Password,
|
||
isDefault,
|
||
status,
|
||
p.Remark,
|
||
)
|
||
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "添加失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if isDefault == 1 {
|
||
_ = services.SetDefaultTenant(p.Uid, p.Tid)
|
||
}
|
||
|
||
// 关联角色(group_id):0 表示未分配,前端展示为"未分配/全权限"
|
||
if p.GroupID != nil && *p.GroupID > 0 {
|
||
_, _ = models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
Filter("id", id).
|
||
Update(map[string]interface{}{"group_id": *p.GroupID})
|
||
} else if isDefault == 1 {
|
||
// 租户的第一个账号即租户管理员:自动绑定全局「租户管理员」角色(拥有 backend 全权限)。
|
||
if adminRoleID, err := models.GetTenantAdminRole(); err == nil && adminRoleID > 0 {
|
||
_, _ = models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
Filter("id", id).
|
||
Update(map[string]interface{}{"group_id": adminRoleID})
|
||
}
|
||
}
|
||
|
||
// 双写:同步到统一认证中心(读回最新记录,确保 group_id 已落库);
|
||
// 身份若已存在,密码以身份层为准,不同步(避免覆盖)
|
||
var created models.SystemTenantUser
|
||
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).One(&created); e == nil {
|
||
syncTenantUserToAuth(&created, false)
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{
|
||
"code": 200,
|
||
"msg": "success",
|
||
"data": map[string]interface{}{"id": id},
|
||
}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// EditUser 编辑后台租户用户
|
||
// POST /backend/editUser/:id
|
||
func (c *BackendAdminUserController) EditUser() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, _ := strconv.ParseUint(idStr, 10, 64)
|
||
|
||
if id == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
p, ok := c.parseTenantUserPayload()
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
_, jwtTid, ok := c.requireBackendIdentity()
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
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()
|
||
return
|
||
}
|
||
|
||
update := map[string]interface{}{}
|
||
|
||
if p.Tid > 0 && jwtTid == 0 {
|
||
update["tid"] = p.Tid
|
||
}
|
||
|
||
if p.Uid > 0 {
|
||
update["uid"] = p.Uid
|
||
}
|
||
|
||
if p.Account != nil {
|
||
account := strings.TrimSpace(*p.Account)
|
||
if account != "" {
|
||
update["account"] = account
|
||
}
|
||
}
|
||
|
||
if p.Name != nil {
|
||
update["name"] = *p.Name
|
||
}
|
||
|
||
if p.Phone != nil {
|
||
update["phone"] = *p.Phone
|
||
}
|
||
|
||
if p.Email != nil {
|
||
update["email"] = *p.Email
|
||
}
|
||
|
||
if p.Sex != nil {
|
||
update["sex"] = *p.Sex
|
||
}
|
||
|
||
if p.Birth != nil {
|
||
birth := strings.TrimSpace(*p.Birth)
|
||
if birth == "" {
|
||
update["birth"] = nil
|
||
} else {
|
||
update["birth"] = birth
|
||
}
|
||
}
|
||
|
||
if p.Password != nil && strings.TrimSpace(*p.Password) != "" {
|
||
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
|
||
}
|
||
update["password"] = hashed
|
||
}
|
||
|
||
if p.IsDefault != nil {
|
||
update["is_default"] = *p.IsDefault
|
||
}
|
||
|
||
if p.Status != nil {
|
||
update["status"] = *p.Status
|
||
}
|
||
|
||
if p.Remark != nil {
|
||
update["remark"] = *p.Remark
|
||
}
|
||
|
||
if p.GroupID != nil {
|
||
update["group_id"] = *p.GroupID
|
||
}
|
||
|
||
if len(update) == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
_, err = models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
Filter("id", row.ID).
|
||
Update(update)
|
||
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "编辑失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if p.IsDefault != nil && *p.IsDefault == 1 {
|
||
_ = 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, false)
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// DeleteUser 删除后台租户用户
|
||
// DELETE /backend/deleteUser/:id
|
||
//
|
||
// 用户管理里的账号属于租户层面的登录凭证(yz_system_tenant_user),与员工
|
||
// (yz_backend_employee)、人事档案(yz_backend_employee_file)、通讯录
|
||
// (yz_backend_erp_contact)是三份不同用途的数据:账号只用于登录系统,员工与档案属于企业数据。
|
||
// 因此这里只解除登录绑定,不做任何级联操作——删除账号不会影响员工档案和通讯录记录。
|
||
// 员工侧的离职/禁用走 deleteEmployee,档案侧的离职走 employeefile/delete。
|
||
func (c *BackendAdminUserController) DeleteUser() {
|
||
idStr := c.Ctx.Input.Param(":id")
|
||
id, _ := strconv.ParseUint(idStr, 10, 64)
|
||
|
||
if id == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
_, jwtTid, ok := c.requireBackendIdentity()
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
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()
|
||
return
|
||
}
|
||
|
||
if err := services.UnbindTenantUser(row.ID); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 双写:删除认证中心中的企业绑定
|
||
removeTenantUserFromAuth(row)
|
||
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// ChangePassword 修改后台租户用户密码
|
||
// POST /backend/changePassword
|
||
func (c *BackendAdminUserController) ChangePassword() {
|
||
var p backendChangePasswordPayload
|
||
|
||
raw, _ := io.ReadAll(c.Ctx.Request.Body)
|
||
if err := json.Unmarshal(raw, &p); err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "参数错误"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if p.ID == 0 {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "id 不能为空"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
if strings.TrimSpace(p.Password) == "" {
|
||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "password 不能为空"}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
_, jwtTid, ok := c.requireBackendIdentity()
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
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()
|
||
return
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
_, err = models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
Filter("id", row.ID).
|
||
Update(map[string]interface{}{
|
||
"password": hashed,
|
||
})
|
||
|
||
if err != nil {
|
||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "修改失败: " + err.Error()}
|
||
_ = c.ServeJSON()
|
||
return
|
||
}
|
||
|
||
// 双写:同步新密码到统一认证中心(改密场景,显式允许更新身份层密码)
|
||
var updated models.SystemTenantUser
|
||
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", row.ID).One(&updated); e == nil {
|
||
syncTenantUserToAuth(&updated, true)
|
||
}
|
||
|
||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"}
|
||
_ = c.ServeJSON()
|
||
}
|