40 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 | 7440 | 团期车务派单只读入口——看板三端点 + 内部读接口 + GROUP_FLEET 双团会话 | admin | wx(GIT) | 新增接口 | deployed | verified | verified | mmg | 5aea88d4 | 2026-09-13 | 2026-09-11 订正:六.8 节的角色授予范围已随 PR #7514(V20260911_007)收回 ADMIN,正文已更新为当前状态;接口契约、错误码与行为均未变。 前端已交付(2026-09-13):新建 fleet/group-dispatch 只读模块(待配车清单+单团总览+联系团期管理员 GROUP_FLEET 会话);resource-schedule 排班端点本轮缓建(本单无配车编辑入口);权限可见即可点+403 兜底,不接 fleet:group-dispatch:view 显隐。 | 2026-09-11 | 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 | 车务未读数 |
请求示例
{
"departDateFrom": "2026-09-01",
"departDateTo": "2026-09-30",
"page": 1,
"pageSize": 20
}
响应示例
{
"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
}
空数据 / 降级响应
{
"code": 200,
"data": {"total": 0, "records": []},
"success": true
}
错误响应
{
"code": 600013,
"message": "排班查询参数非法: departDateFrom 不能晚于 departDateTo",
"success": false
}
{
"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 | 权威服务日 |
| days | List | 逐日派车状态 |
| transferPendingTotal | Integer | 全团接送机未配计数 |
| conversationKey | String | 会话键 |
请求示例
GET /admin/fleet/group-dispatch/batches/1934567890123456789/overview
响应示例
{
"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
}
空数据 / 降级响应
无。
错误响应
{
"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 | ✅ | 1-50个 | 资源ID集合 |
| dateFrom | Query | LocalDate | ✅ | - | 起始日 |
| dateTo | Query | LocalDate | ✅ | ≤31天 | 结束日 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| resourceType | String | 资源维度 |
| resourceId | Long | 资源ID |
| resourceLabel | String | 车牌/司机名 |
| days | List | 排班日历 |
请求示例
GET /admin/fleet/group-dispatch/resource-schedule?resourceType=VEHICLE&resourceIds=1&dateFrom=2026-09-12&dateTo=2026-09-30
响应示例
{
"code": 200,
"data": [
{
"resourceType": "VEHICLE",
"resourceId": 1,
"resourceLabel": "蒙A12345",
"days": []
}
],
"success": true
}
空数据 / 降级响应
返回空days列表。
错误响应
{
"code": 600014,
"message": "不支持的资源类型: BOAT",
"success": false
}
{
"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 |
请求示例
{
"groupBatchId": 1934567890123456789
}
响应示例
{
"code": 200,
"data": {
"conversationKey": "GROUP_FLEET:1934567890123456789",
"conversationId": 1934567890123456789,
"currentSide": "GROUP_ADMIN"
},
"success": true
}
空数据 / 降级响应
无。
错误响应
{
"code": 281018,
"message": "无权访问该团期配车会话",
"success": false
}
{
"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 | ❌ | - | 团期状态白名单,不传/空=提供方默认成团后阶段集 |
| 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 | 权威服务日集合(与 dispatch-baseline 同源同算法) |
| enrolledOrders | Integer | 在团子订单数 |
| enrolledPeople | Integer | 在团人数 |
| requirementConfirmed | Boolean | 整团需求是否已确认 |
| vehicleReady | Boolean | 配车是否已就绪 |
请求示例
{
"departDateFrom": "2026-09-01",
"departDateTo": "2026-09-30",
"requirementConfirmed": true,
"vehicleReady": false,
"page": 1,
"pageSize": 20
}
响应示例
{
"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
}
空数据 / 降级响应
{
"code": 200,
"data": {"total": 0, "list": []},
"success": true
}
错误响应
{
"code": 809401,
"message": "查询参数非法",
"success": false
}
业务边界
- 本 DTO 无 Bean Validation 注解(
GroupBatchVehicleDispatchCandidateReqDTOjavadoc 明写),全部校验在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 | 权威服务日集合(与 dispatch-baseline 同源,消费方不另算) |
| requirementConfirmed / vehicleReady | Boolean | 团期两个标志位 |
| orders | List | 逐户覆盖项,见下 |
orders[] 逐项字段:orderId(Long转String)/ orderNo / customerName / headcount(Integer)/ vehicleControlStatus / travelRequirementId(Long转String,无有效需求为null)/ travelRequirementStatus(无有效需求为null)/ transferDeclared(Boolean)/ transferArrivalDates(List,原始声明值)/ transferDepartureDates(List,原始声明值)。
请求示例
GET /v3/internal/group-batch/1934567890123456789/vehicle-coverage
响应示例
{
"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,不返回空对象)。
错误响应
{
"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 | 消息列表(按时间正序),逐项含messageId/senderAdminId/senderName/senderRole/msgType/priority/content/isMine/readByPeer/sentAt |
请求示例
GET /admin/message/chat/GROUP_FLEET:1934567890123456789/messages?pageSize=20
响应示例
{
"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
}
空数据 / 降级响应
{
"code": 200,
"data": {"conversationKey": "GROUP_FLEET:1934567890123456789", "hasMore": false, "nextCursor": null, "list": []},
"success": true
}
错误响应
{
"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) |
请求示例
{
"content": "9/12 那天大巴上午先送机场",
"msgType": "TEXT",
"priority": "NORMAL"
}
响应示例
{
"code": 200,
"data": {
"messageId": 900001,
"conversationKey": "GROUP_FLEET:1934567890123456789",
"sentAt": "2026-09-11 09:02:31"
},
"success": true
}
空数据 / 降级响应
无。
错误响应
{
"code": 281011,
"message": "消息内容不合法",
"success": false
}
{
"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),无消息为空 |
请求示例
POST /admin/message/chat/GROUP_FLEET:1934567890123456789/read
响应示例
{
"code": 200,
"data": {
"conversationKey": "GROUP_FLEET:1934567890123456789",
"unreadCount": 0,
"lastReadMessageId": 900001
},
"success": true
}
空数据 / 降级响应
无(无消息时 lastReadMessageId 为null,unreadCount 仍为0)。
错误响应
{
"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 | ✅ | @NotEmpty,最多200个 |
一批业务对象id;GROUP_FLEET场景传groupBatchId集合 |
出参字段表
| 字段 | 类型 | 说明 |
|---|---|---|
| unreads | List | 逐项bizId(String)+unreadCount(Integer);无会话的对象统一返0 |
请求示例
{
"adminId": 205,
"bizModule": "GROUP_FLEET",
"unreadScope": "TEAM_FLEET",
"bizIds": [1934567890123456789, 1934567890123456790]
}
响应示例
{
"code": 200,
"data": {
"unreads": [
{"bizId": "1934567890123456789", "unreadCount": 2},
{"bizId": "1934567890123456790", "unreadCount": 0}
]
},
"success": true
}
空数据 / 降级响应
{
"code": 200,
"data": {"unreads": []},
"success": true
}
错误响应
本端点无专属业务错误码,走 @Valid 统一 400(GlobalExceptionHandler.handleValidation,HTTP 状态码仍是 200):
{
"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:1134fleetTeamScope && !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-batchesGET /admin/fleet/group-dispatch/batches/{id}/overviewGET /admin/fleet/group-dispatch/resource-schedule |
VEHICLE_MANAGER |
200,返回真实数据 |
| 同上三个 | ROOM_MANAGER / ADMIN |
403「无权限访问车务管理,请切换到车务角色」 |
POST /v3/internal/group-batch/vehicle-dispatch-candidatesGET /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 | 本单 |
十、相关文档
关联 / 联系人
链接
联系人
- 后端负责人: @wx