Files
yunzerwebsiteallinone/go/controllers/backend_admin_user.go
T
2026-09-19 21:44:04 +08:00

669 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controllers
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"server/models"
"server/pkg/passwordutil"
"server/services"
"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"`
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"`
}
type backendChangePasswordPayload struct {
ID uint64 `json:"id"`
Password string `json:"password"`
}
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
}
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,
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,
}
}
func (c *BackendAdminUserController) getJWTUidTid() (uint64, uint64) {
var uid uint64
var tid uint64
data := c.Ctx.Input.Data()
if jwtUid := data["uid"]; jwtUid != nil {
if v, ok := jwtUid.(uint64); ok {
uid = v
}
}
if jwtTid := data["tid"]; jwtTid != nil {
if v, ok := jwtTid.(uint64); ok {
tid = v
}
}
return uid, tid
}
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
}
func findBackendTenantUser(idOrUid uint64, jwtTid uint64) (*models.SystemTenantUser, error) {
var row models.SystemTenantUser
qs := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("delete_time__isnull", true)
if jwtTid > 0 {
qs = qs.Filter("tid", jwtTid)
}
err := qs.Filter("id", idOrUid).One(&row)
if err == nil {
return &row, nil
}
qs = models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("delete_time__isnull", true)
if jwtTid > 0 {
qs = qs.Filter("tid", jwtTid)
}
err = qs.Filter("uid", idOrUid).One(&row)
if err != nil {
return nil, err
}
return &row, nil
}
// GetAllUsers 获取当前租户后台用户列表
// GET /backend/getAllUsers
func (c *BackendAdminUserController) GetAllUsers() {
_, jwtTid := c.getJWTUidTid()
keyword := strings.TrimSpace(c.GetString("keyword"))
tid, _ := c.GetUint64("tid")
if jwtTid > 0 {
tid = jwtTid
}
cond := orm.NewCondition().And("delete_time__isnull", true)
if tid > 0 {
cond = cond.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 := c.getJWTUidTid()
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
func (c *BackendAdminUserController) GetUserInfo() {
jwtUid, jwtTid := c.getJWTUidTid()
idStr := c.Ctx.Input.Param(":id")
id, _ := strconv.ParseUint(idStr, 10, 64)
if id == 0 {
id = jwtUid
}
if id == 0 {
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录或非法请求"}
_ = c.ServeJSON()
return
}
u, err := findBackendTenantUser(id, jwtTid)
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 := c.getJWTUidTid()
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
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, err := generateTenantUID(p.Tid)
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)
}
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 := c.getJWTUidTid()
row, err := findBackendTenantUser(id, jwtTid)
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)
}
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 := c.getJWTUidTid()
row, err := findBackendTenantUser(id, jwtTid)
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 := c.getJWTUidTid()
row, err := findBackendTenantUser(p.ID, jwtTid)
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)
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "修改成功"}
_ = c.ServeJSON()
}