permission_repo.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. // 权限点/角色矩阵仓储(T6.5):t_permission + t_role_permission 的**平台级**数据访问。
  2. //
  3. // 与 InstitutionRepo/CampusRepo 的关键差异(架构规格 §3.5):
  4. // - **不继承 scope.BaseRepo**:t_permission/t_role_permission 是平台级 RBAC 字典
  5. // (同 t_role 角色字典的处置:平台级表不进 scoping,见 P0-11 收敛规则 7),
  6. // 直接用裸 *gorm.DB;
  7. // - 多租户防线不在此层:权限点矩阵本身是"角色 → 可见菜单/按钮"的全局配置,
  8. // 与机构/校区无关;作用域越权防线在 Service 层(assertInScope,T6)+
  9. // 路由角色白名单(T6.5-d)。
  10. //
  11. // 依赖方向铁律:org repository → org/model + infra/errors(全下层/同域),
  12. // 不依赖 handler/service。
  13. package repository
  14. import (
  15. "context"
  16. stderrors "errors"
  17. "gorm.io/gorm"
  18. apperrors "yuansiwei/unified-backend/internal/infra/errors"
  19. "yuansiwei/unified-backend/internal/org/model"
  20. )
  21. // mapGormErr gorm 原错 → sentinel(与 identity/repository 同名同语义:
  22. // ErrRecordNotFound→ErrNotFound,其它→ErrDBError;平台级仓储统一出口)。
  23. func mapGormErr(err error) *apperrors.AppError {
  24. switch {
  25. case err == nil:
  26. return nil
  27. case stderrors.Is(err, gorm.ErrRecordNotFound):
  28. return apperrors.WithCause(apperrors.ErrNotFound, err)
  29. default:
  30. return apperrors.WithCause(apperrors.ErrDBError, err)
  31. }
  32. }
  33. // PermissionRepo 权限点字典 + 角色矩阵仓储(平台级)。
  34. type PermissionRepo struct {
  35. db *gorm.DB
  36. }
  37. // NewPermissionRepo 构造权限点/角色矩阵仓储。
  38. //
  39. // 入参:db *gorm.DB:数据库句柄(必填)。
  40. // 出参:*PermissionRepo:就绪仓储。
  41. func NewPermissionRepo(db *gorm.DB) *PermissionRepo {
  42. return &PermissionRepo{db: db}
  43. }
  44. // AllActive 取全部 active 权限点(菜单树构建用;按 sort 升序,构树方自行按 parent_code 组装)。
  45. //
  46. // 入参:ctx 请求上下文。
  47. // 出参:
  48. // - []model.Permission:全部 active 权限点(**非 nil**,空时为 [];disabled 不返回);
  49. // - error:ErrDBError(DB 异常)。
  50. //
  51. // 实现说明:权限点是平台级字典(量级 < 百),一次全取 + 进程内缓存(rbac 包),
  52. // 不做分页;Order(sort ASC, code ASC) 保证同级排序稳定。
  53. func (r *PermissionRepo) AllActive(ctx context.Context) ([]model.Permission, error) {
  54. var list []model.Permission
  55. if err := r.db.WithContext(ctx).
  56. Where("status = ?", model.PermissionStatusActive).
  57. Order("sort ASC, code ASC").
  58. Find(&list).Error; err != nil {
  59. return nil, mapGormErr(err)
  60. }
  61. if list == nil {
  62. list = []model.Permission{}
  63. }
  64. return list, nil
  65. }
  66. // RolePermissionCodes 取某角色的全部权限点 code(去重、升序)。
  67. //
  68. // 入参:ctx 请求上下文;roleCode 角色码(t_role 字典值)。
  69. // 出参:
  70. // - []string:权限点 code 切片(**非 nil**,无授权时为 []string{}——前端按空集处理:
  71. // 菜单空 + 按钮全隐;超管的"全量放行"在 rbac 包/中间件层实现,不经本方法);
  72. // - error:ErrDBError(DB 异常)。
  73. func (r *PermissionRepo) RolePermissionCodes(ctx context.Context, roleCode string) ([]string, error) {
  74. var codes []string
  75. if err := r.db.WithContext(ctx).
  76. Model(&model.RolePermission{}).
  77. Where("role_code = ?", roleCode).
  78. Distinct().
  79. Order("permission_code ASC").
  80. Pluck("permission_code", &codes).Error; err != nil {
  81. return nil, mapGormErr(err)
  82. }
  83. if codes == nil {
  84. codes = []string{}
  85. }
  86. return codes, nil
  87. }
  88. // SaveRolePermissions 全量保存某角色的权限点矩阵(超管配置端点,T6.5-e)。
  89. //
  90. // 入参:
  91. // - ctx 请求上下文;
  92. // - roleCode 角色码(必须为 t_role 字典内角色,调用方校验;本方法只做矩阵维护);
  93. // - permissionCodes []string:目标全量权限点集合(可为空 = 清空该角色矩阵)。
  94. //
  95. // 出参:error——ErrDBError(DB 异常,含事务回滚)。
  96. //
  97. // 语义:**删旧插新**(事务内先删该角色全部矩阵行,再批量插入目标集合)——
  98. // "保存即全量替换",避免差集计算;空集合 = 清空(前端"取消全部勾选"场景合法)。
  99. // 幂等:目标集合与原矩阵一致时等价于原样重插(先删后插,结果相同)。
  100. func (r *PermissionRepo) SaveRolePermissions(ctx context.Context, roleCode string, permissionCodes []string) error {
  101. return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
  102. if err := tx.Where("role_code = ?", roleCode).Delete(&model.RolePermission{}).Error; err != nil {
  103. return err
  104. }
  105. if len(permissionCodes) == 0 {
  106. return nil // 清空语义(合法:角色无任何可见菜单/按钮)
  107. }
  108. rows := make([]model.RolePermission, 0, len(permissionCodes))
  109. for _, code := range permissionCodes {
  110. rows = append(rows, model.RolePermission{RoleCode: roleCode, PermissionCode: code})
  111. }
  112. return tx.Create(&rows).Error // GORM 批量 Create(切片)
  113. })
  114. }