22 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 8182 | 通知中心: HOUSE 内部员工站内信 link 由小程序路径订正为管理后台路由,并明确 bizId 不是路由键 | admin | wx(GIT) | 修复 | deployed | not_required | verified | mmg | 191fdc3cd65d789cce3565f948f2333d3eb58eeb | v2.1 | 2026-09-22 | 本条不改变 AdminMessageRespVO / AdminMessagePageReqVO 的字段结构,只修正 notification_event_config.inapp_link_template 中三个内部员工事件码的路由域(小程序 /pages/* → 管理后台 /housekeeper/*),并在 HouseNotificationPublisher 类注释上固化「(bizType, bizId) 不是路由键」这一约束。gateway_status=not_required:零新增路由,受影响的 GET /admin/message/list 与 GET /admin/message/{id} 是既有端点。frontend_status=pending:本条与 22_8155 不同,它需要前端改动——hl-ui 的 jumpToBiz(origin/v2.1 590c1155,src/views/notification/MyMessages/index.vue:268-286)目前只读 bizId/bizModule/bizType/peerRole,对 HOUSE 核房通知会落进 getOrderReadableRoute(bizId) 分支,把 hotelId 当 orderId 打开订单详情页;正确做法是改读 link 字段(该字段早已存在于 AdminMessageRespVO:31,不是本次新增)。backend_status=deployed:hl-user-service 与 hl-order-service-v3 均已滚动到测试服 776c0023d,Flyway 20260922.210 success=1(installed_on 2026-09-22 17:18:51),并在自建酒店夹具上走完「核房提交 → 站内信产出 → GET /admin/message/list 读回」全链路,实测读数见正文第八节。⚠️ admin_message 是快照表:link 在 createMessage 落库那一刻按当时的模板一次性渲染写入行内,不是按需渲染;因此本修复只影响生效后新产生的消息,测试库里此前已产出的 213 条 HOUSE_INVENTORY_CHECKED 历史消息 link 仍是旧的小程序路径,不做批量回刷——order-v3 / fleet 尚未上生产,不涉及生产数据。 前端已交付(191fdc3c):jumpToBiz 改 link 优先直喂 router.push,/pages/ 旧小程序域与空 query 占位(?inquiryId= 剥 query 降级列表页)兜底;HOUSE 非聊天行无可用 link 不渲染入口不反推 bizId;ORDER(#8155)/HOUSE 聊天行既有推导不回退;判定抽 jumpBiz.js 纯函数+8 例定向 spec。 | 2026-09-22 | dev-v3 |
通知中心: HOUSE 内部员工站内信 link 由小程序路径订正为管理后台路由,并明确 bizId 不是路由键
存放目录: 二期(order-v3)→
changelogs-v2/2026-09/服务: hl-user-service(通知分发 / 站内信收件箱 / 配置表)+ hl-order-service-v3(通知发布方,本次仅注释) PR: #8189(迁移 + 注释 + 迁移测试)、#8190(补注释漏枚举) Issue: #8182 日期: 2026-09-22 影响范围: 管理后台站内信中心里 HOUSE 类通知的「跳转」行为
⚠️ 关键变化
-
三个内部员工事件码的
link换了路由域:HOUSE_INVENTORY_CHECKED/HOUSE_INQUIRY_TIMEOUT/HOUSE_INQUIRY_ESCALATED的link由小程序路径/pages/house/...改为管理后台路由/housekeeper/calendar与/housekeeper/todos。字段名、类型、位置都没变,变的是值。 -
「跳转」必须改读
link,不能再从(bizType, bizId)反推。 这是本条与22_8155的关键差别:22_8155的正文写过「前端jumpToBiz只读row.bizId/bizModule/bizType/peerRole,从不读row.link,无需任何前端代码改动」——那句话在REQUIREMENT_REJECTED这一个事件上成立,不能推广到 HOUSE 全域。REQUIREMENT_REJECTED的bizId修好之后确实就是orderId,按订单跳是对的;但核房、团期两类事件的bizId根本不是订单(见第四节 bizId 三义表),任何「拿 bizId 当订单 id 跳」的写法在它们身上必然打开一个不存在的订单。因此本条的frontend_status是pending,不是not_required。 -
link为空、或其路径不在管理后台路由表内时,不要渲染「跳转」入口。存量历史消息的link仍是旧的小程序路径,它们在后台是打不开的。
一、背景
缺陷形态
缺陷不是「link 为空」,而是「link 非空、但写的是另一个端的路由」——这两者在「link 是否非空」这个判据下完全同形,所以此前没人发现。
-- notification_event_config 里 category_code='HOUSE' 的 6 行,inapp_link_template 全部非空
-- 且全部形如 /pages/house/...(小程序路由)
-- 管理后台路由的权威源里,没有任何一条 /pages/* 路径
SELECT COUNT(*) FROM sys_menu WHERE path LIKE '/pages/%' AND status='ACTIVE';
-> 0
-- 测试库里已产出的 HOUSE 站内信,link 100% 落在小程序路由域
SELECT IFNULL(event_code,'<NULL>') ec, kind, COUNT(*) cnt,
SUM(link IS NULL) null_link, SUM(link LIKE '/pages/%') pages_link
FROM admin_message WHERE biz_type='HOUSE' GROUP BY 1,2;
-> HOUSE_INVENTORY_CHECKED NOTIFY 213 0 213
-> <NULL> CHAT 107 107 NULL
(那 107 行 link IS NULL 的是房务 IM 聊天消息,kind='CHAT'、event_code IS NULL,不经通知中心分发,本就不该有 link,与本缺陷无关。)
触发路径
管理后台 → 消息中心 → 一条「核房记录已更新」通知 → 操作列「更多」→「跳转」。
前端侧当前实现(hl-ui origin/v2.1 @ 590c1155,只读查证、未改动):
src/views/notification/MyMessages/index.vue:477—if (row.bizId) more.push({ text: '跳转', ... onClick: jumpToBiz }),只要bizId非空就渲染「跳转」,NOTIFY 行同样渲染。src/views/notification/MyMessages/index.vue:268-286—jumpToBiz(row)依次判断mod === 'FLEET'→ 车务看板;peer === 'CUSTOMIZER' && (mod === 'HOUSE' || mod === 'HOUSE_LEAD')→/housekeeper/orders?orderId=<bizId>;否则 →getOrderReadableRoute(bizId, userStore)。src/utils/orderAccess.js:48-54—getOrderReadableRoute对受限房务角色返回/housekeeper/orders?orderId=<bizId>,其余返回/order-v2/detail/<bizId>。
核房通知是 kind='NOTIFY'、没有 peerRole,于是落进最后那个 else 分支 ⇒ 用 hotelId 去打开 /order-v2/detail/<hotelId>。雪花 ID 形态一致,页面不会报「参数非法」,只会报「订单不存在」或空白——失败是静默的。
数据落地路径(已核实的完整链路)
| 环节 | 位置 |
|---|---|
| 业务入口 | POST /v3/admin/house/hotels/{hotelId}/check-log — HouseHotelAdminController.java:63-70 |
| 组装 extras | HouseInventoryCheckService.java:153-164,放入 hotelId / checkDate / roomTypeCount / operatorId / operatorName / hotelName |
| 发布事件 | HouseNotificationPublisher.publishNotificationEvent("HOUSE_INVENTORY_CHECKED", hotelId, notifyExtras) — HouseNotificationPublisher.java:111-139,bizId 实参就是 hotelId |
| MQ | HL_NOTIFICATION_EVENT_TOPIC → NotificationEventConsumer |
| 渲染 link | NotificationDispatcher 按 notification_event_config.inapp_link_template 用 extras 渲染占位符 |
| 落库 | admin_message(快照表,link 与 biz_id 在 createMessage 那一刻写死在行内) |
| 读回 | GET /admin/message/list — AdminMessageController.java:44-50 |
二、变更接口清单
| 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|
| GET | /admin/message/list |
出参取值变化 | link 字段的值域由 /pages/house/* 改为 /housekeeper/*;字段结构不变 |
| GET | /admin/message/{id} |
出参取值变化 | 同上 |
没有新增 / 删除 / 改名任何字段,也没有新增任何路由。
三、接口详情
1. 站内信分页列表 GET /admin/message/list
使用场景:管理后台消息中心列表。本条只影响其中 bizType='HOUSE' 且 kind='NOTIFY' 的行。
入参:无变化。
出参:AdminMessageRespVO 结构无变化。与本条相关的字段:
| 字段 | 类型 | 说明 |
|---|---|---|
messageId |
String | 消息 ID(雪花,字符串透传) |
categoryCode |
String | SYSTEM,不是 HOUSE——见第六节「边界行为」第 1 条 |
title |
String | 如「核房记录已更新」 |
link |
String | 跳转目标。本条改的就是它。 字段早已存在(AdminMessageRespVO.java:31),不是本次新增 |
bizId |
String | 关联业务 ID,语义随事件码变化,不是路由键,见第四节 |
bizType |
String | HOUSE 域内恒为 "HOUSE" |
kind |
String | NOTIFY=系统通知 / CHAT=聊天 |
实测响应片段(自建夹具,完整取证见第八节):
{
"messageId": "2102328540089466882",
"categoryCode": "SYSTEM",
"title": "核房记录已更新",
"link": "/housekeeper/calendar?hotelId=2102328465699270657&date=2026-09-23",
"bizId": "2102328465699270657",
"bizType": "HOUSE",
"kind": "NOTIFY"
}
错误码:无新增。
2. 站内信详情 GET /admin/message/{id}
同上,返回结构与 link 取值口径一致。
四、契约约束与正确调用方式
「跳转」的唯一正确来源是 link
// ✅ 正确
function jumpToBiz(row) {
const link = String(row.link ?? '')
if (!link || !isKnownAdminRoute(link)) {
// 不渲染 / 不跳转,见下方「兜底要求」
return
}
router.push(link)
}
// ❌ 错误:从 (bizType, bizId) 反推页面
router.push(`/order-v2/detail/${row.bizId}`) // bizId 可能是 hotelId / groupBatchId
bizId 三义表(HOUSE 域,截至 #8182 的全部生产调用点)
bizType 在 HOUSE 域恒为 "HOUSE",而 bizId 承载三种互不相同的业务实体,且三者都是雪花 ID、单看值分辨不出是哪一类:
| 事件码 | bizId 实际是 |
发布位置 |
|---|---|---|
HOUSE_HOTEL_SWAPPED_OLD_HOTEL / _NEW_HOTEL / _CUSTOMER |
orderId | HouseAssignmentService.java:1941/1942/1943 |
GROUP_BATCH_HOUSE_CLAIMED / GROUP_BATCH_HOUSE_RELEASED |
groupBatchId | HouseGroupGrabService.java:731 |
GROUP_BATCH_REQUIREMENT_CONFIRMED |
groupBatchId | GroupBatchRequirementService.java:1286 |
HOUSE_INVENTORY_CHECKED |
hotelId | HouseInventoryCheckService.java:164 |
合计 6 条 publish 语句 / 4 个发起方法(换店三条同在一个方法内)。该约束已固化在 HouseNotificationPublisher 的类注释里(PR #8189 写入、#8190 补齐漏枚举),新增调用点时要一并维护。
为什么不把 bizType 拆细:核房是「某酒店某天」的房态盘点,团期抢单是「某个团期班期」的归属变更,它们在业务上根本没有订单维度,强行编一个 orderId 只会制造假关联。bizId 传各自的真实主键是对的语义,代价就是它不能再兼任路由键。
兜底要求
link 为空、或 link 的路径部分不在管理后台已知路由表内时,不渲染「跳转」入口(而不是渲染出来再跳到 404)。理由见第五节:admin_message 是快照表,历史行里存着的就是旧路由域的字符串。
前端需要知道的取值边界
link是相对路径 + query,不含域名,可直接交给router.push。- query 里的 ID 一律按字符串处理,禁
Number()——雪花 ID 超Number.MAX_SAFE_INTEGER。 ${占位}取不到值时会被渲染成空串,形如?inquiryId=。把这种情况按「参数缺失」处理,降级为打开列表页即可。
五、数据库行为
notification_event_config(Flyway V20260922_210,hl-user-service)
纯 UPDATE 无 DDL,按 event_code(唯一键 uk_event_code)定位,可重复执行:
| event_code | 改前 | 改后 |
|---|---|---|
HOUSE_INVENTORY_CHECKED |
/pages/house/inventory?hotelId=${hotelId}&date=${checkDate} |
/housekeeper/calendar?hotelId=${hotelId}&date=${checkDate} |
HOUSE_INQUIRY_TIMEOUT |
/pages/house/inquiry?id=${inquiryId} |
/housekeeper/todos?inquiryId=${inquiryId} |
HOUSE_INQUIRY_ESCALATED |
/pages/house/inquiry?id=${inquiryId} |
/housekeeper/todos?inquiryId=${inquiryId} |
⚠️ 两条询房事件的 query key 同时由 id 改为 inquiryId(原值取自种子脚本 V20260520_002__house_notification_event_config.sql:171(ESCALATED)与 :202(TIMEOUT),逐字核对;且对全部迁移脚本做过穷举——整个 db/migration 里出现 inapp_link_template 的文件共 8 份,涉及这两个事件码的只有该种子脚本与本单的 V20260922_210,中间无第三份改过它们)。这两个事件码目前在 Java 主代码里没有发布方(见第六节第 4 条),所以 key 改名当前不产生任何实际调用差异,接上发布方时按 inquiryId 读即可。
改与不改的判据是收件人域:这三条的收件人(HOUSE_TEAM / INQUIRY_CLAIMER / HOUSE_LEAD)都是内部员工,点开只会落在管理后台;而换酒店三事件的收件人是酒店联系人与 C 端客户,本就不属于后台路由域,一个字节未动。
两个目标路由在后台菜单表里都存在:
SELECT path, menu_name, status FROM sys_menu WHERE path IN ('/housekeeper/calendar','/housekeeper/todos');
-> /housekeeper/calendar 日历视图 ACTIVE
-> /housekeeper/todos 待处理 ACTIVE
admin_message(快照表)
link 与 biz_id 在 createMessage 落库那一刻一次性写入行内,不是按需渲染。所以:
- 生效后新产生的消息,
link是新路由; - 此前已产出的 213 条
HOUSE_INVENTORY_CHECKED历史消息,link仍是旧的小程序路径,不做批量回刷(order-v3 / fleet 尚未上生产,测试库脏数据不值得回刷,且回刷会掩盖兜底逻辑缺失)。前端按上面的「兜底要求」处理即可。
六、边界行为
-
categoryCode是SYSTEM而不是HOUSE。 核房通知虽然bizType='HOUSE',但落库时category_code='SYSTEM'(AdminMessageRespVO.java:20-22的注释即写明「系统通知为 SYSTEM」)。用categoryCode=HOUSE过滤会得到 0 条,这是实测踩到的坑,按bizType或不过滤来取。 -
换酒店三事件当前 5 个渠道零产出。 它们的
wework_receiver_type(OLD_HOTEL_CONTACT/NEW_HOTEL_CONTACT/ORDER_CUSTOMER)不在NotificationDispatcher.INTERNAL_INAPP_RECEIVER_TYPES(NotificationDispatcher.java:100-103)内 ⇒ 站内信不落admin_message;HouseNotificationPublisher构造消息时不设userId⇒ C 端user_message也不落;其余渠道开关均为 0。所以前端在消息中心里看不到这三类消息——这是它们自身的既有状态,与本次改动无关。 -
三个
GROUP_BATCH_*事件码在配置表里没有行。 实测notification_event_config全表 62 行,event_code LIKE 'GROUP%'零命中(同一条 SQL 查HOUSE_INVENTORY_CHECKED可返回,作阳性对照)。NotificationDispatcher查不到 config 只记日志 ⇒ 团期房务抢单 / 释放 / 需求整体确认一条通知都不产生,即bizId三义中的groupBatchId那一义对任何消费方目前都不可见。已另立单跟进配置补齐。 -
HOUSE_LEAD不在站内信白名单内。 所以HOUSE_INQUIRY_ESCALATED即便将来接上发布方,站内信也不会落admin_message——它缺的是扩白名单,不是改link。本次对它的订正是「把路由域写对」,当前实际效果为零。同理HOUSE_INQUIRY_TIMEOUT/_ESCALATED在 Java 主代码里目前没有发布方。 -
聊天消息(
kind='CHAT')不受影响:link本就是null,event_code也是null,不经通知中心分发。
六.5、枚举 / 数据字典
bizType(AdminMessageRespVO.bizType)HOUSE 域取值恒为 "HOUSE",不具备区分事件类型的能力,不要用它做路由分支。事件类型看 event_code(列表接口不返回该字段),跳转看 link。
kind:NOTIFY=系统通知(有 link)/ CHAT=会话消息(link 为 null)。
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
AdminMessageRespVO.link(核房事件) |
/pages/house/inventory?hotelId=...&date=... |
/housekeeper/calendar?hotelId=...&date=... |
AdminMessageRespVO.bizId(核房事件) |
hotelId | hotelId(不变,本次未动 bizId) |
| 其余字段 | — | 无变化 |
行为级对比
| 场景 | 改前 | 改后 |
|---|---|---|
| 后端渲染出的 link 落在哪个路由域 | 小程序(后台不存在该路径) | 管理后台(sys_menu 内可查到) |
前端按 bizId 反推跳转 |
打开 /order-v2/detail/<hotelId>,订单不存在 |
仍然错——所以前端必须改读 link |
前端按 link 跳转 |
跳到后台不存在的 /pages/* |
正确落到核房日历页 |
⚠️ 这张表的第二行是重点:只改后端不改前端,「跳转」依然是坏的。后端这次把 link 修对了,把它变成一个可用的跳转来源;真正让用户点对页面的动作在前端侧。
六.7、影响评估
- 前端是否必须同步上线:是。
jumpToBiz需改读link并补兜底判断,否则核房类通知的「跳转」仍落空。 - 兼容性:纯取值变化,老前端不会报错(只是跳转依旧不对),不会因为本条出现新的异常或白屏。
- 数据:仅
notification_event_config三行 UPDATE,无 DDL、无数据迁移、无回刷。
七、不影响范围
- 不影响任何接口的字段结构、必填性、类型。
- 不影响 C 端小程序站内信:小程序读的是
user_message,本次改的三个事件码收件人都是内部员工,不落user_message。 - 不影响换酒店三事件(
link一字节未改)。 - 不影响聊天消息与会话聚合。
- 不影响
bizId的取值——本次没有动任何bizId实参。 - 不影响
22_8155的REQUIREMENT_REJECTED结论:那条改的是bizId,本条改的是link,两者互不覆盖。
八、测试环境已验证
部署基线:hl-user-service 与 hl-order-service-v3 均已滚动到测试服 776c0023d(deploy-status.sh 读数:两者 BEHIND=0/N)。
Flyway:
version success installed_on
20260922.210 1 2026-09-22 17:18:51
自建夹具(全部自建,未复用他人数据):
| 项 | 值 |
|---|---|
| 自建酒店 | hotelId=2102328465699270657(8182验收自建酒店-1790069137,status=0 下架) |
| 自建核房记录 | checkLogId=2102328538344615937,checkDate=2026-09-23 |
| 产生的站内信 | messageId=2102328540089466882 |
链路实测:POST /v3/admin/house/hotels/2102328465699270657/check-log → GET /admin/message/list 读回该条,link 为:
/housekeeper/calendar?hotelId=2102328465699270657&date=2026-09-23
三条断言逐条成立:① 以 /housekeeper/calendar 开头;② hotelId= 后为自建酒店 ID,逐字相同;③ date= 后为自建核房日期 2026-09-23,与请求体逐字相同。
库侧独立复核(不依赖接口返回):
id : 2102328540089466882
category_code : SYSTEM
event_code : HOUSE_INVENTORY_CHECKED
kind : NOTIFY
biz_type : HOUSE
biz_id : 2102328465699270657
link : /housekeeper/calendar?hotelId=2102328465699270657&date=2026-09-23
title : 核房记录已更新
biz_id 与 link 里的 hotelId 逐字一致 ⇒ 证明 bizId 语义未被本次改动影响。
存量未回刷的对照:同一次响应里的历史消息 messageId=2100088394469044226(迁移执行前产生)link 仍为 /pages/house/inventory?hotelId=...,与第五节「不回刷」一致。
迁移单测:HouseInappLinkMigrationMysqlTest(Testcontainers 真 MySQL 8.0.33 跑迁移)Tests run: 5, Failures: 0, Errors: 0, Skipped: 0,含阴性对照(换店三事件 link 零字节变化)与幂等复跑。
改前阴性基线:SELECT COUNT(*) FROM sys_menu WHERE path LIKE '/pages/%' AND status='ACTIVE' → 0,即改动前没有任何一条 HOUSE link 能在后台路由表里查到——这条读数同时说明旧验收口径「link 非空」对本缺陷没有分辨力(改动前 6 条就全非空)。
十、相关文档
22_8155_需求驳回站内信bizId改为orderId点跳转落空-修复-管理后台.md— 同一收件箱、相邻缺陷。⚠️ 其中「前端从不读row.link,无需任何前端代码改动」一句的适用范围仅限REQUIREMENT_REJECTED,不适用于本条覆盖的三个事件码,理由见「关键变化」第 2 条。HouseNotificationPublisher类注释(hl-order-service-v3)—bizId三义的权威清单,新增调用点时同步维护。- Flyway
V20260922_210__fix_house_inapp_link_to_admin_routes.sql(hl-user-service)— 脚本头注写明了「按收件人域取舍」以及为什么不能改用白名单当判据。
关联 / 联系人
链接
联系人
- 后端: wx
- 前端: 待认领(
frontend_status: pending)