// 权限点/角色矩阵仓储(T6.5):t_permission + t_role_permission 的**平台级**数据访问。 // // 与 InstitutionRepo/CampusRepo 的关键差异(架构规格 §3.5): // - **不继承 scope.BaseRepo**:t_permission/t_role_permission 是平台级 RBAC 字典 // (同 t_role 角色字典的处置:平台级表不进 scoping,见 P0-11 收敛规则 7), // 直接用裸 *gorm.DB; // - 多租户防线不在此层:权限点矩阵本身是"角色 → 可见菜单/按钮"的全局配置, // 与机构/校区无关;作用域越权防线在 Service 层(assertInScope,T6)+ // 路由角色白名单(T6.5-d)。 // // 依赖方向铁律:org repository → org/model + infra/errors(全下层/同域), // 不依赖 handler/service。 package repository import ( "context" stderrors "errors" "gorm.io/gorm" apperrors "yuansiwei/unified-backend/internal/infra/errors" "yuansiwei/unified-backend/internal/org/model" ) // mapGormErr gorm 原错 → sentinel(与 identity/repository 同名同语义: // ErrRecordNotFound→ErrNotFound,其它→ErrDBError;平台级仓储统一出口)。 func mapGormErr(err error) *apperrors.AppError { switch { case err == nil: return nil case stderrors.Is(err, gorm.ErrRecordNotFound): return apperrors.WithCause(apperrors.ErrNotFound, err) default: return apperrors.WithCause(apperrors.ErrDBError, err) } } // PermissionRepo 权限点字典 + 角色矩阵仓储(平台级)。 type PermissionRepo struct { db *gorm.DB } // NewPermissionRepo 构造权限点/角色矩阵仓储。 // // 入参:db *gorm.DB:数据库句柄(必填)。 // 出参:*PermissionRepo:就绪仓储。 func NewPermissionRepo(db *gorm.DB) *PermissionRepo { return &PermissionRepo{db: db} } // AllActive 取全部 active 权限点(菜单树构建用;按 sort 升序,构树方自行按 parent_code 组装)。 // // 入参:ctx 请求上下文。 // 出参: // - []model.Permission:全部 active 权限点(**非 nil**,空时为 [];disabled 不返回); // - error:ErrDBError(DB 异常)。 // // 实现说明:权限点是平台级字典(量级 < 百),一次全取 + 进程内缓存(rbac 包), // 不做分页;Order(sort ASC, code ASC) 保证同级排序稳定。 func (r *PermissionRepo) AllActive(ctx context.Context) ([]model.Permission, error) { var list []model.Permission if err := r.db.WithContext(ctx). Where("status = ?", model.PermissionStatusActive). Order("sort ASC, code ASC"). Find(&list).Error; err != nil { return nil, mapGormErr(err) } if list == nil { list = []model.Permission{} } return list, nil } // RolePermissionCodes 取某角色的全部权限点 code(去重、升序)。 // // 入参:ctx 请求上下文;roleCode 角色码(t_role 字典值)。 // 出参: // - []string:权限点 code 切片(**非 nil**,无授权时为 []string{}——前端按空集处理: // 菜单空 + 按钮全隐;超管的"全量放行"在 rbac 包/中间件层实现,不经本方法); // - error:ErrDBError(DB 异常)。 func (r *PermissionRepo) RolePermissionCodes(ctx context.Context, roleCode string) ([]string, error) { var codes []string if err := r.db.WithContext(ctx). Model(&model.RolePermission{}). Where("role_code = ?", roleCode). Distinct(). Order("permission_code ASC"). Pluck("permission_code", &codes).Error; err != nil { return nil, mapGormErr(err) } if codes == nil { codes = []string{} } return codes, nil } // SaveRolePermissions 全量保存某角色的权限点矩阵(超管配置端点,T6.5-e)。 // // 入参: // - ctx 请求上下文; // - roleCode 角色码(必须为 t_role 字典内角色,调用方校验;本方法只做矩阵维护); // - permissionCodes []string:目标全量权限点集合(可为空 = 清空该角色矩阵)。 // // 出参:error——ErrDBError(DB 异常,含事务回滚)。 // // 语义:**删旧插新**(事务内先删该角色全部矩阵行,再批量插入目标集合)—— // "保存即全量替换",避免差集计算;空集合 = 清空(前端"取消全部勾选"场景合法)。 // 幂等:目标集合与原矩阵一致时等价于原样重插(先删后插,结果相同)。 func (r *PermissionRepo) SaveRolePermissions(ctx context.Context, roleCode string, permissionCodes []string) error { return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Where("role_code = ?", roleCode).Delete(&model.RolePermission{}).Error; err != nil { return err } if len(permissionCodes) == 0 { return nil // 清空语义(合法:角色无任何可见菜单/按钮) } rows := make([]model.RolePermission, 0, len(permissionCodes)) for _, code := range permissionCodes { rows = append(rows, model.RolePermission{RoleCode: roleCode, PermissionCode: code}) } return tx.Create(&rows).Error // GORM 批量 Create(切片) }) }