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

187 lines
5.0 KiB
Go
Executable File
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 (
"context"
"errors"
"fmt"
"bindbox-game/internal/repository/mysql/dao"
"bindbox-game/internal/repository/mysql/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// BindInviterInput 绑定邀请人输入
type BindInviterInput struct {
InviteCode string // 邀请人的邀请码
}
// BindInviterOutput 绑定邀请人输出
type BindInviterOutput struct {
InviterID int64 `json:"inviter_id"`
InviterNickname string `json:"inviter_nickname"`
}
var (
ErrAlreadyBound = errors.New("already_bound")
ErrInvalidCode = errors.New("invalid_code")
ErrCannotInviteSelf = errors.New("cannot_invite_self")
)
// BindInviter 用户主动绑定邀请人(仅限未绑定过的用户)
func (s *service) BindInviter(ctx context.Context, userID int64, in BindInviterInput) (*BindInviterOutput, error) {
var result *BindInviterOutput
err := s.writeDB.Transaction(func(tx *dao.Query) error {
// 1. 获取当前用户,加行锁
user, err := tx.Users.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).
Where(tx.Users.ID.Eq(userID)).First()
if err != nil {
return err
}
// 2. 检查是否已绑定邀请人
if user.InviterID != 0 {
return ErrAlreadyBound
}
// 3. 查找邀请人
inviter, err := tx.Users.WithContext(ctx).Where(tx.Users.InviteCode.Eq(in.InviteCode)).First()
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrInvalidCode
}
return err
}
// 4. 不能邀请自己
if inviter.ID == userID {
return ErrCannotInviteSelf
}
// 5. 创建邀请记录
invite := &model.UserInvites{
InviterID: inviter.ID,
InviteeID: userID,
InviteCode: in.InviteCode,
}
if err := tx.UserInvites.WithContext(ctx).Create(invite); err != nil {
return err
}
// 6. 更新用户的邀请人ID
if _, err := tx.Users.WithContext(ctx).Where(tx.Users.ID.Eq(userID)).
UpdateColumn(tx.Users.InviterID, inviter.ID); err != nil {
return err
}
result = &BindInviterOutput{
InviterID: inviter.ID,
InviterNickname: inviter.Nickname,
}
return nil
})
if err != nil {
return nil, err
}
return result, nil
}
// AdminBindInviterInput 管理端强制绑定/修改/解绑邀请人输入
type AdminBindInviterInput struct {
TargetUserID int64 // 被操作用户ID
InviterUserID int64 // 新邀请人ID0 表示解绑)
OperatorID int64 // 操作管理员ID
}
// AdminBindInviterOutput 管理端操作结果
type AdminBindInviterOutput struct {
OldInviterID int64 `json:"old_inviter_id"`
OldInviterNickname string `json:"old_inviter_nickname"`
NewInviterID int64 `json:"new_inviter_id"`
NewInviterNickname string `json:"new_inviter_nickname"`
}
// AdminBindInviter 管理端强制设置/修改/解绑用户的邀请人
func (s *service) AdminBindInviter(ctx context.Context, in AdminBindInviterInput) (*AdminBindInviterOutput, error) {
var result *AdminBindInviterOutput
err := s.writeDB.Transaction(func(tx *dao.Query) error {
// 1. 加锁获取目标用户
targetUser, err := tx.Users.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).
Where(tx.Users.ID.Eq(in.TargetUserID)).First()
if err != nil {
return fmt.Errorf("target_user_not_found")
}
// 2. 不允许自己绑自己
if in.InviterUserID > 0 && in.InviterUserID == in.TargetUserID {
return ErrCannotInviteSelf
}
// 3. 查询旧邀请人昵称
oldInviterNickname := ""
if targetUser.InviterID > 0 {
if oldInviter, e := tx.Users.WithContext(ctx).
Where(tx.Users.ID.Eq(targetUser.InviterID)).First(); e == nil {
oldInviterNickname = oldInviter.Nickname
}
}
// 4. 查询新邀请人(解绑时跳过)
newInviterNickname := ""
if in.InviterUserID > 0 {
newInviter, e := tx.Users.WithContext(ctx).
Where(tx.Users.ID.Eq(in.InviterUserID)).First()
if e != nil {
return fmt.Errorf("inviter_user_not_found")
}
newInviterNickname = newInviter.Nickname
}
// 5. 更新 users.inviter_id
if _, err := tx.Users.WithContext(ctx).
Where(tx.Users.ID.Eq(in.TargetUserID)).
Update(tx.Users.InviterID, in.InviterUserID); err != nil {
return err
}
// 6. 同步 user_invites先删旧记录再插新记录
if targetUser.InviterID > 0 {
if _, err := tx.UserInvites.WithContext(ctx).
Where(tx.UserInvites.InviteeID.Eq(in.TargetUserID)).
Where(tx.UserInvites.InviterID.Eq(targetUser.InviterID)).
Delete(); err != nil {
return err
}
}
if in.InviterUserID > 0 {
newInvite := &model.UserInvites{
InviterID: in.InviterUserID,
InviteeID: in.TargetUserID,
InviteCode: fmt.Sprintf("admin_op_by_%d", in.OperatorID),
}
if err := tx.UserInvites.WithContext(ctx).Create(newInvite); err != nil {
return err
}
}
result = &AdminBindInviterOutput{
OldInviterID: targetUser.InviterID,
OldInviterNickname: oldInviterNickname,
NewInviterID: in.InviterUserID,
NewInviterNickname: newInviterNickname,
}
return nil
})
if err != nil {
return nil, err
}
return result, nil
}