文件
hl-api-changelog/changelogs-v2/2026-09/22_8182_HOUSE站内信link订正到后台路由与bizId不是路由键-修复-管理后台.md
T
2026-09-22 17:52:55 +08:00

22 KiB
原始文件 Blame 文件历史

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 类通知的「跳转」行为


⚠️ 关键变化

  1. 三个内部员工事件码的 link 换了路由域:HOUSE_INVENTORY_CHECKED / HOUSE_INQUIRY_TIMEOUT / HOUSE_INQUIRY_ESCALATED 的 link 由小程序路径 /pages/house/... 改为管理后台路由 /housekeeper/calendar 与 /housekeeper/todos。字段名、类型、位置都没变,变的是值。

  2. 「跳转」必须改读 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。

  3. 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 取值口径一致。


四、契约约束与正确调用方式

// ✅ 正确
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 尚未上生产,测试库脏数据不值得回刷,且回刷会掩盖兜底逻辑缺失)。前端按上面的「兜底要求」处理即可。

六、边界行为

  1. categoryCode 是 SYSTEM 而不是 HOUSE。 核房通知虽然 bizType='HOUSE',但落库时 category_code='SYSTEM'(AdminMessageRespVO.java:20-22 的注释即写明「系统通知为 SYSTEM」)。用 categoryCode=HOUSE 过滤会得到 0 条,这是实测踩到的坑,按 bizType 或不过滤来取。

  2. 换酒店三事件当前 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。所以前端在消息中心里看不到这三类消息——这是它们自身的既有状态,与本次改动无关。

  3. 三个 GROUP_BATCH_* 事件码在配置表里没有行。 实测 notification_event_config 全表 62 行,event_code LIKE 'GROUP%' 零命中(同一条 SQL 查 HOUSE_INVENTORY_CHECKED 可返回,作阳性对照)。NotificationDispatcher 查不到 config 只记日志 ⇒ 团期房务抢单 / 释放 / 需求整体确认一条通知都不产生,即 bizId 三义中的 groupBatchId 那一义对任何消费方目前都不可见。已另立单跟进配置补齐。

  4. HOUSE_LEAD 不在站内信白名单内。 所以 HOUSE_INQUIRY_ESCALATED 即便将来接上发布方,站内信也不会落 admin_message——它缺的是扩白名单,不是改 link。本次对它的订正是「把路由域写对」,当前实际效果为零。同理 HOUSE_INQUIRY_TIMEOUT / _ESCALATED 在 Java 主代码里目前没有发布方。

  5. 聊天消息(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)