// 成员关系校验接口(academic 定义,org 侧实现/注入):越权预检的第二道防线。 // // 依据:unified/03-migration/academic-domain-design.md §1.7「全局无作用域表」—— // `t_user_expense`(全局钱包)/ `t_user_divide_into*`(全局推广体系)不带 institution_id, // 无法靠 BaseRepo.Scoping 过滤,必须由 Service 层显式断言"该用户确实属于本机构/校区"。 // // 三层防线分工(架构规格 §3.5): // 1. 路由角色白名单(handler/route.go,AC-14)——粗粒度; // 2. **本接口(MembershipChecker)**——Service 层的对象级越权预检(本次); // 3. Repository 的 BaseRepo.Scoped——数据层强制过滤(academic/repository/base.go)。 package port import "context" // MembershipChecker 成员关系校验器(读 t_membership 只读视图)。 // // 契约: // - 只读,不写 org 表; // - 角色码取 org/model.Role*(super_admin/academic/finance/teacher/student/parent/market); // - `campusID=0` 语义 = 只看机构级(不限定校区)。 type MembershipChecker interface { // HasMembership 判断用户在指定机构/校区是否具备任一给定角色。 // // 入参: // - ctx 请求上下文; // - userID 用户 canonical_user_id(ULID 26 位;空串 → false); // - institutionID 机构 ID(0 = 不限机构,仅平台级判断); // - campusID 校区 ID(0 = 机构级任一校区); // - roles 期望角色码集合(空集合 = 只要有任一角色即算通过)。 // // 出参:bool 是否命中;error 仅数据库异常(infraerrors.ErrDBError)。 HasMembership(ctx context.Context, userID string, institutionID, campusID int64, roles ...string) (bool, error) // AssertInScope 断言用户在作用域内(越权即返回业务错误,供 Service 直接上抛)。 // // 入参:ctx / userID / institutionID / campusID(语义同 HasMembership,但不限定角色)。 // 出参:nil 在范围内;infraerrors.ErrUnscoped(1004) 越权;infraerrors.ErrDBError(5002) DB 异常。 AssertInScope(ctx context.Context, userID string, institutionID, campusID int64) error }