package common import ( "context" "fmt" "strconv" "time" "github.com/perfect-panel/server/internal/config" modeluser "github.com/perfect-panel/server/internal/model/user" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/pkg/logger" "github.com/perfect-panel/server/pkg/xerr" "github.com/pkg/errors" "github.com/redis/go-redis/v9" ) const userEnableCacheTTL = 30 * time.Second func UserEnableCacheKey(userID int64) string { return fmt.Sprintf("%s%d", config.UserEnableKeyPrefix, userID) } func IsUserDisabled(userInfo *modeluser.User) bool { return userInfo != nil && userInfo.Enable != nil && !*userInfo.Enable } func ResolveEnabledUser(ctx context.Context, svcCtx *svc.ServiceContext, userID int64) (*modeluser.User, error) { if userID <= 0 { return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "invalid user id: %d", userID) } cacheKey := UserEnableCacheKey(userID) cached, err := svcCtx.Redis.Get(ctx, cacheKey).Result() if err == nil { if cached == strconv.FormatBool(false) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserDisabled), "User disabled") } return svcCtx.UserModel.FindOne(ctx, userID) } if err != nil && err != redis.Nil { logger.WithContext(ctx).Errorw("get user enable cache failed, fallback to db", logger.Field("user_id", userID), logger.Field("error", err.Error()), ) return loadEnabledUserFromDB(ctx, svcCtx, userID) } userInfo, err := svcCtx.UserModel.FindOne(ctx, userID) if err != nil { return nil, err } if cacheErr := CacheUserEnabled(ctx, svcCtx, userID, !IsUserDisabled(userInfo)); cacheErr != nil { logger.WithContext(ctx).Errorw("cache user enable state failed", logger.Field("user_id", userID), logger.Field("error", cacheErr.Error()), ) } if IsUserDisabled(userInfo) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserDisabled), "User disabled") } return userInfo, nil } func CacheUserEnabled(ctx context.Context, svcCtx *svc.ServiceContext, userID int64, enabled bool) error { return svcCtx.Redis.Set(ctx, UserEnableCacheKey(userID), strconv.FormatBool(enabled), userEnableCacheTTL).Err() } func InvalidateUserEnableCache(ctx context.Context, svcCtx *svc.ServiceContext, userID int64) error { return svcCtx.Redis.Del(ctx, UserEnableCacheKey(userID)).Err() } func loadEnabledUserFromDB(ctx context.Context, svcCtx *svc.ServiceContext, userID int64) (*modeluser.User, error) { var userInfo modeluser.User if err := svcCtx.DB.WithContext(ctx). Model(&modeluser.User{}). Unscoped(). Where("`id` = ?", userID). Preload("UserDevices"). Preload("AuthMethods"). First(&userInfo).Error; err != nil { return nil, err } if IsUserDisabled(&userInfo) { return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserDisabled), "User disabled") } return &userInfo, nil }