Files
iotlcm/worker-svr/internal/service/rolesync/template.go

186 lines
7.0 KiB
Go

package rolesync
import (
"context"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/library/rolepolicy"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/do"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/entity"
"slices"
"strconv"
"strings"
)
func syncTemplateRoles(ctx context.Context, template *entity.SysRole, message *model.AgentRoleSyncMessage) error {
departments, err := templateDescendants(ctx, template)
if err != nil {
return err
}
retrying := len(message.ChildDeptIds) > 0
if !retrying {
message.TotalCount = len(departments)
message.SuccessCount = 0
}
pending := make(map[int64]bool, len(message.ChildDeptIds))
for _, deptID := range message.ChildDeptIds {
pending[deptID] = true
}
failed := make(map[int64]bool)
failedDeptIDs := make([]int64, 0)
var firstErr error
for _, child := range departments {
if err := ctx.Err(); err != nil {
return err
}
if retrying && !pending[child.DeptId] {
continue
}
delete(pending, child.DeptId)
var syncErr error
if failed[child.ParentId] {
syncErr = gerror.Newf("上级机构%d尚未同步成功", child.ParentId)
} else {
syncErr = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
return syncTemplateChildTx(ctx, tx, template, child, message.ActorId)
})
}
if syncErr != nil {
failed[child.DeptId] = true
failedDeptIDs = append(failedDeptIDs, child.DeptId)
if firstErr == nil {
firstErr = syncErr
}
g.Log().Errorf(ctx, "agent template sync child failed, deptId:%d, err:%v", child.DeptId, syncErr)
continue
}
message.SuccessCount++
}
for deptID := range pending {
failedDeptIDs = append(failedDeptIDs, deptID)
if firstErr == nil {
firstErr = gerror.Newf("待同步机构%d已不在当前模板的下级范围,请核对机构关系", deptID)
}
}
slices.Sort(failedDeptIDs)
message.ChildDeptIds = failedDeptIDs
message.FailedCount = len(failedDeptIDs)
if firstErr != nil {
return gerror.Wrapf(firstErr, "代理模板同步失败企业数:%d", len(failedDeptIDs))
}
return nil
}
func templateDescendants(ctx context.Context, template *entity.SysRole) ([]*entity.SysDept, error) {
queue := []int64{template.DeptId}
seen := map[int64]bool{template.DeptId: true}
departments := make([]*entity.SysDept, 0)
for len(queue) > 0 {
if err := ctx.Err(); err != nil {
return nil, err
}
parentID := queue[0]
queue = queue[1:]
var children []*entity.SysDept
if err := dao.SysDept.Ctx(ctx).Master().Where("tenant_id", template.TenantId).Where("parent_id", parentID).Where("dept_type", 1).OrderAsc("dept_id").Scan(&children); err != nil {
return nil, err
}
for _, child := range children {
if seen[child.DeptId] {
return nil, gerror.New("机构上下级存在循环")
}
seen[child.DeptId] = true
queue = append(queue, child.DeptId)
departments = append(departments, child)
}
}
return departments, nil
}
func syncTemplateChildTx(ctx context.Context, tx gdb.TX, template *entity.SysRole, child *entity.SysDept, actorID int64) error {
if child.DeptRole <= 0 {
return gerror.New("下级机构缺失默认角色")
}
lock := tx.Ctx(ctx).Model(dao.SysDept.Table()).Where("dept_id", child.DeptId).Where("tenant_id", template.TenantId)
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
lock = lock.LockUpdate()
}
var locked *entity.SysDept
if err := lock.Scan(&locked); err != nil {
return err
}
if locked == nil || locked.DeptRole != child.DeptRole || locked.ParentId != child.ParentId || locked.DeptType != 1 {
return gerror.New("下级机构角色绑定已变化")
}
currentLock := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where("role_id", child.DeptRole).Where("tenant_id", template.TenantId).Where("dept_id", child.DeptId)
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
currentLock = currentLock.LockUpdate()
}
var current *entity.SysRole
if err := currentLock.Scan(&current); err != nil {
return err
}
if current == nil || current.Status != "0" {
return gerror.New("下级机构默认角色不存在或已删除")
}
if current.CreatedDept != child.ParentId {
return gerror.New("下级机构默认角色非直属上级继承角色,请核对角色来源")
}
configLock := tx.Ctx(ctx).Model(dao.SysConfig.Table()).Where("tenant_id", template.TenantId).Where("config_key", rolepolicy.TemplateConfigKey)
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
configLock = configLock.LockUpdate()
}
var config *entity.SysConfig
if err := configLock.Scan(&config); err != nil {
return err
}
if config == nil || strings.TrimSpace(config.ConfigValue) != strconv.FormatInt(template.RoleId, 10) {
return gerror.New("模板已切换,请重新同步")
}
templateLock := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where("role_id", template.RoleId)
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
templateLock = templateLock.LockUpdate()
}
var latest *entity.SysRole
if err := templateLock.Scan(&latest); err != nil {
return err
}
if latest == nil || latest.Status != "0" || latest.TenantId != template.TenantId || latest.DeptId != template.DeptId || latest.DataScope != consts.SysRoleDataScopeDeptAndBelow || (latest.CreatedDept > 0 && latest.CreatedDept != latest.DeptId) {
return gerror.New("模板不可用")
}
menuLock := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Where("tenant_id", template.TenantId).Where("role_id", template.RoleId)
if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") {
menuLock = menuLock.LockUpdate()
}
var menus []*entity.SysRoleMenu
if err := menuLock.Scan(&menus); err != nil {
return err
}
// Every linked default role receives the same template snapshot. Individual
// changes are intentionally replaced; unrelated roles are never updated.
if _, err := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where("tenant_id", template.TenantId).Where("role_id", current.RoleId).Data(do.SysRole{DataScope: consts.SysRoleDataScopeDeptAndBelow, UpdatedBy: actorID}).Update(); err != nil {
return err
}
if _, err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Where("tenant_id", template.TenantId).Where("role_id", current.RoleId).Delete(); err != nil {
return err
}
menuData := make([]*do.SysRoleMenu, 0, len(menus))
for _, menu := range menus {
if menu.DataScope == consts.SysRoleDataScopeAll || menu.DataScope == consts.SysRoleDataScopeCustom {
return gerror.New("模板按钮数据范围已变化,请重新同步")
}
menuData = append(menuData, &do.SysRoleMenu{TenantId: template.TenantId, RoleId: current.RoleId, MenuId: menu.MenuId, DataScope: consts.SysRoleMenuDataScopeRole})
}
if len(menuData) > 0 {
if _, err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Data(menuData).Batch(200).Insert(); err != nil {
return err
}
}
return nil
}