1034 行
40 KiB
Markdown
1034 行
40 KiB
Markdown
---
|
||
schema: "hl-changelog/v2"
|
||
ticket: "7440"
|
||
title: "团期车务派单只读入口——看板三端点 + 内部读接口 + GROUP_FLEET 双团会话"
|
||
consumer: "admin"
|
||
author: "wx(GIT)"
|
||
change_type: "新增接口"
|
||
backend_status: "deployed"
|
||
gateway_status: "verified"
|
||
frontend_status: "verified"
|
||
frontend_owner: "mmg"
|
||
frontend_ref: "5aea88d4"
|
||
target_release: ""
|
||
verified_at: "2026-09-13"
|
||
status_note: "2026-09-11 订正:六.8 节的角色授予范围已随 PR #7514(V20260911_007)收回 ADMIN,正文已更新为当前状态;接口契约、错误码与行为均未变。 前端已交付(2026-09-13):新建 fleet/group-dispatch 只读模块(待配车清单+单团总览+联系团期管理员 GROUP_FLEET 会话);resource-schedule 排班端点本轮缓建(本单无配车编辑入口);权限可见即可点+403 兜底,不接 fleet:group-dispatch:view 显隐。"
|
||
updated_at: "2026-09-11"
|
||
base: "dev-v3"
|
||
---
|
||
|
||
# fleet/order-v3/user: 团期车务派单只读入口
|
||
|
||
**服务**: hl-fleet-service / hl-order-service-v3 / hl-user-service
|
||
**PR**: #7506
|
||
**Issue**: #7440
|
||
|
||
---
|
||
|
||
## ⚠️ 关键变化
|
||
|
||
本单仅含只读入口,不含配车写入、不含派单进度回写(配车写入与派单进度回写在后续工单)。
|
||
|
||
🔴 **双侧未读必须分开取,传错不报错、只是拿到对方的数字**:车务侧红点取 `unreadScope=TEAM_FLEET`(`admin_id=-1` 水位),团期管理员侧取 `unreadScope=TEAM`(`admin_id=0` 水位)。同一个 `groupBatchId`、两个 scope 会各自返回一个看着都合理的数——接口不会因为传错而报错或返0,前端按当前登录角色选对 scope 是唯一正确性保障。详见 10 号端点。
|
||
|
||
🔴 **`GROUP_FLEET:{groupBatchId}` 与 `FLEET:{orderId}` 是两条互不相干的会话,别混成一个入口**:`GROUP_FLEET:{groupBatchId}` 是本单新建的团期配车团级会话(车务团队 ↔ 团期管理员团队);接送机沟通**仍走已有的 `FLEET:{orderId}`**(一个订单一条车务会话),本单**不改**该链路——对团期子订单调 `open-fleet` 仍返回 `FLEET:{orderId}`,不会因为该订单属于某个团期就产生任何 `GROUP_FLEET` 记录。
|
||
|
||
---
|
||
|
||
## 一、背景
|
||
|
||
本单新增3个admin只读端点供fleet读取团期需求主数据(order-v3)与服务日,以及2个/internal/端点供fleet内部调用。
|
||
|
||
---
|
||
|
||
## 二、变更接口清单
|
||
|
||
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|
||
|---|------|------|------|----------|------|
|
||
| 1 | 待配车团期清单 | GET | `/admin/fleet/group-dispatch/pending-batches` | 新增 | 管理后台车务页首屏清单 |
|
||
| 2 | 团期配车总览 | GET | `/admin/fleet/group-dispatch/batches/{groupBatchId}/overview` | 新增 | 单团详情页派车总览 |
|
||
| 3 | 资源排班 | GET | `/admin/fleet/group-dispatch/resource-schedule` | 新增 | 派单编辑时查资源占用 |
|
||
| 4 | 打开团期配车会话 | POST | `/admin/message/chat/open-group-fleet` | 新增 | 车务↔团期管理员团级会话 |
|
||
| 5 | 待配车候选分页 | POST | `/v3/internal/group-batch/vehicle-dispatch-candidates` | 新增 | 服务间接口(order-v3),前端无需对接,随附供核对契约边界 |
|
||
| 6 | 单团用车覆盖 | GET | `/v3/internal/group-batch/{groupBatchId}/vehicle-coverage` | 新增 | 服务间接口(order-v3),前端无需对接,随附供核对契约边界 |
|
||
| 7 | 拉线程消息 | GET | `/admin/message/chat/{conversationKey}/messages` | 改造 | 对 `GROUP_FLEET:` 键授权分流,路径/入参/响应结构不变 |
|
||
| 8 | 发一条消息 | POST | `/admin/message/chat/{conversationKey}/messages` | 改造 | 新增团级投递分支,路径/入参/响应结构不变 |
|
||
| 9 | 标记已读到最新 | POST | `/admin/message/chat/{conversationKey}/read` | 改造 | 双侧水位各自推进,路径/入参/响应结构不变 |
|
||
| 10 | 批量业务对象未读 | POST | `/internal/message/chat/biz-unread-batch` | 改造 | `unreadScope` 新增 `TEAM_FLEET` 取值,请求体不加字段 |
|
||
|
||
---
|
||
|
||
## 三、接口详情
|
||
|
||
### 1. 待配车团期清单 `GET /admin/fleet/group-dispatch/pending-batches`
|
||
|
||
**VO**: `GroupDispatchPendingBatchPageReqVO → Result<PageResult<GroupDispatchPendingBatchRespVO>>`
|
||
|
||
#### 使用场景
|
||
|
||
管理后台车务-团期配车页面的首屏清单。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| departDateFrom | Query | LocalDate | ❌ | - | 出发日下界 |
|
||
| departDateTo | Query | LocalDate | ❌ | - | 出发日上界 |
|
||
| keyword | Query | String | ❌ | ≤50字符 | 团号/团名 |
|
||
| dispatchProgress | Query | String | ❌ | NOT_STARTED/PARTIAL/FULL | 进度过滤 |
|
||
| page | Query | Integer | ❌ | ≥1 | 页码(默认1) |
|
||
| pageSize | Query | Integer | ❌ | 1-100 | 每页条数(默认20) |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| groupBatchId | Long | 团期ID |
|
||
| batchNo | String | 团号 |
|
||
| batchName | String | 团名 |
|
||
| departDate | LocalDate | 出团日 |
|
||
| dispatchProgress | String | 配车进度 |
|
||
| unreadCount | Integer | 车务未读数 |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"departDateFrom": "2026-09-01",
|
||
"departDateTo": "2026-09-30",
|
||
"page": 1,
|
||
"pageSize": 20
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"total": 15,
|
||
"records": [
|
||
{
|
||
"groupBatchId": 1934567890123456789,
|
||
"batchNo": "GB-26-0912-01",
|
||
"batchName": "额吉的故乡 9/12 团",
|
||
"departDate": "2026-09-12",
|
||
"dispatchProgress": "PARTIAL",
|
||
"unreadCount": 3
|
||
}
|
||
]
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {"total": 0, "records": []},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 600013,
|
||
"message": "排班查询参数非法: departDateFrom 不能晚于 departDateTo",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"code": 600012,
|
||
"message": "团期配车基线不可达,请稍后重试",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
**降级链路**:600013 由本端点自身入参校验抛出(`page<1`/`pageSize`不在1-100/出发日区间倒挂/keyword超50字符/`dispatchProgress`枚举非法,五种情形共用同一个码)。600012 是本端点对外唯一的「上游不可达」码——内部经 Feign 调用 order-v3 团期用车候选口(`POST /v3/internal/group-batch/vehicle-dispatch-candidates`),该内部接口对同一批入参约束会独立抛 `809401 查询参数非法`(`GroupBatchVehicleReadErrorCode.java:38`),但本端点已先行校验挡在前面(不会把非法参数带给 Feign),且 `fetchCandidatesOrFail`(`GroupDispatchQueryService.java:314-336`)对 Feign 返回的任何非成功 Result(含 809401)一律失败关闭转成 600012——调用方看不到 809401 原始码。
|
||
|
||
#### 业务边界
|
||
|
||
- 进度是fleet内存过滤,故先分页再过滤,单页可能少于pageSize
|
||
- unreadCount软依赖,user-service不可达时退0
|
||
|
||
---
|
||
|
||
### 2. 团期配车总览 `GET /admin/fleet/group-dispatch/batches/{groupBatchId}/overview`
|
||
|
||
**VO**: `Long → Result<GroupDispatchOverviewRespVO>`
|
||
|
||
#### 使用场景
|
||
|
||
单团详情页的派车总览。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| groupBatchId | Path | Long | ✅ | - | 团期ID |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| groupBatchId | Long | 团期ID |
|
||
| serviceDates | List<LocalDate> | 权威服务日 |
|
||
| days | List | 逐日派车状态 |
|
||
| transferPendingTotal | Integer | 全团接送机未配计数 |
|
||
| conversationKey | String | 会话键 |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
GET /admin/fleet/group-dispatch/batches/1934567890123456789/overview
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"groupBatchId": 1934567890123456789,
|
||
"serviceDates": ["2026-09-12", "2026-09-13", "2026-09-14"],
|
||
"days": [
|
||
{"serviceDate": "2026-09-12", "dispatched": true}
|
||
],
|
||
"transferPendingTotal": 3,
|
||
"conversationKey": "GROUP_FLEET:1934567890123456789"
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
无。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 600012,
|
||
"message": "团期配车基线不可达,请稍后重试",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
**降级链路(🔴 本端点对外只有 600012 这一个码)**:内部经 Feign 调用 order-v3 单团用车覆盖口(`GET /v3/internal/group-batch/{groupBatchId}/vehicle-coverage`),团期不存在或已软删时该内部接口会抛 `809400 团期不存在`(`GroupBatchVehicleReadErrorCode.java:28`);但 `fetchCoverageOrFail`(`GroupDispatchQueryService.java:526-537`)把 Feign 返回的任何非成功 Result(含 809400)一律失败关闭转成 600012——本端点**不透出 809400**,调用方只会看到 600012,不要按「overview 会返 809400」处理。
|
||
|
||
#### 业务边界
|
||
|
||
- serviceDates是权威口径,fleet不得自己铺行程日
|
||
|
||
---
|
||
|
||
### 3. 资源排班 `GET /admin/fleet/group-dispatch/resource-schedule`
|
||
|
||
**VO**: `ResourceScheduleListReqVO → Result<List<ResourceScheduleRespVO>>`
|
||
|
||
#### 使用场景
|
||
|
||
派单编辑时查询车/司机的排班占用。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| resourceType | Query | String | ✅ | VEHICLE/DRIVER | 资源维度 |
|
||
| resourceIds | Query | List<Long> | ✅ | 1-50个 | 资源ID集合 |
|
||
| dateFrom | Query | LocalDate | ✅ | - | 起始日 |
|
||
| dateTo | Query | LocalDate | ✅ | ≤31天 | 结束日 |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| resourceType | String | 资源维度 |
|
||
| resourceId | Long | 资源ID |
|
||
| resourceLabel | String | 车牌/司机名 |
|
||
| days | List | 排班日历 |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
GET /admin/fleet/group-dispatch/resource-schedule?resourceType=VEHICLE&resourceIds=1&dateFrom=2026-09-12&dateTo=2026-09-30
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": [
|
||
{
|
||
"resourceType": "VEHICLE",
|
||
"resourceId": 1,
|
||
"resourceLabel": "蒙A12345",
|
||
"days": []
|
||
}
|
||
],
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
返回空days列表。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 600014,
|
||
"message": "不支持的资源类型: BOAT",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"code": 600013,
|
||
"message": "排班查询参数非法: 查询跨度不能超过 31 天",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
**触发条件**:600014 仅在 `resourceType` 不是 `VEHICLE`/`DRIVER` 时抛出(示例中 `BOAT` 为非法值占位)。600013 覆盖:`resourceIds` 为空 / 超 50 个、`dateFrom`/`dateTo` 为空、日期倒置、跨度超 31 天,五种情形共用同一个码。本端点只读本域两张业务表,不经 Feign 调用 order-v3,故与 809400/809401 无关。
|
||
|
||
#### 业务边界
|
||
|
||
- 不分页,规模由参数上限封死
|
||
- 返回顺序必须与入参顺序相同
|
||
|
||
---
|
||
|
||
### 4. 打开团期配车会话 `POST /admin/message/chat/open-group-fleet`
|
||
|
||
**VO**: `ChatOpenGroupFleetReqVO → Result<ChatOpenFullRespVO>`
|
||
|
||
#### 使用场景
|
||
|
||
派单看板打开团期的车务↔团期管理员会话。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| groupBatchId | Body | Long | ✅ | - | 团期ID |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| conversationKey | String | 会话键 |
|
||
| conversationId | Long | 会话ID |
|
||
| currentSide | String | GROUP_ADMIN / VEHICLE_TEAM |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"groupBatchId": 1934567890123456789
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"conversationKey": "GROUP_FLEET:1934567890123456789",
|
||
"conversationId": 1934567890123456789,
|
||
"currentSide": "GROUP_ADMIN"
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
无。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 281018,
|
||
"message": "无权访问该团期配车会话",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"code": 281019,
|
||
"message": "团期不存在、已结算或已取消,无法发起配车会话",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
**分工**:281018 = 有登录态但当前角色既非 `VEHICLE_MANAGER`(车务侧)也不持 `group-batch:demand:confirm`(团期管理员侧,超管天然通过)——准入完全按 token 当前角色判,与是否为会话成员无关(`TeamChatAuthorizationService.java:158-169`)。281019 = 团期本身不可联系(不存在/已软删/已结算/已取消),前置条件与 `GROUP_HOUSE` 用的 281017 同源(`TeamChatModule.java:75`)。**281019 只卡 open(本端点)/ send,`messages`/`read` 仍放行**(`requirePrecondition` 参数为 false,见 `ChatMessageController.java` 的 `/{conversationKey}/messages`)——终态团期的历史会话仍可审计读取,前端不要因为拿到过 281019 就连历史消息一起屏蔽。
|
||
|
||
281016「缺少团期上下文」(`ChatErrorCode.java:76`)存在但正常路径触发不到:`groupBatchId` 由请求体 VO 的 `@NotNull` + Controller 的 `@Valid` 先挡成 100001,281016 只在 internal / 单测直调 Service 时才会真抛(见 `ChatManager.java:444-446` 注释),前端按正常表单提交不会遇到它。
|
||
|
||
#### 业务边界
|
||
|
||
- 双团队会话
|
||
- 前置:团期活跃
|
||
|
||
---
|
||
|
||
### 5. 待配车候选分页 `POST /v3/internal/group-batch/vehicle-dispatch-candidates`
|
||
|
||
**VO**: `GroupBatchVehicleDispatchCandidateReqDTO → Result<PageResult<GroupBatchVehicleDispatchCandidateDTO>>`
|
||
|
||
#### 使用场景
|
||
|
||
供 `hl-fleet-service` 的「待配车团期清单」(本文档 1 号端点)内部经 Feign 调用,拉团期主数据与权威服务日;**仅限服务间调用,前端不直接对接**,本节随附是为让消费方核对契约边界,不是要求前端联调本接口。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| departDateFrom | Body | LocalDate | ❌ | - | 出发日下界(含),不传=不限 |
|
||
| departDateTo | Body | LocalDate | ❌ | - | 出发日上界(含),不传=不限;与 From 同传须 From≤To,否则 809401 |
|
||
| keyword | Body | String | ❌ | trim 后 ≤50 字符 | 团号/团名模糊,超长 809401 |
|
||
| requirementConfirmed | Body | Boolean | ❌ | - | 只返需求已确认的团期,不传=不限 |
|
||
| vehicleReady | Body | Boolean | ❌ | - | 只返配车未就绪的团期,不传=不限 |
|
||
| batchStatuses | Body | List<String> | ❌ | - | 团期状态白名单,不传/空=提供方默认成团后阶段集 |
|
||
| page | Body | Integer | ❌ | ≥1,不传默认1 | 传了但<1抛809401(不静默纠正) |
|
||
| pageSize | Body | Integer | ❌ | 1-100,不传默认20 | 传了但越界抛809401(不截断) |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| groupBatchId | Long(转String) | 团期主订单 ID |
|
||
| batchNo | String | 团号 |
|
||
| batchName | String | 团名 |
|
||
| batchStatus | String | 团期生命周期状态(透传) |
|
||
| departDate | LocalDate | 出团日 |
|
||
| endDate | LocalDate | 返团日 |
|
||
| serviceDates | List<LocalDate> | 权威服务日集合(与 dispatch-baseline 同源同算法) |
|
||
| enrolledOrders | Integer | 在团子订单数 |
|
||
| enrolledPeople | Integer | 在团人数 |
|
||
| requirementConfirmed | Boolean | 整团需求是否已确认 |
|
||
| vehicleReady | Boolean | 配车是否已就绪 |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"departDateFrom": "2026-09-01",
|
||
"departDateTo": "2026-09-30",
|
||
"requirementConfirmed": true,
|
||
"vehicleReady": false,
|
||
"page": 1,
|
||
"pageSize": 20
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"total": 12,
|
||
"list": [
|
||
{
|
||
"groupBatchId": "1934567890123456789",
|
||
"batchNo": "GB-26-0912-01",
|
||
"batchName": "额吉的故乡 9/12 团",
|
||
"batchStatus": "RESOURCE_PREPARING",
|
||
"departDate": "2026-09-12",
|
||
"endDate": "2026-09-16",
|
||
"serviceDates": ["2026-09-12", "2026-09-13", "2026-09-14", "2026-09-15", "2026-09-16"],
|
||
"enrolledOrders": 6,
|
||
"enrolledPeople": 17,
|
||
"requirementConfirmed": true,
|
||
"vehicleReady": false
|
||
}
|
||
]
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {"total": 0, "list": []},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 809401,
|
||
"message": "查询参数非法",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- **本 DTO 无 Bean Validation 注解**(`GroupBatchVehicleDispatchCandidateReqDTO` javadoc 明写),全部校验在 `GroupBatchVehicleDispatchQueryService` 的私有方法里 fail-closed 完成;绕过该 Service 直接构造本 DTO 调用的消费方不受任何保护。
|
||
- page/pageSize 越界**不做静默纠正/截断**,一律 809401——截断会让调用方以为拿到了完整结果。
|
||
- 出发日区间倒挂在 SQL 上恒空结果集,本端点选择直接判 809401 而不是静默返空页。
|
||
- 本端点**不含任何配车进度字段**(已排车日数/进度枚举/接送机未配计数),这些是 fleet 侧业务表的事实,由 fleet 拿到候选后自己在内存合并。
|
||
|
||
---
|
||
|
||
### 6. 单团用车覆盖 `GET /v3/internal/group-batch/{groupBatchId}/vehicle-coverage`
|
||
|
||
**VO**: `Long → Result<GroupBatchVehicleCoverageDTO>`
|
||
|
||
#### 使用场景
|
||
|
||
供 `hl-fleet-service` 的「团期配车总览」(本文档 2 号端点)内部经 Feign 调用,拉逐户用车覆盖与接送机声明。**仅限服务间调用,前端不直接对接**。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| groupBatchId | Path | Long | ✅ | - | 团期主订单 ID |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| groupBatchId | Long(转String) | 团期主订单 ID |
|
||
| batchNo / batchName | String | 团号 / 团名 |
|
||
| departDate / endDate | LocalDate | 出团日 / 返团日 |
|
||
| serviceDates | List<LocalDate> | 权威服务日集合(与 dispatch-baseline 同源,消费方不另算) |
|
||
| requirementConfirmed / vehicleReady | Boolean | 团期两个标志位 |
|
||
| orders | List<OrderVehicleCoverageItemDTO> | 逐户覆盖项,见下 |
|
||
|
||
`orders[]` 逐项字段:`orderId`(Long转String)/ `orderNo` / `customerName` / `headcount`(Integer)/ `vehicleControlStatus` / `travelRequirementId`(Long转String,无有效需求为null)/ `travelRequirementStatus`(无有效需求为null)/ `transferDeclared`(Boolean)/ `transferArrivalDates`(List<LocalDate>,原始声明值)/ `transferDepartureDates`(List<LocalDate>,原始声明值)。
|
||
|
||
#### 请求示例
|
||
|
||
```http
|
||
GET /v3/internal/group-batch/1934567890123456789/vehicle-coverage
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"groupBatchId": "1934567890123456789",
|
||
"batchNo": "GB-26-0912-01",
|
||
"batchName": "额吉的故乡 9/12 团",
|
||
"departDate": "2026-09-12",
|
||
"endDate": "2026-09-16",
|
||
"serviceDates": ["2026-09-12", "2026-09-13", "2026-09-14", "2026-09-15", "2026-09-16"],
|
||
"requirementConfirmed": true,
|
||
"vehicleReady": false,
|
||
"orders": [
|
||
{
|
||
"orderId": "1934567890123400000",
|
||
"orderNo": "26-0503",
|
||
"customerName": "赵先生",
|
||
"headcount": 3,
|
||
"vehicleControlStatus": "PENDING_REVIEW",
|
||
"travelRequirementId": "1934567890123400001",
|
||
"travelRequirementStatus": "PENDING_REVIEW",
|
||
"transferDeclared": true,
|
||
"transferArrivalDates": ["2026-09-11"],
|
||
"transferDepartureDates": ["2026-09-17"]
|
||
}
|
||
]
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
无(团期不存在直接 809400,不返回空对象)。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 809400,
|
||
"message": "团期不存在",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- 团期不存在或已软删一律 809400,fleet 侧据此失败关闭,**不得渲染成空覆盖**。
|
||
- `transferArrivalDates` / `transferDepartureDates` 给**未过滤的原始声明值**——不得复用 `PickupDropoffGateResolver.arrivalPickupRequiredDates` 那套与行程日取交集的口径,那会把窗外(到达日=出发日前一天、离开日=返团日后一天)的接送日整体滤掉,导致车务看到的缺口凭空少一半。
|
||
- `travelRequirementId` / `travelRequirementStatus` **当前口径是该户唯一活跃用车需求,不区分 TRAVEL/TRANSFER**(`order_vehicle_requirement.requirementType` 目前只有 HOTEL/VEHICLE/ALL,接送机是同一行需求上的 pickup_required/dropoff_required 标志位),待 #7441 引入需求类型区分后收口,字段名与当前语义暂不完全一致。
|
||
|
||
---
|
||
|
||
**改造接口**(以下 4 个端点路径、入参、响应结构**全部不变**,变的是授权判定 / 投递分支 / 已读水位 / `unreadScope` 可选值——前端按现有对接方式不动代码即可,仅需理解下列行为差异)
|
||
|
||
### 7. 拉线程消息(授权分流) `GET /admin/message/chat/{conversationKey}/messages`
|
||
|
||
**VO**: `ChatThreadPageReqVO → Result<ChatThreadRespVO>`
|
||
|
||
#### 使用场景
|
||
|
||
会话内上滑加载更早历史消息(首屏消息由 open 系列接口已带出)。本次改造只影响 `GROUP_FLEET:{groupBatchId}` 键的鉴权路径,FLEET/GROUP/GROUP_HOUSE/DIRECT 四类既有键的调用方式与行为**逐字不变**。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| conversationKey | Path | String | ✅ | - | 会话键;本单场景传 `GROUP_FLEET:{groupBatchId}` |
|
||
| beforeId | Query | Long | ❌ | - | 游标,拉该messageId之前的历史;首屏不传=拉最新一页 |
|
||
| pageSize | Query | Integer | ❌ | 1-50,默认20 | 每页条数 |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| conversationKey | String | 会话键 |
|
||
| hasMore | Boolean | 是否还有更早历史 |
|
||
| nextCursor | Long | 下次游标(本页最小messageId),无更多为空 |
|
||
| list | List<ChatMessageRespVO> | 消息列表(按时间正序),逐项含messageId/senderAdminId/senderName/senderRole/msgType/priority/content/isMine/readByPeer/sentAt |
|
||
|
||
#### 请求示例
|
||
|
||
```http
|
||
GET /admin/message/chat/GROUP_FLEET:1934567890123456789/messages?pageSize=20
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"conversationKey": "GROUP_FLEET:1934567890123456789",
|
||
"hasMore": false,
|
||
"nextCursor": null,
|
||
"list": [
|
||
{
|
||
"messageId": 900001,
|
||
"senderAdminId": 205,
|
||
"senderName": "车务·老王",
|
||
"senderRole": "VEHICLE_TEAM",
|
||
"msgType": "TEXT",
|
||
"priority": "NORMAL",
|
||
"content": "9/12 那天大巴上午先送机场",
|
||
"isMine": true,
|
||
"readByPeer": false,
|
||
"sentAt": "2026-09-11 09:02:31"
|
||
}
|
||
]
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {"conversationKey": "GROUP_FLEET:1934567890123456789", "hasMore": false, "nextCursor": null, "list": []},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 281018,
|
||
"message": "无权访问该团期配车会话",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- **改前**:无 `GROUP_FLEET` 键这回事。**改后**:`teamChatAuthorizationService.assertConversationAccess` 对 `GROUP_FLEET:` 前缀分流到团级授权——按 `groupBatchId` 判团期存在,再判当前 token 角色是否属于车务侧(`VEHICLE_MANAGER`)或团期管理员侧(持 `group-batch:demand:confirm`),两者都不是返 **281018**;**不解析订单、不查订单摘要**。
|
||
- **281019 在本端点不生效**——团期终态/不存在只卡 `open` 与 `send`,历史消息仍可审计读取。
|
||
- FLEET/GROUP/GROUP_HOUSE/DIRECT 四类既有键的授权行为逐字不变,281001(会话不存在)/281002(非成员)两个既有码继续适用。
|
||
|
||
---
|
||
|
||
### 8. 发一条消息(新增团级投递分支) `POST /admin/message/chat/{conversationKey}/messages`
|
||
|
||
**VO**: `ChatMessageSendReqVO → Result<ChatMessageSendRespVO>`
|
||
|
||
#### 使用场景
|
||
|
||
在 `GROUP_FLEET` 会话发一条文本/图片消息。本次改造只新增一条团级投递分支,`ChatMessageSendReqVO` 的字段与既有校验**逐字不变**。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| conversationKey | Path | String | ✅ | - | 会话键 |
|
||
| content | Body | String | ✅ | `@NotBlank`;长度>1000 抛281011(Service层校验,非Bean Validation注解) | 文本内容;命中本地敏感词替换为`*`,不报错 |
|
||
| priority | Body | String | ❌ | `NORMAL` / `URGENT` | 优先级,默认NORMAL |
|
||
| msgType | Body | String | ❌ | `TEXT` / `IMAGE` / `SYSTEM` | 消息类型,默认TEXT |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| messageId | Long | 新消息ID |
|
||
| conversationKey | String | 会话键 |
|
||
| sentAt | String | 发送时间(yyyy-MM-dd HH:mm:ss) |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"content": "9/12 那天大巴上午先送机场",
|
||
"msgType": "TEXT",
|
||
"priority": "NORMAL"
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"messageId": 900001,
|
||
"conversationKey": "GROUP_FLEET:1934567890123456789",
|
||
"sentAt": "2026-09-11 09:02:31"
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
无。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 281011,
|
||
"message": "消息内容不合法",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"code": 281018,
|
||
"message": "无权访问该团期配车会话",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- **改前**:无 `GROUP_FLEET` 分支。**改后**:`ChatMessageService.sendInternal`(`:216`)对 `GROUP_FLEET` 键新增判据 `dualTeam = authorizedTeam && keyModule.isDualTeam()`(`:230`):收件方**恒取对侧占位adminId**——车务发→`admin_message.admin_id=0`(`TEAM_ADMIN_ID`,团期管理员侧)、`sender_role=VEHICLE_TEAM`;团期管理员发→`admin_id=-1`(`TEAM_FLEET_ADMIN_ID`,车务侧)、`sender_role=GROUP_ADMIN`;`biz_module`/`biz_type`落`GROUP_FLEET`、`biz_id`落`groupBatchId`。**bump对侧团队行未读、refresh本侧团队行预览。**
|
||
- FLEET/GROUP 的既有投递路径(`teamPool`/`peer`计算)**逐字不变**,不受本次改造影响。
|
||
- 281004(限流)与281011(内容非法,>1000字符或空白)两个既有码继续适用,判定仍在`ChatMessageService`而非VO注解层。
|
||
|
||
---
|
||
|
||
### 9. 标记已读到最新(双侧水位各自推进) `POST /admin/message/chat/{conversationKey}/read`
|
||
|
||
**VO**: `无请求体 → Result<ChatReadRespVO>`
|
||
|
||
#### 使用场景
|
||
|
||
把某会话标记为已读到最新,成员水位推进 + `unread_count=0`。本次改造只影响双团队会话(`GROUP_FLEET`)的水位推进范围。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| conversationKey | Path | String | ✅ | - | 会话键 |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| conversationKey | String | 会话键 |
|
||
| unreadCount | Integer | 标记后该会话未读数(恒0) |
|
||
| lastReadMessageId | Long | 已读水位(最新messageId),无消息为空 |
|
||
|
||
#### 请求示例
|
||
|
||
```http
|
||
POST /admin/message/chat/GROUP_FLEET:1934567890123456789/read
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"conversationKey": "GROUP_FLEET:1934567890123456789",
|
||
"unreadCount": 0,
|
||
"lastReadMessageId": 900001
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
无(无消息时 `lastReadMessageId` 为null,`unreadCount` 仍为0)。
|
||
|
||
#### 错误响应
|
||
|
||
```json
|
||
{
|
||
"code": 281018,
|
||
"message": "无权访问该团期配车会话",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
#### 业务边界
|
||
|
||
- 🔴 **这是本单对既有实现的关键修正**:`ChatMessageService.markTeamReadInternal`(`:941`/`:958` 两个重载)对`GROUP_FLEET`(`isDualTeam()`为true,判据见`:995`/`:1008`)**只推进本侧那一条团队水位、只清本侧收件池行,对侧未读一条不动**——车务标已读推`admin_id=-1`那行,团期管理员标已读推`admin_id=0`那行,互不覆盖。
|
||
- FLEET/GROUP 两个既有团队会话(两者都是单团队+定制师个人侧)的已读口径**逐字不变**,仍推进共享的`TEAM_ADMIN_ID=0`那一条水位。
|
||
- READ 回执按对侧角色/权限码定向广播(车务标已读→广播给团期管理员侧连接;反之亦然)。
|
||
|
||
---
|
||
|
||
### 10. 批量业务对象未读(`unreadScope` 新增 `TEAM_FLEET`) `POST /internal/message/chat/biz-unread-batch`
|
||
|
||
**VO**: `ChatBizUnreadBatchReqVO → Result<ChatBizUnreadBatchRespVO>`
|
||
|
||
#### 使用场景
|
||
|
||
fleet 团期配车清单(本文档 1 号端点)批量取一批团期的车务侧未读,渲染清单行内红点。**仅限内部 Feign 调用**,fleet 侧 Feign 客户端 `board/port/ChatUnreadFeignClient.java` 签名不变。
|
||
|
||
#### 入参字段表
|
||
|
||
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|
||
|------|------|------|------|------|------|
|
||
| adminId | Body | Long | ✅ | `@NotNull` | 查谁的未读(列表当前登录人)——⚠️源码为**恒必填**,不因`unreadScope`取值而变(与部分旧文档「PERSONAL时才必填」的描述不一致,以`ChatBizUnreadBatchReqVO.java:23-25`为准) |
|
||
| bizModule | Body | String | ✅ | `@NotNull` | 业务分类:`HOUSE`/`FLEET`/`GROUP`/`GROUP_HOUSE`/`GROUP_FLEET`,本单场景传`GROUP_FLEET` |
|
||
| unreadScope | Body | String | ❌ | 正则`TEAM\|PERSONAL\|TEAM_FLEET` | **本单新增可选值`TEAM_FLEET`**;`FLEET`/`GROUP`/`GROUP_HOUSE`/`GROUP_FLEET`默认`TEAM`,`HOUSE`忽略本字段 |
|
||
| bizIds | Body | List<Long> | ✅ | `@NotEmpty`,最多200个 | 一批业务对象id;`GROUP_FLEET`场景传`groupBatchId`集合 |
|
||
|
||
#### 出参字段表
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|------|------|------|
|
||
| unreads | List<ChatBizUnreadItemVO> | 逐项`bizId`(String)+`unreadCount`(Integer);无会话的对象统一返0 |
|
||
|
||
#### 请求示例
|
||
|
||
```json
|
||
{
|
||
"adminId": 205,
|
||
"bizModule": "GROUP_FLEET",
|
||
"unreadScope": "TEAM_FLEET",
|
||
"bizIds": [1934567890123456789, 1934567890123456790]
|
||
}
|
||
```
|
||
|
||
#### 响应示例
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {
|
||
"unreads": [
|
||
{"bizId": "1934567890123456789", "unreadCount": 2},
|
||
{"bizId": "1934567890123456790", "unreadCount": 0}
|
||
]
|
||
},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 空数据 / 降级响应
|
||
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"data": {"unreads": []},
|
||
"success": true
|
||
}
|
||
```
|
||
|
||
#### 错误响应
|
||
|
||
本端点无专属业务错误码,走 `@Valid` 统一 400(`GlobalExceptionHandler.handleValidation`,HTTP 状态码仍是 200):
|
||
|
||
```json
|
||
{
|
||
"code": 400,
|
||
"message": "unreadScope 仅支持 TEAM / PERSONAL / TEAM_FLEET",
|
||
"success": false
|
||
}
|
||
```
|
||
|
||
(`adminId`/`bizModule` 缺失、`bizIds` 为空或超 200 个同走该通道,文案分别取自 `ChatBizUnreadBatchReqVO.java` 对应字段的校验注解 message,源码逐字。)
|
||
|
||
#### 业务边界
|
||
|
||
- 🔴 **双侧未读要分别取,传错拿到的是对方的数字,不是报错、不是0**:车务侧红点必须传`unreadScope=TEAM_FLEET`(取`admin_id=-1`水位);团期管理员侧必须传`TEAM`(取`admin_id=0`水位)。两个scope对同一个`groupBatchId`会返回**两个不同但看起来都合理**的数字,前端按角色选对scope是唯一正确性保障,接口本身不会因为传错而报错。
|
||
- `bizModule`传非`GROUP_FLEET`时传`TEAM_FLEET`按`TEAM`处理并记WARN日志,不抛错(`ChatMessageService.java:1134` `fleetTeamScope && !module.isDualTeam()`分支)。
|
||
- `TeamChatModule.bizModules()`(现返回四值`["FLEET","GROUP","GROUP_HOUSE","GROUP_FLEET"]`)纳入`GROUP_FLEET`只影响候选源排除逻辑,不改变本端点的计数口径。
|
||
|
||
|
||
---
|
||
|
||
## 四、契约约束与正确调用方式
|
||
|
||
### 错误码汇总(本单新增 7 个,逐条实测见各接口详情的「错误响应」)
|
||
|
||
| 码 | 服务 | 符号 | 文案(源码逐字) | 触发条件 |
|
||
|---|---|---|---|---|
|
||
| 600012 | hl-fleet-service | `GROUP_BATCH_BASELINE_UNREACHABLE` | 团期配车基线不可达,请稍后重试 | order-v3 内部读口 Feign 降级或返回非成功,fleet 侧一律失败关闭(1/2 号端点) |
|
||
| 600013 | hl-fleet-service | `SCHEDULE_QUERY_PARAM_INVALID` | 排班查询参数非法: {0} | 日期区间倒挂 / 分页越界 / 跨度超31天 / 枚举非法(1/3 号端点) |
|
||
| 600014 | hl-fleet-service | `SCHEDULE_RESOURCE_TYPE_UNSUPPORTED` | 不支持的资源类型: {0} | `resourceType` 不是 `VEHICLE`/`DRIVER`(3 号端点) |
|
||
| 809400 | hl-order-service-v3 | `GROUP_BATCH_NOT_FOUND` | 团期不存在 | `groupBatchId` 查无团期或已软删(6 号端点,内部) |
|
||
| 809401 | hl-order-service-v3 | `QUERY_PARAM_INVALID` | 查询参数非法 | page<1 / pageSize不在1-100 / 出发日区间倒挂 / keyword超50字符,四种情形共用同一个码(5 号端点,内部) |
|
||
| 281018 | hl-user-service | `CHAT_GROUP_FLEET_ACCESS_DENIED` | 无权访问该团期配车会话 | 当前 token 角色既非车务(`VEHICLE_MANAGER`)也不持团期管理员权限码(`group-batch:demand:confirm`)(4/7/8/9 号端点) |
|
||
| 281019 | hl-user-service | `CHAT_GROUP_FLEET_NOT_CONTACTABLE` | 团期不存在、已结算或已取消,无法发起配车会话 | `groupBatchId` 查无活跃团期,或 `batch_status ∈ {SETTLED, CANCELLED}`;**只卡 open/send,messages/read 仍放行**(4 号端点) |
|
||
|
||
### 场景对照
|
||
|
||
| 场景 | 结果 |
|
||
|------|------|
|
||
| 分页越界 | 返600013(fleet 侧读口)/ 809401(order-v3 内部读口) |
|
||
| 日期倒置 | 返600013(fleet 侧读口)/ 809401(order-v3 内部读口) |
|
||
| 关键词超长 | 返600013(fleet 侧读口)/ 809401(order-v3 内部读口) |
|
||
| 车务侧/团期管理员侧均不满足准入 | 返281018(open-group-fleet / messages / send / read 四个端点统一) |
|
||
| 团期已结算/取消/不存在,尝试 open 或 send | 返281019;已建立的历史会话 messages/read 仍可用 |
|
||
| 车务侧红点误传 `unreadScope=TEAM`(或反之) | **不报错**,返回对方那一侧的未读数——业务边界,非契约错误 |
|
||
|
||
---
|
||
|
||
## 五、数据库行为
|
||
|
||
本单仅含查询接口,无写操作。
|
||
|
||
---
|
||
|
||
## 六、边界行为
|
||
|
||
- 未登录 → 401
|
||
- 团期不存在 → 600012(fleet 侧只读口)/ 809400(order-v3 内部读口)/ 281019(user-service 会话 open/send)
|
||
- 接送机沟通不新开会话:对团期子订单调 `open-fleet` 仍返回 `FLEET:{orderId}`,不产生任何 `GROUP_FLEET` 记录
|
||
- `GROUP_FLEET` 一侧标记已读只推进本侧团队水位,对侧未读不受影响(见 9 号端点业务边界)
|
||
- `unreadScope=TEAM_FLEET`(车务侧)与 `TEAM`(团期管理员侧)是两个不同的数,传错不报错、只是拿到对方的未读数(见 10 号端点业务边界)
|
||
|
||
---
|
||
|
||
## 六.5、枚举
|
||
|
||
### dispatchProgress
|
||
|
||
| 值 | 中文 |
|
||
|----|------|
|
||
| NOT_STARTED | 未启动 |
|
||
| PARTIAL | 部分完成 |
|
||
| FULL | 已完成 |
|
||
|
||
---
|
||
|
||
## 六.6、修改前后对比
|
||
|
||
> 本单 `change_type=新增接口`;下列 4 个端点是新增 `GROUP_FLEET` 会话能力附带触发的既有端点分支改造,路径/入参/响应结构均未变,故未单独出一版「修改接口」changelog,仍在此列出改前改后对比以免这条变化被埋没。
|
||
|
||
### 行为级对比
|
||
|
||
| 端点 | 改前 | 改后 |
|
||
|------|------|------|
|
||
| `GET .../{conversationKey}/messages` | 只支持 FLEET/GROUP/GROUP_HOUSE/DIRECT 键,按订单+定制师解析授权 | 新增 `GROUP_FLEET:` 键分流:按团期存在性+车务角色/团期管理员权限码判定,不满足返281018 |
|
||
| `POST .../{conversationKey}/messages` | 收件方按「订单定制师 vs 团队」解析 | 新增团级分支:收件方恒取**对侧**占位adminId(车务发→团期管理员侧水位,反之亦然) |
|
||
| `POST .../{conversationKey}/read` | 团队会话已读统一推进`admin_id=0`一条水位 | `GROUP_FLEET`双侧各自推进:本侧标已读只清本侧池行,对侧不受影响 |
|
||
| `POST /internal/.../biz-unread-batch` | `unreadScope`只支持`TEAM`/`PERSONAL` | 新增`TEAM_FLEET`(车务侧团队水位,`admin_id=-1`),`GROUP_FLEET`场景`TEAM`/`TEAM_FLEET`各取一侧 |
|
||
|
||
---
|
||
|
||
## 六.7、影响评估
|
||
|
||
- **是否破坏向后兼容**: 否
|
||
- **前端是否必须同步上线**: 否
|
||
|
||
---
|
||
|
||
## 六.8、菜单与权限
|
||
|
||
- **菜单挂载**(`V20260910_006__add_group_batch_fleet_dispatch_menu.sql`):在既有「车务管理」(`path=/fleet`)节点下新增二级目录「团期配车」(`menu_type=D`,`path=/fleet/group-dispatch`,`icon=Van`,`sort_order=46`),其下唯一三级菜单同名「团期配车」(`menu_type=M`,`path=component=/fleet/group-dispatch`);菜单可见性(`sys_role_menu`)**当前授予 `SUPER_ADMIN` / `VEHICLE_MANAGER` 两个角色**。⚠️ **2026-09-11 订正**:本脚本原本还授予了 `ADMIN`,已由 `V20260911_007__fix_group_dispatch_admin_grants.sql`(PR #7514,14:04:26 部署)收回。原因见八节末尾那条红字——`/admin/fleet/**` 的门禁只放行 `VEHICLE_MANAGER`/`SUPER_ADMIN`,给 `ADMIN` 菜单等于造一个**看得见、点进去必 403** 的死入口。**前端不要按「`ADMIN` 能看到团期配车」设计任何逻辑。**
|
||
- **权限标识**(`V20260910_007__add_group_batch_fleet_permission.sql`):新增权限码 `fleet:group-dispatch:view`(`resource_type=FLEET_GROUP_DISPATCH`,`action=VIEW`),覆盖本单三个只读端点(待配车团期清单/团期配车总览/资源排班);三级菜单的 `permission_code` 字段(V20260910_006 第66行)写的也是这同一个码。`admin_role_permission` **当前授权 `VEHICLE_MANAGER` / `SUPER_ADMIN`**(原本含 `ADMIN`,同由 `V20260911_007` 收回,理由同上);团期管理员侧不在此授权范围内(他们看配车进度走团期管理页,不进车务页面)。
|
||
|
||
---
|
||
|
||
## 七、不影响范围
|
||
|
||
- 房务、C端、既有会话
|
||
|
||
---
|
||
|
||
## 八、测试环境已验证
|
||
|
||
**环境与提交**:测试服部署 `hl-fleet-service` / `hl-order-service-v3` @ `861ebff0c`,`hl-user-service` @ `30761683e`(与前两者不同提交号是部署窗口内 dev-v3 尖端继续前移所致;已核实中间那 5 个提交对 `hl-user-service`/`hl-fleet-service` 零文件改动,且改动集中在 `hl-common-feign`,与本单改的 `hl-common-core`/`dto/fleet` 零重叠,契约不受影响)。
|
||
|
||
**网关实测(6 个端点,每条两轮不同角色)**:
|
||
|
||
| 端点 | 角色 | 结果 |
|
||
|---|---|---|
|
||
| `GET /admin/fleet/group-dispatch/pending-batches`<br>`GET /admin/fleet/group-dispatch/batches/{id}/overview`<br>`GET /admin/fleet/group-dispatch/resource-schedule` | `VEHICLE_MANAGER` | **200**,返回真实数据 |
|
||
| 同上三个 | `ROOM_MANAGER` / `ADMIN` | **403**「无权限访问车务管理,请切换到车务角色」 |
|
||
| `POST /v3/internal/group-batch/vehicle-dispatch-candidates`<br>`GET /v3/internal/group-batch/{id}/vehicle-coverage` | 任意(含带 token) | **403**「接口不可访问」——网关 `JwtAuthFilter` 按 `/v3/internal/**` 路径前缀在鉴权之前就拦掉,与 token/角色无关,外部不可达(符合预期,这两个是服务间接口) |
|
||
| `POST /admin/message/chat/open-group-fleet` | `VEHICLE_MANAGER` | **200**,两次调用 `isNew` 分别为 `true`/`false`(首开建会话、二次找回同一条,幂等生效) |
|
||
|
||
🔴 **`ADMIN` 角色拿到的是 403,不是 200**——fleet 三个只读端点的访问控制是 #5626 的粗粒度角色门禁(仅放行 `VEHICLE_MANAGER` / `SUPER_ADMIN`),**不看权限码** `fleet:group-dispatch:view`(六.8 节新增的那个)。前端如果按「持有该权限码就能调」设计菜单可见性之外的接口调用逻辑,`ADMIN` 角色会在实调时翻车——必须同时满足角色门禁。
|
||
|
||
```
|
||
GET /admin/fleet/group-dispatch/pending-batches → 200 ✓(VEHICLE_MANAGER)
|
||
```
|
||
|
||
---
|
||
|
||
## 九、相关历史 PR
|
||
|
||
| PR | Issue | 说明 |
|
||
|----|-------|------|
|
||
| #7506 | #7440 | 本单 |
|
||
|
||
---
|
||
|
||
## 十、相关文档
|
||
|
||
- Issue: [#7440](https://git.1814.love:8443/wx/HL/issues/7440)
|
||
- PR: [#7506](https://git.1814.love:8443/wx/HL/pulls/7506)
|
||
- Commit: [861ebff0c](https://git.1814.love:8443/wx/HL/commit/861ebff0c)
|
||
|
||
## 关联 / 联系人
|
||
|
||
### 链接
|
||
|
||
- **Issue**: [#7440](https://git.1814.love:8443/wx/HL/issues/7440)
|
||
- **PR**: [#7506](https://git.1814.love:8443/wx/HL/pulls/7506)
|
||
- **Merge commit**: [861ebff0c](https://git.1814.love:8443/wx/HL/commit/861ebff0c)
|
||
|
||
### 联系人
|
||
|
||
- **后端负责人**: @wx
|