From 6afd6eb30710fbaa6dff5369d0f4fdea1cd52dc1 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Thu, 27 Nov 2025 23:58:10 -0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E6=B7=BB=E5=8A=A0=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=BB=91=E5=AE=9A=E6=95=B0=E9=87=8F=E9=99=90=E5=88=B6?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在设备绑定逻辑中添加对设备绑定数量的限制检查,当超过限制时返回特定错误码 同时在用户注册、登录等流程中处理设备绑定数量超限的错误情况 --- internal/logic/auth/bindDeviceLogic.go | 30 ++++++++++++++----- internal/logic/auth/resetPasswordLogic.go | 25 +++++++++------- internal/logic/auth/telephoneLoginLogic.go | 19 +++++++----- .../logic/auth/telephoneUserRegisterLogic.go | 25 +++++++++------- internal/logic/auth/userLoginLogic.go | 19 +++++++----- internal/logic/auth/userRegisterLogic.go | 25 +++++++++------- pkg/xerr/errCode.go | 5 ++-- 7 files changed, 90 insertions(+), 58 deletions(-) diff --git a/internal/logic/auth/bindDeviceLogic.go b/internal/logic/auth/bindDeviceLogic.go index 19b0666..b7fceb1 100644 --- a/internal/logic/auth/bindDeviceLogic.go +++ b/internal/logic/auth/bindDeviceLogic.go @@ -88,7 +88,15 @@ func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, logger.Field("user_id", userId), ) - err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { + // enforce device bind limit before creating + if limit := l.svcCtx.SessionLimit(); limit > 0 { + if _, count, err := l.svcCtx.UserModel.QueryDeviceList(l.ctx, userId); err == nil { + if count >= limit { + return xerr.NewErrCodeMsg(xerr.DeviceBindLimitExceeded, "账户绑定设备数已达上限,请移除其他设备后再登录,您也可以再注册一个新账户使用,点击帮助中心查看更多详情。") + } + } + } + err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { // Create device auth method authMethod := &user.AuthMethods{ UserId: userId, @@ -123,8 +131,8 @@ func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device failed: %v", err) } - return nil - }) + return nil + }) if err != nil { l.Errorw("device creation failed", @@ -144,9 +152,17 @@ func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, } func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, userAgent string, newUserId int64) error { - oldUserId := deviceInfo.UserId + oldUserId := deviceInfo.UserId - err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { + // enforce device bind limit before rebind + if limit := l.svcCtx.SessionLimit(); limit > 0 { + if _, count, err := l.svcCtx.UserModel.QueryDeviceList(l.ctx, newUserId); err == nil { + if count >= limit { + return xerr.NewErrCodeMsg(xerr.DeviceBindLimitExceeded, "账户绑定设备数已达上限,请移除其他设备后再登录,您也可以再注册一个新账户使用,点击帮助中心查看更多详情。") + } + } + } + err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { // Check if old user has other auth methods besides device var authMethods []user.AuthMethods if err := db.Where("user_id = ?", oldUserId).Find(&authMethods).Error; err != nil { @@ -211,8 +227,8 @@ func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, use return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err) } - return nil - }) + return nil + }) if err != nil { l.Errorw("device rebinding failed", diff --git a/internal/logic/auth/resetPasswordLogic.go b/internal/logic/auth/resetPasswordLogic.go index aef245a..bd4cb2e 100644 --- a/internal/logic/auth/resetPasswordLogic.go +++ b/internal/logic/auth/resetPasswordLogic.go @@ -110,17 +110,20 @@ func (l *ResetPasswordLogic) ResetPassword(req *types.ResetPasswordRequest) (res } // Bind device to user if identifier is provided - if req.Identifier != "" { - bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) - if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { - l.Errorw("failed to bind device to user", - logger.Field("user_id", userInfo.Id), - logger.Field("identifier", req.Identifier), - logger.Field("error", err.Error()), - ) - // Don't fail register if device binding fails, just log the error - } - } + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + var ce *xerr.CodeError + if errors.As(err, &ce) && ce.GetErrCode() == xerr.DeviceBindLimitExceeded { + return nil, ce + } + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + } + } if l.ctx.Value(constant.LoginType) != nil { req.LoginType = l.ctx.Value(constant.LoginType).(string) } diff --git a/internal/logic/auth/telephoneLoginLogic.go b/internal/logic/auth/telephoneLoginLogic.go index b3169ec..34c11bc 100644 --- a/internal/logic/auth/telephoneLoginLogic.go +++ b/internal/logic/auth/telephoneLoginLogic.go @@ -128,12 +128,15 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r if req.Identifier != "" { bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + var ce *xerr.CodeError + if errors.As(err, &ce) && ce.GetErrCode() == xerr.DeviceBindLimitExceeded { + return nil, ce + } l.Errorw("failed to bind device to user", logger.Field("user_id", userInfo.Id), logger.Field("identifier", req.Identifier), logger.Field("error", err.Error()), ) - // Don't fail login if device binding fails, just log the error } } @@ -156,16 +159,16 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error()) } - if err = l.svcCtx.EnforceUserSessionLimit(l.ctx, userInfo.Id, sessionId, l.svcCtx.SessionLimit()); err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "enforce session limit error: %v", err.Error()) - } + if err = l.svcCtx.EnforceUserSessionLimit(l.ctx, userInfo.Id, sessionId, l.svcCtx.SessionLimit()); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "enforce session limit error: %v", err.Error()) + } sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId) if err = l.svcCtx.Redis.Set(l.ctx, sessionIdCacheKey, userInfo.Id, time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire)*time.Second).Err(); err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error()) } loginStatus = true - return &types.LoginResponse{ - Token: token, - Limit: l.svcCtx.SessionLimit(), - }, nil + return &types.LoginResponse{ + Token: token, + Limit: l.svcCtx.SessionLimit(), + }, nil } diff --git a/internal/logic/auth/telephoneUserRegisterLogic.go b/internal/logic/auth/telephoneUserRegisterLogic.go index 5fad294..ff8d042 100644 --- a/internal/logic/auth/telephoneUserRegisterLogic.go +++ b/internal/logic/auth/telephoneUserRegisterLogic.go @@ -141,17 +141,20 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR }) // Bind device to user if identifier is provided - if req.Identifier != "" { - bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) - if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { - l.Errorw("failed to bind device to user", - logger.Field("user_id", userInfo.Id), - logger.Field("identifier", req.Identifier), - logger.Field("error", err.Error()), - ) - // Don't fail register if device binding fails, just log the error - } - } + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + var ce *xerr.CodeError + if errors.As(err, &ce) && ce.GetErrCode() == xerr.DeviceBindLimitExceeded { + return nil, ce + } + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + } + } if l.ctx.Value(constant.LoginType) != nil { req.LoginType = l.ctx.Value(constant.LoginType).(string) } diff --git a/internal/logic/auth/userLoginLogic.go b/internal/logic/auth/userLoginLogic.go index e0b8dad..6fe2b72 100644 --- a/internal/logic/auth/userLoginLogic.go +++ b/internal/logic/auth/userLoginLogic.go @@ -85,12 +85,15 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log if req.Identifier != "" { bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + var ce *xerr.CodeError + if errors.As(err, &ce) && ce.GetErrCode() == xerr.DeviceBindLimitExceeded { + return nil, ce + } l.Errorw("failed to bind device to user", logger.Field("user_id", userInfo.Id), logger.Field("identifier", req.Identifier), logger.Field("error", err.Error()), ) - // Don't fail login if device binding fails, just log the error } } if l.ctx.Value(constant.LoginType) != nil { @@ -111,16 +114,16 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error()) } - if err = l.svcCtx.EnforceUserSessionLimit(l.ctx, userInfo.Id, sessionId, l.svcCtx.SessionLimit()); err != nil { - return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "enforce session limit error: %v", err.Error()) - } + if err = l.svcCtx.EnforceUserSessionLimit(l.ctx, userInfo.Id, sessionId, l.svcCtx.SessionLimit()); err != nil { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "enforce session limit error: %v", err.Error()) + } sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId) if err = l.svcCtx.Redis.Set(l.ctx, sessionIdCacheKey, userInfo.Id, time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire)*time.Second).Err(); err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error()) } loginStatus = true - return &types.LoginResponse{ - Token: token, - Limit: l.svcCtx.SessionLimit(), - }, nil + return &types.LoginResponse{ + Token: token, + Limit: l.svcCtx.SessionLimit(), + }, nil } diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index 0198911..1ac69a9 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -127,17 +127,20 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * return nil }) // Bind device to user if identifier is provided - if req.Identifier != "" { - bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) - if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { - l.Errorw("failed to bind device to user", - logger.Field("user_id", userInfo.Id), - logger.Field("identifier", req.Identifier), - logger.Field("error", err.Error()), - ) - // Don't fail register if device binding fails, just log the error - } - } + if req.Identifier != "" { + bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) + if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil { + var ce *xerr.CodeError + if errors.As(err, &ce) && ce.GetErrCode() == xerr.DeviceBindLimitExceeded { + return nil, ce + } + l.Errorw("failed to bind device to user", + logger.Field("user_id", userInfo.Id), + logger.Field("identifier", req.Identifier), + logger.Field("error", err.Error()), + ) + } + } if l.ctx.Value(constant.LoginType) != nil { req.LoginType = l.ctx.Value(constant.LoginType).(string) } diff --git a/pkg/xerr/errCode.go b/pkg/xerr/errCode.go index ba2501f..3c23167 100644 --- a/pkg/xerr/errCode.go +++ b/pkg/xerr/errCode.go @@ -114,8 +114,9 @@ const ( TelephoneError uint32 = 90014 ) const ( - DeviceNotExist uint32 = 90017 - UseridNotMatch uint32 = 90018 + DeviceNotExist uint32 = 90017 + UseridNotMatch uint32 = 90018 + DeviceBindLimitExceeded uint32 = 90019 ) const (