| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101 |
- // 校区仓储(T6):t_campus 的**作用域**数据访问。
- //
- // 与 identity 平台级仓储的关键差异(架构规格 §3.5):
- // - **继承 scope.BaseRepo**:t_campus 隶属机构(institution_id 列),是"租户内的校区"维度资源,
- // 每次查询必须经 Scoping 强制过滤——多租户防线的唯一收口点在 BaseRepo.Scoping,
- // 本包禁止裸查(r.DB.Where(...) 不经过 Scoping = 跨校区/跨机构越权,风险登记册项)。
- // - scoping 行为(由 scope 包保证):
- // Sc 为 nil → panic(fail-fast,正常请求链路 Auth 中间件保证作用域已注入);
- // SuperAdmin=true → 不加机构/校区过滤(平台超管跨机构视角);
- // 普通用户 → 强制 institution_id = Sc.InstitutionID;CampusID>0 时再强制 campus_id
- // (老师角色锁定本校;教务/财务 campus=0 时可看本机构全部校区)。
- //
- // 依赖方向铁律:org repository → infra/scope + org/model + infra/errors(全下层/同域),
- // 不依赖 handler/service。
- package repository
- import (
- "context"
- stderrors "errors"
- "gorm.io/gorm"
- "yuansiwei/unified-backend/internal/infra/errors"
- appscope "yuansiwei/unified-backend/internal/infra/scope"
- "yuansiwei/unified-backend/internal/org/model"
- )
- // CampusRepo 校区仓储(作用域强制过滤)。
- type CampusRepo struct {
- appscope.BaseRepo
- }
- // NewCampusRepo 构造校区仓储(Handler 在拿到 Scope 后调用;db 必填、sc 可为 nil——
- // nil 进入 Scoping 会 panic,属预期 fail-fast,见 scope.BaseRepo 注释)。
- func NewCampusRepo(db *gorm.DB, sc *appscope.Scope) *CampusRepo {
- return &CampusRepo{BaseRepo: appscope.NewBaseRepo(db, sc)}
- }
- // CampusFilter 校区列表筛选条件(keyword 模糊匹配校区名 / 状态精确匹配;均可空)。
- type CampusFilter struct {
- Keyword string
- Status string
- }
- // PagedList 分页取校区列表(强制作用域过滤:普通用户仅见本机构;CampusID>0 时仅见本校区)。
- //
- // 入参:
- // - ctx context.Context:请求上下文(GORM WithContext 传递取消/超时)。
- // - f CampusFilter:筛选条件(Keyword/Status 为空则不过滤该维度)。
- // - page int:页码(从 1 开始;<1 按 1 处理)。
- // - pageSize int:每页条数(0/负数按 10 兜底——与 response.ParsePage 同语义)。
- //
- // 出参:
- // - []model.Campus:当前页记录(无记录时返回非 nil 空切片,JSON 序列化为 [] 而非 null)。
- // - int64:符合筛选条件的总记录数(分页器"共 N 条")。
- // - error:*AppError——ErrDBError(5002) 数据库异常;无作用域由 Scoping panic 兜底(全局 Recover)。
- func (r *CampusRepo) PagedList(ctx context.Context, f CampusFilter, page, pageSize int) ([]model.Campus, int64, error) {
- if page < 1 {
- page = 1
- }
- if pageSize < 1 {
- pageSize = 10
- }
- q := r.Scoping(r.WithContext(ctx).Model(&model.Campus{}))
- if f.Keyword != "" {
- q = q.Where("campus_name LIKE ?", "%"+f.Keyword+"%")
- }
- if f.Status != "" {
- q = q.Where("status = ?", f.Status)
- }
- var total int64
- if err := q.Count(&total).Error; err != nil { // GORM 纪律:Count 收 *int64
- return nil, 0, errors.WithCause(errors.ErrDBError, err)
- }
- list := make([]model.Campus, 0, pageSize)
- if err := q.Order("campus_id ASC").
- Offset((page - 1) * pageSize).
- Limit(pageSize).
- Find(&list).Error; err != nil {
- return nil, 0, errors.WithCause(errors.ErrDBError, err)
- }
- return list, total, nil
- }
- // Get 按校区 ID 取单个校区(强制作用域过滤:非超管取别家/别校校区 → 查不到 → ErrNotFound)。
- //
- // 入参:ctx 请求上下文;campusID 校区 ID(>0)。
- // 出参:*model.Campus(命中);error——ErrNotFound(3001) 不存在或作用域外 / ErrDBError(5002)。
- func (r *CampusRepo) Get(ctx context.Context, campusID int64) (*model.Campus, error) {
- var cp model.Campus
- err := r.Scoping(r.WithContext(ctx)).
- Where("campus_id = ?", campusID).
- First(&cp).Error
- if stderrors.Is(err, gorm.ErrRecordNotFound) {
- return nil, errors.ErrNotFound
- }
- if err != nil {
- return nil, errors.WithCause(errors.ErrDBError, err)
- }
- return &cp, nil
- }
|