Some checks failed
Build docker and publish / build (20.15.1) (push) Has been cancelled
708 lines
28 KiB
Go
708 lines
28 KiB
Go
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, deviceIdentifier)
|
||
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))
|
||
|
||
// 清理用户缓存,确保下次查询能获取到新的认证列表
|
||
if user, err := l.svcCtx.UserModel.FindOne(l.ctx, userId); err == nil && user != nil {
|
||
if err := l.svcCtx.UserModel.BatchClearRelatedCache(l.ctx, user); err != nil {
|
||
l.Errorw("清理用户缓存失败", logger.Field("error", err.Error()), logger.Field("user_id", userId))
|
||
} else {
|
||
l.Infow("清理用户缓存成功", logger.Field("user_id", userId))
|
||
}
|
||
}
|
||
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, deviceIdentifier)
|
||
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, deviceIdentifier string) (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生成,只记录错误
|
||
}
|
||
|
||
// 设置设备缓存映射 (identifier -> sessionId)
|
||
if deviceIdentifier != "" {
|
||
deviceCacheKey := fmt.Sprintf("%v:%v", config.DeviceCacheKeyKey, deviceIdentifier)
|
||
if err := l.svcCtx.Redis.Set(l.ctx, deviceCacheKey, sessionId, time.Duration(accessExpire)*time.Second).Err(); err != nil {
|
||
l.Errorw("设置设备缓存失败", logger.Field("error", err.Error()), logger.Field("device_identifier", deviceIdentifier))
|
||
// 不影响主流程,只记录错误
|
||
} else {
|
||
l.Infow("更新设备Session映射成功", logger.Field("device_identifier", deviceIdentifier), logger.Field("session_id", sessionId))
|
||
}
|
||
}
|
||
|
||
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
|
||
}
|