docs(changelog): #8155 需求驳回站内信 bizId 由需求行 id 改为 orderId(已部署实测)
changelog-filename-gate / validate (push) Failing after 2s
changelog-filename-gate / validate (push) Failing after 2s
REQUIREMENT_REJECTED 事件此前 bizType 固定 ORDER 却把需求行 id 当 bizId 下发, 管理端站内信点「跳转」必然打开一个不存在的订单。后端已改为下发 orderId, 需求行 id 迁到 params(notification_send_log.params_json 实测可反查), 并把 inapp_link_template 前缀从 /order/detail/ 订正为 /order-v2/detail/。 AdminMessageRespVO / AdminMessagePageReqVO 字段结构未变,前端无需改动。 已部署测试服 6af4d93e5 双实例并端到端实测:新产生的 admin_message 行 biz_id 等于 orderId、link 为 /order-v2/detail/2102242483750756354。 覆盖边界:admin_message 是快照表,存量行 biz_id 与 link 仍是旧值、不回刷 (该事件仅在测试环境产生过,order-v3/fleet 未上生产)。 Refs #8155 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
这个提交包含在:
@@ -0,0 +1,446 @@
|
||||
---
|
||||
schema: "hl-changelog/v2"
|
||||
ticket: "8155"
|
||||
title: "通知中心: 需求驳回站内信 bizId 改为 orderId,修复管理端「跳转」打开不存在订单"
|
||||
consumer: "admin"
|
||||
author: "wx(GIT)"
|
||||
change_type: "修复"
|
||||
backend_status: "deployed"
|
||||
gateway_status: "not_required"
|
||||
frontend_status: "not_required"
|
||||
frontend_owner: ""
|
||||
frontend_ref: ""
|
||||
target_release: ""
|
||||
verified_at: ""
|
||||
status_note: "本条不改变 AdminMessageRespVO/AdminMessagePageReqVO 的字段结构,只修正 REQUIREMENT_REJECTED 事件写入 admin_message 的 bizId 取值、以及 notification_event_config.inapp_link_template 的路由前缀。gateway_status=not_required:本条不引入任何新路由,受影响的 GET /admin/message/list 与 GET /admin/message/{id} 是既有端点。frontend_status=not_required:前端 jumpToBiz(hl-ui/src/views/notification/MyMessages/index.vue:268-286)只读 row.bizId/row.bizModule/row.bizType/row.peerRole,从不读 row.link,字段名/类型/位置均未变,无需任何前端代码改动。backend_status=deployed:order-v3 与 user-service 均已部署测试服务器至 6af4d93e5(双实例滚动完成),并已在自建夹具上走完「提交用车需求 → 车控驳回 → 站内信产出」全链路,实测读数见正文第八节。⚠️ admin_message 是快照表:bizId 与渲染后的 link 在 createMessage 落库那一刻一次性写入行内(NotificationDispatcher.java:1038 渲染 link、:1042 起调用 AdminMessageService.createMessage、AdminMessageService.java:121-123 写入实体、:541 toVO 透传),不是按需渲染;因此本修复只影响部署生效后新产生的消息,此前已产出的历史站内信 bizId 与 link 都仍是旧值,不做批量回刷——REQUIREMENT_REJECTED 目前只在测试环境触发过(order-v3/fleet 尚未上生产),不涉及生产数据。"
|
||||
updated_at: "2026-09-22"
|
||||
base: "dev-v3"
|
||||
---
|
||||
|
||||
# 通知中心: 需求驳回站内信 bizId 改为 orderId,修复管理端「跳转」打开不存在订单
|
||||
|
||||
> **存放目录**: 二期(order-v3)→ `changelogs-v2/2026-09/`
|
||||
>
|
||||
> **服务**: hl-order-service-v3(通知发布方)+ hl-user-service(通知分发/站内信收件箱)
|
||||
> **PR**: [#8160](https://git.1814.love:8443/wx/HL/pulls/8160)
|
||||
> **Issue**: [#8155](https://git.1814.love:8443/wx/HL/issues/8155)
|
||||
> **日期**: 2026-09-22
|
||||
> **影响范围**: 管理后台站内信中心「需求驳回」事件的 `bizId` 取值与对应跳转链接前缀
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 关键变化
|
||||
|
||||
**需求驳回站内信的 `bizId` 从需求行 ID 改为订单 ID,前端「跳转」按钮从此能正确打开订单详情。**
|
||||
|
||||
- 改前:站内信声明 `bizType="ORDER"`,但 `bizId` 实际写入的是被驳回的需求行 ID(`vehicle_requirement`/`hotel_requirement` 主键)。前端按 `bizType=ORDER` 的契约把 `bizId` 当 `orderId` 去查订单,订单查不到,「跳转」必然落空。
|
||||
- 改后:`bizId` 恒等于 `order.getOrderId()`;被驳回的需求行 ID 改经 `params` 下发,排查能力不丢(落 `notification_send_log.params_json`)。
|
||||
- **前端不需要改任何代码**:`AdminMessageRespVO` 的字段名、类型、位置都没变,变的只是 `bizId` 这一个既有字段过去写错了、现在写对了。
|
||||
- 同批一并修正了该事件 `inapp_link_template` 的路由前缀(`/order/detail/${orderId}` → `/order-v2/detail/${orderId}`)。该字段前端当前完全不读,此改动不影响现有跳转行为,只是让 link 本身不再是一个过期契约值。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景
|
||||
|
||||
### 缺陷形态
|
||||
|
||||
`NotificationEventHelper.sendRequirementRejected(order, requirementId, requirementType, reason)`(`hl-order-service-v3/src/main/java/com/hulalv/shared/notification/NotificationEventHelper.java`)改前的调用链:
|
||||
|
||||
```
|
||||
sendRequirementRejected(...) → sendInternal("REQUIREMENT_REJECTED", order, params, requirementId)
|
||||
→ buildAndSend(eventCode, order, params, bizId=requirementId, userId=null)
|
||||
→ NotificationEventMessage.bizId = String.valueOf(requirementId)
|
||||
.bizType = "ORDER"
|
||||
```
|
||||
|
||||
而本类同一文件里其余三个事件(`sendContractFailed`/`sendInsuranceFailed`/`sendPaymentReminder`)走的是无 `bizId` 参数的 `send(eventCode, order, params)` 重载,该重载内部固定用 `order.getOrderId()` 作为 `bizId`——这三个事件从未受本缺陷影响,只有 `sendRequirementRejected` 在调用点显式传了 `requirementId` 顶替 `bizId`。
|
||||
|
||||
### 触发路径
|
||||
|
||||
该事件仅在下列 4 个既有写端点判定「打回目标=定制师(CONSULTANT)」时触发,端点本身的请求/响应契约本次未改动:
|
||||
|
||||
| 端点 | 触发条件 |
|
||||
|------|----------|
|
||||
| `POST /v3/admin/order/{id}/vehicle-requirement/reject`(`VehicleRequirementAdminController.java:100`) | 团期管理员打回定制师,恒触发 |
|
||||
| `POST /v3/admin/order/{id}/hotel-requirement/reject`(`HotelRequirementAdminController.java:96`) | 团期管理员打回定制师,恒触发 |
|
||||
| `POST /v3/admin/order/{id}/vehicle-requirement/supplier-reject`(`VehicleRequirementAdminController.java:120`) | 供应商打回,仅非团期(核心)订单派生目标为 CONSULTANT 时触发;团期订单派生目标为管理员,本期不通知 |
|
||||
| `POST /v3/admin/order/{id}/hotel-requirement/supplier-reject`(`HotelRequirementAdminController.java:115`) | 同上 |
|
||||
|
||||
### 数据落地路径(已核实的完整链路)
|
||||
|
||||
```
|
||||
NotificationEventHelper.buildAndSend → MQ → NotificationDispatcher.dispatchAdminInApp
|
||||
→ renderer.render(config.getInappLinkTemplate(), renderParams) (NotificationDispatcher.java:1038)
|
||||
→ AdminMessageService.createMessage(adminId, ..., link, bizId, bizType) (:1042起调用)
|
||||
→ AdminMessage 实体落库 biz_id / link (AdminMessageService.java:121-123)
|
||||
→ AdminMessageService.toVO(msg, ...):vo.setLink/setBizId/setBizType (:541-543)
|
||||
→ GET /admin/message/list、GET /admin/message/{id} 返回给前端
|
||||
```
|
||||
|
||||
`link` 与 `bizId` 在 `createMessage` 落库那一刻一次性写入行内,之后读取(无论列表还是详情)都是直接透传已落库的值,不会按当前配置重新渲染——这是下方「六、边界行为」里历史数据不回刷的直接原因。
|
||||
|
||||
---
|
||||
|
||||
## 二、变更接口清单
|
||||
|
||||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||||
|---|------|------|------|----------|------|
|
||||
| 1 | 站内信分页列表 | GET | `/admin/message/list` | 返回值语义修正 | `REQUIREMENT_REJECTED` 事件产生的消息行,`bizId` 由需求行 ID 改为 orderId,`link` 前缀同步修正;字段结构不变 |
|
||||
| 2 | 站内信详情 | GET | `/admin/message/{id}` | 返回值语义修正 | 同上 |
|
||||
|
||||
---
|
||||
|
||||
## 三、接口详情
|
||||
|
||||
### 1. 站内信分页列表 `GET /admin/message/list`
|
||||
|
||||
**VO**: `AdminMessagePageReqVO → Result<PageResult<AdminMessageRespVO>>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
站内信中心列表页拉取当前登录员工的收件箱,系统通知与聊天消息按 `createTime` 倒序混排返回。`REQUIREMENT_REJECTED` 属于系统通知(`kind=NOTIFY`),随全部系统通知混排在本接口结果里,不需要单独的查询参数。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| pageNo | Query | Integer | ❌ | ≥1,默认 1 | 页码 |
|
||||
| pageSize | Query | Integer | ❌ | 1~100,默认 20 | 每页条数 |
|
||||
| categoryCode | Query | String | ❌ | HOUSE/ORDER/SYSTEM | 消息分类编码,为空查全部;仅对系统通知有效(聊天行/团队池行该字段为 null) |
|
||||
| messageType | Query | String | ❌ | ORDER/NORMAL | 消息类型,为空查全部;按 kind 派生(ORDER=聊天/NORMAL=系统通知) |
|
||||
|
||||
本次未改动入参,照源码列出供自包含核对。
|
||||
|
||||
#### 出参 `Result<PageResult<AdminMessageRespVO>>`
|
||||
|
||||
`PageResult` 字段:`records`(List)、`total`(int)、`page`(int)、`pageSize`(int)。
|
||||
|
||||
`AdminMessageRespVO` 字段(本次仅 `bizId`/`link` 两个字段的**取值**变化,字段结构不变):
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| messageId | String | 消息ID(雪花ID,String透传防精度丢失) |
|
||||
| categoryCode | String | 消息分类编码: HOUSE=房务/ORDER=订单/SYSTEM=系统。系统通知为 SYSTEM;聊天消息(kind=CHAT)该字段为 null |
|
||||
| title | String | 消息标题 |
|
||||
| content | String | 消息内容 |
|
||||
| link | String | 跳转链接。**本次修正**:`REQUIREMENT_REJECTED` 新产生的消息此字段前缀由 `/order/detail/` 改为 `/order-v2/detail/`;前端当前不读该字段 |
|
||||
| bizId | String | 关联业务ID。**本次修正**:`REQUIREMENT_REJECTED` 新产生的消息此字段由需求行 ID 改为订单 ID |
|
||||
| bizType | String | 关联业务类型: ORDER/HOUSE/REFUND |
|
||||
| isRead | Integer | 是否已读(0未读 1已读) |
|
||||
| createTime | Long | 创建时间(时间戳) |
|
||||
| messageType | String | 消息类型(ORDER=订单消息 NORMAL=普通消息) |
|
||||
| messageTypeLabel | String | 消息类型标签(订单消息/普通消息) |
|
||||
| kind | String | 消息特性: NOTIFY=系统通知 / CHAT=聊天消息 |
|
||||
| senderName | String | 发件人姓名(仅聊天消息有值) |
|
||||
| conversationKey | String | 会话键(仅聊天消息有值) |
|
||||
| teamMessage | Boolean | 是否车务团队共享消息 |
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
{ "pageNo": 1, "pageSize": 20, "messageType": "NORMAL" }
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
字段名与类型均照 `AdminMessageRespVO` 源码;`bizId`/`orderId` 取值对齐 `NotificationEventHelperTest` 单测夹具(`baseOrder().orderId = 1001L`),用于展示修正后的取值形态:
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"records": [
|
||||
{
|
||||
"messageId": "1001",
|
||||
"categoryCode": "SYSTEM",
|
||||
"title": "需求已驳回",
|
||||
"content": "订单 1001 的用车需求已被驳回:车型不合适",
|
||||
"link": "/order-v2/detail/1001",
|
||||
"bizId": "1001",
|
||||
"bizType": "ORDER",
|
||||
"isRead": 0,
|
||||
"createTime": 1709452800000,
|
||||
"messageType": "NORMAL",
|
||||
"messageTypeLabel": "普通消息",
|
||||
"kind": "NOTIFY",
|
||||
"senderName": null,
|
||||
"conversationKey": null,
|
||||
"teamMessage": false
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"pageSize": 20
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": { "records": [], "total": 0, "page": 1, "pageSize": 20 },
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 错误响应
|
||||
|
||||
未登录/取不到 adminId 时由 `AdminRequestContextUtil.requireAdminId` 统一拦截(鉴权异常,非本次改动范围),不返回业务错误码;参数校验失败(如 `pageSize>100`)由 `@Valid` 拦截返回标准校验错误结构,本次未改动。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- WHERE 条件含 `adminId`,仅返回当前登录员工自己的收件箱;`VEHICLE_MANAGER` 角色会额外混排车务团队共享池行(`teamMessage=true`),其余角色不受影响。
|
||||
- 本次改动不影响分页、排序、已读状态、团队池行的既有行为,只影响 `REQUIREMENT_REJECTED` 类型行的 `bizId`/`link` 取值。
|
||||
- 部署生效前已产生的 `REQUIREMENT_REJECTED` 历史行,`bizId`/`link` 仍是旧值,见「六、边界行为」。
|
||||
|
||||
### 2. 站内信详情 `GET /admin/message/{id}`
|
||||
|
||||
**VO**: `Long messageId(PathVariable) → Result<AdminMessageRespVO>`
|
||||
|
||||
#### 使用场景
|
||||
|
||||
列表点开一条消息、顶部铃铛点击、详情页刷新(前端仅持 `messageId`)时按 ID 拉取单条详情,含完整正文与跳转关联业务信息。纯查询,不改变已读状态。
|
||||
|
||||
#### 入参
|
||||
|
||||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||||
|------|------|------|------|------|------|
|
||||
| id | Path | Long | ✅ | `{id}` | 消息 ID |
|
||||
|
||||
#### 出参 `Result<AdminMessageRespVO>`
|
||||
|
||||
字段与「1. 站内信分页列表」的 `AdminMessageRespVO` 表完全一致(同一 VO),此处不重复列出各字段说明;`bizId`/`link` 的修正内容同上。
|
||||
|
||||
#### 请求示例
|
||||
|
||||
```json
|
||||
GET /admin/message/1001
|
||||
```
|
||||
|
||||
#### 响应示例
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"message": "成功",
|
||||
"data": {
|
||||
"messageId": "1001",
|
||||
"categoryCode": "SYSTEM",
|
||||
"title": "需求已驳回",
|
||||
"content": "订单 1001 的用车需求已被驳回:车型不合适",
|
||||
"link": "/order-v2/detail/1001",
|
||||
"bizId": "1001",
|
||||
"bizType": "ORDER",
|
||||
"isRead": 0,
|
||||
"createTime": 1709452800000,
|
||||
"messageType": "NORMAL",
|
||||
"messageTypeLabel": "普通消息",
|
||||
"kind": "NOTIFY",
|
||||
"senderName": null,
|
||||
"conversationKey": null,
|
||||
"teamMessage": false
|
||||
},
|
||||
"success": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 空数据 / 降级响应
|
||||
|
||||
不存在空数据形态(单条查询查不到直接走下方错误响应);无下游依赖,无降级分支。
|
||||
|
||||
#### 错误响应
|
||||
|
||||
消息不存在 / 已逻辑删除 / 属于其他员工,三种情况统一返回同一错码(`AdminMessageService.getMessageDetail`,不区分原因、不泄露他人消息是否存在):
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200401,
|
||||
"message": "站内信不存在或无权访问",
|
||||
"data": null,
|
||||
"success": false
|
||||
}
|
||||
```
|
||||
|
||||
错误码定义:`UserProfileErrorCode.ADMIN_MESSAGE_NOT_FOUND`(`hl-user-service/src/main/java/com/hulalv/user/errorcode/UserProfileErrorCode.java:260`),HTTP 状态仍为 200,业务码 `200401`。本次改动未新增/修改该错误码。
|
||||
|
||||
#### 业务边界
|
||||
|
||||
- WHERE 条件含 `adminId`,越权访问一律按不存在处理(不区分"不存在"和"是别人的")。
|
||||
- `VEHICLE_MANAGER` 角色额外放行车务团队池行,其余角色查不到池行,行为不变。
|
||||
- 本次改动不影响该接口的鉴权、越权防护、错误码,只影响返回体内 `bizId`/`link` 两个字段在 `REQUIREMENT_REJECTED` 类型行上的取值。
|
||||
|
||||
---
|
||||
|
||||
## 四、契约约束与正确调用方式
|
||||
|
||||
> 本节写的是响应值契约(`bizType`/`bizId` 的配对保证),不是入参 payload 规则——本次改动不涉及请求参数。
|
||||
|
||||
### `bizType` ⇒ `bizId` 的配对保证
|
||||
|
||||
`bizType` 声明业务对象类型,`bizId` 必须是该类型对象的主键;前端据这一对字段决定跳转目标。本次修复后,`NotificationEventHelper` 发出的全部事件(`CONTRACT_FAILED`/`INSURANCE_FAILED`/`ORDER_PAYMENT_REMINDER`/`REQUIREMENT_REJECTED`)统一在 `buildAndSend` 内部写死 `bizType="ORDER"` 且 `bizId=String.valueOf(order.getOrderId())`,不再接受调用方传入任意 `bizId`——从结构上排除"声明 ORDER 却传别的 id"这一类错误。
|
||||
|
||||
| bizType | bizId 语义 | 前端应有动作 |
|
||||
|---------|-----------|-------------|
|
||||
| `ORDER` | 订单 ID | `getOrderReadableRoute(bizId, userStore)` → `/order-v2/detail/{bizId}`(或管家受限变体) |
|
||||
| `HOUSE` / `HOUSE_LEAD` | 参见前端既有路由逻辑(本次未改动) | 本次未改动 |
|
||||
| `REFUND` | 订单 ID(`RefundNotificationHelper.java:128-129` 本就正确,未改动) | 同 `ORDER` |
|
||||
|
||||
### 前端需要知道的取值边界
|
||||
|
||||
- 若 `order.getOrderId()` 为空,本次修复后**该条通知整条不投递**(不会出现 `bizId` 为字面量 `"null"` 的行);改前会投递一条 `bizId="null"` 的消息。
|
||||
- `requirementId` 不再出现在 `bizId`/`bizType` 里,如需按需求行排查,只能从 `notification_send_log.params_json` 里取(后端排查用途,前端接口不暴露该字段)。
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库行为
|
||||
|
||||
### `admin_message` 表(新产生的 `REQUIREMENT_REJECTED` 消息,示例 orderId=1001、requirementId=9009)
|
||||
|
||||
| 列 | 改前 | 改后 |
|
||||
|----|------|------|
|
||||
| `biz_id` | `9009`(需求行 ID) | `1001`(订单 ID) |
|
||||
| `biz_type` | `ORDER` | `ORDER`(未变) |
|
||||
| `link` | `/order/detail/1001` | `/order-v2/detail/1001` |
|
||||
|
||||
### `notification_send_log.params_json`
|
||||
|
||||
| 改前 | 改后 |
|
||||
|------|------|
|
||||
| 不含 `requirementId` | 新增 key `requirementId`(值为空时落空字符串 `""`,不落字面量 `"null"`) |
|
||||
|
||||
### `notification_event_config` 表(Flyway `V20260922_001`)
|
||||
|
||||
```sql
|
||||
UPDATE `notification_event_config`
|
||||
SET `inapp_link_template` = '/order-v2/detail/${orderId}',
|
||||
`update_time` = NOW()
|
||||
WHERE `event_code` = 'REQUIREMENT_REJECTED';
|
||||
```
|
||||
|
||||
按 `event_code` 唯一键定位,最多影响 1 行;只改前缀不改 `${orderId}` 占位,重复执行第二次起 affected rows 为 0(幂等)。**不改动已合入 dev-v3 的 `V20260812_001`**(Flyway 已冻结迁移不可修改,红线第 ⑫ 条)。
|
||||
|
||||
---
|
||||
|
||||
## 六、边界行为
|
||||
|
||||
- **历史数据不回刷**:`admin_message.biz_id`/`link` 在 `createMessage` 落库那一刻写死,之后读取直接透传,不会按当前配置重新渲染。修复部署生效前已产生的 `REQUIREMENT_REJECTED` 历史站内信,`biz_id` 仍是需求行 ID、`link` 仍是 `/order/detail/...`,两个字段都是旧值,点「跳转」仍会落空。这批数据只存在于测试环境(order-v3/fleet 尚未上生产,该事件在生产从未产生过消息),不做批量回刷。
|
||||
- **测试发送端点有意不对齐**:`POST /admin/notification/config/{id}/test`(`AdminNotificationController.java:139-140`)固定写 `bizId="TEST-" + System.currentTimeMillis()`、`bizType="ORDER"`,走真实分发路径,会在 `admin_message` 落一条 `biz_id` 为 `TEST-17...` 形态的行,点「跳转」必然落空。本单**有意不修**(它是管理端调试工具,不代表真实业务对象)。
|
||||
- **供应商打回分支不总触发通知**:`supplier-reject` 两个端点仅当打回目标解析为 CONSULTANT(非团期订单)时才触发该事件;团期订单打回目标为管理员,本期不发通知(既有行为,本次未改动)。
|
||||
- **orderId 为空时整条不投递**:`order.getOrderId()==null` 时直接跳过发送(不产生该条通知),而不是发一条 `bizId` 为字面量 `"null"` 的消息。
|
||||
- **`link` 字段当前是死字段**:前端 `jumpToBiz`(`hl-ui/src/views/notification/MyMessages/index.vue:268-286`)只读 `bizId`/`bizModule`/`bizType`/`peerRole`,全函数不出现 `link`。`link` 前缀对齐不改变任何现有跳转行为,只是让该字段不再是一个过期契约值。
|
||||
|
||||
## 六.5、枚举 / 数据字典
|
||||
|
||||
### `bizType`(`AdminMessageRespVO.bizType`)
|
||||
|
||||
**所属字段**: `bizId`/`bizType` 成对出现 | **类型**: `String`
|
||||
|
||||
| 值 | 中文 | 说明 |
|
||||
|----|------|------|
|
||||
| `ORDER` | 订单 | `bizId` 为订单 ID;本次修复的对象——`REQUIREMENT_REJECTED` 事件恒为该值 |
|
||||
| `HOUSE` | 房务 | 本次未改动 |
|
||||
| `REFUND` | 退款 | `RefundNotificationHelper` 发出,`bizId` 本就是订单 ID,未受本次缺陷影响 |
|
||||
|
||||
## 六.6、修改前后对比
|
||||
|
||||
### 字段级对比
|
||||
|
||||
| 字段 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| `admin_message.biz_id`(REQUIREMENT_REJECTED 新消息) | 需求行 ID(如 `9009`) | 订单 ID(如 `1001`) |
|
||||
| `admin_message.link`(REQUIREMENT_REJECTED 新消息) | `/order/detail/{orderId}` | `/order-v2/detail/{orderId}` |
|
||||
| `notification_send_log.params_json` | 不含 `requirementId` | 含 `requirementId`(空值落 `""`) |
|
||||
|
||||
### 行为级对比
|
||||
|
||||
| 行为 | 改前 | 改后 |
|
||||
|------|------|------|
|
||||
| 前端点「跳转」(`REQUIREMENT_REJECTED` 类消息) | 拿需求行 ID 当 orderId 查订单,查不到,跳转落空 | 拿正确的 orderId 查订单,正常打开订单详情 |
|
||||
| `order.getOrderId()` 为空 | 仍发送,`bizId` 落字面量 `"null"` | 直接跳过发送,不产生该条通知 |
|
||||
|
||||
## 六.7、影响评估
|
||||
|
||||
- **是否破坏向后兼容**: 否——`AdminMessageRespVO` 字段结构未变,仅 `REQUIREMENT_REJECTED` 一种事件的 `bizId`/`link` 取值修正;历史行不追溯改写。
|
||||
- **前端是否必须同步上线**: 否——字段名/类型/位置均未变,现有读取代码(`jumpToBiz` 等)无需任何修改即可正确工作。
|
||||
- **前端 workaround 清理点**: 未查证前端是否针对该缺陷做过规避(例如捕获跳转 404 静默失败、或对 `REQUIREMENT_REJECTED` 类消息隐藏「跳转」按钮);若存在,现在可以移除,但本次未在 hl-ui 找到此类代码痕迹。
|
||||
|
||||
## 七、不影响范围
|
||||
|
||||
- **仅影响**: `REQUIREMENT_REJECTED` 事件写入 `admin_message` 的 `biz_id`/`link` 取值,及其对应的 `notification_event_config.inapp_link_template` 前缀。
|
||||
- **零影响**:
|
||||
- `AdminMessageRespVO`/`AdminMessagePageReqVO` 的字段结构(名称/类型/位置)
|
||||
- `GET /admin/message/list`、`GET /admin/message/{id}` 之外的全部站内信接口(未读总数、分类未读汇总、标记已读、全部已读、分类已读、删除)——入参/出参/错误码均未改动
|
||||
- 4 个触发该通知的写端点(两个 `.../reject`、两个 `.../supplier-reject`)自身的入参、出参、错误码——这些端点本身代码本次未改动,只是它们触发的通知副作用取值变了
|
||||
- 本类其余 3 个事件(`CONTRACT_FAILED`/`INSURANCE_FAILED`/`ORDER_PAYMENT_REMINDER`)——这 3 个事件改前就已经用 `order.getOrderId()` 作为 `bizId`(走的是无 `bizId` 参数的 `send()` 重载),未受本次缺陷影响;本次改动只是把这个既有正确取值从「各方法各自传入」统一收敛到 `buildAndSend` 内部写死,行为不变
|
||||
- `RefundNotificationHelper` 发出的退款类事件(`bizId=orderId` 本就正确),未改动
|
||||
- C 端 `user_message` 收件箱(`dispatchInApp`/`InternalMpMessageController`):`REQUIREMENT_REJECTED` 不带 `userId`,不投递 C 端,与本次改动无关
|
||||
|
||||
---
|
||||
|
||||
## 八、测试环境已验证
|
||||
|
||||
**部署**:hl-order-service-v3 与 hl-user-service 均已部署到 `dev-v3` 的 `6af4d93e5`,双实例滚动完成(order-v3 8086/8186、user-service 8081/8181,Nacos `namespaceId=test` 四个实例均 `healthy:true`)。`deploy-status.sh` 复核:两服务 `COMMIT=6af4d93e5 BEHIND=0/N STATE=ok`,部署时间 2026-09-22 11:35:43 / 11:36:25。
|
||||
|
||||
**Flyway 迁移**(`hl-user-service-8181.log`):
|
||||
|
||||
```
|
||||
11:36:32.835 [main] INFO o.f.core.internal.command.DbMigrate - Migrating schema `hl_user_service` to version "20260922.001 - fix requirement rejected inapp link prefix"
|
||||
11:36:32.859 [main] INFO o.f.core.internal.command.DbMigrate - Successfully applied 1 migration to schema `hl_user_service`, now at version v20260922.001 (execution time 00:00.035s)
|
||||
```
|
||||
|
||||
**端到端实测**(自建夹具:orderId=2102242483750756354 / orderNo=HL20260922114400396 / 需求行 id=2102243384951558146)——提交用车需求 → 车控驳回 → 站内信产出。新产生的 `admin_message` 行:
|
||||
|
||||
| 字段 | 实测值 |
|
||||
|---|---|
|
||||
| `biz_type` | `ORDER` |
|
||||
| `biz_id` | `2102242483750756354`(= orderId,**不是**需求行 id 2102243384951558146) |
|
||||
| `link` | `/order-v2/detail/2102242483750756354` |
|
||||
| `title` | 您提交的用车需求被驳回 |
|
||||
| `create_time` | 2026-09-22 11:47:51 |
|
||||
|
||||
分发日志(`hl-user-service-8181.log`):
|
||||
|
||||
```
|
||||
11:47:51.310 c.h.u.n.NotificationDispatcher - 分发通知事件: eventCode=REQUIREMENT_REJECTED, userId=null, bizId=2102242483750756354
|
||||
11:47:51.325 c.h.user.service.AdminMessageService - 创建管理端站内信: adminId=1001, categoryCode=SYSTEM, eventCode=REQUIREMENT_REJECTED, title=您提交的用车需求被驳回
|
||||
```
|
||||
|
||||
**需求行 ID 仍可反查**:同一次事件的 `notification_send_log.params_json` 实测落库,含 `"requirementId":"2102243384951558146"`,与夹具需求行 id 一致——需求行 ID 从 `bizId` 换到 `params` 后没有丢失(但它**不在**站内信接口的响应里,详见四、契约约束)。
|
||||
|
||||
**事件配置**:`notification_event_config.inapp_link_template` 实测为 `/order-v2/detail/${orderId}`(字面量,Flyway `placeholder-replacement: false`),占位符在 `NotificationDispatcher` 运行时渲染。
|
||||
|
||||
**定向单测**(`dev-v3` @ `6af4d93e5`):
|
||||
|
||||
| 测试类 | 读数 |
|
||||
|---|---|
|
||||
| `NotificationEventHelperTest` | Tests run: 10, Failures: 0, Errors: 0, Skipped: 0 |
|
||||
| `RequirementServiceTest` | Tests run: 321, Failures: 0, Errors: 0, Skipped: 0 |
|
||||
| `LayerEnforcementTest` | Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 |
|
||||
| `RedLineArchTest` | Tests run: 12, Failures: 0, Errors: 0, Skipped: 0 |
|
||||
|
||||
合计 `Tests run: 348, Failures: 0, Errors: 0, Skipped: 0`,`BUILD SUCCESS`。
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 十、相关文档
|
||||
|
||||
- 关联 Issue: [wx/HL#8155](https://git.1814.love:8443/wx/HL/issues/8155)
|
||||
- 关联 PR: [wx/HL#8160](https://git.1814.love:8443/wx/HL/pulls/8160)
|
||||
|
||||
## 关联 / 联系人
|
||||
|
||||
### 链接
|
||||
|
||||
- **Issue**: [#8155](https://git.1814.love:8443/wx/HL/issues/8155)
|
||||
- **PR**: [#8160](https://git.1814.love:8443/wx/HL/pulls/8160)
|
||||
- **Merge commit**: [6af4d93e5](https://git.1814.love:8443/wx/HL/commit/6af4d93e5)
|
||||
|
||||
### 联系人
|
||||
|
||||
- **后端负责人**: @wx
|
||||
在新工单中引用
屏蔽一个用户