fix: 修复chatlog会话元数据回写与覆盖问题

- 为消息追加命令补充用户、助手与会话标题元信息

- 持久化后按会话聚合触发createOrTouch并优化空值覆盖策略

- 增加ChatPersistMySqlApplyService单测覆盖会话回写链路
This commit is contained in:
2026-04-07 14:51:11 +08:00
parent 3f128e977a
commit 81125ce55c
5 changed files with 201 additions and 8 deletions

View File

@@ -12,7 +12,11 @@ public class ChatAppendMessageCommand implements Serializable {
private BigInteger deptId;
private BigInteger sessionId;
private BigInteger userId;
private String userAccount;
private BigInteger assistantId;
private String assistantCode;
private String assistantName;
private String sessionTitle;
private BigInteger senderId;
private String senderName;
private String senderRole;
@@ -62,6 +66,14 @@ public class ChatAppendMessageCommand implements Serializable {
this.userId = userId;
}
public String getUserAccount() {
return userAccount;
}
public void setUserAccount(String userAccount) {
this.userAccount = userAccount;
}
public BigInteger getAssistantId() {
return assistantId;
}
@@ -70,6 +82,30 @@ public class ChatAppendMessageCommand implements Serializable {
this.assistantId = assistantId;
}
public String getAssistantCode() {
return assistantCode;
}
public void setAssistantCode(String assistantCode) {
this.assistantCode = assistantCode;
}
public String getAssistantName() {
return assistantName;
}
public void setAssistantName(String assistantName) {
this.assistantName = assistantName;
}
public String getSessionTitle() {
return sessionTitle;
}
public void setSessionTitle(String sessionTitle) {
this.sessionTitle = sessionTitle;
}
public BigInteger getSenderId() {
return senderId;
}

View File

@@ -41,19 +41,22 @@ public class MySqlChatSessionRepository {
String sql = "INSERT INTO `" + table + "` " +
"(id, tenant_id, dept_id, user_id, user_account, assistant_id, assistant_code, assistant_name, title, last_message_preview, message_count, access_at, created, created_by, modified, modified_by, is_deleted) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, '', 0, ?, ?, ?, ?, ?, 0) " +
"ON DUPLICATE KEY UPDATE user_account=VALUES(user_account), assistant_id=VALUES(assistant_id), assistant_code=VALUES(assistant_code), " +
"assistant_name=VALUES(assistant_name), title=VALUES(title), access_at=VALUES(access_at), modified=VALUES(modified), modified_by=VALUES(modified_by), is_deleted=0";
"ON DUPLICATE KEY UPDATE user_account=COALESCE(NULLIF(VALUES(user_account), ''), user_account), " +
"assistant_id=VALUES(assistant_id), assistant_code=COALESCE(NULLIF(VALUES(assistant_code), ''), assistant_code), " +
"assistant_name=COALESCE(NULLIF(VALUES(assistant_name), ''), assistant_name), " +
"title=COALESCE(NULLIF(VALUES(title), ''), title), " +
"access_at=VALUES(access_at), modified=VALUES(modified), modified_by=VALUES(modified_by), is_deleted=0";
jdbcTemplate.batchUpdate(sql, commands, commands.size(), (ps, command) -> {
Timestamp operateAt = timestamp(command.getOperateAt());
ps.setObject(1, command.getSessionId());
ps.setObject(2, command.getTenantId());
ps.setObject(3, command.getDeptId());
ps.setObject(4, command.getUserId());
ps.setString(5, command.getUserAccount());
ps.setString(5, safeString(command.getUserAccount()));
ps.setObject(6, command.getAssistantId());
ps.setString(7, command.getAssistantCode());
ps.setString(8, command.getAssistantName());
ps.setString(9, command.getTitle());
ps.setString(7, safeString(command.getAssistantCode()));
ps.setString(8, safeString(command.getAssistantName()));
ps.setString(9, safeString(command.getTitle()));
ps.setTimestamp(10, operateAt);
ps.setTimestamp(11, operateAt);
ps.setObject(12, command.getOperatorId());
@@ -272,4 +275,8 @@ public class MySqlChatSessionRepository {
private Timestamp timestamp(Date value) {
return new Timestamp((value == null ? new Date() : value).getTime());
}
private String safeString(String value) {
return value == null ? "" : value;
}
}

View File

@@ -106,6 +106,7 @@ public class ChatPersistMySqlApplyService {
insertedCommands = logRepository.appendMessages(appendCommands);
}
if (!insertedCommands.isEmpty()) {
sessionRepository.createOrTouchBatch(buildSessionUpserts(insertedCommands));
summaryCommands.clear();
for (ChatAppendMessageCommand insertedCommand : insertedCommands) {
accumulateSummary(summaryCommands, insertedCommand);
@@ -139,9 +140,74 @@ public class ChatPersistMySqlApplyService {
}
}
List<ChatSessionUpsertCommand> buildSessionUpserts(List<ChatAppendMessageCommand> commands) {
if (commands == null || commands.isEmpty()) {
return List.of();
}
Map<BigInteger, ChatSessionUpsertCommand> upserts = new LinkedHashMap<>();
for (ChatAppendMessageCommand command : commands) {
if (command == null || command.getSessionId() == null) {
continue;
}
ChatSessionUpsertCommand upsert = upserts.computeIfAbsent(command.getSessionId(), key -> {
ChatSessionUpsertCommand created = new ChatSessionUpsertCommand();
created.setSessionId(command.getSessionId());
created.setTenantId(command.getTenantId());
created.setDeptId(command.getDeptId());
created.setUserId(command.getUserId());
created.setAssistantId(command.getAssistantId());
created.setOperateAt(null);
return created;
});
if (upsert.getTenantId() == null) {
upsert.setTenantId(command.getTenantId());
}
if (upsert.getDeptId() == null) {
upsert.setDeptId(command.getDeptId());
}
if (upsert.getUserId() == null) {
upsert.setUserId(command.getUserId());
}
if (upsert.getAssistantId() == null) {
upsert.setAssistantId(command.getAssistantId());
}
if (isBlank(upsert.getUserAccount()) && !isBlank(command.getUserAccount())) {
upsert.setUserAccount(command.getUserAccount().trim());
}
if (isBlank(upsert.getAssistantCode()) && !isBlank(command.getAssistantCode())) {
upsert.setAssistantCode(command.getAssistantCode().trim());
}
if (isBlank(upsert.getAssistantName()) && !isBlank(command.getAssistantName())) {
upsert.setAssistantName(command.getAssistantName().trim());
}
if (isBlank(upsert.getTitle()) && !isBlank(command.getSessionTitle())) {
upsert.setTitle(command.getSessionTitle().trim());
}
Date operateAt = defaultDate(command.getCreated());
if (upsert.getOperateAt() == null || !operateAt.before(upsert.getOperateAt())) {
upsert.setOperateAt(operateAt);
upsert.setOperatorId(command.getCreatedBy());
}
}
for (ChatSessionUpsertCommand upsert : upserts.values()) {
if (isBlank(upsert.getUserAccount())) {
upsert.setUserAccount("");
}
if (isBlank(upsert.getTitle())) {
upsert.setTitle("会话-" + upsert.getSessionId());
}
if (upsert.getOperateAt() == null) {
upsert.setOperateAt(new Date());
}
if (upsert.getOperatorId() == null) {
upsert.setOperatorId(upsert.getUserId());
}
}
return new ArrayList<>(upserts.values());
}
private YearMonth resolveMonth(Date createdAt) {
Date created = createdAt == null ? new Date() : createdAt;
return YearMonth.from(created.toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
return YearMonth.from(defaultDate(createdAt).toInstant().atZone(ZoneId.systemDefault()).toLocalDate());
}
private String trimPreview(String text) {
@@ -150,4 +216,12 @@ public class ChatPersistMySqlApplyService {
}
return text.length() <= 200 ? text : text.substring(0, 200);
}
private boolean isBlank(String value) {
return value == null || value.isBlank();
}
private Date defaultDate(Date value) {
return value == null ? new Date() : value;
}
}

View File

@@ -105,7 +105,11 @@ public class ChatlogRuntimeListener implements ChatRuntimeListener {
command.setDeptId(defaultNumber(context.getDeptId()));
command.setSessionId(context.getSessionId());
command.setUserId(defaultNumber(context.getUserId()));
command.setUserAccount(context.getUserAccount());
command.setAssistantId(defaultNumber(context.getAssistantId()));
command.setAssistantCode(context.getAssistantCode());
command.setAssistantName(context.getAssistantName());
command.setSessionTitle(context.getSessionTitle());
command.setSenderId(defaultNumber(message.getSenderId()));
command.setSenderName(message.getSenderName());
command.setSenderRole(message.getRole());

View File

@@ -0,0 +1,72 @@
package tech.easyflow.chatlog.service;
import org.junit.Assert;
import org.junit.Test;
import tech.easyflow.chatlog.domain.command.ChatAppendMessageCommand;
import tech.easyflow.chatlog.domain.command.ChatSessionUpsertCommand;
import java.math.BigInteger;
import java.util.Date;
import java.util.List;
public class ChatPersistMySqlApplyServiceTest {
private final ChatPersistMySqlApplyService service =
new ChatPersistMySqlApplyService(null, null, null, null);
@Test
public void shouldBuildMissingSessionUpsertFromMessageMetadata() {
ChatAppendMessageCommand first = new ChatAppendMessageCommand();
first.setSessionId(BigInteger.valueOf(101));
first.setTenantId(BigInteger.ONE);
first.setDeptId(BigInteger.TEN);
first.setUserId(BigInteger.valueOf(12));
first.setUserAccount("admin");
first.setAssistantId(BigInteger.valueOf(99));
first.setAssistantCode("test-bot");
first.setAssistantName("测试助手");
first.setSessionTitle("你是谁");
first.setCreatedBy(BigInteger.valueOf(12));
first.setCreated(new Date(1_000L));
ChatAppendMessageCommand second = new ChatAppendMessageCommand();
second.setSessionId(BigInteger.valueOf(101));
second.setTenantId(BigInteger.ONE);
second.setDeptId(BigInteger.TEN);
second.setUserId(BigInteger.valueOf(12));
second.setAssistantId(BigInteger.valueOf(99));
second.setCreatedBy(BigInteger.valueOf(12));
second.setCreated(new Date(2_000L));
List<ChatSessionUpsertCommand> upserts = service.buildSessionUpserts(List.of(first, second));
Assert.assertEquals(1, upserts.size());
ChatSessionUpsertCommand upsert = upserts.get(0);
Assert.assertEquals(BigInteger.valueOf(101), upsert.getSessionId());
Assert.assertEquals("admin", upsert.getUserAccount());
Assert.assertEquals("test-bot", upsert.getAssistantCode());
Assert.assertEquals("测试助手", upsert.getAssistantName());
Assert.assertEquals("你是谁", upsert.getTitle());
Assert.assertEquals(new Date(2_000L), upsert.getOperateAt());
}
@Test
public void shouldFallbackTitleWhenMessageMetadataMissing() {
ChatAppendMessageCommand command = new ChatAppendMessageCommand();
command.setSessionId(BigInteger.valueOf(202));
command.setTenantId(BigInteger.ONE);
command.setDeptId(BigInteger.ONE);
command.setUserId(BigInteger.valueOf(7));
command.setAssistantId(BigInteger.valueOf(8));
command.setCreatedBy(BigInteger.valueOf(7));
command.setCreated(new Date(3_000L));
List<ChatSessionUpsertCommand> upserts = service.buildSessionUpserts(List.of(command));
Assert.assertEquals(1, upserts.size());
ChatSessionUpsertCommand upsert = upserts.get(0);
Assert.assertEquals("", upsert.getUserAccount());
Assert.assertEquals("会话-202", upsert.getTitle());
Assert.assertEquals(BigInteger.valueOf(7), upsert.getOperatorId());
}
}