| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- // 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
- }
|