40760aa884
- server:Go 网关(WS 流式识别中继/计费配额/微信登录支付 mock/反馈/埋点),gummy provider 已真实联调 - desktop:Tauri 2(全局快捷键 push-to-talk/浮层/托盘/设置/登录购买/反馈/首启引导) - android:Compose 主 App + IME(键盘内录音直传) - ios:App + 键盘扩展(1A spike 实证键盘内不可录音,走 deep link 听写) - design/design-pipeline:设计系统 + token 导出 iOS/Android 主题 - doc:前后端设计文档(HTML);web:官网宣传页;todo:任务看板 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
104 lines
2.8 KiB
Go
104 lines
2.8 KiB
Go
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
|
||
}
|