From e215ffcae9a664c15e94cdec1d4f7d1772d67c76 Mon Sep 17 00:00:00 2001 From: EUForest Date: Fri, 6 Mar 2026 13:25:01 +0800 Subject: [PATCH] fix(subscribe): invalidate user subscription cache when plan is updated When administrators update subscription plan configurations (traffic limits, nodes, speed limits, etc.), existing subscribers were not seeing the updated settings immediately. This was caused by stale cache entries that were not being invalidated. The issue occurred because: - User subscription queries cache the entire result including preloaded plan details - Plan update/delete operations only cleared the plan's own cache keys - User subscription cache keys (cache:user:subscribe:user:{userId}) remained stale This fix ensures that when a subscription plan is updated or deleted, all associated user subscription caches are properly invalidated by: - Querying all active users subscribed to the plan - Building cache keys for each affected user - Clearing both plan and user subscription caches atomically Users will now immediately see updated plan configurations without waiting for cache expiration. --- internal/model/subscribe/default.go | 48 +++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/internal/model/subscribe/default.go b/internal/model/subscribe/default.go index 29e748c..c35d161 100644 --- a/internal/model/subscribe/default.go +++ b/internal/model/subscribe/default.go @@ -119,13 +119,35 @@ func (m *defaultSubscribeModel) Update(ctx context.Context, data *Subscribe, tx if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { return err } + + // 获取所有使用该套餐的用户订阅缓存 key + var userIds []int64 + err = m.QueryNoCacheCtx(ctx, &userIds, func(conn *gorm.DB, v interface{}) error { + return conn.Table("user_subscribe"). + Where("subscribe_id = ? AND status IN (0, 1)", data.Id). + Distinct("user_id"). + Pluck("user_id", &userIds).Error + }) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + // 构建用户订阅缓存 key 列表 + userSubscribeCacheKeys := make([]string, 0, len(userIds)) + for _, userId := range userIds { + userSubscribeCacheKeys = append(userSubscribeCacheKeys, fmt.Sprintf("cache:user:subscribe:user:%d", userId)) + } + + // 合并套餐缓存 key 和用户订阅缓存 key + allCacheKeys := append(m.getCacheKeys(old), userSubscribeCacheKeys...) + err = m.ExecCtx(ctx, func(conn *gorm.DB) error { db := conn if len(tx) > 0 { db = tx[0] } return db.Save(data).Error - }, m.getCacheKeys(old)...) + }, allCacheKeys...) return err } @@ -137,13 +159,35 @@ func (m *defaultSubscribeModel) Delete(ctx context.Context, id int64, tx ...*gor } return err } + + // 获取所有使用该套餐的用户订阅缓存 key + var userIds []int64 + err = m.QueryNoCacheCtx(ctx, &userIds, func(conn *gorm.DB, v interface{}) error { + return conn.Table("user_subscribe"). + Where("subscribe_id = ? AND status IN (0, 1)", id). + Distinct("user_id"). + Pluck("user_id", &userIds).Error + }) + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + // 构建用户订阅缓存 key 列表 + userSubscribeCacheKeys := make([]string, 0, len(userIds)) + for _, userId := range userIds { + userSubscribeCacheKeys = append(userSubscribeCacheKeys, fmt.Sprintf("cache:user:subscribe:user:%d", userId)) + } + + // 合并套餐缓存 key 和用户订阅缓存 key + allCacheKeys := append(m.getCacheKeys(data), userSubscribeCacheKeys...) + err = m.ExecCtx(ctx, func(conn *gorm.DB) error { db := conn if len(tx) > 0 { db = tx[0] } return db.Delete(&Subscribe{}, id).Error - }, m.getCacheKeys(data)...) + }, allCacheKeys...) return err }