docs: #8166 交接件订正定稿——补 HOUSE NOTIFY 白名单复核项,frontmatter 保留 mmg 回写
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
首版第一节把兜底分支写成 jumpBiz.js 的 else,实测 origin/v2.1 该文件 44 行、0 个 else,兜底在 index.vue:291-293。首版那句已经发出去过,留着会让下一个读这份交接件 的人去找一个不存在的分支,故保留订正块而不是静默改掉。 新增〇节:mmg 回写的订单白名单含 HOUSE,而 jumpBiz.js:4-5 的头部契约明写「HOUSE 域 bizId 三义不是路由键」,isHouseNotifyRow(:36-38) 是 bizType==='HOUSE' && !isChatRow ——白名单若只按 bizType 匹配就分不开 CHAT 与 NOTIFY 两类行,HOUSE NOTIFY 会按 orderId 打开另一张单且页面不报错。给出一行可自检的用例形状。 取证边界:frontend_ref 77b421f8 在 hl-ui 的 git ls-remote origin 15 个 ref 里查无此 对象(阳性对照 16c3506d 查得到),故本节判据全部取自 origin/v2.1 tip 16c3506d。 target_release 从空串补为 "v2.1":verified 状态下留空会被 E_FRONTEND_STATE 拦,取值 按全仓已填条目主流约定(73/376 用 "v2.1");不取 hl-ui@<sha>,那个 sha 不可达。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -10,85 +10,105 @@ gateway_status: "not_required"
|
||||
frontend_status: "verified"
|
||||
frontend_owner: "mmg"
|
||||
frontend_ref: "77b421f839a0eaeaa419c19bb6f79a803dd99205"
|
||||
target_release: ""
|
||||
target_release: "v2.1"
|
||||
verified_at: "2026-09-23"
|
||||
status_note: "关联工单#8166(bizType/bizId契约漂移)的前端交接件。后端已就绪,前端按本件调整三处routing逻辑。前端已交付(2026-09-23,三条必落地+AC-9 全做):①jumpBiz 改白名单兜底——订单白名单(ORDER/HOUSE/HOUSE_LEAD/GROUP/FLEET)才按 orderId 路由,新增/未知 bizType 不入列;②canJumpToBiz 白名单外返 false 不渲染跳转按钮,未知即不跳;③GROUP_HOUSE/GROUP_FLEET/GROUP_BATCH 按 bizId=groupBatchId 路由团期详情页 /order-v2/batch/detail/{id};AC-9 死分支(peer==CUSTOMIZER→housekeeper/orders,peerRole/senderRole/requirementId 后端从未返回)已删。路由解析抽 resolveBizRouteTarget 纯函数,spec 13 例全绿,checkpoint 全过。"
|
||||
status_note: "关联工单#8166(bizType/bizId契约漂移)的前端交接件。后端已就绪,前端按本件调整三处routing逻辑。前端已交付(2026-09-23,三条必落地+AC-9 全做):①jumpBiz 改白名单兜底——订单白名单(ORDER/HOUSE/HOUSE_LEAD/GROUP/FLEET)才按 orderId 路由,新增/未知 bizType 不入列;②canJumpToBiz 白名单外返 false 不渲染跳转按钮,未知即不跳;③GROUP_HOUSE/GROUP_FLEET/GROUP_BATCH 按 bizId=groupBatchId 路由团期详情页 /order-v2/batch/detail/{id};AC-9 死分支(peer==CUSTOMIZER→housekeeper/orders,peerRole/senderRole/requirementId 后端从未返回)已删。路由解析抽 resolveBizRouteTarget 纯函数,spec 13 例全绿,checkpoint 全过。正文首版第一节把兜底分支位置写成 jumpBiz.js 的 else,已于 2026-09-23 订正为 index.vue:291-293,并补一节交付后必查(HOUSE NOTIFY 三义 bizId)。"
|
||||
updated_at: "2026-09-23"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 通知消息 bizType 路由与 bizId 语义契约交接
|
||||
|
||||
关联工单:[#8166](https://git.1814.love:8443/wx/HL/issues/8166) bizType/bizId 契约漂移定案
|
||||
关联工单:[#8166](https://git.1814.love/wx/HL/issues/8166) bizType/bizId 契约漂移定案
|
||||
|
||||
## 一、白名单兜底:未知 bizType 的处理
|
||||
> **2026-09-23 订正**:本件首版把兜底分支的位置写成了 `jumpBiz.js` 的 `else`。实测 `origin/v2.1` 的
|
||||
> `jumpBiz.js` 全文 44 行、**没有 `else`、也没有 bizType 白名单**,兜底在 `index.vue:291-293`。
|
||||
> 第一节已按实际代码重写,其余各节未变。前端已按正确形状交付(见 frontmatter),本节保留是因为首版那句错的描述已经发出去过,留着会让下一个读这份交接件的人以为 `jumpBiz.js` 里有一个并不存在的分支。
|
||||
|
||||
**问题现状**
|
||||
## 〇、交付后必查一项:`HOUSE` 进订单白名单会不会打穿 #8182
|
||||
|
||||
当前 `hl-ui/src/views/notification/MyMessages/jumpBiz.js` 的路由逻辑依赖 `else` 分支做兜底,即只要 bizType 不属于已知分支,就默认按 orderId 路由到订单详情页。这意味着:**新增一个 bizType 时,若前端未同步修改路由逻辑,该消息会静默跳转到错误的订单页面**(业务维度错配,用户不可见)。
|
||||
frontmatter 记的白名单是 `ORDER / HOUSE / HOUSE_LEAD / GROUP / FLEET` → 按 `orderId` 路由。**`HOUSE` 这一项只有在仍受 `isHouseNotifyRow` 约束时才是安全的。**
|
||||
|
||||
**前端改动**(共3条,均为必须落地)
|
||||
判据在 `jumpBiz.js` 自己的头部契约里(`origin/v2.1` = `16c3506d`,`jumpBiz.js:4-5`):
|
||||
|
||||
1. **显式白名单兜底**:修改 `jumpBiz.js` 的 `else` 分支,改为:仅对已知携带 `orderId` 的 bizType(`ORDER`、`HOUSE`-CHAT、`HOUSE_LEAD`、`GROUP`、`FLEET`)才按 orderId 路由。新增或未定义的 bizType 不入此列。
|
||||
> HOUSE 域 bizId 三义(hotelId/groupBatchId/orderId)不是路由键,禁止拿它反推页面
|
||||
> - HOUSE 非聊天行且 link 不可用 → 不渲染入口也不跳(bizId 当 orderId 必开错单)
|
||||
|
||||
2. **未知 bizType 不给跳转**:对于白名单外的 bizType,`canJumpToBiz()` 返回 false,**不渲染跳转按钮**,而非跳到错的地方。失败从「隐形」变成「可见」。
|
||||
`isHouseNotifyRow`(`jumpBiz.js:36-38`)= `(bizModule || bizType) === 'HOUSE' && !isChatRow(row)`。⇒ **`bizType === 'HOUSE'` 这一个条件分不开两类行**:CHAT 行的 bizId 是 orderId(可路由),NOTIFY 行的 bizId 三义(不可路由)。白名单若只按 `bizType` 匹配、且 `isHouseNotifyRow` 那道闸在它之后或被一并删掉,HOUSE NOTIFY 行就会**按 orderId 打开一个错的订单**——而 `hotelId` / `groupBatchId` 也是雪花 id,**页面不会报错,只会显示另一张单**,这正是 #8182 修掉的那个形态。
|
||||
|
||||
3. **团期级 bizType 路由修正**:`GROUP_HOUSE`、`GROUP_FLEET`、`GROUP_BATCH` 这三个 bizType 对应团期维度的会话,其 bizId 是 `groupBatchId` 而非订单 ID。这些类型应路由到**团期详情页**(`hl-ui/src/views/order-v2/batch/detail/`,已存在页面)而非订单详情页。
|
||||
**一句话自检**:`resolveBizRouteTarget` 对一个 `{ bizType: 'HOUSE', kind: 'NOTIFY', conversationKey: null, bizId: '<任意雪花id>', link: '' }` 的行,返回的必须是「不跳」,不是订单路由。spec 里若没有这一例,它就是白名单里唯一一个靠**行内第二个条件**才安全的成员,而那个条件不在白名单的表达式里。
|
||||
|
||||
## 二、AC-9 定案:不补传充字段
|
||||
**取证边界**:本节没有对交付提交本身取证——`77b421f839a0eaeaa419c19bb6f79a803dd99205` 在 `git ls-remote origin`(hl-ui)返回的 15 个 ref 里查无此对象,阳性对照同一命令能查到 `16c3506d`(`refs/heads/v2.1`)。上面的判据因此全部取自 `origin/v2.1` tip `16c3506d`,即交付**之前**的代码;白名单的实际写法请以你们手上那份为准。
|
||||
|
||||
**后端定案内容**
|
||||
## 一、兜底分支:bizId 被当成 orderId
|
||||
|
||||
`hl-user-service` 的 `AdminMessageRespVO` **不补** `peerRole` / `senderRole` 字段。
|
||||
### 代码现状(实测 `hl-ui` `origin/v2.1`)
|
||||
|
||||
**原因分析**
|
||||
跳转链路是**两个文件配合**的,改一处不够:
|
||||
|
||||
前端当前的 `index.vue:282-287` 有一条路由分支判断 `peer === 'CUSTOMIZER' && (mod === 'HOUSE' || mod === 'HOUSE_LEAD')`,该 `peer` 从 `row.peerRole || row.senderRole || ''` 取值。但:
|
||||
| 位置 | 职责 | 现状 |
|
||||
|---|---|---|
|
||||
| `src/views/notification/MyMessages/jumpBiz.js:40-43` `canJumpToBiz()` | 决定**渲不渲染**「跳转」按钮 | `link` 可用→true;无 `bizId`→false;否则 `!isHouseNotifyRow(row)`——**只挡 HOUSE NOTIFY 一类** |
|
||||
| `src/views/notification/MyMessages/index.vue:267-294` `jumpToBiz()` | 决定**跳去哪** | `link` 优先(`:268-272`)→`FLEET`(`:280`)→定制师房务分支(`:282`)→HOUSE NOTIFY 兜底闸(`:288-290`)→**`else`(`:291-293`) `getOrderReadableRoute(bizId)`** |
|
||||
|
||||
- `senderRole` 在定制师广播时**恒为 NULL**(定制师作为消息发送人时,系统记录的 sender_role 为空)
|
||||
- 后端接口从未返回过这两个字段(`AdminMessageRespVO` 实有15个字段,无此二者)
|
||||
- 补了这两个字段等于交付一个在目标场景下永远答不上来的契约
|
||||
`else` 那一支把 `bizId` 无条件当作 **orderId**。凡是落到这一支、而 `bizId` 实际不是订单 ID 的消息,都会打开一个**错的订单详情页**——页面正常渲染,没有报错,用户不知道自己看的不是这条消息说的那个东西。
|
||||
|
||||
**处置**
|
||||
### 谁会落到这一支
|
||||
|
||||
mmg 请根据以下情况处理:
|
||||
`link` 为空的行才走推导。**CHAT 通道的行天然没有 `link`**(`link` 是 NOTIFY 按事件模板渲染出来的),所以团期级会话行 `GROUP_HOUSE` / `GROUP_FLEET` 会:
|
||||
`canJumpToBiz` 判 true(有 bizId、不是 HOUSE NOTIFY)⇒ 按钮渲染 ⇒ `jumpToBiz` 前四支都不命中 ⇒ 落 `else` ⇒ 拿 **groupBatchId** 去开订单详情页。
|
||||
|
||||
1. **删除死代码**:`hl-ui/src/views/notification/MyMessages/index.vue` 的第3层路由分支 `peer === 'CUSTOMIZER' && (mod === 'HOUSE' || mod === 'HOUSE_LEAD')` 目前恒不触发,建议删除。
|
||||
2. **确认字段缺席**:`row.peerRole`、`row.senderRole`、`row.bizModule`、`row.requirementId` 这四个字段后端接口**从未返回过**。若业务需要「房务会话行定向跳到房务订单页」的功能,需另行设计接口补充所需数据。
|
||||
### 方向说明:不要退回「bizType 推路由」
|
||||
|
||||
## 三、AC-10 CHAT 通道 bizId 语义契约
|
||||
`jumpBiz.js` 开头的契约(#8182 建立)写得很明确——**NOTIFY 行跳转的唯一正确来源是 `link`,HOUSE 域 bizId 三义不是路由键**。本件**不是**要推翻它回到按 bizType 推路由,而是给那条**残留的**推导加一道白名单闸:能走推导的只有已确认 `bizId == orderId` 的那几类,其余一律不跳。
|
||||
|
||||
**新增书面契约**
|
||||
## 二、前端改动(3 条,缺一不可)
|
||||
|
||||
后端通过 CHAT 通道(对应 `admin_message.kind = 'CHAT'`)发送的消息,bizId 的语义如下:
|
||||
1. **`index.vue:291-293` 的 `else` 加白名单**:只有 `mod` ∈ `{ORDER, HOUSE, HOUSE_LEAD, GROUP, FLEET}`(即第三节表里 bizId 为 orderId 的那几类)才 `getOrderReadableRoute(bizId)`;其余**直接 return,不跳**。
|
||||
|
||||
| bizType | bizId 实际值 | 含义 |
|
||||
|---------|-----------|------|
|
||||
| `HOUSE` | orderId | 房务订单ID |
|
||||
| `HOUSE_LEAD` | orderId | 房务主单ID |
|
||||
| `FLEET` | orderId | 车务订单ID |
|
||||
| `GROUP` | orderId | 订单ID(团期订单) |
|
||||
| `GROUP_HOUSE` | groupBatchId | 团期房务批次ID |
|
||||
| `GROUP_FLEET` | groupBatchId | 团期车务批次ID |
|
||||
2. **`jumpBiz.js:40-43` 的 `canJumpToBiz` 同步收口**:把同一份白名单用上,否则会渲染出一个**点了没反应**的按钮——那比跳错更让人困惑。两处必须用同一份常量,不要各写一份。
|
||||
|
||||
该契约原仅存在于代码注释(`ChatManager.java` / `TeamChatModule.java`),现升级为书面交接件。
|
||||
3. **团期级 bizType 路由到团期详情页**:`GROUP_HOUSE` / `GROUP_FLEET` / `GROUP_BATCH` 的 `bizId` 是 `groupBatchId`,应路由到团期详情(`src/views/order-v2/batch/detail/`,页面已存在),不是订单详情。
|
||||
|
||||
## 四、覆盖边界与已知约束
|
||||
**这三条落地后的行为变化**:以后后端新增一个 bizType 而前端没跟着改,后果从「**跳到错的订单页**」变成「**不跳**」。失败从隐形变成可见——这是本次改动真正买到的东西,不是多支持了几个跳转。
|
||||
|
||||
### bizType 清单的完备性
|
||||
## 三、bizId 语义契约(原只活在注释里,本件升级为书面契约)
|
||||
|
||||
后端 `AdminNotificationController.java` 的 `.eventCode(config.getEventCode())` 是**运行时入参**,可发送 `notification_event_config` 表里**任意一行**对应的事件码。因此:
|
||||
判据取自 `origin/dev-v3` 源码,不是推测:
|
||||
|
||||
- **任何静态枚举的 bizType 清单都只是下界**,不是全集
|
||||
- 前端的白名单兜底**必须按「未知即不跳」实现**(不能继续假设 else 分支永远指向 orderId)
|
||||
- 新增后端 bizType 时,若不在已知白名单内,前端无需修改代码,消息的"跳转按钮"会自动置灰
|
||||
| bizType | 通道 | `bizId` 实际值 | 出处 |
|
||||
|---|---|---|---|
|
||||
| `ORDER` | CHAT / NOTIFY | orderId | #8155 |
|
||||
| `HOUSE` | CHAT | orderId | `ChatManager.java` / `TeamChatModule.java` |
|
||||
| `HOUSE_LEAD` | CHAT | orderId | 同上 |
|
||||
| `FLEET` | CHAT | orderId | 同上 |
|
||||
| `GROUP` | CHAT | orderId | 同上 |
|
||||
| `GROUP_HOUSE` | CHAT | **groupBatchId** | 同上 |
|
||||
| `GROUP_FLEET` | CHAT | **groupBatchId** | 同上 |
|
||||
| `GROUP_BATCH` | **NOTIFY** | **groupBatchId** | `GroupBatchFormedNotifyService.java:209-210`(`.bizId(String.valueOf(groupBatchId))` + `.bizType("GROUP_BATCH")`) |
|
||||
|
||||
### 一期 v2 与二期 v3 的产出范围
|
||||
⚠️ `HOUSE` 这一行只覆盖 **CHAT** 行。**HOUSE 域的 NOTIFY 行 bizId 是三义的**(hotelId / groupBatchId / orderId),前端已有 `isHouseNotifyRow` 把它挡在推导之外(`jumpBiz.js:35-37`),本次白名单**不要**把 HOUSE NOTIFY 放进来。
|
||||
|
||||
本契约针对二期(`hl-order-service-v3` / `hl-fleet-service` / 生产尚未上线)的建立。一期 v2 在生产上的行为遵循此前的隐式约定,后续统一时另行协调。
|
||||
## 四、`peerRole` / `senderRole` 不补(后端定案)
|
||||
|
||||
## 五、关联
|
||||
`AdminMessageRespVO` **不新增** `peerRole` / `senderRole`。
|
||||
|
||||
- Issue:[#8166](https://git.1814.love:8443/wx/HL/issues/8166)
|
||||
- 后端对应工单:#8166(bizType/bizId 契约漂移)
|
||||
**判据**(实测 `origin/dev-v3`):
|
||||
- 该 VO 现有 **15 个字段**:`messageId` `categoryCode` `title` `content` `link` `bizId` `bizType` `isRead` `createTime` `messageType` `messageTypeLabel` `kind` `senderName` `conversationKey` `teamMessage`。`peerRole` / `senderRole` / `bizModule` / `requirementId` **四个都不在其中,也从未返回过**。
|
||||
- 定制师广播时 `admin_message.sender_role` **恒为 NULL**(`AdminMessageMapper.java:443-444` / `ChatConstants.java:200-201`)⇒ 就算把字段加上,在前端那条分支**正需要它**的场景里它恒为空。
|
||||
|
||||
**前端据此处理**:`index.vue:282-287` 的第三层分支
|
||||
`peer === 'CUSTOMIZER' && (mod === 'HOUSE' || mod === 'HOUSE_LEAD')` 取值自 `:279 row.peerRole || row.senderRole || ''`,两个来源都恒空 ⇒ **该分支恒不触发,是死代码,请删除**。删掉后 `HOUSE` / `HOUSE_LEAD` 的 CHAT 行按第二节的白名单走订单详情,与今天的实际行为一致。
|
||||
|
||||
若业务确实需要「房务会话行定向跳房务订单页」,那是一个新需求:它要的数据后端今天不产出,需要单独定接口。
|
||||
|
||||
## 五、覆盖边界
|
||||
|
||||
1. **bizType 清单是下界,不是全集**:`AdminNotificationController.java:127` 的 `.eventCode(config.getEventCode())` 是运行时入参,能发 `notification_event_config` 表里任意一行的事件码 ⇒ 任何静态枚举都数不全。这正是第二节要白名单兜底而不是黑名单的原因:**白名单对「没见过的类型」给出的是安全答案(不跳),黑名单给出的是错答案(跳错)**。
|
||||
2. **取证范围是 `origin/dev-v3`**。一期 `origin/dev` 的 `hl-user-service` 是另一份代码(渲染器与通道校验都不同),本件对一期的行为不作任何断言,不要把这里的结论套到一期页面上。
|
||||
3. **第三节的表只覆盖本件列出的 8 个 bizType**。表外的类型按第二节落到「不跳」,这是设计如此,不是遗漏。
|
||||
|
||||
## 六、关联
|
||||
|
||||
- 工单:[#8166](https://git.1814.love/wx/HL/issues/8166)
|
||||
- 后端联系人:@wx
|
||||
|
||||
在新工单中引用
屏蔽一个用户