From f8d53fb3b5ece02828f3b41a03a3f264ccd4ddee Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Sat, 9 May 2026 12:25:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20mp=20=E6=B6=88=E6=81=AF=E5=B7=B2?= =?UTF-8?q?=E8=AF=BB/=E6=9C=AA=E8=AF=BB=204=20=E9=A1=B9=20bug=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20(PR=20#1912=20/=20Issue=20#1911)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- .../09_fix_mp_message_read_status_bugs.md | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 changelogs/2026-05/09_fix_mp_message_read_status_bugs.md diff --git a/changelogs/2026-05/09_fix_mp_message_read_status_bugs.md b/changelogs/2026-05/09_fix_mp_message_read_status_bugs.md new file mode 100644 index 0000000..928e10b --- /dev/null +++ b/changelogs/2026-05/09_fix_mp_message_read_status_bugs.md @@ -0,0 +1,93 @@ +# mp 消息已读/未读 4 项 bug 修复(性能 + 正确性) + +> **服务**: hl-user-service(8081) +> **PR**: #1912 +> **Issue**: #1911 +> **日期**: 2026-05-09 +> **影响范围**: 小程序消息中心 / badge 未读数 / 消息列表 + +--- + +## ⚠️ 关键变化(无新增字段,**纯修复 + 性能优化**) + +1. **B1** 单条标已读接口真正生效(原是空实现) +2. **B2** 标已读后 badge 红点秒级刷新(原 30s 缓存不刷) +3. **B3** 消息列表接口零阻塞(原同步 UPDATE 主线程,慢) +4. **B4** 加联合索引加速 UPDATE/SELECT(原全表扫) + +前端**无需改造**。 + +--- + +## 一、变更接口行为 + +| 接口 | 路径 | 行为变更 | +|------|------|---------| +| 单条标已读 | `PUT /mp/message/{id}/read` | **真正更新 DB**(原空实现);标已读后 badge 立即刷新 | +| 全部标已读 | `PUT /mp/message/read-all` | UPDATE 后立即失效缓存(原 30s 后才刷) | +| 分类标已读 | `PUT /mp/message/read-all?category=xxx` | 同上 | +| 消息列表 | `GET /mp/message/list` | **响应不再阻塞 UPDATE**:主线程返回时内存里已 `isRead=true`,DB 异步落 | +| 消息摘要 | `GET /mp/message/summary` | 缓存失效更准(deps `table:user_message` 联动) | + +## 二、Bug 详情 + +### B1 单条标已读真实现 + +**修前**:`InternalMpMessageController.markRead(messageId)` 是空方法,只返 `success`,DB 不更新。 +**修后**:调 `UserMessageService.markRead(messageId, userId)` → `UPDATE user_message SET is_read=1 WHERE id=? AND user_id=? AND is_read=0`。 +- WHERE 限定 `user_id` 防越权(原本相信前端传入) +- 已读消息再标 → rows=0(幂等) + +### B2 缓存失效 + +**修前**:`markAllRead/markCategoryRead` UPDATE 后没通知 `/mp/message/summary` 缓存(`@MpCache(ttl=30, deps={"table:user_message"})`),badge 红点 30 秒内显示旧数。 +**修后**:三处方法都加 `afterCommit` 调 `depsManager.invalidateSource("table:user_message")`,事务提交后立即失效缓存。 + +### B3 listMessages 异步标已读 + +**修前**:`UserMessageService.listMessages` 内部同步 UPDATE 当前分类为已读,**主线程阻塞**。 +**修后**: +- 引入独立 bean `UserMessageReadAsyncTask`(绕 `@Async` self-invocation) +- `@Async("messageReadExecutor")` 独立线程池(core=3, max=8, queue=100) +- 主线程内存里 `setIsRead(1)` 立即返 RespVO,DB 异步落 + +**前端体验**:消息列表打开**毫秒级返回**(原阻塞数百毫秒),已读状态展示无滞后。 + +### B4 联合索引 + +**修前**:`user_message` 表只有 `idx_user_category(user_id, category_code)` + `idx_create_time(create_time)`,WHERE `user_id=? AND is_read=0` 走全表扫(尤其在用户消息多时)。 +**修后**:Flyway V20260509_001 加 `idx_user_read(user_id, is_read)` 联合索引,加速: +- 标已读 UPDATE +- 未读数 SELECT(`/mp/message/summary`) +- 全分类标已读 UPDATE + +## 三、向后兼容 + +- API 字段无变化 +- 前端不需要任何改造 +- 单条 markRead 接口路径不变,只是真生效了 +- 老消息(`is_read=NULL`)由 VO 转换层 `isRead != null && isRead == 1` 防护(已存在) + +## 四、测试覆盖 + +- `UserMessageServiceTest +13`(markRead valid/wrong-user/already-read,markAllRead/markCategoryRead 缓存失效,listMessages async + 内存 isRead) +- `InternalMpMessageControllerTest +1`(markRead 调 service) +- `UserMessageReadAsyncTaskTest 3`(异步执行 + 异常 swallow) +- 共 28 全绿 + +测试服已部署回归: +- ✅ user-service 双实例(8081+8181)新进程启动 +- ✅ Flyway V20260509_001 落库,`idx_user_read` 联合索引就位 + +## 五、部署注意(运维) + +测试服首次部署 V20260509_001 时遇到 `Duplicate key name 'idx_user_read'`(疑似历史手动加过同名索引未记录),Flyway 标 success=0,服务启动失败。**已通过 SQL `UPDATE flyway_schema_history SET success=1 WHERE version='20260509.001'` 修复**。 + +**正式部署前**:运维确认 prod `user_message` 表是否已存在 `idx_user_read` 索引,如有需提前 SQL 设 history success=1 或 DROP 旧索引。 + +## 六、@mmg 关注点 + +无前端改造。本期纯后端修复,前端体验改善: +1. badge 红点秒级刷新(原延迟 30s) +2. 消息列表打开零阻塞 +3. 单条标已读真生效(原前端可能调了无效)