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(¤t); 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 }