Files
smartmate/backend/middleware/cache_deleter.go
LoveLosita 5fc9548420 Version: 0.8.2.dev.260327
后端:
1.修复了消息重试链路的相关问题
2.新增redis乐观写消息机制,即使前端在重试完消息后立刻刷新,也能在redis里面读到数据
前端:
1.修了一些bug
2026-03-27 20:39:05 +08:00

148 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"context"
"log"
"reflect"
"strings"
"github.com/LoveLosita/smartflow/backend/dao"
"github.com/LoveLosita/smartflow/backend/model"
"gorm.io/gorm"
)
type GormCachePlugin struct {
cacheDAO *dao.CacheDAO
}
func NewGormCachePlugin(dao *dao.CacheDAO) *GormCachePlugin {
return &GormCachePlugin{
cacheDAO: dao,
}
}
// Name 插件名称
func (p *GormCachePlugin) Name() string {
return "GormCachePlugin"
}
// Initialize 注册 GORM 钩子
func (p *GormCachePlugin) Initialize(db *gorm.DB) error {
// 在增、删、改成功后,统一触发清理逻辑
_ = db.Callback().Create().After("gorm:create").Register("clear_related_cache_after_create", p.afterWrite)
_ = db.Callback().Update().After("gorm:update").Register("clear_related_cache_after_update", p.afterWrite)
_ = db.Callback().Delete().After("gorm:delete").Register("clear_related_cache_after_delete", p.afterWrite)
return nil
}
func (p *GormCachePlugin) afterWrite(db *gorm.DB) {
if db.Error != nil || db.Statement.Schema == nil {
return
}
// 获取 Model 的真实值(剥掉所有指针)
val := reflect.Indirect(reflect.ValueOf(db.Statement.Model))
// 如果是切片,拿切片里元素的类型
if val.Kind() == reflect.Slice {
if val.Len() > 0 {
p.dispatchCacheLogic(val.Index(0).Interface(), db)
}
} else {
p.dispatchCacheLogic(val.Interface(), db)
}
}
// 根据不同的 Model 类型,调用不同的缓存失效逻辑
func (p *GormCachePlugin) dispatchCacheLogic(modelObj interface{}, db *gorm.DB) {
switch m := modelObj.(type) {
case model.Schedule:
// 无论传的是 &s, s, 还是 &[]s剥开后都是 model.Schedule
p.invalidScheduleCache(m.UserID, m.Week)
case model.TaskClass:
p.invalidTaskClassCache(*m.UserID)
case model.Task:
p.invalidTaskCache(m.UserID)
case model.AgentScheduleState:
p.invalidSchedulePlanPreviewCache(m.UserID, m.ConversationID)
case model.ChatHistory:
p.invalidConversationHistoryCache(m.UserID, m.ChatID)
case model.AgentChat:
p.invalidConversationHistoryCache(m.UserID, m.ChatID)
case model.AgentOutboxMessage, model.User:
// 这些模型目前没有定义缓存逻辑,先不处理
default:
// 只有真正没定义的模型才会到这里
log.Printf("[GORM-Cache] No logic defined for model: %T", modelObj)
}
}
func (p *GormCachePlugin) invalidScheduleCache(userID int, week int) {
if userID == 0 || week == 0 {
return
}
// 3. 异步执行,不阻塞主业务事务
go func() {
// 这里调用你的 CacheDAO 删缓存
_ = p.cacheDAO.DeleteUserWeeklyScheduleFromCache(context.Background(), userID, week)
_ = p.cacheDAO.DeleteUserTodayScheduleFromCache(context.Background(), userID) // 同时删当天日程的缓存,确保数据一致
_ = p.cacheDAO.DeleteUserRecentCompletedSchedulesFromCache(context.Background(), userID) // 同时删最近完成日程的缓存,确保数据一致
_ = p.cacheDAO.DeleteUserOngoingScheduleFromCache(context.Background(), userID) // 同时删正在进行日程的缓存,确保数据一致
log.Printf("[GORM-Cache] Invalidated cache for user %d, week %d", userID, week)
}()
}
func (p *GormCachePlugin) invalidTaskClassCache(userID int) {
if userID == 0 {
return
}
go func() {
_ = p.cacheDAO.DeleteTaskClassList(context.Background(), userID)
log.Printf("[GORM-Cache] Invalidated task class list cache for user %d", userID)
}()
}
func (p *GormCachePlugin) invalidTaskCache(userID int) {
if userID == 0 {
return
}
go func() {
_ = p.cacheDAO.DeleteUserTasksFromCache(context.Background(), userID)
log.Printf("[GORM-Cache] Invalidated task list cache for user %d", userID)
}()
}
func (p *GormCachePlugin) invalidSchedulePlanPreviewCache(userID int, conversationID string) {
normalizedConversationID := strings.TrimSpace(conversationID)
if userID == 0 || normalizedConversationID == "" {
return
}
go func() {
// 1. 这里的调用目的:当排程状态快照发生覆盖写入时,主动删除对应会话预览缓存。
// 2. 这样可以避免“Redis 里还是旧预览,但 MySQL 已经是新快照”的短暂口径不一致。
// 3. 失败策略:缓存删除失败只记日志,不影响主事务提交。
if err := p.cacheDAO.DeleteSchedulePlanPreviewFromCache(context.Background(), userID, normalizedConversationID); err != nil {
log.Printf("[GORM-Cache] Failed to invalidate schedule preview cache for user %d conversation %s: %v", userID, normalizedConversationID, err)
return
}
log.Printf("[GORM-Cache] Invalidated schedule preview cache for user %d conversation %s", userID, normalizedConversationID)
}()
}
func (p *GormCachePlugin) invalidConversationHistoryCache(userID int, conversationID string) {
normalizedConversationID := strings.TrimSpace(conversationID)
if userID == 0 || normalizedConversationID == "" {
return
}
go func() {
// 1. 这里的调用目的:当聊天历史写入或重试补种更新后,删除“前端历史视图缓存”。
// 2. 这样下次访问 conversation-history 时会回源 DB并把最新 retry 版本完整回填缓存。
// 3. 注意:这里只删历史视图缓存,不删 Agent 上下文热缓存,避免影响聊天首 token。
if err := p.cacheDAO.DeleteConversationHistoryFromCache(context.Background(), userID, normalizedConversationID); err != nil {
log.Printf("[GORM-Cache] Failed to invalidate conversation history cache for user %d conversation %s: %v", userID, normalizedConversationID, err)
return
}
log.Printf("[GORM-Cache] Invalidated conversation history cache for user %d conversation %s", userID, normalizedConversationID)
}()
}