From cfe4ef04d768348e7258f237f2b9a7b8e9eaad80 Mon Sep 17 00:00:00 2001 From: kevin Date: Sat, 20 Jun 2026 12:32:05 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=B6=88=E6=81=AF=E9=87=8D?= =?UTF-8?q?=E5=A4=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/store/db.go | 4 ++++ internal/store/llm_store.go | 19 +++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/internal/store/db.go b/internal/store/db.go index 75047f2..65ce66f 100644 --- a/internal/store/db.go +++ b/internal/store/db.go @@ -384,6 +384,10 @@ const ( LLMMessageStatusError = "error" ) +// llmQueueProcessedDedupWindow 是 processed 消息软删除后仍参与去重的时间窗口。 +// 处理完即软删除,但记录会保留至此窗口结束,防止网络延迟/重投导致同一包在刚处理完后又被重复入队。 +const llmQueueProcessedDedupWindow = 15 * time.Second + type NodeInfoRecord struct { NodeID string `gorm:"column:node_id;primaryKey;not null"` NodeNum int64 `gorm:"column:node_num;not null;index"` diff --git a/internal/store/llm_store.go b/internal/store/llm_store.go index 9fde4a9..653ad83 100644 --- a/internal/store/llm_store.go +++ b/internal/store/llm_store.go @@ -321,21 +321,28 @@ func (s *Store) EnqueueLLMMessage(input LLMMessageQueueInput) (*LLMMessageQueueR // 检查是否存在重复消息 // packet_id > 0: 用 bot_id + packet_id 去重(频道消息) // packet_id = 0: 用 bot_id + from_node_id + text 去重(私聊消息,可能没有 packet_id) - // 只排除 pending/processing 状态的消息,允许 error 状态的消息重新入队 + // 命中条件二选一: + // 1. 仍存在 pending/processing 状态的记录(尚未处理完) + // 2. 已软删除(processed)但未超过 dedup 窗口——防止网络延迟/重投导致同一包在刚处理完后又被重复入队 + // error 状态允许重新入队;processed 软删除超过窗口后也允许重新入队。 + // 阈值在 Go 侧算好作为参数传入,避免依赖 SQLite datetime('now') 的时区行为,与其它 time 字段读写保持一致。 + processedCutoff := time.Now().Add(-llmQueueProcessedDedupWindow) + dupCondition := "(deleted_at IS NULL AND status IN (?, ?)) OR (deleted_at IS NOT NULL AND deleted_at > ?)" + var existing LLMMessageQueueRecord if input.PacketID > 0 { // 频道消息:用 bot_id + packet_id 去重 - err = s.db.Where("bot_id = ? AND packet_id = ? AND deleted_at IS NULL AND status IN (?, ?)", - input.BotID, input.PacketID, LLMMessageStatusPending, LLMMessageStatusProcessing). + err = s.db.Where("bot_id = ? AND packet_id = ? AND "+dupCondition, + input.BotID, input.PacketID, LLMMessageStatusPending, LLMMessageStatusProcessing, processedCutoff). Take(&existing).Error } else { // 私聊消息:用 bot_id + from_node_id + text 去重(避免同一人连续发相同内容被拒绝) - err = s.db.Where("bot_id = ? AND from_node_id = ? AND text = ? AND deleted_at IS NULL AND status IN (?, ?)", - input.BotID, input.FromNodeID, input.Text, LLMMessageStatusPending, LLMMessageStatusProcessing). + err = s.db.Where("bot_id = ? AND from_node_id = ? AND text = ? AND "+dupCondition, + input.BotID, input.FromNodeID, input.Text, LLMMessageStatusPending, LLMMessageStatusProcessing, processedCutoff). Take(&existing).Error } if err == nil { - // 存在正在处理或待处理的相同消息,直接返回 + // 存在命中去重的记录(处理中 / 刚处理完未过窗口),直接返回 return &existing, nil } if !errors.Is(err, gorm.ErrRecordNotFound) {