优化平台端租户端
This commit is contained in:
@@ -22,6 +22,7 @@ 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"`
|
||||
@@ -40,6 +41,7 @@ type backendUserInfoDTO struct {
|
||||
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"`
|
||||
@@ -95,6 +97,7 @@ func toBackendUserInfoDTO(u models.SystemTenantUser) backendUserInfoDTO {
|
||||
ID: u.ID,
|
||||
Tid: u.Tid,
|
||||
Uid: u.Uid,
|
||||
GroupID: u.GroupID,
|
||||
Account: u.Account,
|
||||
Name: u.Name,
|
||||
Phone: u.Phone,
|
||||
@@ -395,6 +398,23 @@ func (c *BackendAdminUserController) AddUser() {
|
||||
_ = services.SetDefaultTenant(p.Uid, p.Tid)
|
||||
}
|
||||
|
||||
// 关联角色(group_id):0 表示未分配,前端展示为"未分配/全权限"
|
||||
if p.GroupID != nil && *p.GroupID > 0 {
|
||||
models.EnsureTenantUserGroupColumn()
|
||||
_, _ = 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(p.Tid); err == nil && adminRoleID > 0 {
|
||||
models.EnsureTenantUserGroupColumn()
|
||||
_, _ = models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("id", id).
|
||||
Update(map[string]interface{}{"group_id": adminRoleID})
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{
|
||||
"code": 200,
|
||||
"msg": "success",
|
||||
@@ -493,6 +513,10 @@ func (c *BackendAdminUserController) EditUser() {
|
||||
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()
|
||||
|
||||
@@ -77,6 +77,9 @@ func (c *BackendMenuController) GetMenu() {
|
||||
}
|
||||
|
||||
func (c *BackendMenuController) GetBackendMenu() {
|
||||
// 确保租户用户 group_id 列存在(老库兼容)
|
||||
models.EnsureTenantUserGroupColumn()
|
||||
|
||||
var menus []models.SystemMenu
|
||||
_, err := models.Orm.QueryTable(new(models.SystemMenu)).Filter("status", 1).All(&menus)
|
||||
if err != nil {
|
||||
@@ -84,10 +87,105 @@ func (c *BackendMenuController) GetBackendMenu() {
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(filterMenusByView(menus, 2), 0)}
|
||||
backendMenus := filterMenusByView(menus, 2)
|
||||
|
||||
// 按当前登录用户角色过滤可见功能菜单:
|
||||
// 角色 rights 为空(或未分配角色)→ 全权限,返回全部后端菜单;
|
||||
// 否则仅返回角色允许访问的菜单(含其祖先节点,保证树结构完整)。
|
||||
idStr := c.Ctx.Input.Param(":id")
|
||||
uid, _ := strconv.ParseUint(idStr, 10, 64)
|
||||
if uid > 0 {
|
||||
var tu models.SystemTenantUser
|
||||
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("id", uid).
|
||||
Filter("delete_time__isnull", true).
|
||||
One(&tu); e == nil && tu.GroupID > 0 {
|
||||
var role models.AdminRole
|
||||
if re := models.Orm.QueryTable(new(models.AdminRole)).
|
||||
Filter("id", tu.GroupID).
|
||||
Filter("cid", 2).
|
||||
One(&role); re == nil {
|
||||
backendMenus = filterMenusByRights(backendMenus, role.Rights)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": buildMenuTree(backendMenus, 0)}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// parseRightsToSet 将角色 rights 解析为菜单 ID 集合(兼容 JSON 数组 / 逗号分隔)
|
||||
func parseRightsToSet(raw string) map[uint64]bool {
|
||||
set := map[uint64]bool{}
|
||||
var arr []string
|
||||
if err := json.Unmarshal([]byte(raw), &arr); err == nil {
|
||||
for _, s := range arr {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if n, e := strconv.ParseUint(s, 10, 64); e == nil {
|
||||
set[n] = true
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
for _, s := range strings.Split(raw, ",") {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
if n, e := strconv.ParseUint(s, 10, 64); e == nil {
|
||||
set[n] = true
|
||||
}
|
||||
}
|
||||
return set
|
||||
}
|
||||
|
||||
// filterMenusByRights 按角色 rights(菜单 ID 集合)过滤菜单,并补全祖先节点,保证树结构完整。
|
||||
// rights 为空(nil / 空串)→ 原样返回(全权限)。
|
||||
func filterMenusByRights(menus []models.SystemMenu, rights *string) []models.SystemMenu {
|
||||
if rights == nil || strings.TrimSpace(*rights) == "" {
|
||||
return menus
|
||||
}
|
||||
allowed := parseRightsToSet(*rights)
|
||||
if len(allowed) == 0 {
|
||||
return menus
|
||||
}
|
||||
|
||||
pidOf := make(map[uint64]int64, len(menus))
|
||||
byID := make(map[uint64]models.SystemMenu, len(menus))
|
||||
for _, m := range menus {
|
||||
pidOf[m.ID] = m.Pid
|
||||
byID[m.ID] = m
|
||||
}
|
||||
|
||||
final := make(map[uint64]bool, len(allowed))
|
||||
for id := range allowed {
|
||||
cur := id
|
||||
for {
|
||||
if _, ok := byID[cur]; !ok {
|
||||
break
|
||||
}
|
||||
if final[cur] {
|
||||
break
|
||||
}
|
||||
final[cur] = true
|
||||
p := pidOf[cur]
|
||||
if p == 0 {
|
||||
break
|
||||
}
|
||||
cur = uint64(p)
|
||||
}
|
||||
}
|
||||
|
||||
out := make([]models.SystemMenu, 0, len(final))
|
||||
for id := range final {
|
||||
out = append(out, byID[id])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *BackendMenuController) GetTenantList() {
|
||||
var tid uint64
|
||||
if jwtTid := c.Ctx.Input.GetData("tid"); jwtTid != nil {
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/jwtutil"
|
||||
|
||||
beego "github.com/beego/beego/v2/server/web"
|
||||
)
|
||||
|
||||
// BackendRoleController 租户端(backend)角色管理(yz_system_admin_role, cid=2)
|
||||
// 角色按租户隔离:所有读写均以当前登录租户(JWT 中的 tenant_id)为边界,
|
||||
// 租户仅能查看/管理自己名下的角色,无法越权访问其他租户数据。
|
||||
type BackendRoleController struct {
|
||||
beego.Controller
|
||||
}
|
||||
|
||||
type backendRolePayload struct {
|
||||
Name string `json:"name"`
|
||||
Status *uint8 `json:"status"`
|
||||
Rights interface{} `json:"rights"`
|
||||
}
|
||||
|
||||
// currentTenantID 从 Bearer Token 解析当前登录租户 ID。
|
||||
// 返回 (tid, true) 表示成功;失败时已写入 401/403 响应并返回 (0, false)。
|
||||
func (c *BackendRoleController) currentTenantID() (uint64, bool) {
|
||||
authHeader := c.Ctx.Request.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "未登录"}
|
||||
_ = c.ServeJSON()
|
||||
return 0, false
|
||||
}
|
||||
authParts := strings.SplitN(authHeader, " ", 2)
|
||||
if len(authParts) != 2 || authParts[0] != "Bearer" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "认证信息格式错误"}
|
||||
_ = c.ServeJSON()
|
||||
return 0, false
|
||||
}
|
||||
claims, err := jwtutil.ParseToken(authParts[1])
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "无效的token"}
|
||||
_ = c.ServeJSON()
|
||||
return 0, false
|
||||
}
|
||||
if claims.UserType != "backend" && claims.UserType != "app" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 403, "msg": "无权访问"}
|
||||
_ = c.ServeJSON()
|
||||
return 0, false
|
||||
}
|
||||
if claims.TenantId <= 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 401, "msg": "租户信息缺失"}
|
||||
_ = c.ServeJSON()
|
||||
return 0, false
|
||||
}
|
||||
return uint64(claims.TenantId), true
|
||||
}
|
||||
|
||||
// GetAllRoles 获取当前租户的角色列表
|
||||
// GET /backend/allRoles
|
||||
func (c *BackendRoleController) GetAllRoles() {
|
||||
tid, ok := c.currentTenantID()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// 懒加载补齐当前租户的默认角色(租户管理员/部门负责人/员工)
|
||||
models.EnsureDefaultTenantRoles(tid)
|
||||
|
||||
var rows []models.AdminRole
|
||||
_, err := models.Orm.QueryTable(new(models.AdminRole)).
|
||||
Filter("cid", 2).
|
||||
Filter("tenant_id", tid).
|
||||
OrderBy("-id").
|
||||
All(&rows)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "查询失败"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": rows}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// GetRoleByID 获取当前租户的角色详情
|
||||
// GET /backend/roles/:id
|
||||
func (c *BackendRoleController) GetRoleByID() {
|
||||
tid, ok := c.currentTenantID()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
var role models.AdminRole
|
||||
if err := models.Orm.QueryTable(new(models.AdminRole)).
|
||||
Filter("id", id).
|
||||
Filter("cid", 2).
|
||||
Filter("tenant_id", tid).
|
||||
One(&role); 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": role}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// CreateRole 创建当前租户的角色
|
||||
// POST /backend/roles
|
||||
func (c *BackendRoleController) CreateRole() {
|
||||
tid, ok := c.currentTenantID()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var p backendRolePayload
|
||||
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
|
||||
}
|
||||
p.Name = strings.TrimSpace(p.Name)
|
||||
if p.Name == "" {
|
||||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "name 不能为空"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
status := uint8(1)
|
||||
if p.Status != nil {
|
||||
status = *p.Status
|
||||
}
|
||||
rights := normalizeRights(p.Rights)
|
||||
role := &models.AdminRole{
|
||||
TenantID: tid,
|
||||
Cid: 2,
|
||||
Name: p.Name,
|
||||
Status: status,
|
||||
Rights: rights,
|
||||
}
|
||||
id, err := models.Orm.Insert(role)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "创建失败"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// UpdateRole 更新当前租户的角色
|
||||
// PUT /backend/roles/:id
|
||||
func (c *BackendRoleController) UpdateRole() {
|
||||
tid, ok := c.currentTenantID()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
var p backendRolePayload
|
||||
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
|
||||
}
|
||||
|
||||
update := map[string]interface{}{}
|
||||
if strings.TrimSpace(p.Name) != "" {
|
||||
update["name"] = strings.TrimSpace(p.Name)
|
||||
}
|
||||
if p.Status != nil {
|
||||
update["status"] = *p.Status
|
||||
}
|
||||
if p.Rights != nil {
|
||||
update["rights"] = normalizeRights(p.Rights)
|
||||
}
|
||||
if len(update) == 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 400, "msg": "无更新字段"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
|
||||
// 仅允许更新本租户名下的角色,防止越权改到其他租户
|
||||
cnt, err := models.Orm.QueryTable(new(models.AdminRole)).
|
||||
Filter("id", id).
|
||||
Filter("cid", 2).
|
||||
Filter("tenant_id", tid).
|
||||
Update(update)
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "更新失败"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if cnt == 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "角色不存在"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
|
||||
// DeleteRole 删除当前租户的角色
|
||||
// DELETE /backend/roles/:id
|
||||
func (c *BackendRoleController) DeleteRole() {
|
||||
tid, ok := c.currentTenantID()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
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
|
||||
}
|
||||
// 仅允许删除本租户名下的角色
|
||||
cnt, err := models.Orm.QueryTable(new(models.AdminRole)).
|
||||
Filter("id", id).
|
||||
Filter("cid", 2).
|
||||
Filter("tenant_id", tid).
|
||||
Delete()
|
||||
if err != nil {
|
||||
c.Data["json"] = map[string]interface{}{"code": 500, "msg": "删除失败"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
if cnt == 0 {
|
||||
c.Data["json"] = map[string]interface{}{"code": 404, "msg": "角色不存在"}
|
||||
_ = c.ServeJSON()
|
||||
return
|
||||
}
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success"}
|
||||
_ = c.ServeJSON()
|
||||
}
|
||||
@@ -202,6 +202,14 @@ func (c *PlatformTenantUserController) CreateTenantUser() {
|
||||
}
|
||||
if isDefault == 1 {
|
||||
_ = services.SetDefaultTenant(p.Uid, p.Tid)
|
||||
// 租户第一个账号(普通租户)即租户管理员:自动绑定「租户管理员」角色,
|
||||
// 使"普通租户"与"租户管理员"合二为一(均可登录 backend 并拥有全权限)。
|
||||
if adminRoleID, err := models.GetTenantAdminRole(p.Tid); err == nil && adminRoleID > 0 {
|
||||
models.EnsureTenantUserGroupColumn()
|
||||
_, _ = models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||||
Filter("id", id).
|
||||
Update(map[string]interface{}{"group_id": adminRoleID})
|
||||
}
|
||||
}
|
||||
|
||||
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": map[string]interface{}{"id": id}}
|
||||
|
||||
Reference in New Issue
Block a user