refactor(auth): 优化设备登录逻辑,移除冗余代码并添加设备缓存
Build docker and publish / build (20.15.1) (push) Failing after 6m39s

feat(database): 添加用户算法和盐字段的迁移脚本

fix(subscribe): 修复服务器用户列表缓存问题,临时禁用缓存

style(model): 清理用户模型注释,简化代码结构

chore: 删除无用脚本和测试文件

docs: 添加用户绑定流程文档

perf(login): 优化设备登录性能,添加设备缓存键

fix(unbind): 修复设备解绑时的缓存清理逻辑

refactor(verify): 简化邮箱验证逻辑,移除冗余代码

build(docker): 更新Dockerfile配置,使用scratch基础镜像
This commit is contained in:
2025-10-28 20:46:21 -07:00
parent e6bd78aa76
commit 0f38b3fcd3
33 changed files with 226 additions and 1605 deletions
@@ -141,6 +141,7 @@ func (l *QueryUserSubscribeNodeListLogic) getServers(userSub *user.Subscribe) (u
Name: n.Name,
Uuid: userSub.UUID,
Protocol: n.Protocol,
Protocols: server.Protocols,
Port: n.Port,
Address: n.Address,
Tags: strings.Split(n.Tags, ","),
@@ -246,14 +246,14 @@ func (l *BindEmailWithVerificationLogic) transferDeviceToEmailUser(deviceUserId,
return nil, err
}
// 5. 强制清除邮箱用户的所有相关缓存(确保获取最新数据)// 清除邮箱用户缓存
emailUser, _ := l.svcCtx.UserModel.FindOne(l.ctx, emailUserId)
if emailUser != nil {
// 清除用户的批量相关缓存(包括设备、认证方法等)
if err := l.svcCtx.UserModel.BatchClearRelatedCache(l.ctx, emailUser); err != nil {
l.Errorw("清理邮箱用户相关缓存失败", logger.Field("error", err.Error()), logger.Field("user_id", emailUser.Id))
}
}
// // 5. 强制清除邮箱用户的所有相关缓存(确保获取最新数据)// 清除邮箱用户缓存
// emailUser, _ := l.svcCtx.UserModel.FindOne(l.ctx, emailUserId)
// if emailUser != nil {
// // 清除用户的批量相关缓存(包括设备、认证方法等)
// if err := l.svcCtx.UserModel.BatchClearRelatedCache(l.ctx, emailUser); err != nil {
// l.Errorw("清理邮箱用户相关缓存失败", logger.Field("error", err.Error()), logger.Field("user_id", emailUser.Id))
// }
// }
// 6. 清除设备相关缓存
// l.clearDeviceRelatedCache(deviceIdentifier, deviceUserId, emailUserId)
@@ -41,11 +41,7 @@ func (l *UnbindDeviceLogic) UnbindDevice(req *types.UnbindDeviceRequest) error {
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "device not belong to user")
}
// 保存设备信息用于后续缓存清理
deviceIdentifier := device.Identifier
userId := device.UserId
err = l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
return l.svcCtx.DB.Transaction(func(tx *gorm.DB) error {
var deleteDevice user.Device
err = tx.Model(&deleteDevice).Where("id = ?", req.Id).First(&deleteDevice).Error
if err != nil {
@@ -59,9 +55,6 @@ func (l *UnbindDeviceLogic) UnbindDevice(req *types.UnbindDeviceRequest) error {
err = tx.Model(&userAuth).Where("auth_identifier = ? and auth_type = ?", deleteDevice.Identifier, "device").First(&userAuth).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
l.Infow("设备认证方法不存在,可能已被删除",
logger.Field("device_identifier", deleteDevice.Identifier),
logger.Field("user_id", userId))
return nil
}
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "find device online record err: %v", err)
@@ -72,68 +65,13 @@ func (l *UnbindDeviceLogic) UnbindDevice(req *types.UnbindDeviceRequest) error {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "delete device online record err: %v", err)
}
l.Infow("设备解绑成功",
logger.Field("device_id", req.Id),
logger.Field("device_identifier", deviceIdentifier),
logger.Field("user_id", userId))
//remove device cache
deviceCacheKey := fmt.Sprintf("%v:%v", config.DeviceCacheKeyKey, deleteDevice.Identifier)
if sessionId, err := l.svcCtx.Redis.Get(l.ctx, deviceCacheKey).Result(); err == nil && sessionId != "" {
_ = l.svcCtx.Redis.Del(l.ctx, deviceCacheKey).Err()
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
_ = l.svcCtx.Redis.Del(l.ctx, sessionIdCacheKey).Err()
}
return nil
})
if err != nil {
return err
}
// 事务成功后进行缓存清理
l.clearUnbindDeviceCache(deviceIdentifier, userId, userInfo)
return nil
}
// clearUnbindDeviceCache 清除设备解绑相关的缓存
func (l *UnbindDeviceLogic) clearUnbindDeviceCache(deviceIdentifier string, userId int64, userInfo *user.User) {
// 1. 清除当前SessionId缓存(使当前token失效)
if sessionId := l.ctx.Value(constant.CtxKeySessionID); sessionId != nil {
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
if err := l.svcCtx.Redis.Del(l.ctx, sessionIdCacheKey).Err(); err != nil {
l.Errorw("清理SessionId缓存失败",
logger.Field("error", err.Error()),
logger.Field("session_id", sessionId))
} else {
l.Infow("已清理SessionId缓存", logger.Field("session_id", sessionId))
}
}
// 2. 清除用户缓存
if err := l.svcCtx.UserModel.ClearUserCache(l.ctx, userInfo); err != nil {
l.Errorw("清理用户缓存失败",
logger.Field("error", err.Error()),
logger.Field("user_id", userId))
} else {
l.Infow("已清理用户缓存", logger.Field("user_id", userId))
}
// 3. 清除设备相关缓存
l.clearDeviceRelatedCache(deviceIdentifier, userId)
}
// clearDeviceRelatedCache 清除设备相关缓存
func (l *UnbindDeviceLogic) clearDeviceRelatedCache(deviceIdentifier string, userId int64) {
// 清除设备相关的缓存键
deviceCacheKeys := []string{
fmt.Sprintf("device:%s", deviceIdentifier),
fmt.Sprintf("user_device:%d", userId),
fmt.Sprintf("user_auth:%d", userId),
fmt.Sprintf("device_auth:%s", deviceIdentifier),
}
for _, key := range deviceCacheKeys {
if err := l.svcCtx.Redis.Del(l.ctx, key).Err(); err != nil {
l.Errorw("清除设备缓存失败",
logger.Field("error", err.Error()),
logger.Field("cache_key", key))
} else {
l.Infow("已清除设备缓存", logger.Field("cache_key", key))
}
}
}
+3 -28
View File
@@ -15,16 +15,13 @@ import (
"github.com/pkg/errors"
)
// VerifyEmailLogic 邮箱验证逻辑结构体
// 用于处理用户邮箱验证码验证的业务逻辑
type VerifyEmailLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewVerifyEmailLogic 创建邮箱验证逻辑实例
// 用于初始化邮箱验证处理器
// Verify Email
func NewVerifyEmailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *VerifyEmailLogic {
return &VerifyEmailLogic{
Logger: logger.WithContext(ctx),
@@ -33,68 +30,46 @@ func NewVerifyEmailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Verif
}
}
// CacheKeyPayload Redis缓存中验证码的数据结构
// 用于存储验证码和最后发送时间
type CacheKeyPayload struct {
Code string `json:"code"` // 验证码
LastAt int64 `json:"lastAt"` // 最后发送时间戳
Code string `json:"code"`
LastAt int64 `json:"lastAt"`
}
// VerifyEmail 验证邮箱验证码
// 该方法用于验证用户输入的邮箱验证码是否正确,并将邮箱标记为已验证状态
func (l *VerifyEmailLogic) VerifyEmail(req *types.VerifyEmailRequest) error {
// 构建Redis缓存键,格式:认证码缓存前缀:安全标识:邮箱地址
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Security, req.Email)
// 从Redis中获取验证码缓存数据
value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
if err != nil {
l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey))
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
// 解析缓存中的验证码数据
var payload CacheKeyPayload
err = json.Unmarshal([]byte(value), &payload)
if err != nil {
l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey))
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
// 验证用户输入的验证码是否与缓存中的验证码匹配
if payload.Code != req.Code {
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
// 验证成功后删除Redis中的验证码缓存(一次性使用)
l.svcCtx.Redis.Del(l.ctx, cacheKey)
// 从上下文中获取当前用户信息
u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
if !ok {
logger.Error("current user is not found in context")
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
}
// 根据邮箱地址查找用户的邮箱认证方式记录
method, err := l.svcCtx.UserModel.FindUserAuthMethodByOpenID(l.ctx, "email", req.Email)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "FindUserAuthMethodByOpenID error")
}
// 验证邮箱认证记录是否属于当前用户(安全检查)
if method.UserId != u.Id {
return errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "invalid access")
}
// 将邮箱标记为已验证状态
method.Verified = true
// 更新数据库中的认证方式记录
err = l.svcCtx.UserModel.UpdateUserAuthMethods(l.ctx, method)
if err != nil {
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "UpdateUserAuthMethods error")
}
return nil
}