diff --git a/internal/handler/public/user/deleteAccountHandler.go b/internal/handler/public/user/deleteAccountHandler.go new file mode 100644 index 0000000..67a5ebc --- /dev/null +++ b/internal/handler/public/user/deleteAccountHandler.go @@ -0,0 +1,23 @@ +package user + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/perfect-panel/server/internal/logic/public/user" + "github.com/perfect-panel/server/internal/svc" +) + +// DeleteAccountHandler 注销账号处理器 +// 根据当前token删除所有关联设备,然后根据各自设备ID重新新建账号 +func DeleteAccountHandler(serverCtx *svc.ServiceContext) gin.HandlerFunc { + return func(c *gin.Context) { + l := user.NewDeleteAccountLogic(c.Request.Context(), serverCtx) + resp, err := l.DeleteAccount() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, resp) + } +} diff --git a/internal/handler/routes.go b/internal/handler/routes.go index 3e571cf..19baa25 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -769,13 +769,16 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { { // Query User Affiliate Count - publicUserGroupRouter.GET("/affiliate/count", publicUser.QueryUserAffiliateHandler(serverCtx)) + publicUserGroupRouter.GET("/affiliate/count", publicUser.QueryUserAffiliateHandler(serverCtx)) - // Query User Affiliate List - publicUserGroupRouter.GET("/affiliate/list", publicUser.QueryUserAffiliateListHandler(serverCtx)) + // Query User Affiliate List + publicUserGroupRouter.GET("/affiliate/list", publicUser.QueryUserAffiliateListHandler(serverCtx)) - // Query User Balance Log - publicUserGroupRouter.GET("/balance_log", publicUser.QueryUserBalanceLogHandler(serverCtx)) + // Delete Account + publicUserGroupRouter.GET("/delete_account", publicUser.DeleteAccountHandler(serverCtx)) + + // Query User Balance Log + publicUserGroupRouter.GET("/balance_log", publicUser.QueryUserBalanceLogHandler(serverCtx)) // Update Bind Email publicUserGroupRouter.PUT("/bind_email", publicUser.UpdateBindEmailHandler(serverCtx)) diff --git a/internal/logic/public/user/deleteAccountLogic.go b/internal/logic/public/user/deleteAccountLogic.go new file mode 100644 index 0000000..59c35e1 --- /dev/null +++ b/internal/logic/public/user/deleteAccountLogic.go @@ -0,0 +1,156 @@ +package user + +import ( + "context" + "crypto/rand" + "encoding/hex" + + "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) + if err != nil { + return errors.Wrap(err, "创建新用户失败") + } + newUserId = userInfo.Id // 保留最后一个新用户ID + } + + return nil + }) + + if err != nil { + return nil, err + } + + resp.Success = true + resp.Message = "账户注销成功,已创建新账户" + resp.UserId = newUserId + + 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 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: "", + UserId: userInfo.Id, + 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 +} diff --git a/internal/types/types.go b/internal/types/types.go index 9970643..4a536bb 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -150,6 +150,10 @@ type BatchDeleteSubscribeGroupRequest struct { Ids []int64 `json:"ids" validate:"required"` } +type DeleteSubscribeRequest struct { + Id int64 `json:"id" validate:"required"` +} + type BatchDeleteSubscribeRequest struct { Ids []int64 `json:"ids" validate:"required"` } @@ -517,19 +521,21 @@ type DeleteSubscribeGroupRequest struct { Id int64 `json:"id" validate:"required"` } -type DeleteSubscribeRequest struct { +type DeleteUserDeivceRequest struct { Id int64 `json:"id" validate:"required"` } +type DeleteAccountResponse struct { + Success bool `json:"success"` + Message string `json:"message,omitempty"` + UserId int64 `json:"user_id,omitempty"` +} + type DeleteUserAuthMethodRequest struct { UserId int64 `json:"user_id"` AuthType string `json:"auth_type"` } -type DeleteUserDeivceRequest struct { - Id int64 `json:"id"` -} - type DeleteUserSubscribeRequest struct { UserSubscribeId int64 `json:"user_subscribe_id"` }