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") }