Version: 0.6.0.dev.260315
新增对话落库对话计数原子自增机制,确保聊天记录和消息计数二者同时落库,保证一致性
This commit is contained in:
@@ -3,7 +3,9 @@ package dao
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/LoveLosita/smartflow/backend/model"
|
"github.com/LoveLosita/smartflow/backend/model"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
@@ -18,16 +20,41 @@ func NewAgentDAO(db *gorm.DB) *AgentDAO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AgentDAO) SaveChatHistory(ctx context.Context, userID int, conversationID string, role, message string) error {
|
func (a *AgentDAO) SaveChatHistory(ctx context.Context, userID int, conversationID string, role, message string) error {
|
||||||
|
// 1. 同步落库路径也要保证“消息写入”和“会话计数更新”原子一致。
|
||||||
|
// 因此这里使用事务,避免出现“有消息但 message_count 没加”或反过来的不一致状态。
|
||||||
|
return a.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
// 1.1 先写 chat_histories。
|
||||||
userChat := model.ChatHistory{
|
userChat := model.ChatHistory{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
MessageContent: &message,
|
MessageContent: &message,
|
||||||
Role: &role,
|
Role: &role,
|
||||||
ChatID: conversationID,
|
ChatID: conversationID,
|
||||||
}
|
}
|
||||||
if err := a.db.WithContext(ctx).Create(&userChat).Error; err != nil {
|
if err := tx.Create(&userChat).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 1.2 再原子更新 agent_chats 的统计字段:
|
||||||
|
// - message_count: +1
|
||||||
|
// - last_message_at: 当前时间
|
||||||
|
// 这样 message_count 语义就稳定等于“已成功落库的消息条数”。
|
||||||
|
now := time.Now()
|
||||||
|
updates := map[string]interface{}{
|
||||||
|
"message_count": gorm.Expr("message_count + ?", 1),
|
||||||
|
"last_message_at": &now,
|
||||||
|
}
|
||||||
|
result := tx.Model(&model.AgentChat{}).
|
||||||
|
Where("user_id = ? AND chat_id = ?", userID, conversationID).
|
||||||
|
Updates(updates)
|
||||||
|
if result.Error != nil {
|
||||||
|
return result.Error
|
||||||
|
}
|
||||||
|
if result.RowsAffected == 0 {
|
||||||
|
// 会话不存在视为数据不一致,回滚事务,防止产生“孤儿历史记录”。
|
||||||
|
return fmt.Errorf("conversation not found when updating stats: user_id=%d chat_id=%s", userID, conversationID)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AgentDAO) CreateNewChat(userID int, chatID string) (int64, error) {
|
func (a *AgentDAO) CreateNewChat(userID int, chatID string) (int64, error) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/LoveLosita/smartflow/backend/model"
|
"github.com/LoveLosita/smartflow/backend/model"
|
||||||
@@ -224,9 +225,28 @@ func (d *Repository) PersistChatHistoryAndMarkConsumed(ctx context.Context, outb
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 业务写入成功后,把 outbox 推进到 consumed 最终态。
|
// 3. 同一事务内原子更新会话统计信息:
|
||||||
// 并清理错误与重试字段,表示该消息生命周期结束。
|
// - message_count + 1
|
||||||
|
// - last_message_at = now
|
||||||
|
// 这样可以保证 message_count 与 chat_histories 的真实落库条数一致。
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
chatUpdates := map[string]interface{}{
|
||||||
|
"message_count": gorm.Expr("message_count + ?", 1),
|
||||||
|
"last_message_at": &now,
|
||||||
|
}
|
||||||
|
chatResult := tx.Model(&model.AgentChat{}).
|
||||||
|
Where("user_id = ? AND chat_id = ?", payload.UserID, payload.ConversationID).
|
||||||
|
Updates(chatUpdates)
|
||||||
|
if chatResult.Error != nil {
|
||||||
|
return chatResult.Error
|
||||||
|
}
|
||||||
|
if chatResult.RowsAffected == 0 {
|
||||||
|
// 会话不存在时回滚,让 outbox 继续重试/告警,而不是吞掉不一致。
|
||||||
|
return fmt.Errorf("conversation not found when updating stats: user_id=%d chat_id=%s", payload.UserID, payload.ConversationID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 业务写入成功后,把 outbox 推进到 consumed 最终态。
|
||||||
|
// 并清理错误与重试字段,表示该消息生命周期结束。
|
||||||
updates := map[string]interface{}{
|
updates := map[string]interface{}{
|
||||||
"status": model.OutboxStatusConsumed,
|
"status": model.OutboxStatusConsumed,
|
||||||
"consumed_at": &now,
|
"consumed_at": &now,
|
||||||
|
|||||||
Reference in New Issue
Block a user