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.
This commit is contained in:
EUForest 2026-03-06 13:25:01 +08:00
parent 7d46b31866
commit e215ffcae9

View File

@ -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
}