From 5f1a546bbe80e99d43010107c5627e45f45926af Mon Sep 17 00:00:00 2001 From: Rust Date: Thu, 22 Jan 2026 23:43:49 +0700 Subject: [PATCH] fix: resolve trial subscription cache issue on new user registration When new users register with trial subscription enabled, the subscription link fails to connect in Clash clients. This is caused by missing cache invalidation after transaction commit. Changes: - Add cache clearing after successful trial subscription creation - Clear user subscription cache, subscription details cache, and server cache - Modify activeTrial functions to return subscription object for cache clearing - Apply fix to all registration methods: email, phone, device, and OAuth This ensures subscription links work immediately after registration without requiring manual subscription reset. --- internal/logic/auth/deviceLoginLogic.go | 37 +++++++++++++---- .../auth/oauth/oAuthLoginGetTokenLogic.go | 34 ++++++++++++--- .../logic/auth/telephoneUserRegisterLogic.go | 41 +++++++++++++++---- internal/logic/auth/userRegisterLogic.go | 38 ++++++++++++++--- 4 files changed, 121 insertions(+), 29 deletions(-) diff --git a/internal/logic/auth/deviceLoginLogic.go b/internal/logic/auth/deviceLoginLogic.go index 8c0926d..9218713 100644 --- a/internal/logic/auth/deviceLoginLogic.go +++ b/internal/logic/auth/deviceLoginLogic.go @@ -152,6 +152,7 @@ func (l *DeviceLoginLogic) registerUserAndDevice(req *types.DeviceLoginRequest) ) var userInfo *user.User + var trialSubscribe *user.Subscribe err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { // Create new user userInfo = &user.User{ @@ -212,8 +213,10 @@ func (l *DeviceLoginLogic) registerUserAndDevice(req *types.DeviceLoginRequest) // Activate trial if enabled if l.svcCtx.Config.Register.EnableTrial { - if err := l.activeTrial(userInfo.Id, db); err != nil { - return err + var trialErr error + trialSubscribe, trialErr = l.activeTrial(userInfo.Id, db) + if trialErr != nil { + return trialErr } } @@ -228,6 +231,25 @@ func (l *DeviceLoginLogic) registerUserAndDevice(req *types.DeviceLoginRequest) return nil, err } + // Clear cache after transaction success + if l.svcCtx.Config.Register.EnableTrial && trialSubscribe != nil { + // Clear user subscription cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, trialSubscribe); err != nil { + l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("userSubscribeId", trialSubscribe.Id)) + // Don't return error, just log it + } + // Clear subscription cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, trialSubscribe.SubscribeId); err != nil { + l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("subscribeId", trialSubscribe.SubscribeId)) + // Don't return error, just log it + } + // Clear all server cache + if err = l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); err != nil { + l.Errorf("ClearServerAllCache error: %v", err.Error()) + // Don't return error, just log it + } + } + l.Infow("device registration completed successfully", logger.Field("user_id", userInfo.Id), logger.Field("identifier", req.Identifier), @@ -260,7 +282,7 @@ func (l *DeviceLoginLogic) registerUserAndDevice(req *types.DeviceLoginRequest) return userInfo, nil } -func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) error { +func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) (*user.Subscribe, error) { sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe) if err != nil { l.Errorw("failed to find trial subscription template", @@ -268,7 +290,7 @@ func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) error { logger.Field("trial_subscribe_id", l.svcCtx.Config.Register.TrialSubscribe), logger.Field("error", err.Error()), ) - return err + return nil, err } startTime := time.Now() @@ -295,7 +317,7 @@ func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) error { logger.Field("user_id", userId), logger.Field("error", err.Error()), ) - return err + return nil, err } l.Infow("trial subscription activated successfully", @@ -305,8 +327,5 @@ func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) error { logger.Field("traffic", sub.Traffic), ) - if clearErr := l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); clearErr != nil { - l.Errorf("ClearServerAllCache error: %v", clearErr.Error()) - } - return nil + return userSub, nil } diff --git a/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go b/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go index 4e12d2f..f8d4cf0 100644 --- a/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go +++ b/internal/logic/auth/oauth/oAuthLoginGetTokenLogic.go @@ -341,6 +341,7 @@ func (l *OAuthLoginGetTokenLogic) register(email, avatar, method, openid, reques } var userInfo *user.User + var trialSubscribe *user.Subscribe err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error { if email != "" { l.Debugw("checking if email already exists", @@ -397,8 +398,10 @@ func (l *OAuthLoginGetTokenLogic) register(email, avatar, method, openid, reques logger.Field("request_id", requestID), logger.Field("user_id", userInfo.Id), ) - if err := l.activeTrial(userInfo.Id, requestID); err != nil { - return err + var trialErr error + trialSubscribe, trialErr = l.activeTrial(userInfo.Id, requestID) + if trialErr != nil { + return trialErr } } @@ -415,6 +418,25 @@ func (l *OAuthLoginGetTokenLogic) register(email, avatar, method, openid, reques return userInfo, err } + // Clear cache after transaction success + if l.svcCtx.Config.Register.EnableTrial && trialSubscribe != nil { + // Clear user subscription cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, trialSubscribe); err != nil { + l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("userSubscribeId", trialSubscribe.Id)) + // Don't return error, just log it + } + // Clear subscription cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, trialSubscribe.SubscribeId); err != nil { + l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("subscribeId", trialSubscribe.SubscribeId)) + // Don't return error, just log it + } + // Clear all server cache + if err = l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); err != nil { + l.Errorf("ClearServerAllCache error: %v", err.Error()) + // Don't return error, just log it + } + } + l.Infow("user registration completed successfully", logger.Field("request_id", requestID), logger.Field("user_id", userInfo.Id), @@ -793,7 +815,7 @@ func (l *OAuthLoginGetTokenLogic) findOrRegisterUser(authType, openID, email, av return userInfo, nil } -func (l *OAuthLoginGetTokenLogic) activeTrial(uid int64, requestID string) error { +func (l *OAuthLoginGetTokenLogic) activeTrial(uid int64, requestID string) (*user.Subscribe, error) { l.Debugw("fetching trial subscription template", logger.Field("request_id", requestID), logger.Field("user_id", uid), @@ -808,7 +830,7 @@ func (l *OAuthLoginGetTokenLogic) activeTrial(uid int64, requestID string) error logger.Field("trial_subscribe_id", l.svcCtx.Config.Register.TrialSubscribe), logger.Field("error", err.Error()), ) - return err + return nil, err } startTime := time.Now() @@ -848,7 +870,7 @@ func (l *OAuthLoginGetTokenLogic) activeTrial(uid int64, requestID string) error logger.Field("user_id", uid), logger.Field("error", err.Error()), ) - return err + return nil, err } l.Infow("trial subscription activated successfully", @@ -858,5 +880,5 @@ func (l *OAuthLoginGetTokenLogic) activeTrial(uid int64, requestID string) error logger.Field("expire_time", expireTime), logger.Field("traffic", sub.Traffic), ) - return nil + return userSub, nil } diff --git a/internal/logic/auth/telephoneUserRegisterLogic.go b/internal/logic/auth/telephoneUserRegisterLogic.go index ac54796..006e956 100644 --- a/internal/logic/auth/telephoneUserRegisterLogic.go +++ b/internal/logic/auth/telephoneUserRegisterLogic.go @@ -45,6 +45,7 @@ func NewTelephoneUserRegisterLogic(ctx context.Context, svcCtx *svc.ServiceConte func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneRegisterRequest) (resp *types.LoginResponse, err error) { c := l.svcCtx.Config.Register + var trialSubscribe *user.Subscribe // Check if the registration is stopped if c.StopRegister { return nil, errors.Wrapf(xerr.NewErrCode(xerr.StopRegister), "stop register") @@ -135,12 +136,36 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR } if l.svcCtx.Config.Register.EnableTrial { // Active trial - if err = l.activeTrial(userInfo.Id); err != nil { - return err + var trialErr error + trialSubscribe, trialErr = l.activeTrial(userInfo.Id) + if trialErr != nil { + return trialErr } } return nil }) + if err != nil { + return nil, err + } + + // Clear cache after transaction success + if l.svcCtx.Config.Register.EnableTrial && trialSubscribe != nil { + // Clear user subscription cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, trialSubscribe); err != nil { + l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("userSubscribeId", trialSubscribe.Id)) + // Don't return error, just log it + } + // Clear subscription cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, trialSubscribe.SubscribeId); err != nil { + l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("subscribeId", trialSubscribe.SubscribeId)) + // Don't return error, just log it + } + // Clear all server cache + if err = l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); err != nil { + l.Errorf("ClearServerAllCache error: %v", err.Error()) + // Don't return error, just log it + } + } // Bind device to user if identifier is provided if req.Identifier != "" { @@ -229,10 +254,10 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR }, nil } -func (l *TelephoneUserRegisterLogic) activeTrial(uid int64) error { +func (l *TelephoneUserRegisterLogic) activeTrial(uid int64) (*user.Subscribe, error) { sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe) if err != nil { - return err + return nil, err } userSub := &user.Subscribe{ Id: 0, @@ -250,10 +275,8 @@ func (l *TelephoneUserRegisterLogic) activeTrial(uid int64) error { } err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub) if err != nil { - return err + return nil, err } - if clearErr := l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); clearErr != nil { - l.Errorf("ClearServerAllCache error: %v", clearErr.Error()) - } - return err + return userSub, nil } + diff --git a/internal/logic/auth/userRegisterLogic.go b/internal/logic/auth/userRegisterLogic.go index e128a69..7172753 100644 --- a/internal/logic/auth/userRegisterLogic.go +++ b/internal/logic/auth/userRegisterLogic.go @@ -42,6 +42,7 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * c := l.svcCtx.Config.Register email := l.svcCtx.Config.Email var referer *user.User + var trialSubscribe *user.Subscribe // Check if the registration is stopped if c.StopRegister { return nil, errors.Wrapf(xerr.NewErrCode(xerr.StopRegister), "stop register") @@ -127,12 +128,36 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * if l.svcCtx.Config.Register.EnableTrial { // Active trial - if err = l.activeTrial(userInfo.Id); err != nil { - return err + var trialErr error + trialSubscribe, trialErr = l.activeTrial(userInfo.Id) + if trialErr != nil { + return trialErr } } return nil }) + if err != nil { + return nil, err + } + + // Clear cache after transaction success + if l.svcCtx.Config.Register.EnableTrial && trialSubscribe != nil { + // Clear user subscription cache + if err = l.svcCtx.UserModel.ClearSubscribeCache(l.ctx, trialSubscribe); err != nil { + l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("userSubscribeId", trialSubscribe.Id)) + // Don't return error, just log it + } + // Clear subscription cache + if err = l.svcCtx.SubscribeModel.ClearCache(l.ctx, trialSubscribe.SubscribeId); err != nil { + l.Errorw("ClearSubscribeCache failed", logger.Field("error", err.Error()), logger.Field("subscribeId", trialSubscribe.SubscribeId)) + // Don't return error, just log it + } + // Clear all server cache + if err = l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); err != nil { + l.Errorf("ClearServerAllCache error: %v", err.Error()) + // Don't return error, just log it + } + } // Bind device to user if identifier is provided if req.Identifier != "" { bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx) @@ -220,10 +245,10 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp * }, nil } -func (l *UserRegisterLogic) activeTrial(uid int64) error { +func (l *UserRegisterLogic) activeTrial(uid int64) (*user.Subscribe, error) { sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe) if err != nil { - return err + return nil, err } userSub := &user.Subscribe{ UserId: uid, @@ -238,5 +263,8 @@ func (l *UserRegisterLogic) activeTrial(uid int64) error { UUID: uuidx.NewUUID().String(), Status: 1, } - return l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub) + if err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub); err != nil { + return nil, err + } + return userSub, nil }