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

437 lines
12 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"
"errors"
"fmt"
"io"
"log"
"math/rand"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/passwordutil"
"server/services"
authsvc "server/services/auth"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// syncTenantUserToAuth 把租户用户同步到统一认证中心(双写)。
// 同步失败只记日志,不影响平台端主流程(认证中心为新增能力)。
func syncTenantUserToAuth(row *models.SystemTenantUser) {
if row == nil {
return
}
in := authsvc.SyncTenantUserInput{
Tid: row.Tid,
GroupID: row.GroupID,
OrgID: row.OrgID,
Status: row.Status,
IsDefault: row.IsDefault,
}
if row.Account != nil {
in.Account = strings.TrimSpace(*row.Account)
}
if row.Name != nil {
in.Name = strings.TrimSpace(*row.Name)
}
if row.Phone != nil {
in.Phone = strings.TrimSpace(*row.Phone)
}
if row.Email != nil {
in.Email = strings.TrimSpace(*row.Email)
}
if row.Password != nil {
in.PasswordHash = *row.Password
}
if err := authsvc.SyncTenantUser(in); err != nil {
log.Printf("[auth] 同步租户用户到认证中心失败 tid=%d account=%s: %v", row.Tid, in.Account, err)
}
}
// removeTenantUserFromAuth 删除认证中心中的企业绑定(保留身份)
func removeTenantUserFromAuth(row *models.SystemTenantUser) {
if row == nil {
return
}
account, phone, email := "", "", ""
if row.Account != nil {
account = strings.TrimSpace(*row.Account)
}
if row.Phone != nil {
phone = strings.TrimSpace(*row.Phone)
}
if row.Email != nil {
email = strings.TrimSpace(*row.Email)
}
if err := authsvc.RemoveTenantUser(row.Tid, account, phone, email); err != nil {
log.Printf("[auth] 删除认证中心企业绑定失败 tid=%d account=%s: %v", row.Tid, account, err)
}
}
// PlatformTenantUserController 平台租户用户绑定管理
type PlatformTenantUserController struct {
beego.Controller
}
type tenantUserPayload struct {
Tid uint64 `json:"tid"`
Uid uint64 `json:"uid"`
Account *string `json:"account"`
Name *string `json:"name"`
Phone *string `json:"phone"`
Email *string `json:"email"`
Password *string `json:"password"`
IsDefault *int8 `json:"is_default"`
Status *int8 `json:"status"`
OrgID *uint64 `json:"org_id"`
Remark *string `json:"remark"`
}
// GetTenantUserList 获取绑定列表(支持按 tid / uid 过滤,keyword 对姓名/手机/邮箱/账号模糊匹配)
// GET /platform/tenantUser/list?tid=1&uid=2&keyword=xxx
func (c *PlatformTenantUserController) GetTenantUserList() {
tid, _ := c.GetUint64("tid")
uid, _ := c.GetUint64("uid")
keyword := strings.TrimSpace(c.GetString("keyword"))
qs := models.Orm.QueryTable(new(models.SystemTenantUser))
var cond *orm.Condition
needCond := false
if tid > 0 {
if cond == nil {
cond = orm.NewCondition()
}
cond = cond.And("tid", tid)
needCond = true
}
if uid > 0 {
if cond == nil {
cond = orm.NewCondition()
}
cond = cond.And("uid", uid)
needCond = true
}
if keyword != "" {
kwCond := orm.NewCondition()
kwCond = kwCond.Or("name__icontains", keyword).
Or("phone__icontains", keyword).
Or("email__icontains", keyword).
Or("account__icontains", keyword)
if cond == nil {
cond = kwCond
} else {
cond = cond.AndCond(kwCond)
}
needCond = true
}
if needCond {
qs = qs.SetCond(cond)
}
var rows []models.SystemTenantUser
_, err := qs.OrderBy("-is_default", "-id").All(&rows)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"list": rows,
"total": len(rows),
},
}
_ = c.ServeJSON()
}
// GetTenantUsersByTid 兼容旧路由,根据租户 ID 获取租户用户列表
// GET /platform/getTenantUsers/:tid
func (c *PlatformTenantUserController) GetTenantUsersByTid() {
tidStr := c.Ctx.Input.Param(":tid")
tid, _ := strconv.ParseUint(tidStr, 10, 64)
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).
OrderBy("-is_default", "-id").
All(&rows)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败: " + err.Error()}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{"list": rows, "total": len(rows)},
}
_ = c.ServeJSON()
}
// GetTenantUserDetail 获取绑定详情
// GET /platform/tenantUser/detail/:id
func (c *PlatformTenantUserController) GetTenantUserDetail() {
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"}
_ = c.ServeJSON()
return
}
var row models.SystemTenantUser
err = models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).One(&row)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "记录不存在"}
_ = c.ServeJSON()
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": row}
_ = c.ServeJSON()
}
// CreateTenantUser 创建租户用户绑定(写入 yz_system_tenant_user;uid 为空时自动生成)
// POST /platform/tenantUser/create
func (c *PlatformTenantUserController) CreateTenantUser() {
p, ok := c.parsePayload()
if !ok {
return
}
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
}
hashed, err := passwordutil.Hash(*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
}
// 用户数配额校验:租户用户数达到上限时不允许新增(增购用户数后放开)
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
}
isDefault := int8(0)
status := int8(1)
if p.IsDefault != nil {
isDefault = *p.IsDefault
}
if p.Status != nil {
status = *p.Status
}
id, err := services.BindTenantUser(p.Tid, p.Uid, p.Account, p.Name, p.Phone, p.Email, nil, nil, p.Password, isDefault, status, p.Remark)
if err != nil {
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败: " + err.Error()}
_ = c.ServeJSON()
return
}
if p.OrgID != nil && *p.OrgID > 0 {
_, _ = models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("id", id).
Update(map[string]interface{}{"org_id": *p.OrgID})
}
if isDefault == 1 {
_ = services.SetDefaultTenant(p.Uid, p.Tid)
// 租户的第一个账号即租户管理员:自动绑定全局「租户管理员」角色(拥有 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/org_id 已落库)
var created models.SystemTenantUser
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).One(&created); err == nil {
syncTenantUserToAuth(&created)
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}}
_ = c.ServeJSON()
}
// EditTenantUser 编辑绑定
// POST /platform/tenantUser/edit/:id
func (c *PlatformTenantUserController) EditTenantUser() {
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"}
_ = c.ServeJSON()
return
}
p, ok := c.parsePayload()
if !ok {
return
}
update := map[string]interface{}{}
if p.Tid > 0 {
update["tid"] = p.Tid
}
if p.Uid > 0 {
update["uid"] = p.Uid
}
if p.Account != nil {
update["account"] = p.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.Password != nil {
hashed, err := passwordutil.Hash(*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.OrgID != nil {
update["org_id"] = *p.OrgID
}
if p.Remark != nil {
update["remark"] = p.Remark
}
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", 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 && p.Uid > 0 && p.Tid > 0 {
_ = services.SetDefaultTenant(p.Uid, p.Tid)
}
// 双写:同步到统一认证中心
var updated models.SystemTenantUser
if err := models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).One(&updated); err == nil {
syncTenantUserToAuth(&updated)
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
_ = c.ServeJSON()
}
// DeleteTenantUser 删除绑定
// DELETE /platform/tenantUser/delete/:id
func (c *PlatformTenantUserController) DeleteTenantUser() {
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无效ID"}
_ = c.ServeJSON()
return
}
// 删除前先读出记录,供认证中心侧定位(删除后就读不到了)
var row models.SystemTenantUser
_ = models.Orm.QueryTable(new(models.SystemTenantUser)).Filter("id", id).One(&row)
if err := services.UnbindTenantUser(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()
}
func (c *PlatformTenantUserController) parsePayload() (tenantUserPayload, bool) {
var p tenantUserPayload
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 tenantUserPayload{}, false
}
return p, true
}
func generateTenantUID(tid uint64) (uint64, error) {
rand.Seed(time.Now().UnixNano())
for i := 0; i < 8; i++ {
uid := uint64(10000000 + rand.Intn(90000000))
cnt, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tid).
Filter("uid", uid).
Count()
if err != nil {
return 0, err
}
if cnt == 0 {
return uid, nil
}
}
return 0, errors.New("uid collision")
}