// RBAC 角色-权限点矩阵配置服务(T6.5-e,RBAC 三层模型的"超管配置后台",2026-11-06 用户拍板 #2)。 // // 职责(平台级,不带多租户作用域——角色权限矩阵是全局配置,与机构/校区无关): // - 查某角色当前矩阵 + 全量权限点字典(超管配置页渲染勾选树用); // - 全量保存某角色矩阵(删旧插新事务;校验 roleCode 存在于 t_role + 权限码存在于 t_permission); // - 保存成功后调 rbac.Resolver.Invalidate() 清本实例缓存(跨实例靠 TTL 30s 自然过期)。 // // 与 OrgService 的分工:OrgService 面向机构/校区(随请求注入 *scope.Scope,强制 Scoping); // 本服务面向 RBAC 平台级字典(t_role / t_permission / t_role_permission),无作用域过滤—— // 多租户防线不在本层(路由角色白名单 RequireRole("super_admin") 是唯一访问入口,见 handler)。 // // 依赖方向:service → org/model + org/repository(PermissionRepo) + org/rbac(Resolver) + // infra/errors(全下层/同域),不依赖 handler/main。 package service import ( "context" "gorm.io/gorm" apperrors "yuansiwei/unified-backend/internal/infra/errors" "yuansiwei/unified-backend/internal/org/model" "yuansiwei/unified-backend/internal/org/rbac" "yuansiwei/unified-backend/internal/org/repository" ) // RolePermissionView 角色-权限点矩阵查询出参(超管配置页渲染依据)。 type RolePermissionView struct { RoleCode string `json:"roleCode"` // 角色码(回显,t_role 字典值) Assigned []string `json:"assigned"` // 该角色当前拥有的权限点 code(升序去重;非 nil) Dictionary []model.Permission `json:"dictionary"` // 全量 active 权限点(非 nil;前端按 parent_code 构树做勾选) } // RolePermissionService RBAC 矩阵配置服务(启动装配一次,全进程共享)。 type RolePermissionService struct { db *gorm.DB permRepo *repository.PermissionRepo resolver *rbac.Resolver // 保存成功后调 Invalidate 清本实例缓存(nil 时跳过) } // NewRolePermissionService 构造 RBAC 矩阵配置服务(fail-fast:db/permRepo 必填)。 // // 入参: // - db *gorm.DB:数据库句柄(必填,用于 t_role 角色存在性校验——平台级直查,无作用域); // - permRepo *repository.PermissionRepo:权限点/矩阵仓储(必填); // - resolver *rbac.Resolver:RBAC 解析器(可空;nil 时保存后不清缓存——纯单测/无缓存场景, // 生产装配必给,否则超管改配最长 30s 才生效)。 func NewRolePermissionService(db *gorm.DB, permRepo *repository.PermissionRepo, resolver *rbac.Resolver) (*RolePermissionService, error) { if db == nil { return nil, apperrors.New(apperrors.CodeInternal, "rbac service: DB 未注入") } if permRepo == nil { return nil, apperrors.New(apperrors.CodeInternal, "rbac service: PermissionRepo 未注入") } return &RolePermissionService{db: db, permRepo: permRepo, resolver: resolver}, nil } // ListRolePermissions 查某角色的矩阵 + 全量权限点字典。 // // 入参:ctx 请求上下文;roleCode 角色码(t_role 字典值)。 // 出参: // - *RolePermissionView:角色矩阵 + 字典(非 nil 切片)。 // - error:ErrBadParam(2001) roleCode 不在 t_role 字典 / ErrDBError(5002)。 func (s *RolePermissionService) ListRolePermissions(ctx context.Context, roleCode string) (*RolePermissionView, error) { if err := s.validateRole(ctx, roleCode); err != nil { return nil, err } assigned, err := s.permRepo.RolePermissionCodes(ctx, roleCode) if err != nil { return nil, err } dictionary, err := s.permRepo.AllActive(ctx) if err != nil { return nil, err } return &RolePermissionView{RoleCode: roleCode, Assigned: assigned, Dictionary: dictionary}, nil } // SaveRolePermissions 全量保存某角色的权限点矩阵(删旧插新;保存后清本实例缓存)。 // // 入参: // - ctx 请求上下文; // - roleCode 角色码(须存在于 t_role 字典); // - codes []string:目标全量权限点集合(可为空 = 清空该角色矩阵;每项须存在于 t_permission)。 // // 出参: // - error:ErrBadParam(2001) roleCode 非法 / 含未知权限码 / ErrDBError(5002)(含事务回滚)。 // // 语义:保存即全量替换(前端"取消全部勾选"合法 = 传空集);幂等(目标与原矩阵一致时等价于原样重插)。 // 保存成功后调 resolver.Invalidate() 清本实例缓存,使超管改配立即对本实例生效(跨实例靠 TTL 30s)。 func (s *RolePermissionService) SaveRolePermissions(ctx context.Context, roleCode string, codes []string) error { if err := s.validateRole(ctx, roleCode); err != nil { return err } if err := s.validateCodes(ctx, codes); err != nil { return err } if err := s.permRepo.SaveRolePermissions(ctx, roleCode, codes); err != nil { return err } if s.resolver != nil { s.resolver.Invalidate() // 清本实例缓存(跨实例不广播,TTL 30s 自然一致) } return nil } // validateRole 校验 roleCode 存在于 t_role 字典(平台级直查,无作用域)。 // // 出参:error——ErrBadParam(2001) 角色码非法 / ErrDBError(5002)。 func (s *RolePermissionService) validateRole(ctx context.Context, roleCode string) error { if roleCode == "" { return apperrors.ErrBadParam } var count int64 if err := s.db.WithContext(ctx).Model(&model.RoleModel{}). Where("role_code = ?", roleCode). Count(&count).Error; err != nil { return apperrors.WithCause(apperrors.ErrDBError, err) } if count == 0 { return apperrors.New(apperrors.CodeBadParam, "角色码非法(不在 t_role 字典)") } return nil } // validateCodes 校验权限码全部存在于 t_permission(防"保存未知 code"污染矩阵)。 // // 语义:空集合法(清空矩阵);非空时要求每项都在 t_permission(任意 status—— // 允许预置 disabled 权限点,待其 active 后自然显现,不强制必须 active)。 // 出参:error——ErrBadParam(2001) 含未知权限码 / ErrDBError(5002)。 func (s *RolePermissionService) validateCodes(ctx context.Context, codes []string) error { if len(codes) == 0 { return nil // 清空语义(合法) } // 去重(避免重复 code 干扰 count 比对) seen := make(map[string]struct{}, len(codes)) unique := make([]string, 0, len(codes)) for _, c := range codes { if c == "" { return apperrors.New(apperrors.CodeBadParam, "权限码不能为空字符串") } if _, ok := seen[c]; !ok { seen[c] = struct{}{} unique = append(unique, c) } } var count int64 if err := s.db.WithContext(ctx).Model(&model.Permission{}). Where("code IN ?", unique). Count(&count).Error; err != nil { return apperrors.WithCause(apperrors.ErrDBError, err) } if count != int64(len(unique)) { return apperrors.New(apperrors.CodeBadParam, "含未知权限码(不在 t_permission 字典)") } return nil }