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" "strings" ) func SyncInheritedRole(ctx context.Context, message *model.AgentRoleSyncMessage) error { if message == nil || message.SourceRoleId <= 0 || message.SourceDeptId <= 0 || message.TenantId == "" { return gerror.New("invalid agent role sync message") } template, templateErr := rolepolicy.Template(ctx) if templateErr != nil { return templateErr } if message.TemplateRole && (template == nil || message.SourceRoleId != template.RoleId) { message.Superseded = true return nil } if template != nil && message.SourceRoleId == template.RoleId && message.SourceDeptId == template.DeptId && (message.TenantId == template.TenantId || message.TenantId == "") { return syncTemplateRoles(ctx, template, message) } if template != nil { message.Superseded = true return nil } var source *entity.SysRole sourceModel := dao.SysRole.Ctx(ctx).Master().Where(dao.SysRole.Columns().RoleId, message.SourceRoleId) if message.TenantId != "" { sourceModel = sourceModel.Where(dao.SysRole.Columns().TenantId, message.TenantId) } if err := sourceModel.Scan(&source); err != nil { return err } if source == nil || source.IsPass != 1 || source.DeletedAt != nil { message.Superseded = true return nil } if message.SourceDeptId > 0 && message.SourceDeptId != source.DeptId { return gerror.New("role sync source department changed") } var children []*entity.SysDept if err := dao.SysDept.Ctx(ctx).Master(). Where(dao.SysDept.Columns().TenantId, source.TenantId). Where(dao.SysDept.Columns().ParentId, source.DeptId). Where(dao.SysDept.Columns().DeptType, 1). Scan(&children); err != nil { return err } failed := 0 if len(message.ChildDeptIds) == 0 { message.TotalCount = len(children) message.SuccessCount = 0 } failedDeptIds := make([]int64, 0) var firstErr error for _, child := range children { if err := ctx.Err(); err != nil { return err } if len(message.ChildDeptIds) > 0 && !slices.Contains(message.ChildDeptIds, child.DeptId) { continue } err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { return syncInheritedRoleChildrenTx(ctx, tx, message.SourceRoleId, source.TenantId, source.DeptId, message.ActorId, child.DeptId) }) if err != nil { failed++ failedDeptIds = append(failedDeptIds, child.DeptId) if firstErr == nil { firstErr = err } g.Log().Warningf(ctx, "agent role sync child failed, sourceRoleId:%d, childDeptId:%d, err:%v", source.RoleId, child.DeptId, err) } else { message.SuccessCount++ } } message.ChildDeptIds = failedDeptIds message.FailedCount = len(failedDeptIds) if firstErr != nil { return gerror.Wrapf(firstErr, "agent role sync failed for %d of %d children", failed, len(children)) } return nil } func syncInheritedRoleChildrenTx(ctx context.Context, tx gdb.TX, sourceRoleID int64, tenantId string, sourceDeptId, actorId, childDeptId int64) error { if sourceRoleID == 0 { return nil } var source *entity.SysRole sourceModel := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where(dao.SysRole.Columns().RoleId, sourceRoleID) if tenantId != "" { sourceModel = sourceModel.Where(dao.SysRole.Columns().TenantId, tenantId) } if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") { sourceModel = sourceModel.LockUpdate() } if err := sourceModel.Scan(&source); err != nil { return err } if source == nil || source.RoleId == 0 || source.DeletedAt != nil || source.IsPass != 1 { return nil } if sourceDeptId > 0 && source.DeptId != sourceDeptId { return gerror.New("role sync source department changed") } var childDepts []*entity.SysDept childDeptModel := tx.Ctx(ctx).Model(dao.SysDept.Table()).Where(dao.SysDept.Columns().TenantId, source.TenantId).Where(dao.SysDept.Columns().ParentId, source.DeptId).Where(dao.SysDept.Columns().DeptType, 1).Where(dao.SysDept.Columns().DeptId, childDeptId) if !strings.EqualFold(g.DB().GetConfig().Type, "sqlite") { childDeptModel = childDeptModel.LockUpdate() } if err := childDeptModel.Scan(&childDepts); err != nil { return err } var menus []*entity.SysRoleMenu if err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Where(dao.SysRoleMenu.Columns().RoleId, sourceRoleID).OrderAsc(dao.SysRoleMenu.Columns().MenuId).Scan(&menus); err != nil { return err } menuIDs := make([]int64, 0, len(menus)) for _, menu := range menus { menuIDs = append(menuIDs, menu.MenuId) } allowed, err := rolepolicy.RestrictMenus(ctx, source.TenantId, source.DeptId, menuIDs) if err != nil { return err } template, templateErr := rolepolicy.Template(ctx) if templateErr != nil { return templateErr } filtered := make([]*entity.SysRoleMenu, 0, len(menus)) for _, menu := range menus { if slices.Contains(allowed, menu.MenuId) { copy := *menu if template != nil { copy.DataScope = consts.SysRoleMenuDataScopeRole } filtered = append(filtered, ©) } } menus = filtered for _, childDept := range childDepts { var children []*entity.SysRole if err := tx.Ctx(ctx).Model(dao.SysRole.Table()+" r"). Fields("r.*"). Where("r.tenant_id", source.TenantId). Where("r.dept_id", childDept.DeptId). Where("r.created_dept", source.DeptId). Where("r.role_id", childDept.DeptRole). Scan(&children); err != nil { return err } if len(children) > 1 { return gerror.Newf("multiple inherited roles in department %d for role %d", childDept.DeptId, source.RoleId) } if len(children) == 0 { var legacy *entity.SysRole if childDept.DeptRole > 0 { if err := tx.Ctx(ctx).Model(dao.SysRole.Table()). Where(dao.SysRole.Columns().RoleId, childDept.DeptRole). Where(dao.SysRole.Columns().DeptId, childDept.DeptId). Where(dao.SysRole.Columns().TenantId, source.TenantId). Scan(&legacy); err != nil { return err } } if legacy != nil && legacy.RoleId > 0 { if legacy.CreatedDept != source.DeptId || legacy.RoleKey != source.RoleKey { return gerror.Newf("cannot identify inherited role for department %d", childDept.DeptId) } children = []*entity.SysRole{legacy} } else { if childDept.DeptRole > 0 { return gerror.Newf("missing default role for department %d", childDept.DeptId) } return gerror.Newf("missing default role for department %d", childDept.DeptId) } } for _, child := range children { if _, err := tx.Ctx(ctx).Model(dao.SysRole.Table()).Where(dao.SysRole.Columns().RoleId, child.RoleId).Data(do.SysRole{RoleName: strings.TrimSpace(source.RoleName), RoleKey: source.RoleKey, RoleSort: source.RoleSort, DataScope: source.DataScope, MenuCheckStrictly: source.MenuCheckStrictly, DeptCheckStrictly: source.DeptCheckStrictly, Status: source.Status, Remark: source.Remark, UpdatedBy: actorId}).OmitNil().Update(); err != nil { return err } if _, err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Where(dao.SysRoleMenu.Columns().RoleId, child.RoleId).Delete(); err != nil { return err } menuData := make([]*do.SysRoleMenu, 0, len(menus)) for _, menu := range menus { menuData = append(menuData, &do.SysRoleMenu{TenantId: source.TenantId, RoleId: child.RoleId, MenuId: menu.MenuId, DataScope: menu.DataScope}) } if len(menuData) > 0 { if _, err := tx.Ctx(ctx).Model(dao.SysRoleMenu.Table()).Data(menuData).OmitNil().Batch(200).Insert(); err != nil { return err } } } } return nil }