package auth import ( "context" "errors" "strings" "github.com/google/uuid" "gorm.io/gorm" "dudu/server/internal/store" ) // WechatIdentityInfo 微信授权换取的身份。 type WechatIdentityInfo struct { OpenID string UnionID string Nickname string Avatar string } // WechatClient 微信 OAuth 客户端抽象:真实实现走开放平台 API(2A 凭证就绪后接入), // 开发期用 MockWechat。 type WechatClient interface { // ExchangeCode code 换身份;appType: web | mobile ExchangeCode(ctx context.Context, code, appType string) (WechatIdentityInfo, error) } // MockWechat 开发期 mock:code 直接映射 openid(同 code 幂等同一用户), // unionid = openid 加前缀,昵称取 code 前 8 字符。 type MockWechat struct{} func (MockWechat) ExchangeCode(_ context.Context, code, _ string) (WechatIdentityInfo, error) { if code == "" { return WechatIdentityInfo{}, errors.New("empty code") } nick := code if len(nick) > 8 { nick = nick[:8] } return WechatIdentityInfo{ OpenID: "mock-open-" + code, UnionID: "mock-union-" + code, Nickname: nick, }, nil } // MaskNickname 昵称脱敏:"wangjia" → "wang***";中文取首字 + ***。 func MaskNickname(n string) string { r := []rune(strings.TrimSpace(n)) if len(r) == 0 { return "用户***" } keep := 4 if r[0] > 0x2E80 { // CJK 起始附近,中文名只保留首字 keep = 1 } if len(r) < keep { keep = len(r) } return string(r[:keep]) + "***" } // FindOrCreateUser unionid 优先关联(同一用户多端 openid 归一),其次 openid。 func FindOrCreateUser(db *gorm.DB, info WechatIdentityInfo, appType string) (*store.User, error) { var ident store.WechatIdentity err := db.Where("open_id = ?", info.OpenID).First(&ident).Error if err == nil { var u store.User return &u, db.First(&u, "id = ?", ident.UserID).Error } if !errors.Is(err, gorm.ErrRecordNotFound) { return nil, err } var user store.User err = db.Transaction(func(tx *gorm.DB) error { // unionid 已存在 → 关联到既有用户 if info.UnionID != "" { var other store.WechatIdentity if err := tx.Where("union_id = ?", info.UnionID).First(&other).Error; err == nil { if err := tx.First(&user, "id = ?", other.UserID).Error; err != nil { return err } return tx.Create(&store.WechatIdentity{ UserID: user.ID, OpenID: info.OpenID, UnionID: info.UnionID, AppType: appType, }).Error } } // 新用户 user = store.User{ ID: strings.ReplaceAll(uuid.NewString(), "-", "")[:24], Nickname: info.Nickname, AvatarURL: info.Avatar, } if err := tx.Create(&user).Error; err != nil { return err } return tx.Create(&store.WechatIdentity{ UserID: user.ID, OpenID: info.OpenID, UnionID: info.UnionID, AppType: appType, }).Error }) return &user, err }