// IdentityRepo 登录身份仓储(t_identity,一人多登录方式,(type,identifier,appid) 唯一·v1.2)。 // // 平台级访问(同 user_repo.go 包注释):不继承 scope.BaseRepo,裸 *gorm.DB。 // 登录主链路的核心查询:按 (type, identifier, appid) 唯一定位 → 取 canonical_user_id → 再查 user。 // v1.2:唯一键从 (type,identifier) 扩为 (type,identifier,appid)——非微信类型 appid 恒 "", // 微信类型 appid 必填(per-appid 唯一,P0-07 M5 实测同 appid 同 openid 重复=0)。 package repository import ( "context" "gorm.io/gorm" "yuansiwei/unified-backend/internal/identity/model" ) // IdentityRepo 登录身份仓储。 type IdentityRepo struct { db *gorm.DB } // NewIdentityRepo 构造登录身份仓储。 // // 入参:db *gorm.DB:数据库句柄(必填)。 // 出参:*IdentityRepo:就绪仓储。 func NewIdentityRepo(db *gorm.DB) *IdentityRepo { return &IdentityRepo{db: db} } // GetByTypeIdentifierAppID 按 (type, identifier, appid) 唯一键取登录身份记录(v1.2)。 // // 入参: // - ctx context.Context:请求上下文。 // - typ string:身份类型(model.IdentityType* 之一:phone/email/wechat/username/legacy_academic/legacy_teaching)。 // - identifier string:归一后的标识值(phone 11 位 / email 小写 / wechat open_id / username 原值)。 // - appid string:来源 appid(v1.2 进唯一键;非微信类型恒 "",微信类型必填)。 // // 出参: // - *model.Identity:身份记录(含 CanonicalUserID,供 Service 再查 user)。 // - error: // - errors.ErrNotFound:该 (type, identifier, appid) 无身份记录(登录"账号不存在"路径之一,防枚举统一文案); // - errors.ErrDBError:DB 异常。 // // 归一纪律(调用方责任): // - email 必须小写、phone 必须 11 位数字、wechat 必须 open_id 原值——Repository 不做归一, // 归一发生在 Service 入参处理(与旧系统数据归一规则一致,见 P0-02 盘点)。 // - 非微信类型 appid 必须传 ""(对齐 DDL NOT NULL DEFAULT '');微信类型 appid 必须传实际值。 // // 实现说明:用 model(&model.Identity{}) 显式指定实体(GORM 自动映射表名 t_identity,TableName() 已锁定), // Where 用 (type, identifier, appid) 三列联合条件 + First 取单条(UNIQUE 键保证最多 1 条,无需 Limit 但 First 自带取一)。 func (r *IdentityRepo) GetByTypeIdentifierAppID(ctx context.Context, typ, identifier, appid string) (*model.Identity, error) { var it model.Identity if err := r.db.WithContext(ctx). Where("type = ? AND identifier = ? AND appid = ?", typ, identifier, appid). First(&it).Error; err != nil { return nil, mapGormErr(err) } return &it, nil }