diff --git a/internal/apps/admin/push/channels.go b/internal/apps/admin/push/channels.go index 6c308068..34c32a7e 100644 --- a/internal/apps/admin/push/channels.go +++ b/internal/apps/admin/push/channels.go @@ -107,6 +107,9 @@ func CreateChannel(c *gin.Context) { return } + // 缓存一致性:清除渠道缓存 + model.DeleteActivePushChannelCache(ctx, channel.Name) + c.JSON(http.StatusOK, response.OK(channel)) } @@ -174,6 +177,9 @@ func UpdateChannel(c *gin.Context) { return } + // 缓存一致性:清除渠道缓存 + model.DeleteActivePushChannelCache(ctx, channel.Name) + c.JSON(http.StatusOK, response.OK(channel)) } @@ -210,6 +216,9 @@ func DeleteChannel(c *gin.Context) { return } + // 缓存一致性:清除渠道缓存 + model.DeleteActivePushChannelCache(ctx, channel.Name) + c.JSON(http.StatusOK, response.OKNil()) } diff --git a/internal/apps/admin/push/events.go b/internal/apps/admin/push/events.go index 250b60db..4e699097 100644 --- a/internal/apps/admin/push/events.go +++ b/internal/apps/admin/push/events.go @@ -109,9 +109,8 @@ func (t *EventTrigger) Trigger(ctx context.Context, meta EventMetadata, body map body["user"] = getSystemUser(asyncCtx) } - // 1. Check if the event is enabled in the database - var event model.PushEvent - err := db.DB(asyncCtx).Where("event_key = ? AND enabled = ?", meta.Key, true).First(&event).Error + // 1. Check if the event is enabled (try Redis cache first) + eventPtr, err := model.GetActivePushEventByKey(asyncCtx, meta.Key) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return @@ -119,6 +118,7 @@ func (t *EventTrigger) Trigger(ctx context.Context, meta EventMetadata, body map logger.ErrorF(asyncCtx, "push_event_trigger: failed to get active event %s: %v", meta.Key, err) return } + event := *eventPtr if len(event.Channels) == 0 { return @@ -222,11 +222,10 @@ func (t *EventTrigger) parseDefaultTemplate(meta EventMetadata, flatBody map[str func (t *EventTrigger) enqueuePushTasks(ctx context.Context, meta EventMetadata, event *model.PushEvent, msg NotificationMessage, flatBody map[string]any) { for _, channelName := range event.Channels { - // 检查是不是自定义数据库渠道 - var customChannel model.PushChannel - err := db.DB(ctx).Where("name = ? AND enabled = ?", channelName, true).First(&customChannel).Error + // 检查是不是自定义数据库渠道 (使用 Redis 缓存优先) + customChannel, err := model.GetActivePushChannelByName(ctx, channelName) if err == nil { - t.enqueueCustomPushChannelTasks(ctx, meta, event, &customChannel, msg, flatBody) + t.enqueueCustomPushChannelTasks(ctx, meta, event, customChannel, msg, flatBody) continue } diff --git a/internal/apps/admin/push/routers.go b/internal/apps/admin/push/routers.go index 901f2077..8858c5f7 100644 --- a/internal/apps/admin/push/routers.go +++ b/internal/apps/admin/push/routers.go @@ -231,6 +231,9 @@ func CreateEvent(c *gin.Context) { return } + // 缓存一致性:清除旧事件缓存 + model.DeleteActivePushEventCache(ctx, event.EventKey) + c.JSON(http.StatusOK, response.OK(event)) } @@ -267,6 +270,9 @@ func DeleteEvent(c *gin.Context) { return } + // 缓存一致性:清除事件缓存 + model.DeleteActivePushEventCache(ctx, event.EventKey) + c.JSON(http.StatusOK, response.OKNil()) } @@ -320,6 +326,9 @@ func UpdateEvent(c *gin.Context) { return } + // 缓存一致性:清除事件缓存 + model.DeleteActivePushEventCache(c.Request.Context(), event.EventKey) + c.JSON(http.StatusOK, response.OKNil()) } @@ -360,6 +369,9 @@ func ToggleEvent(c *gin.Context) { return } + // 缓存一致性:清除事件缓存 + model.DeleteActivePushEventCache(c.Request.Context(), event.EventKey) + c.JSON(http.StatusOK, response.OK(event.Enabled)) } diff --git a/internal/model/push_channel.go b/internal/model/push_channel.go index de96b4d6..515a0dd2 100644 --- a/internal/model/push_channel.go +++ b/internal/model/push_channel.go @@ -12,6 +12,7 @@ import ( "time" "github.com/Rain-kl/Wavelet/internal/db" + "gorm.io/gorm" ) const ( @@ -112,3 +113,47 @@ func GetPushChannelByName(ctx context.Context, name string) (*PushChannel, error } return &channel, nil } + +const activePushChannelCacheTTL = 24 * time.Hour + +// GetActivePushChannelByName 根据名称获取启用的消息通道 (优先从 Redis 缓存获取) +func GetActivePushChannelByName(ctx context.Context, name string) (*PushChannel, error) { + cacheKey := "push:channel:active:" + name + var channel PushChannel + if db.Redis != nil { + if err := db.GetJSON(ctx, cacheKey, &channel); err == nil { + return &channel, nil + } + } + + err := db.DB(ctx).Where("name = ? AND enabled = ?", name, true).First(&channel).Error + if err != nil { + return nil, err + } + + if db.Redis != nil { + // 缓存有效时间设置为 24 小时 + _ = db.SetJSON(ctx, cacheKey, channel, activePushChannelCacheTTL) + } + + return &channel, nil +} + +// DeleteActivePushChannelCache 清理启用消息通道的缓存 +func DeleteActivePushChannelCache(ctx context.Context, name string) { + if db.Redis != nil { + _ = db.Redis.Del(ctx, db.PrefixedKey("push:channel:active:"+name)).Err() + } +} + +// AfterSave GORM 保存后钩子,用于自动清理缓存 +func (pc *PushChannel) AfterSave(tx *gorm.DB) error { + DeleteActivePushChannelCache(tx.Statement.Context, pc.Name) + return nil +} + +// AfterDelete GORM 删除后钩子,用于自动清理缓存 +func (pc *PushChannel) AfterDelete(tx *gorm.DB) error { + DeleteActivePushChannelCache(tx.Statement.Context, pc.Name) + return nil +} diff --git a/internal/model/push_event.go b/internal/model/push_event.go index 9f806c35..7e4c5c89 100644 --- a/internal/model/push_event.go +++ b/internal/model/push_event.go @@ -10,6 +10,7 @@ import ( "time" "github.com/Rain-kl/Wavelet/internal/db" + "gorm.io/gorm" ) // PushEvent 系统通知事件模型 @@ -52,12 +53,46 @@ func (pe *PushEvent) Validate() error { return nil } -// GetActivePushEventByKey 获取启用的通知事件 +const activePushEventCacheTTL = 24 * time.Hour + +// GetActivePushEventByKey 获取启用的通知事件 (优先从 Redis 缓存获取) func GetActivePushEventByKey(ctx context.Context, key string) (*PushEvent, error) { + cacheKey := "push:event:active:" + key var event PushEvent + if db.Redis != nil { + if err := db.GetJSON(ctx, cacheKey, &event); err == nil { + return &event, nil + } + } + err := db.DB(ctx).Where("event_key = ? AND enabled = ?", key, true).First(&event).Error if err != nil { return nil, err } + + if db.Redis != nil { + // 缓存有效时间设置为 24 小时 + _ = db.SetJSON(ctx, cacheKey, event, activePushEventCacheTTL) + } + return &event, nil } + +// DeleteActivePushEventCache 清理启用通知事件的缓存 +func DeleteActivePushEventCache(ctx context.Context, key string) { + if db.Redis != nil { + _ = db.Redis.Del(ctx, db.PrefixedKey("push:event:active:"+key)).Err() + } +} + +// AfterSave GORM 保存后钩子,用于自动清理缓存 +func (pe *PushEvent) AfterSave(tx *gorm.DB) error { + DeleteActivePushEventCache(tx.Statement.Context, pe.EventKey) + return nil +} + +// AfterDelete GORM 删除后钩子,用于自动清理缓存 +func (pe *PushEvent) AfterDelete(tx *gorm.DB) error { + DeleteActivePushEventCache(tx.Statement.Context, pe.EventKey) + return nil +}