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 }