diff --git a/features/udm_user/repo/repo_udm_sub_user.go b/features/udm_user/repo/repo_udm_sub_user.go index bb38dd2c..428d7a38 100644 --- a/features/udm_user/repo/repo_udm_sub_user.go +++ b/features/udm_user/repo/repo_udm_sub_user.go @@ -208,7 +208,7 @@ func (r *RepoUdmSubUser) ClearAndInsert(neID string, subArr []model.UdmSubUser) batch := subArr[i:end] // multi-tenancy - r.SetTenantName(batch) + r.SetTenantName(&batch) // 调用 InsertMulti 函数将批量数据插入数据库 results, err := datasource.DefaultDB().Table("u_sub_user").InsertMulti(batch) @@ -253,7 +253,7 @@ func (r *RepoUdmSubUser) Inserts(subUser []model.UdmSubUser) int64 { batch := subUser[i:end] // multi-tenancy - r.SetTenantName(batch) + r.SetTenantName(&batch) // 调用 InsertMulti 函数将批量数据插入数据库 results, err := datasource.DefaultDB().Table("u_sub_user").InsertMulti(batch) @@ -285,7 +285,7 @@ func (r *RepoUdmSubUser) Insert4G(neID string, subUser model.UdmSubUser) int64 { // multi-tenancy subUserSlice := []model.UdmSubUser{subUser} - r.SetTenantName(subUserSlice) + r.SetTenantName(&subUserSlice) results, err := datasource.DefaultDB().Table("u_sub_user").Insert(subUser) if err == nil { @@ -475,15 +475,18 @@ func (r *RepoUdmSubUser) Deletes(neID, imsi, num string) int64 { } // multi-tenancy solution, get tenant_name by imsi -func (r *RepoUdmSubUser) SetTenantName(subArr []model.UdmSubUser) { - for s := 0; s < len(subArr); s++ { +func (r *RepoUdmSubUser) SetTenantName(subArr *[]model.UdmSubUser) { + for s := 0; s < len(*subArr); s++ { var tenantName string err := datasource.DefaultDB().Table("sys_tenant_map"). - Where("mapping_type='udm_sub' and mapping_key='?'", subArr[s].Imsi).Cols("tenant_name").Find(tenantName) + Where("mapping_type='udm_sub' and mapping_key='?'", (*subArr)[s].Imsi).Cols("tenant_name").Find(tenantName) if err != nil { log.Errorf("Find tenant_name err => %v", err) continue } - subArr[s].TenantName = tenantName + if tenantName != "" { + log.Infof("tenantName=%s imsi=%s", tenantName, (*subArr)[s].Imsi) + } + (*subArr)[s].TenantName = tenantName } } diff --git a/src/framework/vo/treeselect.go b/src/framework/vo/treeselect.go index 01fcf895..704304f6 100644 --- a/src/framework/vo/treeselect.go +++ b/src/framework/vo/treeselect.go @@ -49,3 +49,21 @@ func SysDeptTreeSelect(sysDept systemModel.SysDept) TreeSelect { return t } + +// SysDeptTreeSelect 使用给定的 SysDept 对象解析为 TreeSelect 对象 +func SysTenantTreeSelect(sysTenant systemModel.SysTenant) TreeSelect { + t := TreeSelect{} + t.ID = sysTenant.TenantID + t.Label = sysTenant.TenantName + + if len(sysTenant.Children) > 0 { + for _, tenant := range sysTenant.Children { + child := SysTenantTreeSelect(tenant) + t.Children = append(t.Children, child) + } + } else { + t.Children = []TreeSelect{} + } + + return t +} diff --git a/src/modules/network_element/repository/udm_sub.impl.go b/src/modules/network_element/repository/udm_sub.impl.go index 997c6acb..851e25f3 100644 --- a/src/modules/network_element/repository/udm_sub.impl.go +++ b/src/modules/network_element/repository/udm_sub.impl.go @@ -1,6 +1,7 @@ package repository import ( + "fmt" "strconv" "strings" @@ -40,7 +41,7 @@ var NewUDMSubImpl = &UDMSubImpl{ "context_id": "ContextId", "apn_context": "ApnContext", "static_ip": "StaticIp", - "tenant_name": "TenantName", // Tenant ID for multi-tenancy + "tenant_name": "TenantName", // Tenant name for multi-tenancy }, } @@ -97,8 +98,9 @@ func (r *UDMSubImpl) SelectPage(query map[string]any) map[string]any { } // for multi-tenancy solution if v, ok := query["tenantName"]; ok && v != "" { - conditions = append(conditions, "tenant_name = '?'") + conditions = append(conditions, "tenant_name = ?") params = append(params, v) + fmt.Printf("tenantName = %s", v) } // 构建查询条件语句 diff --git a/src/modules/system/controller/sys_tenant.go b/src/modules/system/controller/sys_tenant.go new file mode 100644 index 00000000..5342a307 --- /dev/null +++ b/src/modules/system/controller/sys_tenant.go @@ -0,0 +1,381 @@ +package controller + +import ( + "strings" + + "be.ems/src/framework/constants/common" + "be.ems/src/framework/i18n" + "be.ems/src/framework/utils/ctx" + "be.ems/src/framework/vo" + "be.ems/src/framework/vo/result" + "be.ems/src/modules/system/model" + "be.ems/src/modules/system/service" + + "github.com/gin-gonic/gin" + "github.com/gin-gonic/gin/binding" +) + +// 实例化控制层 SysTenantController 结构体 +var NewSysTenant = &SysTenantController{ + sysTenantService: service.NewSysTenantImpl, +} + +// 租户信息 +// +// PATH /system/tenant +type SysTenantController struct { + // 租户服务 + sysTenantService service.ISysTenant +} + +// 租户列表 +// +// GET /list +func (s *SysTenantController) List(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys struct { + // 租户ID + TenantID string `form:"tenantId"` + // 父租户ID + ParentID string `form:"parentId" ` + // 租户名称 + TenantName string `form:"tenantName" ` + // 租户状态(0正常 1停用) + Status string `form:"status"` + } + err := c.ShouldBindQuery(&querys) + if err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 多语言值转key查询 + if querys.TenantName != "" { + querys.TenantName = i18n.TFindKeyPrefix(language, "tenant", querys.TenantName) + } + + SysTenantController := model.SysTenant{ + TenantID: querys.TenantID, + ParentID: querys.ParentID, + TenantName: querys.TenantName, + Status: querys.Status, + } + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysTenantService.SelectTenantList(SysTenantController, dataScopeSQL) + + // 闭包函数处理多语言 + converI18n := func(language string, arr *[]model.SysTenant) { + for i := range *arr { + (*arr)[i].TenantName = i18n.TKey(language, (*arr)[i].TenantName) + } + } + converI18n(language, &data) + + c.JSON(200, result.OkData(data)) +} + +// 租户信息 +// +// GET /:tenantId +func (s *SysTenantController) Info(c *gin.Context) { + language := ctx.AcceptLanguage(c) + tenantId := c.Param("tenantId") + if tenantId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + data := s.sysTenantService.SelectTenantById(tenantId) + if data.TenantID == tenantId { + // 处理多语言 + data.TenantName = i18n.TKey(language, data.TenantName) + c.JSON(200, result.OkData(data)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 租户新增 +// +// POST / +func (s *SysTenantController) Add(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body model.SysTenant + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.TenantID != "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 父级ID不为0是要检查 + if body.ParentID != "0" { + tenantParent := s.sysTenantService.SelectTenantById(body.ParentID) + if tenantParent.TenantID != body.ParentID { + // 没有可访问租户数据! + c.JSON(200, result.ErrMsg(i18n.TKey(language, "tenant.noData"))) + return + } + if tenantParent.Status == common.STATUS_NO { + // 上级租户【%s】停用,不允许新增 + msg := i18n.TTemplate(language, "tenant.errParentStatus", map[string]any{"name": tenantParent.TenantName}) + c.JSON(200, result.ErrMsg(msg)) + return + } + if tenantParent.DelFlag == common.STATUS_YES { + // 上级租户【%s】已删除,不允许新增 + msg := i18n.TTemplate(language, "tenant.errParentDelFlag", map[string]any{"name": tenantParent.TenantName}) + c.JSON(200, result.ErrMsg(msg)) + return + } + body.Ancestors = tenantParent.Ancestors + "," + body.ParentID + } else { + body.Ancestors = "0" + } + + // 检查同级下名称唯一 + uniqueTenantName := s.sysTenantService.CheckUniqueTenantName(body.TenantName, body.ParentID, "") + if !uniqueTenantName { + // 租户新增【%s】失败,租户名称已存在 + msg := i18n.TTemplate(language, "tenant.errNameExists", map[string]any{"name": body.TenantName}) + c.JSON(200, result.ErrMsg(msg)) + return + } + + body.CreateBy = ctx.LoginUserToUserName(c) + insertId := s.sysTenantService.InsertTenant(body) + if insertId != "" { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 租户修改 +// +// PUT / +func (s *SysTenantController) Edit(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var body model.SysTenant + err := c.ShouldBindBodyWith(&body, binding.JSON) + if err != nil || body.TenantID == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 上级租户不能选自己 + if body.TenantID == body.ParentID { + // 租户修改【%s】失败,上级租户不能是自己 + msg := i18n.TTemplate(language, "tenant.errParentID", map[string]any{"name": body.TenantName}) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查数据是否存在 + tenantInfo := s.sysTenantService.SelectTenantById(body.TenantID) + if tenantInfo.TenantID != body.TenantID { + // 没有可访问租户数据! + c.JSON(200, result.ErrMsg(i18n.TKey(language, "tenant.noData"))) + return + } + // 父级ID不为0是要检查 + if body.ParentID != "0" { + tenantParent := s.sysTenantService.SelectTenantById(body.ParentID) + if tenantParent.TenantID != body.ParentID { + // 没有可访问租户数据! + c.JSON(200, result.ErrMsg(i18n.TKey(language, "tenant.noData"))) + return + } + } + + // 检查同级下名称唯一 + uniqueTenantName := s.sysTenantService.CheckUniqueTenantName(body.TenantName, body.ParentID, body.TenantID) + if !uniqueTenantName { + // 租户修改【%s】失败,租户名称已存在 + msg := i18n.TTemplate(language, "tenant.errNameExists", map[string]any{"name": body.TenantName}) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 上级停用需要检查下级是否有在使用 + if body.Status == common.STATUS_NO { + hasChild := s.sysTenantService.HasChildByTenantId(body.TenantID) + if hasChild > 0 { + // 该租户包含未停用的子租户数量:%d + msg := i18n.TTemplate(language, "tenant.errHasChildUse", map[string]any{"num": hasChild}) + c.JSON(200, result.ErrMsg(msg)) + return + } + } + + // 多语言非原始值 + i18nValue := i18n.TKey(language, tenantInfo.TenantName) + if i18nValue != tenantInfo.TenantName { + i18n.UpdateKeyValue(language, tenantInfo.TenantName, body.TenantName) + body.TenantName = tenantInfo.TenantName + } + + body.UpdateBy = ctx.LoginUserToUserName(c) + rows := s.sysTenantService.UpdateTenant(body) + if rows > 0 { + c.JSON(200, result.Ok(nil)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 租户删除 +// +// DELETE /:tenantId +func (s *SysTenantController) Remove(c *gin.Context) { + language := ctx.AcceptLanguage(c) + tenantId := c.Param("tenantId") + if tenantId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + // 检查数据是否存在 + tenant := s.sysTenantService.SelectTenantById(tenantId) + if tenant.TenantID != tenantId { + // 没有可访问租户数据! + c.JSON(200, result.ErrMsg(i18n.TKey(language, "tenant.noData"))) + return + } + + // 检查是否存在子租户 + hasChild := s.sysTenantService.HasChildByTenantId(tenantId) + if hasChild > 0 { + // 不允许删除,存在子租户数:%d + msg := i18n.TTemplate(language, "tenant.errHasChildUse", map[string]any{"num": hasChild}) + c.JSON(200, result.ErrMsg(msg)) + return + } + + // 检查是否分配给用户 + existUser := s.sysTenantService.CheckTenantExistUser(tenantId) + if existUser > 0 { + // 不允许删除,租户已分配给用户数:%d + msg := i18n.TTemplate(language, "tenant.errHasUserUse", map[string]any{"num": existUser}) + c.JSON(200, result.ErrMsg(msg)) + return + } + + rows := s.sysTenantService.DeleteTenantById(tenantId) + if rows > 0 { + // 删除成功:%d + msg := i18n.TTemplate(language, "app.common.deleteSuccess", map[string]any{"num": rows}) + c.JSON(200, result.OkMsg(msg)) + return + } + c.JSON(200, result.Err(nil)) +} + +// 租户列表(排除节点) +// +// GET /list/exclude/:tenantId +func (s *SysTenantController) ExcludeChild(c *gin.Context) { + language := ctx.AcceptLanguage(c) + tenantId := c.Param("tenantId") + if tenantId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + data := s.sysTenantService.SelectTenantList(model.SysTenant{}, dataScopeSQL) + + // 过滤排除节点 + filtered := make([]model.SysTenant, 0) + for _, tenant := range data { + hasAncestor := false + ancestorList := strings.Split(tenant.Ancestors, ",") + for _, ancestor := range ancestorList { + if ancestor == tenantId { + hasAncestor = true + break + } + } + if !(tenant.TenantID == tenantId || hasAncestor) { + tenant.TenantName = i18n.TKey(language, tenant.TenantName) + filtered = append(filtered, tenant) + } + } + c.JSON(200, result.OkData(filtered)) +} + +// 租户树结构列表 +// +// GET /treeSelect +func (s *SysTenantController) TreeSelect(c *gin.Context) { + language := ctx.AcceptLanguage(c) + var querys struct { + // 租户ID + TenantID string `json:"tenantId"` + // 父租户ID + ParentID string `json:"parentId" ` + // 租户名称 + TenantName string `json:"tenantName" ` + // 租户状态(0正常 1停用) + Status string `json:"status"` + } + err := c.ShouldBindQuery(&querys) + if err != nil { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + SysTenantController := model.SysTenant{ + TenantID: querys.TenantID, + ParentID: querys.ParentID, + TenantName: querys.TenantName, + Status: querys.Status, + } + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + tenantTreeSelect := s.sysTenantService.SelectTenantTreeSelect(SysTenantController, dataScopeSQL) + + // 闭包函数处理多语言 + var converI18n func(language string, arr *[]vo.TreeSelect) + converI18n = func(language string, arr *[]vo.TreeSelect) { + for i := range *arr { + (*arr)[i].Label = i18n.TKey(language, (*arr)[i].Label) + if len((*arr)[i].Children) > 0 { + converI18n(language, &(*arr)[i].Children) + } + } + } + converI18n(language, &tenantTreeSelect) + + c.JSON(200, result.OkData(tenantTreeSelect)) +} + +// 租户树结构列表(指定角色) +// +// GET /roleTenantTreeSelect/:roleId +func (s *SysTenantController) RoleTenantTreeSelect(c *gin.Context) { + language := ctx.AcceptLanguage(c) + roleId := c.Param("roleId") + if roleId == "" { + c.JSON(400, result.CodeMsg(400, i18n.TKey(language, "app.common.err400"))) + return + } + + dataScopeSQL := ctx.LoginUserToDataScopeSQL(c, "d", "") + tenantTreeSelect := s.sysTenantService.SelectTenantTreeSelect(model.SysTenant{}, dataScopeSQL) + checkedKeys := s.sysTenantService.SelectTenantListByRoleId(roleId) + + // 闭包函数处理多语言 + var converI18n func(language string, arr *[]vo.TreeSelect) + converI18n = func(language string, arr *[]vo.TreeSelect) { + for i := range *arr { + (*arr)[i].Label = i18n.TKey(language, (*arr)[i].Label) + if len((*arr)[i].Children) > 0 { + converI18n(language, &(*arr)[i].Children) + } + } + } + converI18n(language, &tenantTreeSelect) + + c.JSON(200, result.OkData(map[string]any{ + "tenants": tenantTreeSelect, + "checkedKeys": checkedKeys, + })) +} diff --git a/src/modules/system/model/sys_role.go b/src/modules/system/model/sys_role.go index c4a8d9eb..5de708b5 100644 --- a/src/modules/system/model/sys_role.go +++ b/src/modules/system/model/sys_role.go @@ -16,6 +16,8 @@ type SysRole struct { MenuCheckStrictly string `json:"menuCheckStrictly"` // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示) DeptCheckStrictly string `json:"deptCheckStrictly"` + // 部门树选择项是否关联显示(0:父子不互相关联显示 1:父子互相关联显示) + TenantCheckStrictly string `json:"tenantCheckStrictly"` // 角色状态(0停用 1正常) Status string `json:"status"` // 删除标志(0代表存在 1代表删除) @@ -37,4 +39,6 @@ type SysRole struct { MenuIds []string `json:"menuIds,omitempty"` // 部门组(数据权限) DeptIds []string `json:"deptIds,omitempty"` + // 租户权限 + TenantIds []string `json:"tenantIds,omitempty"` } diff --git a/src/modules/system/model/sys_role_tenant.go b/src/modules/system/model/sys_role_tenant.go new file mode 100644 index 00000000..4c522567 --- /dev/null +++ b/src/modules/system/model/sys_role_tenant.go @@ -0,0 +1,15 @@ +package model + +// SysRoleDept 角色和部门关联对象 sys_role_dept +type SysRoleTenant struct { + RoleID string `json:"roleId"` // 角色ID + TenantID string `json:"TenantID"` // 部门ID +} + +// NewSysRoleDept 创建角色和部门关联对象的构造函数 +func NewSysRoleTenant(roleID string, tenantID string) SysRoleTenant { + return SysRoleTenant{ + RoleID: roleID, + TenantID: tenantID, + } +} diff --git a/src/modules/system/model/sys_tenant.go b/src/modules/system/model/sys_tenant.go new file mode 100644 index 00000000..4023e0bf --- /dev/null +++ b/src/modules/system/model/sys_tenant.go @@ -0,0 +1,39 @@ +package model + +// SysTenant 部门对象 sys_Tenant +type SysTenant struct { + // 部门ID + TenantID string `json:"TenantId"` + // 父部门ID + ParentID string `json:"parentId" binding:"required"` + // 祖级列表 + Ancestors string `json:"ancestors"` + // 部门名称 + TenantName string `json:"TenantName" binding:"required"` + // 显示顺序 + OrderNum int `json:"orderNum"` + // tenancy type: sd-sst, apn, imsi, msisdn + Type string `json:"type"` + // tenancy key: key of sd-sst, apn, imsi, msisdn + Key string `json:"key"` + // 部门状态(0正常 1停用) + Status string `json:"status"` + // 删除标志(0代表存在 1代表删除) + DelFlag string `json:"delFlag"` + // 创建者 + CreateBy string `json:"createBy"` + // 创建时间 + CreateTime int64 `json:"createTime"` + // 更新者 + UpdateBy string `json:"updateBy"` + // 更新时间 + UpdateTime int64 `json:"updateTime"` + + // ====== 非数据库字段属性 ====== + + // 子部门列表 + Children []SysTenant `json:"children,omitempty"` + + // 父部门名称 + ParentName string `json:"parentName,omitempty"` +} diff --git a/src/modules/system/repository/sys_role_tenant.go b/src/modules/system/repository/sys_role_tenant.go new file mode 100644 index 00000000..3a401fcd --- /dev/null +++ b/src/modules/system/repository/sys_role_tenant.go @@ -0,0 +1,15 @@ +package repository + +import "be.ems/src/modules/system/model" + +// ISysRoleTenant 角色与部门关联表 数据层接口 +type ISysRoleTenant interface { + // DeleteRoleTenant 批量删除角色部门关联信息 + DeleteRoleTenant(roleIds []string) int64 + + // DeleteTenantRole 批量删除部门角色关联信息 + DeleteTenantRole(tenantIds []string) int64 + + // BatchRoleTenant 批量新增角色部门信息 + BatchRoleTenant(sysRoleTenants []model.SysRoleTenant) int64 +} diff --git a/src/modules/system/repository/sys_role_tenant.impl.go b/src/modules/system/repository/sys_role_tenant.impl.go new file mode 100644 index 00000000..0ce0bc8f --- /dev/null +++ b/src/modules/system/repository/sys_role_tenant.impl.go @@ -0,0 +1,58 @@ +package repository + +import ( + "fmt" + "strings" + + "be.ems/src/framework/datasource" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/repo" + "be.ems/src/modules/system/model" +) + +// 实例化数据层 SysRoleTenantImpl 结构体 +var NewSysRoleTenantImpl = &SysRoleTenantImpl{} + +// SysRoleTenantImpl 角色与部门关联表 数据层处理 +type SysRoleTenantImpl struct{} + +// DeleteRoleTenant 批量删除角色部门关联信息 +func (r *SysRoleTenantImpl) DeleteRoleTenant(roleIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(roleIds)) + sql := "delete from sys_role_tenant where role_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(roleIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteTenantRole 批量删除部门角色关联信息 +func (r *SysRoleTenantImpl) DeleteTenantRole(tenantIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(tenantIds)) + sql := "delete from sys_role_tenant where tenant_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(tenantIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// BatchRoleTenant 批量新增角色部门信息 +func (r *SysRoleTenantImpl) BatchRoleTenant(sysRoleTenants []model.SysRoleTenant) int64 { + keyValues := make([]string, 0) + for _, item := range sysRoleTenants { + keyValues = append(keyValues, fmt.Sprintf("(%s,%s)", item.RoleID, item.TenantID)) + } + sql := "insert into sys_role_tenant(role_id, tenant_id) values " + strings.Join(keyValues, ",") + results, err := datasource.ExecDB("", sql, nil) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/repository/sys_tenant.go b/src/modules/system/repository/sys_tenant.go new file mode 100644 index 00000000..ec2f1618 --- /dev/null +++ b/src/modules/system/repository/sys_tenant.go @@ -0,0 +1,42 @@ +package repository + +import "be.ems/src/modules/system/model" + +// ISysTenant 租户表 数据层接口 +type ISysTenant interface { + // SelectTenantList 查询租户管理数据 + SelectTenantList(sysTenant model.SysTenant, dataScopeSQL string) []model.SysTenant + + // SelectTenantListByRoleId 根据角色ID查询租户树信息 + SelectTenantListByRoleId(roleId string, tenantCheckStrictly bool) []string + + // SelectTenantById 根据租户ID查询信息 + SelectTenantById(tenantId string) model.SysTenant + + // SelectChildrenTenantById 根据ID查询所有子租户 + SelectChildrenTenantById(tenantId string) []model.SysTenant + + // HasChildByTenantId 是否存在子节点 + HasChildByTenantId(tenantId string) int64 + + // CheckTenantExistUser 查询租户是否存在用户 + CheckTenantExistUser(tenantId string) int64 + + // CheckUniqueTenant 校验租户是否唯一 + CheckUniqueTenant(sysTenant model.SysTenant) string + + // InsertTenant 新增租户信息 + InsertTenant(sysTenant model.SysTenant) string + + // UpdateTenant 修改租户信息 + UpdateTenant(sysTenant model.SysTenant) int64 + + // UpdateTenantStatusNormal 修改所在租户正常状态 + UpdateTenantStatusNormal(tenantIds []string) int64 + + // UpdateTenantChildren 修改子元素关系 + UpdateTenantChildren(sysTenants []model.SysTenant) int64 + + // DeleteTenantById 删除租户管理信息 + DeleteTenantById(tenantId string) int64 +} diff --git a/src/modules/system/repository/sys_tenant.impl.go b/src/modules/system/repository/sys_tenant.impl.go new file mode 100644 index 00000000..4d28c42b --- /dev/null +++ b/src/modules/system/repository/sys_tenant.impl.go @@ -0,0 +1,386 @@ +package repository + +import ( + "fmt" + "strings" + "time" + + "be.ems/src/framework/datasource" + "be.ems/src/framework/logger" + "be.ems/src/framework/utils/parse" + "be.ems/src/framework/utils/repo" + "be.ems/src/modules/system/model" +) + +// 实例化数据层 SysTenantImpl 结构体 +var NewSysTenantImpl = &SysTenantImpl{ + selectSql: `select + t.tenant_id, t.parent_id, t.ancestors, t.tenant_name, t.order_num, t.type, t.key, t.status, t.del_flag, t.create_by, t.create_time + from sys_tenant t`, + + resultMap: map[string]string{ + "tenant_id": "TenantID", + "parent_id": "ParentID", + "ancestors": "Ancestors", + "tenant_name": "TenantName", + "order_num": "OrderNum", + "type": "Type", + "key": "Key", + "status": "Status", + "del_flag": "DelFlag", + "create_by": "CreateBy", + "create_time": "CreateTime", + "update_by": "UpdateBy", + "update_time": "UpdateTime", + "parent_name": "ParentName", + }, +} + +// SysTenantImpl 部门表 数据层处理 +type SysTenantImpl struct { + // 查询视图对象SQL + selectSql string + // 结果字段与实体映射 + resultMap map[string]string +} + +// convertResultRows 将结果记录转实体结果组 +func (r *SysTenantImpl) convertResultRows(rows []map[string]any) []model.SysTenant { + arr := make([]model.SysTenant, 0) + for _, row := range rows { + sysTenant := model.SysTenant{} + for key, value := range row { + if keyMapper, ok := r.resultMap[key]; ok { + repo.SetFieldValue(&sysTenant, keyMapper, value) + } + } + arr = append(arr, sysTenant) + } + return arr +} + +// SelectTenantList 查询部门管理数据 +func (r *SysTenantImpl) SelectTenantList(sysTenant model.SysTenant, dataScopeSQL string) []model.SysTenant { + // 查询条件拼接 + var conditions []string + var params []any + if sysTenant.TenantID != "" { + conditions = append(conditions, "tenant_id = ?") + params = append(params, sysTenant.TenantID) + } + if sysTenant.ParentID != "" { + conditions = append(conditions, "parent_id = ?") + params = append(params, sysTenant.ParentID) + } + if sysTenant.TenantName != "" { + conditions = append(conditions, "tenant_name like concat(?, '%')") + params = append(params, sysTenant.TenantName) + } + if sysTenant.Status != "" { + conditions = append(conditions, "status = ?") + params = append(params, sysTenant.Status) + } + + // 构建查询条件语句 + whereSql := " where t.del_flag = '0' " + if len(conditions) > 0 { + whereSql += " and " + strings.Join(conditions, " and ") + } + + // 查询数据 + orderSql := " order by t.parent_id, t.order_num asc " + querySql := r.selectSql + whereSql + dataScopeSQL + orderSql + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysTenant{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// SelectTenantListByRoleId 根据角色ID查询部门树信息 +func (r *SysTenantImpl) SelectTenantListByRoleId(roleId string, tenantCheckStrictly bool) []string { + querySql := `select t.tenant_id as 'str' from sys_tenant d + left join sys_role_tenant rd on t.tenant_id = rt.tenant_id + where rt.role_id = ? ` + var params []any + params = append(params, roleId) + // 展开 + if tenantCheckStrictly { + querySql += ` and t.tenant_id not in + (select t.parent_id from sys_tenant d + inner join sys_role_tenant rd on t.tenant_id = rt.tenant_id + and rt.role_id = ?) ` + params = append(params, roleId) + } + querySql += "order by t.parent_id, t.order_num" + + // 查询结果 + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err => %v", err) + return []string{} + } + + if len(results) > 0 { + ids := make([]string, 0) + for _, v := range results { + ids = append(ids, fmt.Sprintf("%v", v["str"])) + } + return ids + } + return []string{} +} + +// SelectTenantById 根据部门ID查询信息 +func (r *SysTenantImpl) SelectTenantById(tenantId string) model.SysTenant { + querySql := `select t.tenant_id, t.parent_id, t.ancestors, + t.tenant_name, t.order_num, t.type, t.key, t.status, + (select tenant_name from sys_tenant where tenant_id = t.parent_id) parent_name + from sys_tenant t where t.tenant_id = ?` + results, err := datasource.RawDB("", querySql, []any{tenantId}) + if err != nil { + logger.Errorf("query err => %v", err) + return model.SysTenant{} + } + // 转换实体 + rows := r.convertResultRows(results) + if len(rows) > 0 { + return rows[0] + } + return model.SysTenant{} +} + +// SelectChildrenTenantById 根据ID查询所有子部门 +func (r *SysTenantImpl) SelectChildrenTenantById(tenantId string) []model.SysTenant { + querySql := r.selectSql + " where find_in_set(?, t.ancestors)" + results, err := datasource.RawDB("", querySql, []any{tenantId}) + if err != nil { + logger.Errorf("query err => %v", err) + return []model.SysTenant{} + } + + // 转换实体 + return r.convertResultRows(results) +} + +// HasChildByTenantId 是否存在子节点 +func (r *SysTenantImpl) HasChildByTenantId(tenantId string) int64 { + querySql := "select count(1) as 'total' from sys_tenant where status = '1' and parent_id = ? limit 1" + results, err := datasource.RawDB("", querySql, []any{tenantId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckTenantExistUser 查询部门是否存在用户 +func (r *SysTenantImpl) CheckTenantExistUser(tenantId string) int64 { + querySql := "select count(1) as 'total' from sys_user where tenant_id = ? and del_flag = '0'" + results, err := datasource.RawDB("", querySql, []any{tenantId}) + if err != nil { + logger.Errorf("query err => %v", err) + return 0 + } + if len(results) > 0 { + return parse.Number(results[0]["total"]) + } + return 0 +} + +// CheckUniqueTenant 校验部门是否唯一 +func (r *SysTenantImpl) CheckUniqueTenant(sysTenant model.SysTenant) string { + // 查询条件拼接 + var conditions []string + var params []any + if sysTenant.TenantName != "" { + conditions = append(conditions, "tenant_name = ?") + params = append(params, sysTenant.TenantName) + } + if sysTenant.ParentID != "" { + conditions = append(conditions, "parent_id = ?") + params = append(params, sysTenant.ParentID) + } + + // 构建查询条件语句 + whereSql := "" + if len(conditions) > 0 { + whereSql += " where " + strings.Join(conditions, " and ") + } + if whereSql == "" { + return "" + } + + // 查询数据 + querySql := "select tenant_id as 'str' from sys_tenant " + whereSql + " and del_flag = '0' limit 1" + results, err := datasource.RawDB("", querySql, params) + if err != nil { + logger.Errorf("query err %v", err) + return "" + } + if len(results) > 0 { + return fmt.Sprint(results[0]["str"]) + } + return "" +} + +// InsertTenant 新增部门信息 +func (r *SysTenantImpl) InsertTenant(sysTenant model.SysTenant) string { + // 参数拼接 + params := make(map[string]any) + if sysTenant.TenantID != "" { + params["tenant_id"] = sysTenant.TenantID + } + if sysTenant.ParentID != "" { + params["parent_id"] = sysTenant.ParentID + } + if sysTenant.TenantName != "" { + params["tenant_name"] = sysTenant.TenantName + } + if sysTenant.Ancestors != "" { + params["ancestors"] = sysTenant.Ancestors + } + if sysTenant.OrderNum > 0 { + params["order_num"] = sysTenant.OrderNum + } + if sysTenant.Type != "" { + params["type"] = sysTenant.Type + } + if sysTenant.Key != "" { + params["key"] = sysTenant.Key + } + if sysTenant.Status != "" { + params["status"] = sysTenant.Status + } + if sysTenant.CreateBy != "" { + params["create_by"] = sysTenant.CreateBy + params["create_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, placeholder, values := repo.KeyPlaceholderValueByInsert(params) + sql := "insert into sys_tenant (" + strings.Join(keys, ",") + ")values(" + placeholder + ")" + + db := datasource.DefaultDB() + // 开启事务 + tx := db.Begin() + // 执行插入 + err := tx.Exec(sql, values...).Error + if err != nil { + logger.Errorf("insert row : %v", err.Error()) + tx.Rollback() + return "" + } + // 获取生成的自增 ID + var insertedID string + err = tx.Raw("select last_insert_id()").Row().Scan(&insertedID) + if err != nil { + logger.Errorf("insert last id : %v", err.Error()) + tx.Rollback() + return "" + } + // 提交事务 + tx.Commit() + return insertedID +} + +// UpdateTenant 修改部门信息 +func (r *SysTenantImpl) UpdateTenant(sysTenant model.SysTenant) int64 { + // 参数拼接 + params := make(map[string]any) + if sysTenant.ParentID != "" { + params["parent_id"] = sysTenant.ParentID + } + if sysTenant.TenantName != "" { + params["tenant_name"] = sysTenant.TenantName + } + if sysTenant.Ancestors != "" { + params["ancestors"] = sysTenant.Ancestors + } + if sysTenant.OrderNum > 0 { + params["order_num"] = sysTenant.OrderNum + } + if sysTenant.Type != "" { + params["type"] = sysTenant.Type + } + if sysTenant.Key != "" { + params["key"] = sysTenant.Key + } + if sysTenant.Status != "" { + params["status"] = sysTenant.Status + } + if sysTenant.UpdateBy != "" { + params["update_by"] = sysTenant.UpdateBy + params["update_time"] = time.Now().UnixMilli() + } + + // 构建执行语句 + keys, values := repo.KeyValueByUpdate(params) + sql := "update sys_tenant set " + strings.Join(keys, ",") + " where tenant_id = ?" + + // 执行更新 + values = append(values, sysTenant.TenantID) + rows, err := datasource.ExecDB("", sql, values) + if err != nil { + logger.Errorf("update row : %v", err.Error()) + return 0 + } + return rows +} + +// UpdateTenantStatusNormal 修改所在部门正常状态 +func (r *SysTenantImpl) UpdateTenantStatusNormal(tenantIds []string) int64 { + placeholder := repo.KeyPlaceholderByQuery(len(tenantIds)) + sql := "update sys_tenant set status = '1' where tenant_id in (" + placeholder + ")" + parameters := repo.ConvertIdsSlice(tenantIds) + results, err := datasource.ExecDB("", sql, parameters) + if err != nil { + logger.Errorf("update err => %v", err) + return 0 + } + return results +} + +// UpdateTenantChildren 修改子元素关系 +func (r *SysTenantImpl) UpdateTenantChildren(sysTenants []model.SysTenant) int64 { + // 无参数 + if len(sysTenants) == 0 { + return 0 + } + + // 更新条件拼接 + var conditions []string + var params []any + for _, tenant := range sysTenants { + caseSql := fmt.Sprintf("WHEN tenant_id = '%s' THEN '%s'", tenant.TenantID, tenant.Ancestors) + conditions = append(conditions, caseSql) + params = append(params, tenant.TenantID) + } + + cases := strings.Join(conditions, " ") + placeholders := repo.KeyPlaceholderByQuery(len(params)) + sql := "update sys_tenant set ancestors = CASE " + cases + " END where tenant_id in (" + placeholders + ")" + results, err := datasource.ExecDB("", sql, params) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} + +// DeleteTenantById 删除部门管理信息 +func (r *SysTenantImpl) DeleteTenantById(tenantId string) int64 { + sql := "update sys_tenant set status = '0', del_flag = '1' where tenant_id = ?" + results, err := datasource.ExecDB("", sql, []any{tenantId}) + if err != nil { + logger.Errorf("delete err => %v", err) + return 0 + } + return results +} diff --git a/src/modules/system/service/sys_tenant.go b/src/modules/system/service/sys_tenant.go new file mode 100644 index 00000000..5f501b13 --- /dev/null +++ b/src/modules/system/service/sys_tenant.go @@ -0,0 +1,39 @@ +package service + +import ( + "be.ems/src/framework/vo" + "be.ems/src/modules/system/model" +) + +// ISysTenant 租户管理 服务层接口 +type ISysTenant interface { + // SelectTenantList 查询租户管理数据 + SelectTenantList(sysTenant model.SysTenant, dataScopeSQL string) []model.SysTenant + + // SelectTenantListByRoleId 根据角色ID查询租户树信息 + SelectTenantListByRoleId(roleId string) []string + + // SelectTenantById 根据租户ID查询信息 + SelectTenantById(tenantId string) model.SysTenant + + // HasChildByTenantId 是否存在子节点 + HasChildByTenantId(tenantId string) int64 + + // CheckTenantExistUser 查询租户是否存在用户 + CheckTenantExistUser(tenantId string) int64 + + // CheckUniqueTenantName 校验同级租户名称是否唯一 + CheckUniqueTenantName(tenantName, parentId, tenantId string) bool + + // InsertTenant 新增租户信息 + InsertTenant(sysTenant model.SysTenant) string + + // UpdateTenant 修改租户信息 + UpdateTenant(sysTenant model.SysTenant) int64 + + // DeleteTenantById 删除租户管理信息 + DeleteTenantById(tenantId string) int64 + + // SelectTenantTreeSelect 查询租户树结构信息 + SelectTenantTreeSelect(sysTenant model.SysTenant, dataScopeSQL string) []vo.TreeSelect +} diff --git a/src/modules/system/service/sys_tenant.impl.go b/src/modules/system/service/sys_tenant.impl.go new file mode 100644 index 00000000..b79f7773 --- /dev/null +++ b/src/modules/system/service/sys_tenant.impl.go @@ -0,0 +1,202 @@ +package service + +import ( + "strings" + + "be.ems/src/framework/constants/common" + "be.ems/src/framework/vo" + "be.ems/src/modules/system/model" + "be.ems/src/modules/system/repository" +) + +// 实例化服务层 SysTenantImpl 结构体 +var NewSysTenantImpl = &SysTenantImpl{ + sysTenantRepository: repository.NewSysTenantImpl, + sysRoleRepository: repository.NewSysRoleImpl, + sysRoleTenantRepository: repository.NewSysRoleTenantImpl, +} + +// SysTenantImpl 部门表 服务层处理 +type SysTenantImpl struct { + // 部门服务 + sysTenantRepository repository.ISysTenant + // 角色服务 + sysRoleRepository repository.ISysRole + // 角色与部门关联服务 + sysRoleTenantRepository repository.ISysRoleTenant +} + +// SelectTenantList 查询部门管理数据 +func (r *SysTenantImpl) SelectTenantList(sysTenant model.SysTenant, dataScopeSQL string) []model.SysTenant { + return r.sysTenantRepository.SelectTenantList(sysTenant, dataScopeSQL) +} + +// SelectTenantListByRoleId 根据角色ID查询部门树信息 TODO +func (r *SysTenantImpl) SelectTenantListByRoleId(roleId string) []string { + roles := r.sysRoleRepository.SelectRoleByIds([]string{roleId}) + if len(roles) > 0 { + role := roles[0] + if role.RoleID == roleId { + return r.sysTenantRepository.SelectTenantListByRoleId( + role.RoleID, + role.TenantCheckStrictly == "1", + ) + } + } + return []string{} +} + +// SelectTenantById 根据部门ID查询信息 +func (r *SysTenantImpl) SelectTenantById(tenantId string) model.SysTenant { + return r.sysTenantRepository.SelectTenantById(tenantId) +} + +// HasChildByTenantId 是否存在子节点 +func (r *SysTenantImpl) HasChildByTenantId(tenantId string) int64 { + return r.sysTenantRepository.HasChildByTenantId(tenantId) +} + +// CheckTenantExistUser 查询部门是否存在用户 +func (r *SysTenantImpl) CheckTenantExistUser(tenantId string) int64 { + return r.sysTenantRepository.CheckTenantExistUser(tenantId) +} + +// CheckUniqueTenantName 校验同级部门名称是否唯一 +func (r *SysTenantImpl) CheckUniqueTenantName(tenantName, parentId, tenantId string) bool { + uniqueId := r.sysTenantRepository.CheckUniqueTenant(model.SysTenant{ + TenantName: tenantName, + ParentID: parentId, + }) + if uniqueId == tenantId { + return true + } + return uniqueId == "" +} + +// InsertTenant 新增部门信息 +func (r *SysTenantImpl) InsertTenant(sysTenant model.SysTenant) string { + return r.sysTenantRepository.InsertTenant(sysTenant) +} + +// UpdateTenant 修改部门信息 +func (r *SysTenantImpl) UpdateTenant(sysTenant model.SysTenant) int64 { + parentTenant := r.sysTenantRepository.SelectTenantById(sysTenant.ParentID) + tenant := r.sysTenantRepository.SelectTenantById(sysTenant.TenantID) + // 上级与当前部门祖级列表更新 + if parentTenant.TenantID == sysTenant.ParentID && tenant.TenantID == sysTenant.TenantID { + newAncestors := parentTenant.Ancestors + "," + parentTenant.TenantID + oldAncestors := tenant.Ancestors + // 祖级列表不一致时更新 + if newAncestors != oldAncestors { + sysTenant.Ancestors = newAncestors + r.updateTenantChildren(sysTenant.TenantID, newAncestors, oldAncestors) + } + } + // 如果该部门是启用状态,则启用该部门的所有上级部门 + if sysTenant.Status == common.STATUS_YES && parentTenant.Status == common.STATUS_NO { + r.updateTenantStatusNormal(sysTenant.Ancestors) + } + return r.sysTenantRepository.UpdateTenant(sysTenant) +} + +// updateTenantStatusNormal 修改所在部门正常状态 +func (r *SysTenantImpl) updateTenantStatusNormal(ancestors string) int64 { + if ancestors == "" || ancestors == "0" { + return 0 + } + tenantIds := strings.Split(ancestors, ",") + return r.sysTenantRepository.UpdateTenantStatusNormal(tenantIds) +} + +// updateTenantChildren 修改子元素关系 +func (r *SysTenantImpl) updateTenantChildren(tenantId, newAncestors, oldAncestors string) int64 { + childrens := r.sysTenantRepository.SelectChildrenTenantById(tenantId) + if len(childrens) == 0 { + return 0 + } + // 替换父ID + for i := range childrens { + child := &childrens[i] + ancestors := strings.Replace(child.Ancestors, oldAncestors, newAncestors, -1) + child.Ancestors = ancestors + } + return r.sysTenantRepository.UpdateTenantChildren(childrens) +} + +// DeleteTenantById 删除部门管理信息 +func (r *SysTenantImpl) DeleteTenantById(tenantId string) int64 { + // 删除角色与部门关联 + r.sysRoleTenantRepository.DeleteTenantRole([]string{tenantId}) + return r.sysTenantRepository.DeleteTenantById(tenantId) +} + +// SelectTenantTreeSelect 查询部门树结构信息 +func (r *SysTenantImpl) SelectTenantTreeSelect(sysTenant model.SysTenant, dataScopeSQL string) []vo.TreeSelect { + sysTenants := r.sysTenantRepository.SelectTenantList(sysTenant, dataScopeSQL) + tenants := r.parseDataToTree(sysTenants) + tree := make([]vo.TreeSelect, 0) + for _, tenant := range tenants { + tree = append(tree, vo.SysTenantTreeSelect(tenant)) + } + return tree +} + +// parseDataToTree 将数据解析为树结构,构建前端所需要下拉树结构 +func (r *SysTenantImpl) parseDataToTree(sysTenants []model.SysTenant) []model.SysTenant { + // 节点分组 + nodesMap := make(map[string][]model.SysTenant) + // 节点id + treeIds := []string{} + // 树节点 + tree := []model.SysTenant{} + + for _, item := range sysTenants { + parentID := item.ParentID + // 分组 + mapItem, ok := nodesMap[parentID] + if !ok { + mapItem = []model.SysTenant{} + } + mapItem = append(mapItem, item) + nodesMap[parentID] = mapItem + // 记录节点ID + treeIds = append(treeIds, item.TenantID) + } + + for key, value := range nodesMap { + // 选择不是节点ID的作为树节点 + found := false + for _, id := range treeIds { + if id == key { + found = true + break + } + } + if !found { + tree = append(tree, value...) + } + } + + for i, node := range tree { + iN := r.parseDataToTreeComponet(node, &nodesMap) + tree[i] = iN + } + + return tree +} + +// parseDataToTreeComponet 递归函数处理子节点 +func (r *SysTenantImpl) parseDataToTreeComponet(node model.SysTenant, nodesMap *map[string][]model.SysTenant) model.SysTenant { + id := node.TenantID + children, ok := (*nodesMap)[id] + if ok { + node.Children = children + } + if len(node.Children) > 0 { + for i, child := range node.Children { + icN := r.parseDataToTreeComponet(child, nodesMap) + node.Children[i] = icN + } + } + return node +} diff --git a/src/modules/system/system.go b/src/modules/system/system.go index 407a94e0..7db0237a 100644 --- a/src/modules/system/system.go +++ b/src/modules/system/system.go @@ -103,6 +103,46 @@ func Setup(router *gin.Engine) { ) } + // 租户管理信息 + sysTenantGroup := router.Group("/system/tenant") + { + sysTenantGroup.GET("/list", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:tenant:list"}}), + controller.NewSysTenant.List, + ) + sysTenantGroup.GET("/:tenantId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:tenant:query"}}), + controller.NewSysTenant.Info, + ) + sysTenantGroup.POST("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:tenant:add"}}), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysTenant", collectlogs.BUSINESS_TYPE_INSERT)), + controller.NewSysTenant.Add, + ) + sysTenantGroup.PUT("", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:tenant:edit"}}), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysTenant", collectlogs.BUSINESS_TYPE_UPDATE)), + controller.NewSysTenant.Edit, + ) + sysTenantGroup.DELETE("/:tenantId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:tenant:remove"}}), + collectlogs.OperateLog(collectlogs.OptionNew("log.operate.title.sysTenant", collectlogs.BUSINESS_TYPE_DELETE)), + controller.NewSysTenant.Remove, + ) + sysTenantGroup.GET("/list/exclude/:tenantId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:tenant:list"}}), + controller.NewSysTenant.ExcludeChild, + ) + sysTenantGroup.GET("/treeSelect", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:tenant:list", "system:user:list"}}), + controller.NewSysTenant.TreeSelect, + ) + sysTenantGroup.GET("/roleTenantTreeSelect/:roleId", + middleware.PreAuthorize(map[string][]string{"hasPerms": {"system:tenant:query", "system:user:edit"}}), + controller.NewSysTenant.RoleTenantTreeSelect, + ) + } + // 字典数据信息 sysDictDataGroup := router.Group("/system/dict/data") {