187 lines
5.0 KiB
Go
Executable File
187 lines
5.0 KiB
Go
Executable File
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 // 新邀请人ID(0 表示解绑)
|
||
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
|
||
}
|