feat: 统一列表模糊搜索行为

- 统一管理端和用户中心搜索参数及多字段包含匹配

- 修复聊天搜索竞态并优化部门重名路径展示

- 补充部门展开、模型空白词和聊天查询回归测试
This commit is contained in:
2026-08-13 22:29:59 +08:00
parent 765006747a
commit 64a85c6a5b
83 changed files with 1134 additions and 208 deletions

View File

@@ -6,6 +6,7 @@ public class ChatPageQuery implements Serializable {
private long pageNumber = 1;
private long pageSize = 20;
private String keyword;
public long getPageNumber() {
return pageNumber;
@@ -26,4 +27,22 @@ public class ChatPageQuery implements Serializable {
public long getOffset() {
return (pageNumber - 1) * pageSize;
}
/**
* 获取会话搜索关键字。
*
* @return 会话标题、最近消息或助手名称关键字
*/
public String getKeyword() {
return keyword;
}
/**
* 设置会话搜索关键字。
*
* @param keyword 会话标题、最近消息或助手名称关键字
*/
public void setKeyword(String keyword) {
this.keyword = keyword == null ? null : keyword.trim();
}
}

View File

@@ -20,6 +20,7 @@ import tech.easyflow.common.analyticaldb.core.AnalyticalDBOperations;
import tech.easyflow.common.analyticaldb.page.AnalyticalDBPageRequest;
import tech.easyflow.common.analyticaldb.page.AnalyticalDBPageResult;
import tech.easyflow.common.analyticaldb.support.AnalyticalDBHealthSupport;
import tech.easyflow.common.util.SearchKeywordUtil;
import java.math.BigInteger;
import java.sql.Timestamp;
@@ -746,7 +747,7 @@ public class ChatAnalyticalDBRepository {
}
if (query.getUserAccount() != null && !query.getUserAccount().isBlank()) {
sql.append(" AND user_account LIKE ?");
args.add("%" + query.getUserAccount().trim() + "%");
args.add(SearchKeywordUtil.literalContainsPattern(query.getUserAccount()));
}
if (query.getStartTime() != null) {
String startTime = formatDateTime(query.getStartTime());

View File

@@ -10,6 +10,7 @@ import tech.easyflow.chatlog.domain.event.payload.ChatSessionDeletePayload;
import tech.easyflow.chatlog.domain.event.payload.ChatSessionRenamePayload;
import tech.easyflow.chatlog.domain.query.ChatPageQuery;
import tech.easyflow.chatlog.support.ChatTableRouter;
import tech.easyflow.common.util.SearchKeywordUtil;
import java.math.BigInteger;
import java.sql.ResultSet;
@@ -122,6 +123,7 @@ public class MySqlChatSessionRepository {
sql.append(" AND assistant_code=?");
params.add(assistantCode);
}
appendKeywordCondition(sql, params, query);
sql.append(" ORDER BY last_message_at DESC, id DESC LIMIT ? OFFSET ?");
params.add(query.getPageSize());
params.add(query.getOffset());
@@ -133,6 +135,20 @@ public class MySqlChatSessionRepository {
}
public long countSessions(BigInteger userId, BigInteger assistantId, String assistantCode) {
return countSessions(userId, assistantId, assistantCode, null);
}
/**
* 按用户、助手和关键字统计会话数量。
*
* @param userId 用户 ID
* @param assistantId 助手 ID可为空
* @param assistantCode 助手编码,可为空
* @param query 分页与关键字条件,可为空
* @return 符合条件的会话数量
*/
public long countSessions(BigInteger userId, BigInteger assistantId,
String assistantCode, ChatPageQuery query) {
String table = tableRouter.resolveSessionTable();
List<Object> params = new ArrayList<>();
StringBuilder sql = new StringBuilder("SELECT COUNT(1) FROM `").append(table)
@@ -146,10 +162,31 @@ public class MySqlChatSessionRepository {
sql.append(" AND assistant_code=?");
params.add(assistantCode);
}
appendKeywordCondition(sql, params, query);
Long count = jdbcTemplate.queryForObject(sql.toString(), Long.class, params.toArray());
return count == null ? 0L : count;
}
/**
* 向会话 SQL 追加标题、最近消息和助手名称的普通文本包含匹配。
*
* @param sql SQL 构造器
* @param params SQL 参数
* @param query 查询条件
*/
private void appendKeywordCondition(StringBuilder sql, List<Object> params, ChatPageQuery query) {
String keyword = query == null ? null : query.getKeyword();
if (keyword == null || keyword.isBlank()) {
return;
}
String pattern = SearchKeywordUtil.literalContainsPattern(keyword);
sql.append(" AND (title LIKE ? ESCAPE '\\\\' OR last_message_preview LIKE ? ESCAPE '\\\\'"
+ " OR assistant_name LIKE ? ESCAPE '\\\\')");
params.add(pattern);
params.add(pattern);
params.add(pattern);
}
public ChatSessionSummary findBySessionIdAndUserId(BigInteger sessionId, BigInteger userId) {
String table = tableRouter.resolveSessionTable();
List<ChatSessionSummary> list = jdbcTemplate.query(

View File

@@ -64,7 +64,7 @@ public class ChatSessionQueryServiceImpl implements ChatSessionQueryService {
page.setPageNumber(query.getPageNumber());
page.setPageSize(query.getPageSize());
page.setTotal(sessionRepository.countSessions(userId, assistantId, assistantCode));
page.setTotal(sessionRepository.countSessions(userId, assistantId, assistantCode, query));
page.setRecords(listSessions(userId, assistantId, assistantCode, query));
return page;
}

View File

@@ -69,6 +69,27 @@ public class ChatSessionQueryServiceImplTest {
Assert.assertEquals("AGENT", sessionRepository.capturedCountAssistantCode);
}
/**
* 会话关键词必须同时下推到列表和计数查询,保证分页总数一致。
*/
@Test
public void pageSessionsShouldPassKeywordToListAndCountQueries() {
FakeSessionRepository sessionRepository = new FakeSessionRepository();
ChatSessionQueryServiceImpl service = new ChatSessionQueryServiceImpl(
sessionRepository,
new FakeLogRepository(),
new FakeTableManager(List.of()),
new FakeHotStateService()
);
ChatPageQuery query = new ChatPageQuery();
query.setKeyword("最近消息");
service.pageSessions(BigInteger.valueOf(7), null, query);
Assert.assertSame(query, sessionRepository.capturedListQuery);
Assert.assertSame(query, sessionRepository.capturedCountQuery);
}
/**
* 工作台消息分页必须走 MySQL 热表主线查询,并保持分页参数语义。
*/
@@ -147,6 +168,8 @@ public class ChatSessionQueryServiceImplTest {
private int listSessionsCalls;
private String capturedListAssistantCode;
private String capturedCountAssistantCode;
private ChatPageQuery capturedListQuery;
private ChatPageQuery capturedCountQuery;
private ChatSessionSummary summary;
private List<ChatSessionSummary> sessions = new ArrayList<>();
@@ -163,6 +186,7 @@ public class ChatSessionQueryServiceImplTest {
public List<ChatSessionSummary> listSessions(BigInteger userId, BigInteger assistantId, String assistantCode, ChatPageQuery query) {
listSessionsCalls += 1;
capturedListAssistantCode = assistantCode;
capturedListQuery = query;
return sessions;
}
@@ -173,8 +197,15 @@ public class ChatSessionQueryServiceImplTest {
@Override
public long countSessions(BigInteger userId, BigInteger assistantId, String assistantCode) {
return countSessions(userId, assistantId, assistantCode, null);
}
@Override
public long countSessions(BigInteger userId, BigInteger assistantId,
String assistantCode, ChatPageQuery query) {
countSessionsCalls += 1;
capturedCountAssistantCode = assistantCode;
capturedCountQuery = query;
return count;
}