// 仓储接口(Service 层依赖抽象,供纯 mock 单测——不连 DB 也能覆盖 AuthService 逻辑)。 // // 设计决策(依赖倒置 + 零返工): // - Service 只依赖这 3 个**接口**(不 import 具体结构体); // 生产装配时注入具体实现(NewUserRepo(db) 等),单测时注入 mock。 // - 接口签名与具体仓储方法**逐一对齐**(入参/出参/error 语义完全一致), // 生产装配时具体结构体隐式满足接口——若后续改方法签名,main 装配处编译期即暴露。 // // 命名约定:接口名带 "…Repository"(UserRepository/IdentityRepository/MembershipRepository), // 与本包内具体结构体(UserRepo/IdentityRepo/MembershipRepo)区分,避免同名冲突。 // // 为什么在 repository 包内定义(而非 service 包): // - 接口的"自然归属"是数据访问层(描述"能取哪些数据"); // - service 包 import repository 包取接口(service → repository,方向正确); // - 若接口放 service 包,repository 包要 import service 包实现它 → 反向依赖,违反铁律。 // // 平台级访问(同 user_repo.go 包注释):这 3 个接口的实现不继承 scope.BaseRepo—— // 登录/用户/成员关系是平台级全局查询,多租户防线在后续业务域 Repository 才启动。 package repository import ( "context" "yuansiwei/unified-backend/internal/identity/model" orgmodel "yuansiwei/unified-backend/internal/org/model" ) // UserRepository 用户主表数据访问接口(*UserRepo 隐式满足)。 type UserRepository interface { // GetByCanonicalUserID 按 canonical_user_id(ULID)取单个用户(含密码三列——仅登录链路在内存消费)。 GetByCanonicalUserID(ctx context.Context, uid string) (*model.User, error) // UpdatePasswordLazyUpgrade 惰性升级写回(argon2 重哈希 + salt 置 NULL + hash_version='argon2')。 UpdatePasswordLazyUpgrade(ctx context.Context, uid, newHash string) error } // IdentityRepository 登录身份数据访问接口(*IdentityRepo 隐式满足)。 type IdentityRepository interface { // GetByTypeIdentifierAppID 按 (type, identifier, appid) 唯一键取登录身份记录(v1.2)。 // 非微信类型 appid 传 "",微信类型传实际 appid。 GetByTypeIdentifierAppID(ctx context.Context, typ, identifier, appid string) (*model.Identity, error) } // MembershipRepository 成员关系数据访问接口(*MembershipRepo 隐式满足)。 type MembershipRepository interface { // ListByCanonicalUserID 取某用户全部成员关系(登录第一步返回机构/校区选择列表用;空时返非 nil 空切片)。 ListByCanonicalUserID(ctx context.Context, uid string) ([]orgmodel.Membership, error) // GetByUserInstitutionCampus 按 (canonical_user_id, institution_id, campus_id) 取唯一 membership // (登录第二步 /auth/issue 选定作用域后取 role;同作用域多角色取 created_at 最新)。 GetByUserInstitutionCampus(ctx context.Context, uid string, institutionID, campusID int64) (*orgmodel.Membership, error) } // 编译期接口断言:具体仓储结构体须满足对应接口(防止"接口与实现签名漂移"—— // 改任一侧方法签名时,main 装配处把具体实现当接口传参会编译失败,此处再显式兜一层)。 var ( _ UserRepository = (*UserRepo)(nil) _ IdentityRepository = (*IdentityRepo)(nil) _ MembershipRepository = (*MembershipRepo)(nil) )