This commit is contained in:
parent
f4fe0e32a8
commit
9f4d71770b
@ -17,8 +17,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// DeleteAccountHandler 注销账号处理器
|
// DeleteAccountHandler 注销账号处理器
|
||||||
// 根据当前token删除所有关联设备,然后根据各自设备ID重新新建账号
|
// 仅执行注销登录与家庭退出/解散,不删除账号主体。
|
||||||
// 新增:需携带邮箱验证码,验证通过后执行注销
|
// 需携带邮箱验证码,验证通过后执行。
|
||||||
type deleteAccountReq struct {
|
type deleteAccountReq struct {
|
||||||
Email string `json:"email" binding:"required,email"` // 用户邮箱
|
Email string `json:"email" binding:"required,email"` // 用户邮箱
|
||||||
Code string `json:"code" binding:"required"` // 邮箱验证码
|
Code string `json:"code" binding:"required"` // 邮箱验证码
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Delete Current User Account
|
// Delete Current User Account
|
||||||
|
// Legacy destructive endpoint: this physically deletes account data.
|
||||||
|
// Keep this behavior for backward compatibility.
|
||||||
func DeleteCurrentUserAccountHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
func DeleteCurrentUserAccountHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import (
|
|||||||
func UnbindDeviceHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
func UnbindDeviceHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
var req types.UnbindDeviceRequest
|
var req types.UnbindDeviceRequest
|
||||||
_ = c.ShouldBind(&req)
|
_ = c.ShouldBindJSON(&req)
|
||||||
validateErr := svcCtx.Validate(&req)
|
validateErr := svcCtx.Validate(&req)
|
||||||
if validateErr != nil {
|
if validateErr != nil {
|
||||||
result.ParamErrorResult(c, validateErr)
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
|||||||
@ -69,14 +69,17 @@ func (l *QueryServerTotalDataLogic) QueryServerTotalData() (resp *types.ServerTo
|
|||||||
yesterday := todayStart.Add(-24 * time.Hour).Format(time.DateOnly)
|
yesterday := todayStart.Add(-24 * time.Hour).Format(time.DateOnly)
|
||||||
|
|
||||||
var yesterdayLog log.SystemLog
|
var yesterdayLog log.SystemLog
|
||||||
err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", yesterday, log.TypeUserTrafficRank).First(&yesterdayLog).Error
|
yesterdayUserRankQuery := query.Model(&log.SystemLog{}).
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
Where("`date` = ? AND `type` = ?", yesterday, log.TypeUserTrafficRank).
|
||||||
l.Errorw("[QueryServerTotalDataLogic] Query yesterday user traffic rank log error", logger.Field("error", err.Error()))
|
Limit(1).
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query yesterday user traffic rank log error: %v", err)
|
Find(&yesterdayLog)
|
||||||
|
if yesterdayUserRankQuery.Error != nil {
|
||||||
|
l.Errorw("[QueryServerTotalDataLogic] Query yesterday user traffic rank log error", logger.Field("error", yesterdayUserRankQuery.Error.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query yesterday user traffic rank log error: %v", yesterdayUserRankQuery.Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var yesterdayUserRankData []types.UserTrafficData
|
var yesterdayUserRankData []types.UserTrafficData
|
||||||
if yesterdayLog.Id > 0 {
|
if yesterdayUserRankQuery.RowsAffected > 0 {
|
||||||
var rank log.UserTrafficRank
|
var rank log.UserTrafficRank
|
||||||
err = rank.Unmarshal([]byte(yesterdayLog.Content))
|
err = rank.Unmarshal([]byte(yesterdayLog.Content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -122,12 +125,15 @@ func (l *QueryServerTotalDataLogic) QueryServerTotalData() (resp *types.ServerTo
|
|||||||
// query server traffic rank yesterday
|
// query server traffic rank yesterday
|
||||||
var yesterdayTop10Server []types.ServerTrafficData
|
var yesterdayTop10Server []types.ServerTrafficData
|
||||||
var yesterdayServerTrafficLog log.SystemLog
|
var yesterdayServerTrafficLog log.SystemLog
|
||||||
err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", yesterday, log.TypeServerTrafficRank).First(&yesterdayServerTrafficLog).Error
|
yesterdayServerRankQuery := query.Model(&log.SystemLog{}).
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
Where("`date` = ? AND `type` = ?", yesterday, log.TypeServerTrafficRank).
|
||||||
l.Errorw("[QueryServerTotalDataLogic] Query yesterday server traffic rank log error", logger.Field("error", err.Error()))
|
Limit(1).
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query yesterday server traffic rank log error: %v", err)
|
Find(&yesterdayServerTrafficLog)
|
||||||
|
if yesterdayServerRankQuery.Error != nil {
|
||||||
|
l.Errorw("[QueryServerTotalDataLogic] Query yesterday server traffic rank log error", logger.Field("error", yesterdayServerRankQuery.Error.Error()))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query yesterday server traffic rank log error: %v", yesterdayServerRankQuery.Error)
|
||||||
}
|
}
|
||||||
if yesterdayServerTrafficLog.Id > 0 {
|
if yesterdayServerRankQuery.RowsAffected > 0 {
|
||||||
var rank log.ServerTrafficRank
|
var rank log.ServerTrafficRank
|
||||||
err = rank.Unmarshal([]byte(yesterdayServerTrafficLog.Content))
|
err = rank.Unmarshal([]byte(yesterdayServerTrafficLog.Content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -195,12 +201,15 @@ func (l *QueryServerTotalDataLogic) QueryServerTotalData() (resp *types.ServerTo
|
|||||||
for i := now.Day() - 1; i >= 1; i-- {
|
for i := now.Day() - 1; i >= 1; i-- {
|
||||||
var logInfo log.SystemLog
|
var logInfo log.SystemLog
|
||||||
date := time.Date(now.Year(), now.Month(), i, 0, 0, 0, 0, now.Location()).Format(time.DateOnly)
|
date := time.Date(now.Year(), now.Month(), i, 0, 0, 0, 0, now.Location()).Format(time.DateOnly)
|
||||||
err = query.Model(&log.SystemLog{}).Where("`date` = ? AND `type` = ?", date, log.TypeTrafficStat).First(&logInfo).Error
|
trafficStatQuery := query.Model(&log.SystemLog{}).
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
Where("`date` = ? AND `type` = ?", date, log.TypeTrafficStat).
|
||||||
l.Errorw("[QueryServerTotalDataLogic] Query daily traffic stat log error", logger.Field("error", err.Error()), logger.Field("date", date))
|
Limit(1).
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query daily traffic stat log error: %v", err)
|
Find(&logInfo)
|
||||||
|
if trafficStatQuery.Error != nil {
|
||||||
|
l.Errorw("[QueryServerTotalDataLogic] Query daily traffic stat log error", logger.Field("error", trafficStatQuery.Error.Error()), logger.Field("date", date))
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "Query daily traffic stat log error: %v", trafficStatQuery.Error)
|
||||||
}
|
}
|
||||||
if logInfo.Id > 0 {
|
if trafficStatQuery.RowsAffected > 0 {
|
||||||
var stat log.TrafficStat
|
var stat log.TrafficStat
|
||||||
err = stat.Unmarshal([]byte(logInfo.Content))
|
err = stat.Unmarshal([]byte(logInfo.Content))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -2,9 +2,8 @@ package user
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/perfect-panel/server/internal/config"
|
"github.com/perfect-panel/server/internal/config"
|
||||||
@ -13,7 +12,6 @@ import (
|
|||||||
"github.com/perfect-panel/server/internal/types"
|
"github.com/perfect-panel/server/internal/types"
|
||||||
"github.com/perfect-panel/server/pkg/constant"
|
"github.com/perfect-panel/server/pkg/constant"
|
||||||
"github.com/perfect-panel/server/pkg/logger"
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
"github.com/perfect-panel/server/pkg/uuidx"
|
|
||||||
"github.com/perfect-panel/server/pkg/xerr"
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@ -34,379 +32,255 @@ func NewDeleteAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Del
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAccount 注销当前设备账号逻辑 (改为精准解绑)
|
// DeleteAccount 保留兼容入口,统一走全量注销登录逻辑
|
||||||
func (l *DeleteAccountLogic) DeleteAccount() (resp *types.DeleteAccountResponse, err error) {
|
func (l *DeleteAccountLogic) DeleteAccount() (resp *types.DeleteAccountResponse, err error) {
|
||||||
// 获取当前用户
|
return l.DeleteAccountAll()
|
||||||
currentUser, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取当前调用设备 ID
|
|
||||||
currentDeviceId, _ := l.ctx.Value(constant.CtxKeyDeviceID).(int64)
|
|
||||||
|
|
||||||
resp = &types.DeleteAccountResponse{}
|
|
||||||
var newUserId int64
|
|
||||||
|
|
||||||
// 如果没有识别到设备 ID (可能是旧版 Token),则执行安全注销:仅清除 Session
|
|
||||||
if currentDeviceId == 0 {
|
|
||||||
l.Infow("未识别到设备 ID,仅清理当前会话", logger.Field("user_id", currentUser.Id))
|
|
||||||
l.clearCurrentSession(currentUser.Id)
|
|
||||||
resp.Success = true
|
|
||||||
resp.Message = "会话已清除"
|
|
||||||
return resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// 开始数据库事务
|
|
||||||
err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error {
|
|
||||||
// 1. 查找当前设备
|
|
||||||
var currentDevice user.Device
|
|
||||||
if err := tx.Where("id = ? AND user_id = ?", currentDeviceId, currentUser.Id).First(¤tDevice).Error; err != nil {
|
|
||||||
l.Infow("当前请求设备记录不存在或归属不匹配", logger.Field("device_id", currentDeviceId), logger.Field("error", err.Error()))
|
|
||||||
return nil // 不抛错,直接走清理 Session 流程
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 检查用户是否有其他认证方式 (如邮箱) 或 其他设备
|
|
||||||
var authMethodsCount int64
|
|
||||||
tx.Model(&user.AuthMethods{}).Where("user_id = ?", currentUser.Id).Count(&authMethodsCount)
|
|
||||||
|
|
||||||
var devicesCount int64
|
|
||||||
tx.Model(&user.Device{}).Where("user_id = ?", currentUser.Id).Count(&devicesCount)
|
|
||||||
|
|
||||||
// 判定是否是主账号解绑:如果除了当前设备外,还有邮箱或其他设备,则只解绑当前设备
|
|
||||||
isMainAccount := authMethodsCount > 1 || devicesCount > 1
|
|
||||||
|
|
||||||
if isMainAccount {
|
|
||||||
l.Infow("主账号解绑,仅迁移当前设备", logger.Field("user_id", currentUser.Id), logger.Field("device_id", currentDeviceId))
|
|
||||||
|
|
||||||
// 【重要】先删除旧的认证记录,再创建新用户,避免唯一键冲突
|
|
||||||
// 从原用户删除当前设备的认证方式 (按 Identifier 准确删除)
|
|
||||||
if err := tx.Where("user_id = ? AND auth_type = ? AND auth_identifier = ?", currentUser.Id, "device", currentDevice.Identifier).Delete(&user.AuthMethods{}).Error; err != nil {
|
|
||||||
return errors.Wrap(err, "删除原设备认证失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从原用户删除当前设备记录
|
|
||||||
if err := tx.Where("id = ?", currentDeviceId).Delete(&user.Device{}).Error; err != nil {
|
|
||||||
return errors.Wrap(err, "删除原设备记录失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为当前设备创建新用户并迁移
|
|
||||||
newUser, err := l.registerUserAndDevice(tx, currentDevice.Identifier, currentDevice.Ip, currentDevice.UserAgent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newUserId = newUser.Id
|
|
||||||
|
|
||||||
} else {
|
|
||||||
l.Infow("纯设备账号注销,执行物理删除并重置", logger.Field("user_id", currentUser.Id), logger.Field("device_id", currentDeviceId))
|
|
||||||
|
|
||||||
exitHelper := newFamilyExitHelper(l.ctx, l.svcCtx)
|
|
||||||
if err := exitHelper.removeUserFromActiveFamily(tx, currentUser.Id, true); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 完全删除原用户相关资产
|
|
||||||
tx.Where("user_id = ?", currentUser.Id).Delete(&user.AuthMethods{})
|
|
||||||
tx.Where("user_id = ?", currentUser.Id).Delete(&user.Device{})
|
|
||||||
tx.Where("user_id = ?", currentUser.Id).Delete(&user.Subscribe{})
|
|
||||||
tx.Delete(&user.User{}, currentUser.Id)
|
|
||||||
|
|
||||||
// 重新注册一个新用户
|
|
||||||
newUser, err := l.registerUserAndDevice(tx, currentDevice.Identifier, currentDevice.Ip, currentDevice.UserAgent)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
newUserId = newUser.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最终清理当前 Session
|
|
||||||
l.clearCurrentSession(currentUser.Id)
|
|
||||||
|
|
||||||
resp.Success = true
|
|
||||||
resp.Message = "注销成功"
|
|
||||||
resp.UserId = newUserId
|
|
||||||
resp.Code = 200
|
|
||||||
|
|
||||||
return resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteAccountAll 注销账号逻辑 (全部解绑默认创建账号)
|
// DeleteAccountAll 注销登录 + 退出家庭/解散家庭,不删除账号主体
|
||||||
func (l *DeleteAccountLogic) DeleteAccountAll() (resp *types.DeleteAccountResponse, err error) {
|
func (l *DeleteAccountLogic) DeleteAccountAll() (resp *types.DeleteAccountResponse, err error) {
|
||||||
// 获取当前用户
|
|
||||||
currentUser, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
currentUser, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||||
if !ok {
|
if !ok || currentUser == nil {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取当前调用设备 ID
|
affectedUserIDs := []int64{currentUser.Id}
|
||||||
currentDeviceId, _ := l.ctx.Value(constant.CtxKeyDeviceID).(int64)
|
|
||||||
currentIdentifier, _ := l.ctx.Value(constant.CtxKeyIdentifier).(string)
|
|
||||||
currentIdentifier = strings.TrimSpace(currentIdentifier)
|
|
||||||
|
|
||||||
if currentDeviceId == 0 && currentIdentifier != "" {
|
|
||||||
deviceInfo, deviceErr := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, currentIdentifier)
|
|
||||||
switch {
|
|
||||||
case deviceErr == nil:
|
|
||||||
if deviceInfo.UserId == currentUser.Id {
|
|
||||||
currentDeviceId = deviceInfo.Id
|
|
||||||
} else {
|
|
||||||
l.Infow("当前标识符设备归属与用户不匹配",
|
|
||||||
logger.Field("user_id", currentUser.Id),
|
|
||||||
logger.Field("device_id", deviceInfo.Id),
|
|
||||||
logger.Field("identifier", currentIdentifier),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
case errors.Is(deviceErr, gorm.ErrRecordNotFound):
|
|
||||||
l.Infow("未通过标识符找到当前设备", logger.Field("user_id", currentUser.Id), logger.Field("identifier", currentIdentifier))
|
|
||||||
default:
|
|
||||||
l.Errorw("通过标识符查找当前设备失败",
|
|
||||||
logger.Field("user_id", currentUser.Id),
|
|
||||||
logger.Field("identifier", currentIdentifier),
|
|
||||||
logger.Field("error", deviceErr.Error()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp = &types.DeleteAccountResponse{}
|
|
||||||
var newUserId int64
|
|
||||||
var firstMigratedUserId int64
|
|
||||||
|
|
||||||
// 开始数据库事务
|
|
||||||
err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error {
|
err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error {
|
||||||
// 1. 预先查找该用户下的所有设备记录 (因为稍后要迁移)
|
familyUserIDs, collectErr := l.collectAffectedFamilyUserIDs(tx, currentUser.Id)
|
||||||
var userDevices []user.Device
|
if collectErr != nil {
|
||||||
if err := tx.Where("user_id = ?", currentUser.Id).Find(&userDevices).Error; err != nil {
|
return collectErr
|
||||||
l.Errorw("查询用户设备列表失败", logger.Field("user_id", currentUser.Id), logger.Field("error", err.Error()))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有识别到调用设备 ID,记录日志但继续执行 (全量注销不应受限)
|
|
||||||
if currentDeviceId == 0 {
|
|
||||||
l.Infow("未识别到当前设备 ID,将执行全量注销并尝试迁移所有已知设备", logger.Field("user_id", currentUser.Id), logger.Field("found_devices", len(userDevices)))
|
|
||||||
}
|
}
|
||||||
|
affectedUserIDs = familyUserIDs
|
||||||
|
|
||||||
exitHelper := newFamilyExitHelper(l.ctx, l.svcCtx)
|
exitHelper := newFamilyExitHelper(l.ctx, l.svcCtx)
|
||||||
if err := exitHelper.removeUserFromActiveFamily(tx, currentUser.Id, true); err != nil {
|
if removeErr := exitHelper.removeUserFromActiveFamily(tx, currentUser.Id, true); removeErr != nil {
|
||||||
return err
|
return removeErr
|
||||||
}
|
}
|
||||||
|
|
||||||
l.Infow("执行账号全量注销-迁移设备并删除旧数据", logger.Field("user_id", currentUser.Id), logger.Field("device_count", len(userDevices)))
|
|
||||||
|
|
||||||
// 2. 循环为每个设备创建新用户并迁移记录 (保留设备ID)
|
|
||||||
for _, dev := range userDevices {
|
|
||||||
// A. 创建新匿名用户
|
|
||||||
newUser, err := l.createAnonymousUser(tx)
|
|
||||||
if err != nil {
|
|
||||||
l.Errorw("为设备分配新用户主体失败", logger.Field("device_id", dev.Id), logger.Field("error", err.Error()))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if firstMigratedUserId == 0 {
|
|
||||||
firstMigratedUserId = newUser.Id
|
|
||||||
}
|
|
||||||
|
|
||||||
// B. 迁移设备记录 (Update user_id)
|
|
||||||
if err := tx.Model(&user.Device{}).Where("id = ?", dev.Id).Update("user_id", newUser.Id).Error; err != nil {
|
|
||||||
l.Errorw("迁移设备记录失败", logger.Field("device_id", dev.Id), logger.Field("error", err.Error()))
|
|
||||||
return errors.Wrap(err, "迁移设备记录失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
// C. 迁移设备认证方式 (Update user_id)
|
|
||||||
if err := tx.Model(&user.AuthMethods{}).
|
|
||||||
Where("user_id = ? AND auth_type = ? AND auth_identifier = ?", currentUser.Id, "device", dev.Identifier).
|
|
||||||
Update("user_id", newUser.Id).Error; err != nil {
|
|
||||||
l.Errorw("迁移设备认证失败", logger.Field("device_id", dev.Id), logger.Field("error", err.Error()))
|
|
||||||
return errors.Wrap(err, "迁移设备认证失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果是当前请求的设备,记录其新 UserID 返回给前端
|
|
||||||
isCurrentRequestDevice := currentDeviceId > 0 && dev.Id == currentDeviceId
|
|
||||||
if !isCurrentRequestDevice && currentIdentifier != "" {
|
|
||||||
isCurrentRequestDevice = dev.Identifier == currentIdentifier
|
|
||||||
}
|
|
||||||
if isCurrentRequestDevice {
|
|
||||||
newUserId = newUser.Id
|
|
||||||
}
|
|
||||||
l.Infow("旧设备已迁移至新匿名账号",
|
|
||||||
logger.Field("old_user_id", currentUser.Id),
|
|
||||||
logger.Field("new_user_id", newUser.Id),
|
|
||||||
logger.Field("device_id", dev.Id),
|
|
||||||
logger.Field("identifier", dev.Identifier))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 删除旧账号的剩余数据
|
|
||||||
// 删除剩余的认证方式 (排除已迁移的device类型,剩下的如email/mobile等)
|
|
||||||
// 注意:刚才已经把由currentUser拥有的device类型auth都迁移走了,所以这里直接删剩下的即可
|
|
||||||
if err := tx.Where("user_id = ?", currentUser.Id).Delete(&user.AuthMethods{}).Error; err != nil {
|
|
||||||
return errors.Wrap(err, "删除剩余认证方式失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设备记录已经全部迁移,理论上 user_id = currentUser.Id 的 device 应该没了,但为了保险可以删一下(或者是0)
|
|
||||||
if err := tx.Where("user_id = ?", currentUser.Id).Delete(&user.Device{}).Error; err != nil {
|
|
||||||
return errors.Wrap(err, "删除残留设备记录失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除所有订阅
|
|
||||||
if err := tx.Where("user_id = ?", currentUser.Id).Delete(&user.Subscribe{}).Error; err != nil {
|
|
||||||
return errors.Wrap(err, "删除订阅失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除用户主体
|
|
||||||
if err := tx.Delete(&user.User{}, currentUser.Id).Error; err != nil {
|
|
||||||
return errors.Wrap(err, "删除用户失败")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最终清理所有 Session (踢掉所有设备)
|
|
||||||
l.clearAllSessions(currentUser.Id)
|
l.clearAllSessions(currentUser.Id)
|
||||||
if newUserId == 0 {
|
if cacheErr := l.clearUserAndSubscribeCaches(affectedUserIDs); cacheErr != nil {
|
||||||
newUserId = firstMigratedUserId
|
l.Errorw("clear user related cache failed",
|
||||||
|
logger.Field("user_id", currentUser.Id),
|
||||||
|
logger.Field("affected_user_ids", affectedUserIDs),
|
||||||
|
logger.Field("error", cacheErr.Error()),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Success = true
|
return &types.DeleteAccountResponse{
|
||||||
resp.Message = "注销成功"
|
Success: true,
|
||||||
resp.UserId = newUserId
|
Message: "注销成功",
|
||||||
resp.Code = 200
|
UserId: currentUser.Id,
|
||||||
|
Code: 200,
|
||||||
return resp, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助方法:通过 ID 查找 Identifier (以防 currentDeviceId 只是 ID)
|
func (l *DeleteAccountLogic) collectAffectedFamilyUserIDs(tx *gorm.DB, userID int64) ([]int64, error) {
|
||||||
func (l *DeleteAccountLogic) getIdentifierByDeviceID(devices []user.Device, id int64) string {
|
affected := []int64{userID}
|
||||||
for _, d := range devices {
|
|
||||||
if d.Id == id {
|
var relation struct {
|
||||||
return d.Identifier
|
FamilyId int64 `gorm:"column:family_id"`
|
||||||
|
}
|
||||||
|
err := tx.Model(&user.UserFamilyMember{}).
|
||||||
|
Select("user_family_member.family_id").
|
||||||
|
Joins("JOIN user_family ON user_family.id = user_family_member.family_id AND user_family.deleted_at IS NULL AND user_family.status = ?", user.FamilyStatusActive).
|
||||||
|
Where("user_family_member.user_id = ? AND user_family_member.status = ? AND user_family_member.deleted_at IS NULL", userID, user.FamilyMemberActive).
|
||||||
|
First(&relation).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return affected, nil
|
||||||
|
}
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family relation failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var memberIDs []int64
|
||||||
|
if err = tx.Model(&user.UserFamilyMember{}).
|
||||||
|
Where("family_id = ? AND status = ? AND deleted_at IS NULL", relation.FamilyId, user.FamilyMemberActive).
|
||||||
|
Pluck("user_id", &memberIDs).Error; err != nil {
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query family members failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
affected = append(affected, memberIDs...)
|
||||||
|
idSet := make(map[int64]struct{}, len(affected))
|
||||||
|
unique := make([]int64, 0, len(affected))
|
||||||
|
for _, id := range affected {
|
||||||
|
if id <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := idSet[id]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idSet[id] = struct{}{}
|
||||||
|
unique = append(unique, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(unique) == 0 {
|
||||||
|
return []int64{userID}, nil
|
||||||
|
}
|
||||||
|
return unique, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DeleteAccountLogic) clearUserAndSubscribeCaches(userIDs []int64) error {
|
||||||
|
if len(userIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idSet := make(map[int64]struct{}, len(userIDs))
|
||||||
|
uniqueIDs := make([]int64, 0, len(userIDs))
|
||||||
|
for _, userID := range userIDs {
|
||||||
|
if userID <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := idSet[userID]; exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idSet[userID] = struct{}{}
|
||||||
|
uniqueIDs = append(uniqueIDs, userID)
|
||||||
|
}
|
||||||
|
if len(uniqueIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
userModels := make([]*user.User, 0, len(uniqueIDs))
|
||||||
|
subscribeModels := make([]*user.Subscribe, 0, len(uniqueIDs))
|
||||||
|
for _, userID := range uniqueIDs {
|
||||||
|
u, findErr := l.svcCtx.UserModel.FindOne(l.ctx, userID)
|
||||||
|
switch {
|
||||||
|
case findErr == nil:
|
||||||
|
userModels = append(userModels, u)
|
||||||
|
case errors.Is(findErr, gorm.ErrRecordNotFound):
|
||||||
|
// no-op
|
||||||
|
default:
|
||||||
|
l.Errorw("find user for cache clearing failed",
|
||||||
|
logger.Field("user_id", userID),
|
||||||
|
logger.Field("error", findErr.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribeModels = append(subscribeModels, &user.Subscribe{UserId: userID})
|
||||||
|
subscribes, queryErr := l.svcCtx.UserModel.QueryUserSubscribe(l.ctx, userID)
|
||||||
|
if queryErr != nil {
|
||||||
|
l.Errorw("query user subscribes for cache clearing failed",
|
||||||
|
logger.Field("user_id", userID),
|
||||||
|
logger.Field("error", queryErr.Error()),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, subscribe := range subscribes {
|
||||||
|
subscribeModels = append(subscribeModels, &user.Subscribe{
|
||||||
|
Id: subscribe.Id,
|
||||||
|
UserId: subscribe.UserId,
|
||||||
|
SubscribeId: subscribe.SubscribeId,
|
||||||
|
Token: subscribe.Token,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// clearCurrentSession 清理当前请求的会话
|
if len(userModels) > 0 {
|
||||||
func (l *DeleteAccountLogic) clearCurrentSession(userId int64) {
|
if err := l.svcCtx.UserModel.ClearUserCache(l.ctx, userModels...); err != nil {
|
||||||
if sessionId, ok := l.ctx.Value(constant.CtxKeySessionID).(string); ok && sessionId != "" {
|
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "clear user cache failed")
|
||||||
sessionKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
}
|
||||||
_ = l.svcCtx.Redis.Del(l.ctx, sessionKey).Err()
|
|
||||||
// 从用户会话集合中移除当前session
|
|
||||||
sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, userId)
|
|
||||||
_ = l.svcCtx.Redis.ZRem(l.ctx, sessionsKey, sessionId).Err()
|
|
||||||
|
|
||||||
l.Infow("[SessionMonitor] 注销账号清除 Session",
|
|
||||||
logger.Field("user_id", userId),
|
|
||||||
logger.Field("session_id", sessionId))
|
|
||||||
}
|
}
|
||||||
|
if len(subscribeModels) > 0 {
|
||||||
|
if err := l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, subscribeModels...); err != nil {
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "clear subscribe cache failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// clearAllSessions 清理指定用户的所有会话
|
// clearAllSessions 清理指定用户的所有会话
|
||||||
func (l *DeleteAccountLogic) clearAllSessions(userId int64) {
|
func (l *DeleteAccountLogic) clearAllSessions(userId int64) {
|
||||||
sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, userId)
|
sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, userId)
|
||||||
|
sessionSet := make(map[string]struct{})
|
||||||
|
|
||||||
// 获取所有 session id
|
|
||||||
sessions, err := l.svcCtx.Redis.ZRange(l.ctx, sessionsKey, 0, -1).Result()
|
sessions, err := l.svcCtx.Redis.ZRange(l.ctx, sessionsKey, 0, -1).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Errorw("获取用户会话列表失败", logger.Field("user_id", userId), logger.Field("error", err.Error()))
|
l.Errorw("获取用户会话索引失败", logger.Field("user_id", userId), logger.Field("error", err.Error()))
|
||||||
return
|
} else {
|
||||||
|
for _, sessionID := range sessions {
|
||||||
|
if sessionID != "" {
|
||||||
|
sessionSet[sessionID] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除每个 session 的详情 key
|
userIDText := strconv.FormatInt(userId, 10)
|
||||||
for _, sid := range sessions {
|
pattern := fmt.Sprintf("%s:*", config.SessionIdKey)
|
||||||
sessionKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sid)
|
var cursor uint64
|
||||||
_ = l.svcCtx.Redis.Del(l.ctx, sessionKey).Err()
|
for {
|
||||||
|
keys, nextCursor, scanErr := l.svcCtx.Redis.Scan(l.ctx, cursor, pattern, 200).Result()
|
||||||
|
if scanErr != nil {
|
||||||
|
l.Errorw("扫描会话键失败", logger.Field("user_id", userId), logger.Field("error", scanErr.Error()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
// 同时尝试删除 detail key (如果存在)
|
for _, sessionKey := range keys {
|
||||||
detailKey := fmt.Sprintf("%s:detail:%s", config.SessionIdKey, sid)
|
value, getErr := l.svcCtx.Redis.Get(l.ctx, sessionKey).Result()
|
||||||
_ = l.svcCtx.Redis.Del(l.ctx, detailKey).Err()
|
if getErr != nil || value != userIDText {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sessionID := strings.TrimPrefix(sessionKey, config.SessionIdKey+":")
|
||||||
|
if sessionID == "" || strings.HasPrefix(sessionID, "detail:") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sessionSet[sessionID] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = nextCursor
|
||||||
|
if cursor == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除用户的 session 集合 key
|
deviceKeySet := make(map[string]struct{})
|
||||||
_ = l.svcCtx.Redis.Del(l.ctx, sessionsKey).Err()
|
devicePattern := fmt.Sprintf("%s:*", config.DeviceCacheKeyKey)
|
||||||
|
cursor = 0
|
||||||
|
for {
|
||||||
|
keys, nextCursor, scanErr := l.svcCtx.Redis.Scan(l.ctx, cursor, devicePattern, 200).Result()
|
||||||
|
if scanErr != nil {
|
||||||
|
l.Errorw("扫描设备会话映射失败", logger.Field("user_id", userId), logger.Field("error", scanErr.Error()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for _, deviceKey := range keys {
|
||||||
|
sessionID, getErr := l.svcCtx.Redis.Get(l.ctx, deviceKey).Result()
|
||||||
|
if getErr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := sessionSet[sessionID]; exists {
|
||||||
|
deviceKeySet[deviceKey] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor = nextCursor
|
||||||
|
if cursor == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
l.Infow("[SessionMonitor] 注销账号-清除所有Session", logger.Field("user_id", userId), logger.Field("count", len(sessions)))
|
pipe := l.svcCtx.Redis.TxPipeline()
|
||||||
}
|
for sessionID := range sessionSet {
|
||||||
|
sessionKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionID)
|
||||||
// generateReferCode 生成推荐码
|
pipe.Del(l.ctx, sessionKey)
|
||||||
func generateReferCode() string {
|
pipe.Del(l.ctx, fmt.Sprintf("%s:detail:%s", config.SessionIdKey, sessionID))
|
||||||
bytes := make([]byte, 4)
|
pipe.ZRem(l.ctx, sessionsKey, sessionID)
|
||||||
rand.Read(bytes)
|
}
|
||||||
return hex.EncodeToString(bytes)
|
pipe.Del(l.ctx, sessionsKey)
|
||||||
}
|
|
||||||
|
for deviceKey := range deviceKeySet {
|
||||||
func (l *DeleteAccountLogic) registerUserAndDevice(tx *gorm.DB, identifier, ip, userAgent string) (*user.User, error) {
|
pipe.Del(l.ctx, deviceKey)
|
||||||
// 1. 创建新用户
|
}
|
||||||
userInfo := &user.User{
|
|
||||||
Salt: "default",
|
if _, err = pipe.Exec(l.ctx); err != nil {
|
||||||
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
l.Errorw("清理会话缓存失败", logger.Field("user_id", userId), logger.Field("error", err.Error()))
|
||||||
}
|
}
|
||||||
if err := tx.Create(userInfo).Error; err != nil {
|
|
||||||
l.Errorw("failed to create user", logger.Field("error", err.Error()))
|
l.Infow("[SessionMonitor] 注销账号-清除所有Session",
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create user failed: %v", err)
|
logger.Field("user_id", userId),
|
||||||
}
|
logger.Field("count", len(sessionSet)),
|
||||||
|
)
|
||||||
// 2. 更新推荐码
|
|
||||||
userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
|
|
||||||
if err := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
|
|
||||||
l.Errorw("failed to update refer code", logger.Field("user_id", userInfo.Id), logger.Field("error", err.Error()))
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update refer code failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. 创建设备认证方式
|
|
||||||
authMethod := &user.AuthMethods{
|
|
||||||
UserId: userInfo.Id,
|
|
||||||
AuthType: "device",
|
|
||||||
AuthIdentifier: identifier,
|
|
||||||
Verified: true,
|
|
||||||
}
|
|
||||||
if err := tx.Create(authMethod).Error; err != nil {
|
|
||||||
l.Errorw("failed to create device auth method", logger.Field("user_id", userInfo.Id), logger.Field("identifier", identifier), logger.Field("error", err.Error()))
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 插入设备记录
|
|
||||||
deviceInfo := &user.Device{
|
|
||||||
Ip: ip,
|
|
||||||
UserId: userInfo.Id,
|
|
||||||
UserAgent: userAgent,
|
|
||||||
Identifier: identifier,
|
|
||||||
Enabled: true,
|
|
||||||
Online: false,
|
|
||||||
}
|
|
||||||
if err := tx.Create(deviceInfo).Error; err != nil {
|
|
||||||
l.Errorw("failed to insert device", logger.Field("user_id", userInfo.Id), logger.Field("identifier", identifier), logger.Field("error", err.Error()))
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert device failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return userInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// createAnonymousUser 创建一个新的匿名用户主体 (仅User表)
|
|
||||||
func (l *DeleteAccountLogic) createAnonymousUser(tx *gorm.DB) (*user.User, error) {
|
|
||||||
// 1. 创建新用户
|
|
||||||
userInfo := &user.User{
|
|
||||||
Salt: "default",
|
|
||||||
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
|
||||||
}
|
|
||||||
if err := tx.Create(userInfo).Error; err != nil {
|
|
||||||
l.Errorw("failed to create user", logger.Field("error", err.Error()))
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create user failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 更新推荐码
|
|
||||||
userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
|
|
||||||
if err := tx.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
|
|
||||||
l.Errorw("failed to update refer code", logger.Field("user_id", userInfo.Id), logger.Field("error", err.Error()))
|
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update refer code failed: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return userInfo, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
109
internal/logic/public/user/deleteAccountLogic_test.go
Normal file
109
internal/logic/public/user/deleteAccountLogic_test.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/alicebob/miniredis/v2"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClearAllSessions_RemovesUserSessionsAndDeviceMappings(t *testing.T) {
|
||||||
|
logic, redisClient, cleanup := newDeleteAccountRedisTestLogic(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:sid-user-1", "1001")
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:sid-user-2", "1001")
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:sid-other", "2002")
|
||||||
|
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:detail:sid-user-1", "detail")
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:detail:sid-other", "detail")
|
||||||
|
|
||||||
|
mustRedisSet(t, redisClient, "auth:device_identifier:dev-user-1", "sid-user-1")
|
||||||
|
mustRedisSet(t, redisClient, "auth:device_identifier:dev-user-2", "sid-user-2")
|
||||||
|
mustRedisSet(t, redisClient, "auth:device_identifier:dev-other", "sid-other")
|
||||||
|
|
||||||
|
mustRedisZAdd(t, redisClient, "auth:user_sessions:1001", "sid-user-3", 1)
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:sid-user-3", "1001")
|
||||||
|
|
||||||
|
logic.clearAllSessions(1001)
|
||||||
|
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:session_id:sid-user-1")
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:session_id:sid-user-2")
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:session_id:sid-user-3")
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:session_id:detail:sid-user-1")
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:user_sessions:1001")
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:device_identifier:dev-user-1")
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:device_identifier:dev-user-2")
|
||||||
|
|
||||||
|
mustRedisExist(t, redisClient, "auth:session_id:sid-other")
|
||||||
|
mustRedisExist(t, redisClient, "auth:session_id:detail:sid-other")
|
||||||
|
mustRedisExist(t, redisClient, "auth:device_identifier:dev-other")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClearAllSessions_ScanFallbackWorksWithoutUserSessionIndex(t *testing.T) {
|
||||||
|
logic, redisClient, cleanup := newDeleteAccountRedisTestLogic(t)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:sid-a", "3003")
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:sid-b", "3003")
|
||||||
|
mustRedisSet(t, redisClient, "auth:session_id:sid-c", "4004")
|
||||||
|
|
||||||
|
logic.clearAllSessions(3003)
|
||||||
|
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:session_id:sid-a")
|
||||||
|
mustRedisNotExist(t, redisClient, "auth:session_id:sid-b")
|
||||||
|
mustRedisExist(t, redisClient, "auth:session_id:sid-c")
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDeleteAccountRedisTestLogic(t *testing.T) (*DeleteAccountLogic, *redis.Client, func()) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
miniRedis := miniredis.RunT(t)
|
||||||
|
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||||
|
logic := NewDeleteAccountLogic(context.Background(), &svc.ServiceContext{Redis: redisClient})
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
_ = redisClient.Close()
|
||||||
|
miniRedis.Close()
|
||||||
|
}
|
||||||
|
return logic, redisClient, cleanup
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRedisSet(t *testing.T, redisClient *redis.Client, key, value string) {
|
||||||
|
t.Helper()
|
||||||
|
if err := redisClient.Set(context.Background(), key, value, time.Hour).Err(); err != nil {
|
||||||
|
t.Fatalf("redis set %s failed: %v", key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRedisZAdd(t *testing.T, redisClient *redis.Client, key, member string, score float64) {
|
||||||
|
t.Helper()
|
||||||
|
if err := redisClient.ZAdd(context.Background(), key, redis.Z{Member: member, Score: score}).Err(); err != nil {
|
||||||
|
t.Fatalf("redis zadd %s failed: %v", key, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRedisExist(t *testing.T, redisClient *redis.Client, key string) {
|
||||||
|
t.Helper()
|
||||||
|
exists, err := redisClient.Exists(context.Background(), key).Result()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("redis exists %s failed: %v", key, err)
|
||||||
|
}
|
||||||
|
if exists == 0 {
|
||||||
|
t.Fatalf("expected redis key %s to exist", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mustRedisNotExist(t *testing.T, redisClient *redis.Client, key string) {
|
||||||
|
t.Helper()
|
||||||
|
exists, err := redisClient.Exists(context.Background(), key).Result()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("redis exists %s failed: %v", key, err)
|
||||||
|
}
|
||||||
|
if exists != 0 {
|
||||||
|
t.Fatalf("expected redis key %s to be deleted", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -228,6 +228,7 @@ func (rw *ResponseWriter) Decrypt() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
rw.c.Request.Body = io.NopCloser(bytes.NewBuffer([]byte(decrypt)))
|
rw.c.Request.Body = io.NopCloser(bytes.NewBuffer([]byte(decrypt)))
|
||||||
|
rw.c.Request.Header.Set("Content-Type", "application/json")
|
||||||
rw.c.Set(ctxDecryptedBodyKey, decrypt)
|
rw.c.Set(ctxDecryptedBodyKey, decrypt)
|
||||||
rw.c.Set(ctxDeviceDecryptStatusKey, "success")
|
rw.c.Set(ctxDeviceDecryptStatusKey, "success")
|
||||||
return true
|
return true
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user