bindbox-game/internal/service/user/login_douyin.go

200 lines
5.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package user
import (
"bytes"
"context"
"encoding/base64"
"errors"
"image/png"
"bindbox-game/internal/pkg/douyin"
"bindbox-game/internal/repository/mysql/dao"
"bindbox-game/internal/repository/mysql/model"
randomname "github.com/DanPlayer/randomname"
identicon "github.com/issue9/identicon/v2"
"go.uber.org/zap"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// LoginDouyinInput 抖音登录输入参数
type LoginDouyinInput struct {
Code string // tt.login 获取的 code
AnonymousCode string // 匿名登录 code
Nickname string
AvatarURL string
InviteCode string
ChannelCode string
}
// LoginDouyinOutput 抖音登录输出结果
type LoginDouyinOutput struct {
User *model.Users
IsNewUser bool
InviterID int64
}
// LoginDouyin 抖音小程序登录
func (s *service) LoginDouyin(ctx context.Context, in LoginDouyinInput) (*LoginDouyinOutput, error) {
// 1. 调用抖音 code2session 获取 openid
if in.Code == "" && in.AnonymousCode == "" {
return nil, errors.New("code 或 anonymous_code 不能为空")
}
resp, err := douyin.Code2Session(ctx, in.Code, in.AnonymousCode)
if err != nil {
s.logger.Error("抖音 code2session 失败", zap.Error(err))
return nil, err
}
openID := resp.Data.OpenID
if openID == "" {
openID = resp.Data.AnonymousOpenID
}
if openID == "" {
return nil, errors.New("获取抖音 openid 失败")
}
unionID := resp.Data.UnionID
var u *model.Users
// 事务处理:创建/更新用户 + 处理邀请
var isNewUser bool
var inviterID int64
err = s.writeDB.Transaction(func(tx *dao.Query) error {
var err error
// 2. 先通过 douyin_id 查找用户
u, err = tx.Users.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).
Where(tx.Users.DouyinID.Eq(openID)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
// 3. 如果有 unionid尝试通过 unionid 关联到已有的微信用户
if u == nil && unionID != "" {
u, err = tx.Users.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).
Where(tx.Users.Unionid.Eq(unionID)).First()
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return err
}
// 找到微信用户绑定抖音ID
if u != nil && u.DouyinID == "" {
_, _ = tx.Users.WithContext(ctx).Where(tx.Users.ID.Eq(u.ID)).
Updates(map[string]any{"douyin_id": openID})
u.DouyinID = openID
}
}
// 查找渠道ID
var channelID int64
if in.ChannelCode != "" {
ch, _ := s.readDB.Channels.WithContext(ctx).Where(s.readDB.Channels.Code.Eq(in.ChannelCode)).First()
if ch != nil {
channelID = ch.ID
}
}
// 4. 如果用户不存在,创建新用户
isNewUser = false
if u == nil {
isNewUser = true
code := s.generateInviteCode(ctx)
nickname := in.Nickname
if nickname == "" {
nickname = randomname.GenerateName()
}
avatar := in.AvatarURL
if avatar == "" {
seed := openID
if seed == "" {
seed = nickname
}
img := identicon.S2(128).Make([]byte(seed))
var buf bytes.Buffer
_ = png.Encode(&buf, img)
avatar = "data:image/png;base64," + base64.StdEncoding.EncodeToString(buf.Bytes())
}
u = &model.Users{
Nickname: nickname,
Avatar: avatar,
DouyinID: openID,
Unionid: unionID,
InviteCode: code,
Status: 1,
ChannelID: channelID,
}
if err := tx.Users.WithContext(ctx).Create(u); err != nil {
return err
}
} else {
// 更新用户信息
set := map[string]any{}
if in.Nickname != "" && u.Nickname == "" {
set["nickname"] = in.Nickname
}
if in.AvatarURL != "" && u.Avatar == "" {
set["avatar"] = in.AvatarURL
}
if unionID != "" && u.Unionid == "" {
set["unionid"] = unionID
}
if channelID > 0 && u.ChannelID == 0 {
set["channel_id"] = channelID
}
if len(set) > 0 {
if _, err := tx.Users.WithContext(ctx).Where(tx.Users.ID.Eq(u.ID)).Updates(set); err != nil {
return err
}
u, _ = tx.Users.WithContext(ctx).Where(tx.Users.ID.Eq(u.ID)).First()
}
}
// 5. 处理邀请逻辑
// 只有在真正创建新用户记录时才发放邀请奖励,防止多账号切换重复刷奖励
if in.InviteCode != "" && isNewUser {
// 查询邀请人
var inviter model.Users
// First() 返回 (result, error)
inviterResult, err := tx.Users.WithContext(ctx).Where(tx.Users.InviteCode.Eq(in.InviteCode)).First()
if err == nil && inviterResult != nil {
inviter = *inviterResult
// 创建邀请记录
invite := &model.UserInvites{
InviteeID: u.ID, // UserID -> InviteeID
InviterID: inviter.ID,
InviteCode: in.InviteCode,
// Status: 1, // Removed
}
if err := tx.UserInvites.WithContext(ctx).Create(invite); err != nil {
return err
}
// 更新被邀请人的邀请人ID
// UpdateColumn for single column update
if _, err := tx.Users.WithContext(ctx).Where(tx.Users.ID.Eq(u.ID)).
UpdateColumn(tx.Users.InviterID, inviter.ID); err != nil {
return err
}
// 返回邀请人ID以便外层触发任务中心逻辑
inviterID = inviter.ID
s.logger.Info("抖音登录邀请关系建立成功", zap.Int64("user_id", u.ID), zap.Int64("inviter_id", inviter.ID))
}
}
return nil
})
if err != nil {
return nil, err
}
return &LoginDouyinOutput{
User: u,
IsNewUser: isNewUser,
InviterID: inviterID,
}, nil
}