From a9c832cb7c09e0cccf8cbf51bab4047ae3bcd2c3 Mon Sep 17 00:00:00 2001 From: Chang lue Tsen Date: Sat, 27 Dec 2025 10:45:28 -0500 Subject: [PATCH] feat(user): implement soft deletion for user accounts and update related logic --- apis/types.api | 1 - internal/logic/auth/userLoginLogic.go | 4 ++ internal/logic/auth/userRegisterLogic.go | 7 +++- internal/model/user/default.go | 25 ++++-------- internal/model/user/user.go | 49 +++++++++++++----------- internal/types/types.go | 1 - 6 files changed, 42 insertions(+), 45 deletions(-) diff --git a/apis/types.api b/apis/types.api index 8b4a9aa..85ecf63 100644 --- a/apis/types.api +++ b/apis/types.api @@ -32,7 +32,6 @@ type ( CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` DeletedAt int64 `json:"deleted_at,omitempty"` - IsDel bool `json:"is_del,omitempty"` } Follow { Id int64 `json:"id"` diff --git a/internal/logic/auth/userLoginLogic.go b/internal/logic/auth/userLoginLogic.go index deecaae..bc3c2fa 100644 --- a/internal/logic/auth/userLoginLogic.go +++ b/internal/logic/auth/userLoginLogic.go @@ -68,6 +68,10 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log userInfo, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) + if userInfo.DeletedAt.Valid { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email deleted: %v", req.Email) + } + if err != nil { if errors.As(err, &gorm.ErrRecordNotFound) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "user email not exist: %v", req.Email) diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index cf959a9..8b622c0 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -79,13 +79,16 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * } } // Check if the user exists - _, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) + u, err := l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email) if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { l.Errorw("FindOneByEmail Error", logger.Field("error", err)) return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user info failed: %v", err.Error()) - } else if err == nil { + } else if err == nil && !u.DeletedAt.Valid { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "user email exist: %v", req.Email) + } else if err == nil && u.DeletedAt.Valid { + return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserDisabled), "user email deleted: %v", req.Email) } + // Generate password pwd := tool.EncodePassWord(req.Password) userInfo := &user.User{ diff --git a/internal/model/user/default.go b/internal/model/user/default.go index e2a326a..8eeb399 100644 --- a/internal/model/user/default.go +++ b/internal/model/user/default.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/perfect-panel/server/pkg/cache" + "github.com/perfect-panel/server/pkg/logger" "github.com/redis/go-redis/v9" "gorm.io/gorm" ) @@ -72,7 +73,7 @@ func (m *defaultUserModel) FindOneByEmail(ctx context.Context, email string) (*U if err := conn.Model(&AuthMethods{}).Where("`auth_type` = 'email' AND `auth_identifier` = ?", email).First(&data).Error; err != nil { return err } - return conn.Model(&User{}).Where("`id` = ?", data.UserId).Preload("UserDevices").Preload("AuthMethods").First(v).Error + return conn.Model(&User{}).Unscoped().Where("`id` = ?", data.UserId).Preload("UserDevices").Preload("AuthMethods").First(v).Error }) return &user, err } @@ -91,7 +92,7 @@ func (m *defaultUserModel) FindOne(ctx context.Context, id int64) (*User, error) userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id) var resp User err := m.QueryCtx(ctx, &resp, userIdKey, func(conn *gorm.DB, v interface{}) error { - return conn.Model(&User{}).Where("`id` = ?", id).Preload("UserDevices").Preload("AuthMethods").First(&resp).Error + return conn.Model(&User{}).Unscoped().Where("`id` = ?", id).Preload("UserDevices").Preload("AuthMethods").First(&resp).Error }) return &resp, err } @@ -119,10 +120,11 @@ func (m *defaultUserModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) return err } - // 使用批量相关缓存清理,包含所有相关数据的缓存 + // Use batch related cache cleaning, including a cache of all relevant data defer func() { if clearErr := m.BatchClearRelatedCache(ctx, data); clearErr != nil { - // 记录清理缓存错误,但不阻断删除操作 + // Record cache cleaning errors, but do not block deletion operations + logger.Errorf("failed to clear related cache for user %d: %v", id, clearErr.Error()) } }() @@ -130,24 +132,11 @@ func (m *defaultUserModel) Delete(ctx context.Context, id int64, tx ...*gorm.DB) if len(tx) > 0 { db = tx[0] } - - // 删除用户相关的所有数据 + // Soft deletion of user information without any processing of other information (Determine whether to allow login/subscription based on the user's deletion status) if err := db.Model(&User{}).Where("`id` = ?", id).Delete(&User{}).Error; err != nil { return err } - if err := db.Model(&AuthMethods{}).Where("`user_id` = ?", id).Delete(&AuthMethods{}).Error; err != nil { - return err - } - - if err := db.Model(&Subscribe{}).Where("`user_id` = ?", id).Delete(&Subscribe{}).Error; err != nil { - return err - } - - if err := db.Model(&Device{}).Where("`user_id` = ?", id).Delete(&Device{}).Error; err != nil { - return err - } - return nil }) } diff --git a/internal/model/user/user.go b/internal/model/user/user.go index 923594e..80db348 100644 --- a/internal/model/user/user.go +++ b/internal/model/user/user.go @@ -2,32 +2,35 @@ package user import ( "time" + + "gorm.io/gorm" ) type User struct { - Id int64 `gorm:"primaryKey"` - Password string `gorm:"type:varchar(100);not null;comment:User Password"` - Algo string `gorm:"type:varchar(20);default:'default';comment:Encryption Algorithm"` - Salt string `gorm:"type:varchar(20);default:null;comment:Password Salt"` - Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"` - Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount - ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"` - RefererId int64 `gorm:"index:idx_referer;comment:Referrer ID"` - Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount - ReferralPercentage uint8 `gorm:"default:0;comment:Referral"` // Referral Percentage - OnlyFirstPurchase *bool `gorm:"default:true;not null;comment:Only First Purchase"` // Only First Purchase Referral - GiftAmount int64 `gorm:"default:0;comment:User Gift Amount"` - Enable *bool `gorm:"default:true;not null;comment:Is Account Enabled"` - IsAdmin *bool `gorm:"default:false;not null;comment:Is Admin"` - EnableBalanceNotify *bool `gorm:"default:false;not null;comment:Enable Balance Change Notifications"` - EnableLoginNotify *bool `gorm:"default:false;not null;comment:Enable Login Notifications"` - EnableSubscribeNotify *bool `gorm:"default:false;not null;comment:Enable Subscription Notifications"` - EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"` - AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"` - UserDevices []Device `gorm:"foreignKey:UserId;references:Id"` - Rules string `gorm:"type:TEXT;comment:User Rules"` - CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` - UpdatedAt time.Time `gorm:"comment:Update Time"` + Id int64 `gorm:"primaryKey"` + Password string `gorm:"type:varchar(100);not null;comment:User Password"` + Algo string `gorm:"type:varchar(20);default:'default';comment:Encryption Algorithm"` + Salt string `gorm:"type:varchar(20);default:null;comment:Password Salt"` + Avatar string `gorm:"type:MEDIUMTEXT;comment:User Avatar"` + Balance int64 `gorm:"default:0;comment:User Balance"` // User Balance Amount + ReferCode string `gorm:"type:varchar(20);default:'';comment:Referral Code"` + RefererId int64 `gorm:"index:idx_referer;comment:Referrer ID"` + Commission int64 `gorm:"default:0;comment:Commission"` // Commission Amount + ReferralPercentage uint8 `gorm:"default:0;comment:Referral"` // Referral Percentage + OnlyFirstPurchase *bool `gorm:"default:true;not null;comment:Only First Purchase"` // Only First Purchase Referral + GiftAmount int64 `gorm:"default:0;comment:User Gift Amount"` + Enable *bool `gorm:"default:true;not null;comment:Is Account Enabled"` + IsAdmin *bool `gorm:"default:false;not null;comment:Is Admin"` + EnableBalanceNotify *bool `gorm:"default:false;not null;comment:Enable Balance Change Notifications"` + EnableLoginNotify *bool `gorm:"default:false;not null;comment:Enable Login Notifications"` + EnableSubscribeNotify *bool `gorm:"default:false;not null;comment:Enable Subscription Notifications"` + EnableTradeNotify *bool `gorm:"default:false;not null;comment:Enable Trade Notifications"` + AuthMethods []AuthMethods `gorm:"foreignKey:UserId;references:Id"` + UserDevices []Device `gorm:"foreignKey:UserId;references:Id"` + Rules string `gorm:"type:TEXT;comment:User Rules"` + CreatedAt time.Time `gorm:"<-:create;comment:Creation Time"` + UpdatedAt time.Time `gorm:"comment:Update Time"` + DeletedAt gorm.DeletedAt `gorm:"index;comment:Deletion Time"` } func (*User) TableName() string { diff --git a/internal/types/types.go b/internal/types/types.go index e6353e9..c7bf4bf 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -2540,7 +2540,6 @@ type User struct { CreatedAt int64 `json:"created_at"` UpdatedAt int64 `json:"updated_at"` DeletedAt int64 `json:"deleted_at,omitempty"` - IsDel bool `json:"is_del,omitempty"` } type UserAffiliate struct {