package user import ( "context" "encoding/json" "fmt" "time" "github.com/perfect-panel/server/internal/config" "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "github.com/perfect-panel/server/pkg/constant" "github.com/perfect-panel/server/pkg/jwt" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/tool" "github.com/perfect-panel/server/pkg/uuidx" "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "gorm.io/gorm" ) type BindEmailWithVerificationLogic struct { logger.Logger ctx context.Context svcCtx *svc.ServiceContext } // NewBindEmailWithVerificationLogic Bind Email With Verification func NewBindEmailWithVerificationLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindEmailWithVerificationLogic { return &BindEmailWithVerificationLogic{ Logger: logger.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } // BindEmailWithVerification 处理邮箱绑定流程,并在邮箱已存在时转移设备归属 // 参数说明: // - req: 绑定邮箱请求,包含邮箱地址等信息 // 返回值: // - *types.BindEmailWithVerificationResponse: 包含绑定结果、消息、token、用户ID // - error: 发生错误时返回具体错误 func (l *BindEmailWithVerificationLogic) BindEmailWithVerification(req *types.BindEmailWithVerificationRequest) (*types.BindEmailWithVerificationResponse, error) { // 获取当前用户 u, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User) if !ok { return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access") } type payload struct { Code string `json:"code"` LastAt int64 `json:"lastAt"` } var ( scenes = []string{constant.Security.String(), constant.Register.String()} verified = false ) for _, scene := range scenes { cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, req.Email) value, getErr := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result() if getErr != nil || value == "" { continue } var p payload if err := json.Unmarshal([]byte(value), &p); err != nil { continue } // 校验验证码及有效期(15分钟) if p.Code == req.Code && time.Now().Unix()-p.LastAt <= l.svcCtx.Config.VerifyCode.ExpireTime { _ = l.svcCtx.Redis.Del(l.ctx, cacheKey).Err() verified = true break } } if !verified { return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error or expired") } // 获取当前用户的设备标识符 deviceIdentifier, err := l.getCurrentUserDeviceIdentifier(l.ctx, u.Id) if err != nil { l.Errorw("获取用户设备标识符失败", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "获取用户设备信息失败: %v", err) } // 检测当设备是否已经绑定过邮箱 existingDevice, err := l.svcCtx.UserModel.FindUserAuthMethods(l.ctx, u.Id) if err != nil { l.Errorw("查询用户设备信息失败", logger.Field("error", err.Error()), logger.Field("device_identifier", deviceIdentifier)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "查询用户设备信息失败") } for _, method := range existingDevice { if method.AuthType == "email" { return nil, errors.Wrapf(xerr.NewErrCode(xerr.EmailBindError), "该设备已绑定邮箱") } } // 检查邮箱是否已被其他用户绑定 existingMethod, err := l.svcCtx.UserModel.FindUserAuthMethodByOpenID(l.ctx, "email", req.Email) var emailUserId int64 if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { // 邮箱不存在,绑定邮箱认证到当前设备用户(不增加设备数量) l.Infow(" 为当前设备做 邮箱绑定操作; 在 user_auth_methods 中添加记录", logger.Field("email", req.Email)) err = l.addAuthMethodForEmailUser(u.Id, req.Email) if err != nil { l.Errorw("添加邮箱用户认证方法失败", logger.Field("error", err.Error()), logger.Field("user_id", u.Id)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "添加邮箱用户认证方法失败") } emailUserId = u.Id } else { l.Errorw("查询邮箱绑定状态失败", logger.Field("error", err.Error())) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "查询邮箱绑定状态失败") } } else if existingMethod.Id != 0 { // 邮箱已存在,使用现有的邮箱用户 emailUserId = existingMethod.UserId // 设备绑定数量上限校验(目标邮箱用户) if limit := l.svcCtx.SessionLimit(); limit > 0 { if _, count, cntErr := l.svcCtx.UserModel.QueryDeviceList(l.ctx, emailUserId); cntErr == nil { if count >= limit { return nil, xerr.NewErrCodeMsg(xerr.DeviceBindLimitExceeded, "账户绑定设备数已达上限,请移除其他设备后再登录,您也可以再注册一个新账户使用,点击帮助中心查看更多详情。") } } } // 获取原设备信息,以保留IP和UserAgent // oldDevice, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, deviceIdentifier) // var deviceIP, deviceUA string // if err == nil && oldDevice != nil { // deviceIP = oldDevice.Ip // deviceUA = oldDevice.UserAgent // } l.Infow("邮箱已存在,将设备转移到现有邮箱用户", logger.Field("email", req.Email), logger.Field("email_user_id", emailUserId)) // 创建前 需要 吧 原本的 user_devices 表中过的数据删除掉 防止出现两个记录 devices, _, err := l.svcCtx.UserModel.QueryDeviceList(l.ctx, u.Id) if err != nil { l.Errorw("查询用户设备列表失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "查询用户设备列表失败") } // 保存原用户ID,用于踢出旧连接 originalUserId := u.Id for _, device := range devices { // 删除原本的设备记录 // err = l.svcCtx.UserModel.DeleteDevice(l.ctx, device.Id) // if err != nil { // l.Errorw("删除邮箱用户设备记录失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) // return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "删除原本的设备记录失败") // } // 更新 设备 归属 device.UserId = emailUserId err = l.svcCtx.UserModel.UpdateDevice(l.ctx, device) if err != nil { l.Errorw("更新邮箱用户设备记录失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "更新原本的设备记录失败") } // 踢出设备的旧 WebSocket 连接(使用原用户ID) l.svcCtx.DeviceManager.KickDevice(originalUserId, device.Identifier) l.Infow("已踢出设备旧连接", logger.Field("device_identifier", device.Identifier), logger.Field("original_user_id", originalUserId), logger.Field("new_user_id", emailUserId)) // 清理设备相关的 Redis 缓存 deviceCacheKey := fmt.Sprintf("%v:%v", config.DeviceCacheKeyKey, device.Identifier) if sessionId, rerr := l.svcCtx.Redis.Get(l.ctx, deviceCacheKey).Result(); rerr == 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() // 清理 user_sessions sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, originalUserId) _ = l.svcCtx.Redis.ZRem(l.ctx, sessionsKey, sessionId).Err() l.Infow("已清理设备缓存", logger.Field("device_identifier", device.Identifier), logger.Field("session_id", sessionId)) } } // 再次更新 user_auth_method : 因为之前 默认 设备登录的时候 创建了一个设备认证数据 // 现在需要 更新 为 邮箱认证 err = l.updateAuthMethodForEmailUser(emailUserId, deviceIdentifier) if err != nil { l.Errorw("更新邮箱用户认证方法失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "更新邮箱用户认证方法失败") } // 需要删除原本 user 表中的 记录: 根据 设备 ID err = l.deleteUserRecordForEmailUser(u.Id) if err != nil { l.Errorw("删除用户记录失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseDeletedError), "删除用户记录失败") } // err = l.createDeviceRecordForEmailUser(emailUserId, deviceIdentifier, deviceIP, deviceUA) // if err != nil { // l.Errorw("创建邮箱用户设备记录失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) // return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "创建邮箱用户设备记录失败") // } } // 4. 生成新的JWT token token, err := l.generateTokenForUser(emailUserId) if err != nil { l.Errorw("生成JWT token失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "生成JWT token失败") } return &types.BindEmailWithVerificationResponse{ Success: true, Message: "设备关联成功", Token: token, UserId: emailUserId, }, nil } // getCurrentUserDeviceIdentifier 获取当前用户的设备标识符 func (l *BindEmailWithVerificationLogic) getCurrentUserDeviceIdentifier(ctx context.Context, userId int64) (string, error) { authMethods, err := l.svcCtx.UserModel.FindUserAuthMethods(ctx, userId) if err != nil { return "", err } // 查找设备认证方式 for _, method := range authMethods { if method.AuthType == "device" { return method.AuthIdentifier, nil } } return "", errors.New("用户没有设备认证方式") } // checkIfPureDeviceUser 检查用户是否为纯设备用户(只有设备认证方式) func (l *BindEmailWithVerificationLogic) checkIfPureDeviceUser(ctx context.Context, userId int64) (bool, string, error) { authMethods, err := l.svcCtx.UserModel.FindUserAuthMethods(ctx, userId) if err != nil { l.Errorw("查询用户认证方式失败", logger.Field("error", err.Error()), logger.Field("user_id", userId)) return false, "", errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "查询用户认证方式失败") } // 检查是否只有一个设备认证方式 if len(authMethods) == 1 && authMethods[0].AuthType == "device" { return true, authMethods[0].AuthIdentifier, nil } return false, "", nil } // 邮箱存在的情况:在 user_devices 中创建一条设备记录 func (l *BindEmailWithVerificationLogic) createDeviceRecordForEmailUser(emailUserId int64, deviceIdentifier, ip, userAgent string) error { // online 默认 0 enabled 默认 1 l.Infow("创建邮箱用户设备记录", logger.Field("email_user_id", emailUserId), logger.Field("device_identifier", deviceIdentifier), logger.Field("online", true), logger.Field("enabled", false), logger.Field("user_agent", userAgent), logger.Field("ip", ip), ) err := l.svcCtx.UserModel.InsertDevice(l.ctx, &user.Device{ UserId: emailUserId, Identifier: deviceIdentifier, Online: false, Enabled: false, UserAgent: userAgent, Ip: ip, }) if err != nil { l.Errorw("创建邮箱用户设备记录失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "创建邮箱用户设备记录失败") } l.Infow("成功创建邮箱用户设备记录", logger.Field("email_user_id", emailUserId), logger.Field("device_identifier", deviceIdentifier)) return nil } // 邮箱不存在的情况: 在 user_auth_methods 中添加记录 以当前设备ID 做关联, 此时 user_devices 中不需要变动 func (l *BindEmailWithVerificationLogic) addAuthMethodForEmailUser(userId int64, email string) error { l.Infow("添加邮箱用户认证方法", logger.Field("user_id", userId), logger.Field("email", email)) // 插入邮箱用户认证方法 err := l.svcCtx.UserModel.InsertUserAuthMethods(l.ctx, &user.AuthMethods{ UserId: userId, AuthType: "email", AuthIdentifier: email, }) if err != nil { l.Errorw("插入邮箱用户认证方法失败", logger.Field("error", err.Error()), logger.Field("user_id", userId)) return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "插入邮箱用户认证方法失败") } l.Infow("成功添加邮箱用户认证方法", logger.Field("user_id", userId), logger.Field("email", email)) return nil } // transferDeviceToEmailUser func (l *BindEmailWithVerificationLogic) transferDeviceToEmailUser(deviceUserId, emailUserId int64, deviceIdentifier string) (*types.BindEmailWithVerificationResponse, error) { l.Infow("开始设备转移", logger.Field("device_user_id", deviceUserId), logger.Field("email_user_id", emailUserId), logger.Field("device_identifier", deviceIdentifier)) // 1. 先获取当前用户的SessionId,用于后续清理 可以不需要 currentSessionId := "" if sessionIdValue := l.ctx.Value(constant.CtxKeySessionID); sessionIdValue != nil { currentSessionId = sessionIdValue.(string) } // 2. 在事务中执行设备转移 err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { // 1. 检查目标邮箱用户状态 emailUser, err := l.svcCtx.UserModel.FindOne(l.ctx, emailUserId) if err != nil { l.Errorw("查询邮箱用户失败", logger.Field("error", err.Error()), logger.Field("email_user_id", emailUserId)) return err } // 2. 获取原设备用户信息 deviceUser, err := l.svcCtx.UserModel.FindOne(l.ctx, deviceUserId) if err != nil { l.Errorw("查询设备用户失败", logger.Field("error", err.Error()), logger.Field("device_user_id", deviceUserId)) return err } // 3. 如果邮箱用户没有ReferCode,则从设备用户转移或生成新的 if emailUser.ReferCode == "" { if deviceUser.ReferCode != "" { // 转移设备用户的ReferCode if err := db.Model(&user.User{}).Where("id = ?", emailUserId).Update("refer_code", deviceUser.ReferCode).Error; err != nil { l.Errorw("转移ReferCode失败", logger.Field("error", err.Error())) return err } l.Infow("已转移设备用户的ReferCode到邮箱用户", logger.Field("device_user_id", deviceUserId), logger.Field("email_user_id", emailUserId), logger.Field("refer_code", deviceUser.ReferCode)) } else { // 为邮箱用户生成新的ReferCode newReferCode := uuidx.UserInviteCode(emailUserId) if err := db.Model(&user.User{}).Where("id = ?", emailUserId).Update("refer_code", newReferCode).Error; err != nil { l.Errorw("生成邮箱用户ReferCode失败", logger.Field("error", err.Error())) return err } l.Infow("已为邮箱用户生成新的ReferCode", logger.Field("email_user_id", emailUserId), logger.Field("refer_code", newReferCode)) } } // 4. 如果邮箱用户没有RefererId,但设备用户有,则转移RefererId if emailUser.RefererId == 0 && deviceUser.RefererId != 0 { if err := db.Model(&user.User{}).Where("id = ?", emailUserId).Update("referer_id", deviceUser.RefererId).Error; err != nil { l.Errorw("转移RefererId失败", logger.Field("error", err.Error())) return err } l.Infow("已转移设备用户的RefererId到邮箱用户", logger.Field("device_user_id", deviceUserId), logger.Field("email_user_id", emailUserId), logger.Field("referer_id", deviceUser.RefererId)) } // 5. 检查设备是否已经关联到目标用户 existingDevice, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, deviceIdentifier) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { l.Errorw("查询设备信息失败", logger.Field("error", err.Error()), logger.Field("device_identifier", deviceIdentifier)) return err } if existingDevice != nil && existingDevice.UserId == emailUserId { // 设备已经关联到目标用户,直接生成token l.Infow("设备已关联到目标用户", logger.Field("device_id", existingDevice.Id)) return nil } // 6. 处理设备冲突 - 将现有设备记录的归属修改为邮箱用户 if existingDevice != nil && existingDevice.UserId != emailUserId { l.Infow("更新冲突设备记录的归属", logger.Field("existing_device_id", existingDevice.Id), logger.Field("old_user_id", existingDevice.UserId), logger.Field("new_user_id", emailUserId)) if err := db.Model(&user.Device{}).Where("identifier = ? AND user_id = ?", deviceIdentifier, existingDevice.UserId).Update("user_id", emailUserId).Error; err != nil { l.Errorw("更新冲突设备记录归属失败", logger.Field("error", err.Error())) return err } } // 7. 更新user_auth_methods表 - 将设备认证方式转移到邮箱用户 if err := db.Model(&user.AuthMethods{}). Where("user_id = ? AND auth_type = ? AND auth_identifier = ?", deviceUserId, "device", deviceIdentifier). Update("user_id", emailUserId).Error; err != nil { l.Errorw("更新设备认证方式失败", logger.Field("error", err.Error())) return err } // 8. 更新user_device表 - 将设备记录转移到邮箱用户 if err := db.Model(&user.Device{}). Where("user_id = ? AND identifier = ?", deviceUserId, deviceIdentifier). Update("user_id", emailUserId).Error; err != nil { l.Errorw("更新设备记录失败", logger.Field("error", err.Error())) return err } l.Infow("设备转移成功", logger.Field("device_user_id", deviceUserId), logger.Field("email_user_id", emailUserId), logger.Field("device_identifier", deviceIdentifier)) return nil }) if err != nil { return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "设备转移失败: %v", err) } // 3. 清理原用户的SessionId缓存(使旧token失效) if currentSessionId != "" { sessionKey := fmt.Sprintf("%v:%v", config.SessionIdKey, currentSessionId) if err := l.svcCtx.Redis.Del(l.ctx, sessionKey).Err(); err != nil { l.Errorw("清理原SessionId缓存失败", logger.Field("error", err.Error()), logger.Field("session_id", currentSessionId)) // 不返回错误,继续执行 } else { l.Infow("已清理原SessionId缓存", logger.Field("session_id", currentSessionId)) } } // 4. 生成新的JWT token token, err := l.generateTokenForUser(emailUserId) if err != nil { 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)) // } // } // 6. 清除设备相关缓存 // l.clearDeviceRelatedCache(deviceIdentifier, deviceUserId, emailUserId) return &types.BindEmailWithVerificationResponse{ Success: true, Message: "设备关联成功", Token: token, UserId: emailUserId, }, nil } // generateTokenForUser 为指定用户生成JWT token func (l *BindEmailWithVerificationLogic) generateTokenForUser(userId int64) (string, error) { // 生成JWT token accessExpire := l.svcCtx.Config.JwtAuth.AccessExpire sessionId := uuidx.NewUUID().String() jwtToken, err := jwt.NewJwtToken( l.svcCtx.Config.JwtAuth.AccessSecret, time.Now().Unix(), l.svcCtx.Config.JwtAuth.AccessExpire, jwt.WithOption("UserId", userId), jwt.WithOption("SessionId", sessionId), jwt.WithOption("LoginType", "device"), ) if err != nil { l.Errorw("生成JWT token失败", logger.Field("error", err.Error()), logger.Field("user_id", userId)) return "", errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "生成token失败: %v", err) } // 设置session缓存 sessionKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId) if err := l.svcCtx.Redis.Set(l.ctx, sessionKey, userId, time.Duration(accessExpire)*time.Second).Err(); err != nil { l.Errorw("设置session缓存失败", logger.Field("error", err.Error()), logger.Field("user_id", userId)) // session缓存失败不影响token生成,只记录错误 } l.Infow("为用户生成token成功", logger.Field("user_id", userId)) return jwtToken, nil } // createEmailUser 创建新的邮箱用户 func (l *BindEmailWithVerificationLogic) createEmailUser(email string) (int64, error) { // 检查是否启用了强制邀请码 if l.svcCtx.Config.Invite.ForcedInvite { l.Errorw("邮箱绑定创建新用户时需要邀请码,但当前API不支持邀请码参数", logger.Field("email", email), logger.Field("forced_invite", true)) return 0, xerr.NewErrMsg("创建新用户需要邀请码,请使用支持邀请码的注册方式") } var newUserId int64 err := l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error { // 1. 创建新用户 enabled := true newUser := &user.User{ Enable: &enabled, // 启用状态 OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase, } if err := tx.Create(newUser).Error; err != nil { l.Errorw("创建用户失败", logger.Field("error", err.Error())) return err } newUserId = newUser.Id l.Infow("创建新用户成功", logger.Field("user_id", newUserId)) // 2. 生成并设置用户的ReferCode newUser.ReferCode = uuidx.UserInviteCode(newUserId) if err := tx.Model(&user.User{}).Where("id = ?", newUserId).Update("refer_code", newUser.ReferCode).Error; err != nil { l.Errorw("更新用户ReferCode失败", logger.Field("error", err.Error())) return err } l.Infow("设置用户ReferCode成功", logger.Field("user_id", newUserId), logger.Field("refer_code", newUser.ReferCode)) // 3. 创建邮箱认证方法 emailAuth := &user.AuthMethods{ UserId: newUserId, AuthType: "email", AuthIdentifier: email, Verified: true, // 直接设置为已验证 } if err := tx.Create(emailAuth).Error; err != nil { l.Errorw("创建邮箱认证方法失败", logger.Field("error", err.Error())) return err } l.Infow("创建邮箱认证方法成功", logger.Field("user_id", newUserId), logger.Field("email", email)) // 4. 检查是否需要激活试用订阅 if l.svcCtx.Config.Register.EnableTrial { if err := l.activeTrial(newUserId, tx); err != nil { l.Errorw("激活试用订阅失败", logger.Field("error", err.Error()), logger.Field("user_id", newUserId)) return err } l.Infow("激活试用订阅成功", logger.Field("user_id", newUserId)) } return nil }) if err != nil { return 0, err } return newUserId, nil } // clearDeviceRelatedCache 清除设备相关缓存 func (l *BindEmailWithVerificationLogic) clearDeviceRelatedCache(deviceIdentifier string, oldUserId, newUserId int64) { // 清除设备相关的缓存键 deviceCacheKeys := []string{ fmt.Sprintf("cache:device:identifier:%s", deviceIdentifier), fmt.Sprintf("cache:user:devices:%d", oldUserId), fmt.Sprintf("cache:user:devices:%d", newUserId), fmt.Sprintf("cache:user:auth_methods:%d", oldUserId), fmt.Sprintf("cache:user:auth_methods:%d", newUserId), fmt.Sprintf("cache:user:%d", oldUserId), fmt.Sprintf("cache:user:%d", newUserId), } 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)) } } } // activeTrial 为新用户激活试用订阅 func (l *BindEmailWithVerificationLogic) activeTrial(userId int64, tx *gorm.DB) error { // 获取试用订阅模板 sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe) if err != nil { l.Errorw("获取试用订阅模板失败", logger.Field("user_id", userId), logger.Field("trial_subscribe_id", l.svcCtx.Config.Register.TrialSubscribe), logger.Field("error", err.Error())) return err } // 计算试用期时间 startTime := time.Now() expireTime := tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, startTime) subscribeToken := uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", userId)) subscribeUUID := uuidx.NewUUID().String() // 创建用户订阅记录 userSub := &user.Subscribe{ UserId: userId, OrderId: 0, SubscribeId: sub.Id, StartTime: startTime, ExpireTime: expireTime, Traffic: sub.Traffic, Download: 0, Upload: 0, Token: subscribeToken, UUID: subscribeUUID, Status: 1, } // 插入订阅记录 if err := tx.Create(userSub).Error; err != nil { l.Errorw("插入试用订阅记录失败", logger.Field("user_id", userId), logger.Field("error", err.Error())) return err } l.Infow("试用订阅激活成功", logger.Field("user_id", userId), logger.Field("subscribe_id", sub.Id), logger.Field("expire_time", expireTime), logger.Field("traffic", sub.Traffic)) return nil } // updateAuthMethodForEmailUser 根据 设备ID 找到原本的记录 然后 调整 user_id func (l *BindEmailWithVerificationLogic) updateAuthMethodForEmailUser(userId int64, deviceIdentifier string) error { var userAuth user.AuthMethods if err := l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error { // 查询设备认证方法 if err := tx.Model(&user.AuthMethods{}). Where("auth_identifier = ? AND auth_type = ?", deviceIdentifier, "device"). First(&userAuth).Error; err != nil { l.Errorw("查询设备认证方法失败", logger.Field("device_identifier", deviceIdentifier), logger.Field("error", err.Error())) return err } // 更新用户认证方法为 email if err := tx.Model(&user.AuthMethods{}). Where("id = ?", userAuth.Id). Update("user_id", userId).Error; err != nil { l.Errorw("更新用户设备 用户关联 失败", logger.Field("user_id", userId), logger.Field("device_identifier", deviceIdentifier), logger.Field("error", err.Error())) return err } l.Infow("更新用户设备 用户关联 成功", logger.Field("user_id", userId), logger.Field("device_identifier", deviceIdentifier)) return nil }); err != nil { return err } return nil } // 根据用户iD 删除 user 表中的记录 func (l *BindEmailWithVerificationLogic) deleteUserRecordForEmailUser(userId int64) error { if err := l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error { // 删除用户记录 if err := tx.Delete(&user.User{}, userId).Error; err != nil { l.Errorw("删除用户记录失败", logger.Field("user_id", userId), logger.Field("error", err.Error())) return err } l.Infow("删除用户记录成功", logger.Field("user_id", userId)) return nil }); err != nil { return err } return nil }