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 }