hi-server/internal/logic/public/user/deleteAccountLogic.go
shanshanzhong fd185bcfe1
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 5m57s
refactor(handler): 使用result包统一处理HTTP响应
在deleteAccountHandler中使用result.HttpResult统一处理HTTP响应,提高代码一致性和可维护性
2025-12-22 20:32:12 -08:00

169 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package user
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"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/logger"
"github.com/perfect-panel/server/pkg/uuidx"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type DeleteAccountLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewDeleteAccountLogic 创建注销账号逻辑实例
func NewDeleteAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeleteAccountLogic {
return &DeleteAccountLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
// DeleteAccount 注销账号逻辑
// 1. 获取当前用户信息
// 2. 删除所有关联数据(用户、认证方式、设备)
// 3. 根据原设备信息创建全新账号
// 4. 返回新账号信息
func (l *DeleteAccountLogic) DeleteAccount() (resp *types.DeleteAccountResponse, err error) {
// 获取当前用户
currentUser, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
if !ok {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
}
resp = &types.DeleteAccountResponse{}
var newUserId int64
// 开始数据库事务
err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error {
// 1. 查找用户的所有设备(用于后续创建新账号)
devices, _, err := l.svcCtx.UserModel.QueryDeviceList(l.ctx, currentUser.Id)
if err != nil {
return errors.Wrap(err, "查询用户设备失败")
}
// 2. 删除用户的所有认证方式
err = tx.Model(&user.AuthMethods{}).Where("`user_id` = ?", currentUser.Id).Delete(&user.AuthMethods{}).Error
if err != nil {
return errors.Wrap(err, "删除用户认证方式失败")
}
// 3. 删除用户的所有设备
err = tx.Model(&user.Device{}).Where("`user_id` = ?", currentUser.Id).Delete(&user.Device{}).Error
if err != nil {
return errors.Wrap(err, "删除用户设备失败")
}
// 4. 删除用户的订阅信息
err = tx.Model(&user.Subscribe{}).Where("`user_id` = ?", currentUser.Id).Delete(&user.Subscribe{}).Error
if err != nil {
return errors.Wrap(err, "删除用户订阅信息失败")
}
// 5. 删除用户本身
err = tx.Model(&user.User{}).Where("`id` = ?", currentUser.Id).Delete(&user.User{}).Error
if err != nil {
return errors.Wrap(err, "删除用户失败")
}
// 7. 为每个原设备创建新的用户(使用同一事务)
for _, oldDevice := range devices {
userInfo, err := l.registerUserAndDevice(tx, oldDevice.Identifier, oldDevice.Ip, oldDevice.UserAgent)
if err != nil {
return errors.Wrap(err, "创建新用户失败")
}
newUserId = userInfo.Id // 保留最后一个新用户ID
}
return nil
})
if err != nil {
return nil, err
}
// 注销当前会话token删除Redis中的会话标记并从用户会话集合移除
if sessionId, ok := l.ctx.Value(constant.CtxKeySessionID).(string); ok && sessionId != "" {
sessionKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
_ = l.svcCtx.Redis.Del(l.ctx, sessionKey).Err()
// 从用户会话集合中移除当前session避免残留
sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, currentUser.Id)
_ = l.svcCtx.Redis.ZRem(l.ctx, sessionsKey, sessionId).Err()
}
resp.Success = true
resp.Message = "账户注销成功"
resp.UserId = newUserId
resp.Code = 200
return resp, nil
}
// generateReferCode 生成推荐码
func generateReferCode() string {
bytes := make([]byte, 4)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}
func (l *DeleteAccountLogic) registerUserAndDevice(tx *gorm.DB, identifier, ip, userAgent string) (*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)
}
// 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
}