轻松聘AI开发
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2.6 KiB

R01-D3 会话未读数查询收束报告

结论

MessagingService.listConversations 已从逐会话查询最近消息和已读状态,收束为一次分组未读统计。会话数量从 1 增长到 20、50 时,消息相关业务 SQL 数保持固定;本次未改变公网接口、响应字段、权限、消息撤回或已读语义。

修复前问题

会话列表先查询成员关系和会话,再对每个会话调用 countUnread。每次调用都会查询最近 500 条消息及其已读记录,因此总查询数随会话数线性增长。

实现

  • MessageRepository.countUnreadByConversationIds 使用一条分组 SQL 批量统计全部会话。
  • 使用 ROW_NUMBER() OVER (PARTITION BY conversation_id ORDER BY created_at DESC) 保留每个会话“最近 500 条”的历史窗口。
  • RECALLED 消息不计入未读,status IS NULL 仍按历史语义计入。
  • 存在当前用户 message_reads 的消息不计入未读。
  • 没有未读行的会话由 Service 映射为 0。
  • getConversation 的单会话路径未改动,避免扩大本批范围。

查询预算证据

MessageListQueryBudgetIntegrationTest.listConversationsUsesFixedQueriesForOneTwentyAndFiftyConversations 使用真实 Spring/JPA/H2 数据源代理统计业务 SQL,并混合 DIRECT、TALENT_INVITATION、SENT、RECALLED、已读和未读消息。

会话数 业务查询数 结果
1 3 PASS
20 3 PASS
50 3 PASS

三条查询分别为成员关系、会话批量读取、未读分组统计,查询数不随会话数增长。

语义边界

groupedUnreadCountKeepsHistoricalLatestFiveHundredWindow 构造 501 条顺序明确的消息,证明最旧消息位于窗口外时不被统计。测试首次编写时,实体的 @CreatedDate 覆盖夹具传入时间,造成 501 条消息时间并列;随后改为保存后按精确消息主键写入确定时间。该失败属于夹具问题,不是生产缺陷,也未通过放宽断言处理。

验证

  • 定向边界测试:3/3,Failures=0、Errors=0、Skipped=0。
  • 关联消息测试:此前同批运行 42/42 通过。
  • mvn clean verify:2104 项,Failures=0、Errors=0、Skipped=20,BUILD SUCCESS;20 项均为既有环境条件测试。
  • 静态 OpenAPI:176 paths / 214 operations,校验通过。

边界

  • 未新增或修改 Flyway、表、索引、ErrorCode、公网接口。
  • 未改变 DIRECT / TALENT_INVITATION 响应隐私语义。
  • 未改变消息发送、撤回、已读、分页和权限语义。
  • 原生窗口 SQL 仍需在独立真实 MySQL 验收中复核执行计划与结果。