From ba4e5a0aac6b88b9aa36185bcfe8dc69053e9b59 Mon Sep 17 00:00:00 2001 From: yyg-max <175597134+yyg-max@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:38:28 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8Duserid=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/apps/oauth/models.go | 72 ++++++++++++++++++++++- internal/apps/oauth/utils.go | 105 +++++++++++----------------------- 2 files changed, 101 insertions(+), 76 deletions(-) diff --git a/internal/apps/oauth/models.go b/internal/apps/oauth/models.go index 5cb7db8..6ab70e4 100644 --- a/internal/apps/oauth/models.go +++ b/internal/apps/oauth/models.go @@ -27,14 +27,19 @@ package oauth import ( "context" "encoding/json" + "errors" "fmt" + "net/http" + "time" + + "github.com/google/uuid" + "github.com/hibiken/asynq" "github.com/linux-do/cdk/internal/config" "github.com/linux-do/cdk/internal/db" "github.com/linux-do/cdk/internal/logger" + "github.com/linux-do/cdk/internal/task" + "github.com/linux-do/cdk/internal/task/schedule" "github.com/linux-do/cdk/internal/utils" - "net/http" - "time" - "gorm.io/gorm" ) @@ -128,3 +133,64 @@ func (u *User) UpdateUserScore(ctx context.Context, newScore int) error { logger.InfoF(ctx, "用户[%s]徽章分数更新成功: %d -> %d", u.Username, u.Score, newScore) return nil } + +// UpdateFromOAuthInfo 根据 OAuth 信息更新用户数据 +func (u *User) UpdateFromOAuthInfo(oauthInfo *OAuthUserInfo) { + u.Username = oauthInfo.Username + u.Nickname = oauthInfo.Name + u.AvatarUrl = oauthInfo.AvatarUrl + u.IsActive = oauthInfo.Active + u.TrustLevel = oauthInfo.TrustLevel + u.LastLoginAt = time.Now() +} + +// CheckActive 检查用户账户是否激活,未激活则返回错误 +func (u *User) CheckActive() error { + if !u.IsActive { + return errors.New(BannedAccount) + } + return nil +} + +// EnqueueBadgeScoreTask 为用户下发徽章分数计算任务 +func (u *User) EnqueueBadgeScoreTask(ctx context.Context) { + payload, _ := json.Marshal(map[string]interface{}{ + "user_id": u.ID, + }) + if _, err := schedule.AsynqClient.Enqueue(asynq.NewTask(task.UpdateSingleUserBadgeScoreTask, payload)); err != nil { + logger.ErrorF(ctx, "下发用户[%s]徽章分数计算任务失败: %v", u.Username, err) + } else { + logger.InfoF(ctx, "下发用户[%s]徽章分数计算任务成功", u.Username) + } +} + +// MarkAsDeactivatedAndCreateNew 将当前用户标记为已注销,并创建新用户 +func (u *User) MarkAsDeactivatedAndCreateNew(ctx context.Context, oauthInfo *OAuthUserInfo) (*User, error) { + err := db.DB(ctx).Transaction(func(tx *gorm.DB) error { + // 将旧用户名修改为注销状态 + oldUsername := fmt.Sprintf("%s已注销: %s", u.Username, uuid.NewString()) + if err := tx.Model(u).Updates(map[string]interface{}{ + "username": oldUsername, + "is_active": false, + }).Error; err != nil { + return err + } + + // 创建新用户 + newUser := User{ + ID: oauthInfo.Id, + Username: oauthInfo.Username, + Nickname: oauthInfo.Name, + AvatarUrl: oauthInfo.AvatarUrl, + IsActive: oauthInfo.Active, + TrustLevel: oauthInfo.TrustLevel, + LastLoginAt: time.Now(), + } + if err := tx.Create(&newUser).Error; err != nil { + return err + } + *u = newUser + return nil + }) + return u, err +} diff --git a/internal/apps/oauth/utils.go b/internal/apps/oauth/utils.go index eaf4463..437a152 100644 --- a/internal/apps/oauth/utils.go +++ b/internal/apps/oauth/utils.go @@ -28,16 +28,9 @@ import ( "context" "encoding/json" "errors" - "fmt" "io" "time" - "github.com/google/uuid" - "github.com/hibiken/asynq" - "github.com/linux-do/cdk/internal/logger" - "github.com/linux-do/cdk/internal/task" - "github.com/linux-do/cdk/internal/task/schedule" - "github.com/gin-contrib/sessions" "github.com/gin-gonic/gin" "github.com/linux-do/cdk/internal/config" @@ -113,12 +106,24 @@ func doOAuth(ctx context.Context, code string) (*User, error) { return nil, err } - // save to db + // 处理用户信息同步逻辑 var user User - tx := db.DB(ctx).Where("username = ?", userInfo.Username).First(&user) - if tx.Error != nil { - // create user - if errors.Is(tx.Error, gorm.ErrRecordNotFound) { + + txByUsername := db.DB(ctx).Where("username = ?", userInfo.Username).First(&user) + if txByUsername.Error != nil { + txByID := db.DB(ctx).Where("id = ?", userInfo.Id).First(&user) + if txByID.Error == nil { + // ID 存在但 username 不匹配(用户改名) + if err = user.CheckActive(); err != nil { + span.SetStatus(codes.Error, err.Error()) + return nil, err + } + user.UpdateFromOAuthInfo(&userInfo) + if err = db.DB(ctx).Save(&user).Error; err != nil { + span.SetStatus(codes.Error, err.Error()) + return nil, err + } + } else if errors.Is(txByUsername.Error, gorm.ErrRecordNotFound) { user = User{ ID: userInfo.Id, Username: userInfo.Username, @@ -128,79 +133,33 @@ func doOAuth(ctx context.Context, code string) (*User, error) { TrustLevel: userInfo.TrustLevel, LastLoginAt: time.Now(), } - tx = db.DB(ctx).Create(&user) - if tx.Error != nil { - span.SetStatus(codes.Error, tx.Error.Error()) - return nil, tx.Error - } - payload, _ := json.Marshal(map[string]interface{}{ - "user_id": user.ID, - }) - - if _, errTask := schedule.AsynqClient.Enqueue(asynq.NewTask(task.UpdateSingleUserBadgeScoreTask, payload)); errTask != nil { - logger.ErrorF(ctx, "下发用户[%s]徽章分数计算任务失败: %v", user.Username, errTask) - } else { - logger.InfoF(ctx, "下发用户[%s]徽章分数计算任务成功", user.Username) + if err = db.DB(ctx).Create(&user).Error; err != nil { + span.SetStatus(codes.Error, err.Error()) + return nil, err } + user.EnqueueBadgeScoreTask(ctx) } else { - // response failed - span.SetStatus(codes.Error, tx.Error.Error()) - return nil, tx.Error + // query failed + span.SetStatus(codes.Error, txByUsername.Error.Error()) + return nil, txByUsername.Error } } else { if user.ID != userInfo.Id { - err = db.DB(ctx).Transaction(func(tx *gorm.DB) error { - oldUsername := fmt.Sprintf("%s已注销: %s", user.Username, uuid.NewString()) - if errUpdate := tx.Model(&user).Updates(map[string]interface{}{ - "username": oldUsername, - "is_active": false, - }).Error; errUpdate != nil { - return errUpdate - } - // create user - user = User{ - ID: userInfo.Id, - Username: userInfo.Username, - Nickname: userInfo.Name, - AvatarUrl: userInfo.AvatarUrl, - IsActive: userInfo.Active, - TrustLevel: userInfo.TrustLevel, - LastLoginAt: time.Now(), - } - if errCreate := tx.Create(&user).Error; errCreate != nil { - return errCreate - } - return nil - }) - if err != nil { + // username 相同但 ID 不同(账户注销后被新用户占用) + if _, err = user.MarkAsDeactivatedAndCreateNew(ctx, &userInfo); err != nil { span.SetStatus(codes.Error, err.Error()) return nil, err } - payload, _ := json.Marshal(map[string]interface{}{ - "user_id": user.ID, - }) - if _, errTask := schedule.AsynqClient.Enqueue(asynq.NewTask(task.UpdateSingleUserBadgeScoreTask, payload)); errTask != nil { - logger.ErrorF(ctx, "下发用户[%s]徽章分数计算任务失败: %v", user.Username, errTask) - } else { - logger.InfoF(ctx, "下发用户[%s]徽章分数计算任务成功", user.Username) - } + user.EnqueueBadgeScoreTask(ctx) } else { - if !user.IsActive { - err = errors.New(BannedAccount) + if err = user.CheckActive(); err != nil { span.SetStatus(codes.Error, err.Error()) return nil, err } - // update user - user.Username = userInfo.Username - user.Nickname = userInfo.Name - user.AvatarUrl = userInfo.AvatarUrl - user.IsActive = userInfo.Active - user.TrustLevel = userInfo.TrustLevel - user.LastLoginAt = time.Now() - tx = db.DB(ctx).Save(&user) - if tx.Error != nil { - span.SetStatus(codes.Error, tx.Error.Error()) - return nil, tx.Error + user.UpdateFromOAuthInfo(&userInfo) + if err = db.DB(ctx).Save(&user).Error; err != nil { + span.SetStatus(codes.Error, err.Error()) + return nil, err } } }