21 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 | 8202 | 子订单行程用车车型大类接入车队字典校验(新增 582032/582033) | admin | wx(GIT) | 修改接口 | deployed | verified | verified | mmg | c314c8c67ddc61b8a1e4f4373d79e5a731b13272 | 2026-09-23 | PR #8224 已合并 dev-v3(b439565be)并滚动部署 TEST 双实例,Nacos 注册均 healthy:true。用车需求提交/调整两条写路径共用的车型大类校验链新增车队活字典比对,新增 582032/582033 两码;存量码 582022(填法不合法)不变。真实网关实测:两端点各以 vehicleType="__NOT_A_REAL_CATEGORY__" 提交均返回 582022(填法层拦截优先于字典层)。前端 v2.1 已提前于 7df292624(09-23 09:27)改为车型字典下拉,新提交不会撞新码;仅存量已落库的非字典值在编辑态回显仍需前端显式提示。 | 2026-09-23 mmg 收尾交付:存量回显占位提示+fail-closed 拦截+JSDoc 订正,随 #8221 同 commit | 2026-09-23 | dev-v3 |
用车需求模块: 子订单行程用车车型大类接入车队字典校验
存放目录: 二期(v3) →
changelogs-v2/2026-09/服务: hl-order-service-v3 PR: #8224 Issue: #8202 日期: 2026-09-23 影响范围: 管理后台子订单用车需求提交/修改、订单调整统一提交中的用车/接送机车型校验
⚠️ 关键变化(非必须,本版与上版行为不同 / 纠错 / 撤销时必写)
- 本次变化:
vehicleType归一成功(如大巴→bus)后,新增一次车队活字典比对;该大类若已整类下线(车队活字典无一行在架代表),提交被拒。 - 前端以前以为的:只要
vehicleType能归一为suv/mpv/bus/sedan之一(或直接传规范 key),提交就会成功。 - 实际新行为:归一成功只是必要条件,还须该大类当前在车队活字典内;否则报新码
582032。存量码582022(这串字符连大类都归一不出来)保持不变、且仍是校验链第一关——按当前测试库数据,前端实际会撞上的是582022(自由文本/拼写问题),582032只在大类被整类下线时触发(详见下方六.5/六.6)。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 提交/修改/调整用车需求 | PUT | /v3/admin/order/{id}/vehicle-requirement |
校验链新增字典比对 | fleet[].vehicleType 归一后追加车队活字典校验 |
| 2 | 调整订单统一提交 | POST | /v3/admin/order/{id}/adjustment/submit |
校验链新增字典比对 | updates.vehicleRequirement.fleet[]/updates.transferRequirement.fleet[] 复用同一条校验链 |
三、接口详情
1. 提交/修改/调整用车需求 PUT /v3/admin/order/{id}/vehicle-requirement
VO: VehicleRequirementReqVO → VehicleRequirementRespVO
使用场景
管理端子订单详情页提交/编辑用车需求(TRAVEL 行程用车 / TRANSFER 接送机)时调用;三分支由后端自动判断(无 active 需求=INIT_SUBMIT,PENDING=PENDING_EDIT,DONE=DONE_ADJUST),前端不用区分分支、始终整份提交。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
| id | Path | Long | ✅ | 订单 ID | 目标订单 |
| kind | Body | String | ❌ | 正则 TRAVEL|TRANSFER |
需求类别,缺省按订单类型推断 |
| fleet | Body | Array<FleetItem> | ✅ | 非空 | 车型组合列表 |
| fleet[].vehicleType | Body | String | ✅ | 须能归一为 suv/mpv/bus/sedan(582022),归一后须在车队活字典内(582032,本次新增) |
车型大类,示例 mpv,可传中文别名(如"大巴") |
| fleet[].seats | Body | Integer | ✅ | ≥0;须在该大类真实车型的座位选项内(582024) | 单车座位数 |
| fleet[].count | Body | Integer | ✅ | >0 | 该车型数量 |
| specialTags | Body | Array<String> | ❌ | 须在 vehicle_special_demand 字典内 |
通用特殊诉求 |
| pickupRequired / dropoffRequired | Body | Boolean | ❌ | - | 接送机方向标记(兼容字段) |
| remark | Body | String | ❌ | ≤500 字 | 备注 |
出参 Result<VehicleRequirementRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Long | 需求记录 ID |
| kind | String | TRAVEL / TRANSFER |
| version | Integer | 版本号 |
| status | String | 需求状态 |
| branchTaken | String | 本次实际走的分支:INIT_SUBMIT / PENDING_EDIT / DONE_ADJUST |
| passengerCount | Integer | 出行人数 |
| vehicleCount | Integer | 车辆总数 |
| totalSeatCount | Integer | 总座位数 |
| driverSeatCount | Integer | 司机座位数(= vehicleCount) |
| passengerSeatCapacity | Integer | 载客座位容量 |
| remainingPassengerSeats | Integer | 剩余可载客座位 |
| previousVersion | Integer | DONE_ADJUST 分支下的上一版本号 |
| assignmentDeletedCount | Integer | DONE_ADJUST 分支下被清理的派单数 |
请求示例
{
"kind": "TRAVEL",
"fleet": [
{ "vehicleType": "大巴", "seats": 7, "count": 1 }
],
"remark": "接机后直达酒店"
}
响应示例
{
"code": 200,
"message": "成功",
"data": {
"id": 2098765432109876543,
"kind": "TRAVEL",
"version": 2,
"status": "PENDING",
"branchTaken": "PENDING_EDIT",
"vehicleCount": 1,
"totalSeatCount": 7
},
"success": true
}
空数据 / 降级响应
写接口无空数据场景;提交成功即返回最新需求快照,无降级分支。
{ "code": 200, "data": { "branchTaken": "INIT_SUBMIT" }, "success": true }
错误响应
fleet[].vehicleType 无法归一为 suv/mpv/bus/sedan 之一(含全部自由文本,如「别克GL8」)——当前测试库数据下前端会实际撞上的错误(存量码 582022,非本次新增,且排在校验链第一关):
{ "code": 582022, "message": "车型必须是车型大类(SUV/MPV/大巴/轿车)", "success": false, "data": null }
本次新增两码,仅在 vehicleType 归一成功之后才有机会触发:
{ "code": 582032, "message": "车型 bus 不在车型字典内(不存在或已下线),请从下拉项中选择", "success": false, "data": null }
582032 仅当归一得到的规范大类在车队活字典(fleet_vehicle_type)中已整类下线(该大类下所有型号均软删)时触发;测试库当前四个大类各有 1 行在架代表,本码在现有测试数据下不可达。
{ "code": 582033, "message": "车型字典不可用,请稍后重试", "success": false, "data": null }
582033 仅当车队字典服务不可用(Feign 兜底返回空 Map)时触发,与本次具体填了什么车型无关,空字典按 fail-closed 处理。
业务边界
- 鉴权:未登录 → 业务码
401。 - 校验顺序:
fleet非空(582021)→ 逐项归一 + 座位数/数量非负校验(582022/582023)→ 车队活字典比对(582032/582033,本次新增) → 座位选项校验(582024)→ 特殊诉求校验(582025/582026)。任一步失败即拒,零写入。 - 存量已落库、含已下线大类的需求原样再次提交同一接口同样会被 582032 拒绝——这是有意设计(放行已下线大类等同于让下线动作失效),与团期批量车务 809119 同口径。
- 车型下拉数据源固定为
GET /admin/fleet/vehicle-types/list(车队服务只读端点,网关已放行订单域只读调用,无需新增路由配置)。
2. 调整订单统一提交 POST /v3/admin/order/{id}/adjustment/submit
VO: AdjustmentSubmitReqVO → AdjustmentSubmitRespVO
使用场景
管理端订单调整弹窗统一提交入口;前端在内存里收集出行人/改期/行程/房需求/用车需求/接送机需求等多个子领域的改动后一次性提交。本次改动只影响 updates.vehicleRequirement.fleet[](行程用车)与 updates.transferRequirement.fleet[](接送机)两个子领域各自的车型大类校验,其余子领域字段与行为不受影响。
入参
仅列与本次改动相关的字段;其余子领域(
people/schedule/itinerary/hotelRequirement等)字段结构未变,不在此重复列出。
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
| Authorization | Header | String | ✅ | - | 管理端登录令牌 |
| id | Path | Long | ✅ | 订单 ID | 目标订单 |
| updates | Body | Object | ✅ | 至少一个子领域非 null | 各子领域修改内容容器 |
| updates.vehicleRequirement.fleet | Body | Array<FleetItem> | ❌(该子领域非 null 时必传) | 非空则整组重新走校验链;结构与端点 1 的 fleet 完全相同 |
行程用车车型组合 |
| updates.vehicleRequirement.fleet[].vehicleType | Body | String | 同端点 1 | 须归一成功(582022)且归一后在车队活字典内(582032,本次新增) | 车型大类 |
| updates.transferRequirement.fleet | Body | Array<FleetItem> | ❌(该子领域非 null 时必传) | 与 vehicleRequirement.fleet 走同一条校验方法,服务日由大交通信息派生 |
接送机车型组合 |
| updates.transferRequirement.fleet[].vehicleType | Body | String | 同端点 1 | 同上 | 车型大类 |
出参 Result<AdjustmentSubmitRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
| success | Boolean | 提交是否成功;失败由全局异常处理器返回 Result{code,message,data:null},不会出现该字段为 false 的情形 |
请求示例
{
"updates": {
"vehicleRequirement": {
"fleet": [
{ "vehicleType": "mpv", "seats": 7, "count": 1 }
],
"remark": "新增 2 名成员,需更大车型"
}
}
}
响应示例
{ "code": 200, "message": "成功", "data": { "success": true }, "success": true }
空数据 / 降级响应
写接口无空数据场景;updates 内某子领域传 null 表示该子领域本次不改,不参与本次校验也不产生该子领域的变更记录。
{ "code": 200, "data": { "success": true }, "success": true }
错误响应
与端点 1 完全同码同序(AdjustmentService.validateVehicleSeatOptionsBeforeWrite 从 updates.vehicleRequirement/updates.transferRequirement 的 fleet 字段构造 VehicleRequirementReqVO 后,调用与端点 1 相同的 validateVehicleRequirementSeatOptions)。当前测试库数据下前端会实际撞上的同样是 582022:
{ "code": 582022, "message": "车型必须是车型大类(SUV/MPV/大巴/轿车)", "success": false, "data": null }
{ "code": 582032, "message": "车型 bus 不在车型字典内(不存在或已下线),请从下拉项中选择", "success": false, "data": null }
{ "code": 582033, "message": "车型字典不可用,请稍后重试", "success": false, "data": null }
业务边界
- 鉴权:未登录 → 业务码
401。 submit整体在一个@Transactional(rollbackFor = Exception.class)事务内;updates.vehicleRequirement与updates.transferRequirement两个子领域各自独立校验一次,任一子领域校验失败即整单回滚,已通过校验的其他子领域改动也不会落库。updates.vehicleRequirement/updates.transferRequirement为null时该子领域完全跳过(既不校验也不改动),不受本次变更影响。- 存量已落库、含已下线大类的需求原样再次经本接口提交同样会被 582032 拒绝,同端点 1。
四、契约约束与正确调用方式(接口类必写)
本节只写后端接受/拒绝 payload 的规则,不写 UI 渲染建议。
✅ 正确 / ❌ 错误 payload 对照
| 场景 | payload | 结果 |
|---|---|---|
✅ 从 GET /admin/fleet/vehicle-types/list 下拉选值提交 |
vehicleType 取字典返回的规范 key(suv/mpv/bus/sedan) |
通过归一 + 字典校验 |
| ✅ 提交中文别名 | vehicleType: "大巴" |
归一为 bus 后再查字典,与直接传 bus 等价 |
| ❌ 自由文本/型号名 | vehicleType: "别克GL8" |
582022,归一失败,校验链第一关即拒 |
| ❌ 大类已被车队整类下线 | vehicleType: "bus" 但活字典无 bus 在架代表 |
582032,归一成功但字典比对不通过 |
| ❌ 车队字典服务不可用 | 任意合法 vehicleType |
582033,fail-closed,不做逐项放行 |
vehicleType一律以归一后的规范 key(非原始填法)落库;重新查看已提交需求时看到的也是规范 key,不是用户当时填的中文别名。- 端点 1(
vehicle-requirement)与端点 2(adjustment/submit)中的fleet[]结构与校验规则完全一致,前端不需要为两个入口分别处理错误码。
前端已就绪的状态
mmg v2.1 分支 7df292624(2026-09-23 09:27)已将车型录入从自由文本改为 GET /admin/fleet/vehicle-types/list 字典下拉,新提交的 vehicleType 均取自字典返回值,不会触发本次新增的 582032/582033。
尚待前端覆盖的场景:存量已落库需求的编辑态回显——若某行 vehicleType 不在当前字典返回值内(历史遗留或大类刚被下线),下拉组件默认会渲染成空选项且用户无法感知原因;此时应显式提示"当前值不在字典内,请重新选择",而非静默清空。
受影响的前端组件(实测点名,非推断):src/views/order-v2/detail/modals/FunItemAdjustModal.vue——hl-ui v2.1 上唯一调用 putVehicleRequirement 的 .vue(:1177 import,:2968 TRAVEL / :2970 TRANSFER 两个调用点);api 封装在 src/api/orderV2.js:1345 putVehicleRequirement。该组件已引入车型字典 api(getVehicleTypesList),本次要补的是「回显值不在字典返回集内」这一态的显式提示,不是接入下拉本身。
⚠️ 顺带一条可查证的事实:src/api/orderV2.js 中 putVehicleRequirement 的 JSDoc 错误码一行写的是 582024 / 582091,不含本次新增的 582032 / 582033。注释不影响运行,但下一个照注释排错的人会找不到新码。
五、数据库行为(涉及写操作时必写)
- 无 Flyway migration、无 DDL、无表结构变更。
- 本次校验是写前置校验,不涉及任何新增列或索引;
vehicle_requirement表落库字段与校验前完全一致(仅vehicle_type列存的值继续是归一后的规范 key)。 - 校验失败(582022/582032/582033/582024 等)的请求零写入——包括
vehicle_requirement主表、审计记录、派单联动,一律不产生任何行。 - 车队活字典本身(
fleet_vehicle_type表)不因本次改动新增/修改任何行,字典维护仍走车队服务既有入口。
六、边界行为
- 未登录 → 业务码
401(网关包装 HTTP 200)。 fleet为空数组或缺失 → 582021。vehicleType无法归一 → 582022(存量码,未变)。vehicleType归一成功但大类已整类下线 → 582032(本次新增)。- 车队字典服务不可用(Feign 兜底返回空 Map)→ 582033(本次新增),对所有请求 fail-closed,不逐项放行。
seats/count非法 → 582023;座位数不在该大类真实型号座位选项内 → 582024(582024 判定排在字典校验之后,因此大类已下线时不会先看到一个指错方向的座位错误)。- 存量已落库、含已下线大类的需求原样重新提交同一接口 → 同样被 582032 拒绝,零写入,不因"内容未变"豁免。
adjustment/submit路径下,validateVehicleTypesAgainstFleetDict内的 Feign 调用运行在AdjustmentService.submit的物理事务内(PROPAGATION_REQUIRED挂入);这是该路径已有的事务边界,本次改动未改变这一行为,也不在本单修复范围。
六.5、枚举 / 数据字典
fleet[].vehicleType(车型大类规范 key)
所属字段: VehicleRequirementReqVO.fleet[].vehicleType / AdjustmentSubmitReqVO.updates.vehicleRequirement.fleet[].vehicleType / ...transferRequirement.fleet[].vehicleType | 类型: String
| 值 | 说明 |
|---|---|
suv |
SUV |
mpv |
MPV / 商务车 |
bus |
大巴 |
sedan |
轿车 |
- 归一表(含中文别名,如"大巴"→
bus)由VehicleCategoryNormalizer维护,四值闭集,本次未新增第五类。 - 实际是否可选取决于车队活字典当前有哪些大类在架,前端应以
GET /admin/fleet/vehicle-types/list的实时返回为准,不要硬编码这四个 key 一定全部可用。
六.6、修改前后对比(修改/删除类接口必写,新增跳过)
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
fleet[].vehicleType |
只要能归一为 suv/mpv/bus/sedan 之一即接受,不管该大类当前是否在车队还在营 |
归一成功后额外要求该大类在车队活字典内有 ≥1 行在架代表,否则拒 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
提交一个已被车队整类下线的大类(如运营已下线 bus) |
校验通过,正常落库 | 582032 拒绝,零写入 |
| 车队字典服务不可用 | 不影响本校验链(该服务原本不参与车型大类校验) | 582033 拒绝,fail-closed |
| 存量含已下线大类的需求原样重新提交 | 通过 | 582032 拒绝(与团期批量车务 809119 同口径) |
六.7、影响评估(修改/删除类必写)
- 是否破坏向后兼容: 部分破坏,且是有意为之。请求/响应结构不变;仅"归一成功但大类已被下线"这一种此前被接受的取值,改为拒绝。影响面严格限定在"大类已整类下线"这一种运营主动下线的场景,不影响任何仍在营的大类。
- 前端是否必须同步上线: 否,mmg 已提前于本次后端上线(
7df292624早于b439565be)改为字典下拉,新提交路径已规避新码。 - 前端 workaround 清理点: 存量已落库需求在编辑态若命中已下线大类,需前端显式提示"当前值不在字典内,请重新选择"(见"四、契约约束"节),避免下拉渲染成空白且用户无法定位原因。落点组件:
src/views/order-v2/detail/modals/FunItemAdjustModal.vue。
七、不影响范围(显式声明, 帮前端/QA 缩小排查面)
- 仅影响: 两个端点中
fleet[].vehicleType的字典比对这一步;其余校验(座位数、数量、特殊诉求、座位选项)逻辑不变。 - 零影响:
VehicleCategoryNormalizer的四值归一表与别名规则本身(未新增/删减大类)。adjustment/submit中people/schedule/itinerary/hotelRequirement等与用车无关的子领域。- 团期批量车务(
GroupVehicleRequirementService)校验链,809119/809120 已是同口径独立实现,本单未改动团级代码。 - 派单、车队分配、司机端等下游链路的既有行为。
GET /admin/fleet/vehicle-types/list端点本身(既有只读端点,本单未修改其实现或路由)。
八、测试环境已验证
单元测试(RequirementServiceVehicleTypeDictValidateTest):
validateVehicleTypes_retiredCategoryRejectedAndLiveCategoryAccepted:同一桩字典(刻意不含bus)下,大巴(归一为bus)被拒且断言错误码为 582032、非 582022;同一字典下suv正常放行。validateVehicleTypes_emptyDict_throws582033NotFail582032:空字典(模拟车队服务不可用)下,suv被拒且断言错误码为 582033、非 582032,报文不含用户填的车型值。
真实网关(TEST 环境)实证:
PUT /v3/admin/order/{id}/vehicle-requirement vehicleType=__NOT_A_REAL_CATEGORY__ → 582022 ✓
POST /v3/admin/order/{id}/adjustment/submit vehicleType=__NOT_A_REAL_CATEGORY__ → 582022 ✓
两端点对同一非法值返回一致的错误码(582022),验证校验链在两个入口上等价生效、归一失败判定优先于字典比对。
部署: PR #8224 已合并 dev-v3(合并提交 b439565be),TEST 环境双实例滚动部署完成,Nacos 注册均 healthy:true。
十、相关文档
- 关联 Issue: wx/HL#8202
- 关联 PR: wx/HL#8224
关联 / 联系人
链接
联系人
- 后端负责人: @wx