文件
hl-api-changelog/changelogs-v2/2026-09/13_7439_团期车务地基-内部接口-修改接口-管理后台.md
API Changelog Bot 1e8404a047
changelog-filename-gate / validate (push) Failing after 1s
docs(changelog): #7439 团期车务地基——用车需求分家(对外 7 端点 + 内部 4 端点)
两份分册按 BACKEND_CHANGELOG_DELIVERY_GUIDE.md 2.5 节拆分(/v3/internal 必须单独成篇)。
2026-09-13 测试环境逐端点实测:对外 7 个经网关,内部 4 个直连服务带 X-Internal-Token
(网关按设计一律 403 拦 internal,不是路由失败)。部署 HEAD a82367e15,
order-v3 / fleet / gateway 同批。

实测顺带订正两处文档与实现不符的契约,已改在 §二/§三 正文而非附注:
1. 打回·定制师 / 打回·车控 的 kind 是 @RequestParam Query 参数,不在 RejectReqVO 里。
   原文档标成 Body —— 按原文档把 kind 塞进 JSON 会被 Jackson 静默忽略、
   不报错也不返 400、服务端按默认 TRAVEL 处理,接送机的打回会落到行程需求上。
2. 提交/修改用车需求的非法 kind 实测返 400(VehicleRequirementReqVO 的 @Pattern
   先于业务代码拦截),809000 对该端点的 body 参数不可达;809000 仍可经内部接口的
   query 入口触发。同一处校验、两个入口两种表现,两条分支都要处理。
2026-09-13 14:47:04 +08:00

22 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 7439 团期车务地基(内部接口分册)——需求池 kind 过滤 / 变更历史按 kind 分组 / fleet 按 (orderId,kind) 拉取 / 接送机服务日回填 internal wx(GIT) 修改接口 deployed verified not_required 本文件是 #7439 的内部接口分册,与对外分册 13_7439_团期车务地基-用车需求分家-修改接口-管理后台.md 同批交付。拆分依据 BACKEND_CHANGELOG_DELIVERY_GUIDE.md 2.5 节:/v3/internal/* Feign 接口必须单独成篇,不得与 admin/mp 接口塞同一份。 2026-09-13 dev-v3

order-v3: 团期车务地基(内部接口分册)

服务: hl-order-service-v3 消费方: hl-fleet-service(Feign,/v3/internal/**,不经 JWT,靠网关不暴露该前缀做隔离) PR: #7551(f035b85be)、#7572(600430303)、#7595(fdebc58c4)、#7597(bc75393fd) Issue: #7439

本文件是分册,不是全部。 #7439 共改 11 个端点,其中 7 个对外端点(/v3/admin/**)在 13_7439_团期车务地基-用车需求分家-修改接口-管理后台.md。 两份合起来才是本单的完整契约面;本册的收件人是后端与 fleet 侧,不是 mmg。


⚠️ 关键变化

  1. 需求池按 kind 过滤:kind 不传默认 TRAVEL,传 TRANSFER 取接送机池,传 ALL 取两类
  2. 变更历史按 kind 分组:不传 kind 时两类都返并按 kind 分组,不得混在一条时间线里
  3. 按 (orderId, kind) 取当前生效需求:同订单可并存 TRAVEL 与 TRANSFER 两条活跃需求,取数必须带 kind
  4. 新增服务日回填端点:补齐迁移遗留的空 service_dates 并推进需求状态,带 outcome 三态

调用顺序是硬约束(见「四、契约约束」):消费 TRANSFER 需求必须先无条件调回填端点、按 outcome 分流后 再调 GET .../requirement/vehicle?kind=TRANSFER;倒过来先 GET 判空再回填的写法永远走不到回填。


一、背景

团期车务从单一用车需求(行程)扩展到双需求并存(+接送机)。 fleet 侧原先按 orderId 取唯一需求的假设在本单之后不再成立——同一订单可以同时有两条活跃需求, 所有内部读写口都必须显式带 kind,否则拿到的是哪一条取决于实现细节而不是契约。


二、变更接口清单

# 接口 方法 路径 变更类型 说明
1 查询用车需求(内部) GET /v3/internal/order/vehicle-requirements 改造 需求池按 kind 过滤,不传默认仅返 TRAVEL,传 ALL 取两类
2 查询需求变更历史(内部) GET /v3/internal/order/orders/{orderId}/requirement-history 改造 车需求历史按 kind 分组返回,不传 kind 时两类都返
3 查询订单车需求(内部) GET /v3/internal/order/orders/{orderId}/requirement/vehicle 改造 fleet 按 (orderId, kind) 拉取当前生效需求
4 回填接送机服务日(内部) POST /v3/internal/order/orders/{orderId}/requirement/transfer/service-dates/backfill 新增 补齐迁移遗留的空服务日并推进需求状态

三、接口详情

1. 查询用车需求(内部) GET /v3/internal/order/vehicle-requirements

VO: String status, String kind → Result<List<VehicleRequirementPoolVO>>

使用场景

fleet 拉取待配车需求池(调度用)。不经 JWT 鉴权,依赖网络隔离(网关不暴露 /internal/** 至公网)。

入参字段表

字段 位置 类型 必填 约束 说明
status Query String ❌ 当前仅支持 PENDING 需求状态过滤,默认 PENDING
kind Query String ❌ TRAVEL/TRANSFER/ALL 🆕 默认 TRAVEL;传 TRANSFER 取接送机池,传 ALL 取两类

出参字段表

字段 类型 说明
requirementId/orderId/status/version/fleet/specialTags/pickupRequired/dropoffRequired/remark/dispatchRemark/submittedAt - 既有字段,结构不变
kind String 🆕 需求类别(TRAVEL/TRANSFER)

请求示例

GET /v3/internal/order/vehicle-requirements?status=PENDING&kind=TRANSFER

响应示例

{
  "code": 200,
  "data": [
    {
      "requirementId": "90022334455",
      "orderId": "60123456789013",
      "status": "PENDING",
      "version": 1,
      "fleet": "[{\"carType\":\"商务车\",\"seats\":7,\"count\":1}]",
      "specialTags": ["中文司机", "大行李厢"],
      "pickupRequired": true,
      "dropoffRequired": true,
      "remark": "",
      "dispatchRemark": "",
      "submittedAt": "2026-09-10T14:35:10",
      "kind": "TRANSFER"
    }
  ],
  "success": true
}

空数据 / 降级响应

{
  "code": 200,
  "data": [],
  "success": true
}

错误响应

{
  "code": 809000,
  "message": "用车需求类别非法",
  "success": false
}

业务边界

  • 🔴 默认值是有意为之:不传 kind 时只返 TRAVEL 池,TRANSFER 不进现有池,避免 V-4 接线前 fleet 拉到服务日为 NULL 的行
  • 传 kind=ALL 可取两类
  • 不经 JWT,依赖网络隔离,网关不暴露 /internal/** 至公网

2. 查询需求变更历史(内部) GET /v3/internal/order/orders/{orderId}/requirement-history

VO: Long orderId, String requirementType, String kind → Result<RequirementHistoryVO>

使用场景

按订单查需求全量历史(含全部版本+抢单审计流水),Feign 内部接口。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Path Long ✅ - 订单 ID
requirementType Query String ❌ HOTEL/VEHICLE/ALL 既有参数,需求域过滤,默认 ALL
kind Query String ❌ TRAVEL/TRANSFER 🆕 不传=两类都返,按 kind 分组返回

出参字段表

字段 类型 说明
hotelVersions List 房需历史版本列表,不变
vehicleVersions List 车需历史版本列表,不变
vehicleVersions[].kind String 🆕 需求类别;不传 kind 查询参数时,两类的版本序列必须分组返回、不得混在一条时间线里

请求示例

GET /v3/internal/order/orders/60123456789013/requirement-history?requirementType=VEHICLE&kind=TRANSFER

响应示例

{
  "code": 200,
  "data": {
    "hotelVersions": [],
    "vehicleVersions": [
      {
        "kind": "TRANSFER",
        "serviceDates": ["2026-09-11", "2026-09-18"]
      }
    ]
  },
  "success": true
}

空数据 / 降级响应

{
  "code": 200,
  "data": {"hotelVersions": [], "vehicleVersions": []},
  "success": true
}

错误响应

{
  "code": 809000,
  "message": "用车需求类别非法",
  "success": false
}

业务边界

  • 改前一条车需求版本链;改后至多两条,各自从 v1 起
  • 不传 kind 参数时两类的版本序列必须分组返回,不得混在一条时间线里

3. 查询订单车需求(内部) GET /v3/internal/order/orders/{orderId}/requirement/vehicle

VO: Long orderId, String kind → Result<VehicleRequirementForFleetDTO>

使用场景

fleet 拉取该订单当前生效用车需求,装配 VehicleRequirementForFleetDTO,供 fleet 看板核对与换车/改版 reconcile。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Path Long ✅ - 订单 ID
kind Query String ❌ TRAVEL/TRANSFER 🆕 默认 TRAVEL;指定拉取哪一类需求

出参字段表

字段 类型 说明
requirementId/version/isActive/status/manualUrgent/orderCancelled/dispatchable/serviceStartDate/serviceEndDate/serviceDates/headcount/pickupRequired/dropoffRequired/submittedAt/fleet/specialTags/reassignReasons/remark - 既有字段,结构不变
kind String 🆕 需求类别;服务端按 (orderId, kind) 定位需求

请求示例

GET /v3/internal/order/orders/60123456789013/requirement/vehicle?kind=TRANSFER

响应示例

{
  "code": 200,
  "data": {
    "requirementId": "1934567890123456790",
    "version": 1,
    "isActive": true,
    "status": "PENDING",
    "manualUrgent": false,
    "orderCancelled": false,
    "dispatchable": true,
    "serviceStartDate": "2026-09-11",
    "serviceEndDate": "2026-09-18",
    "serviceDates": ["2026-09-11", "2026-09-18"],
    "headcount": 6,
    "pickupRequired": true,
    "dropoffRequired": true,
    "submittedAt": "2026-09-10 10:05:00",
    "fleet": [{"vehicleType": "中巴", "seats": 35, "count": 1}],
    "specialTags": ["中文司机"],
    "reassignReasons": [],
    "remark": "",
    "kind": "TRANSFER"
  },
  "success": true
}

空数据 / 降级响应

无(正文未明确该订单无对应 kind 当前生效需求时的具体返回形态,见交付说明留空条目)。

错误响应

{
  "code": 809000,
  "message": "用车需求类别非法",
  "success": false
}
{
  "code": 809007,
  "message": "接送机需求的服务日尚未回填,requirementId=1934567890123456790",
  "success": false
}

业务边界

  • 按 (orderId, kind) 定位需求
  • ⚠️ 分工边界:本单只引入 kind 参数与响应字段;V-4(#7443)在此基础上再加 groupBatchId
  • TRANSFER 需求的 service_dates 为 NULL 或空数组时被本端点消费会触发 809007(存量迁移行未回填即被取用),失败关闭;与 809002(提交时派生不出)分工不同

4. 回填接送机服务日(内部,🆕 新增) POST /v3/internal/order/orders/{orderId}/requirement/transfer/service-dates/backfill

VO: Long orderId → Result<TransferServiceDatesBackfillRespVO>

使用场景

把 service_dates 为 NULL、status=PENDING_REVIEW 的活跃 TRANSFER 需求用大交通计划补齐日期并推进到 PENDING。v7 后本单自身不再产生这种行,端点仍交付供 #7443 展开前置与运维批量补数脚本调用。不经 JWT,依赖网络隔离。

入参字段表

字段 位置 类型 必填 约束 说明
orderId Path Long ✅ - 订单 ID

请求体:无。

出参字段表

字段 类型 说明
requirementId Long 被处理的活跃 TRANSFER 需求 ID;该订单无活跃 TRANSFER 需求时为 null
outcome String BACKFILLED(本次补齐并推 PENDING_REVIEW→PENDING)/ ALREADY_PRESENT(本来就有非空服务日,未改动)/ NO_TRANSPORT(大交通派生不出日期,未写未推状态)/ NOT_FOUND(无活跃 TRANSFER 需求)
serviceDates List 补齐后的服务日;NO_TRANSPORT/NOT_FOUND 时为空数组

请求示例

POST /v3/internal/order/orders/60123456789013/requirement/transfer/service-dates/backfill

响应示例

{
  "code": 200,
  "data": {
    "requirementId": "1934567890123456790",
    "outcome": "BACKFILLED",
    "serviceDates": ["2026-09-11", "2026-09-18"]
  },
  "success": true
}

空数据 / 降级响应

{
  "code": 200,
  "data": {"requirementId": null, "outcome": "NOT_FOUND", "serviceDates": []},
  "success": true
}

错误响应

不新增错误码;NO_TRANSPORT 是正常返回态不是异常(客人还没填大交通,属业务待办不是系统故障),本端点不过 809007 守卫。以下示例是订单本身不存在时推测复用的同域既有错误码(RequirementErrorCode.ORDER_NOT_FOUND),工单正文未明确交代这一点,见交付说明留空条目:

{
  "code": 582001,
  "message": "订单不存在",
  "success": false
}

业务边界

  • 幂等:连调两次第二次必返 ALREADY_PRESENT,service_dates 与 status 均不再变化
  • 本端点是 TRANSFER 消费链路的第一步、不是兜底步:调用方须先无条件调本端点,按 outcome 分流后再调 GET .../requirement/vehicle?kind=TRANSFER;不得倒过来先 GET 判空再回填(迁移产生的 NULL 日期行会在 GET 那一步被 809007 打掉,永远走不到回填)
  • 拿到 NO_TRANSPORT 的调用方必须就地失败关闭、不得再调 GET
  • 本端点只补迁移遗留的空日期,不是「日期同步接口」;已有非空 service_dates 的行一律 ALREADY_PRESENT 原样返回,不会拿最新大交通覆盖(改签要跟上必须走换版重提)

四、契约约束与正确调用方式

场景 结果
查询 TRANSFER,serviceDates 为空 返 809007(迁移遗留行,必须先走回填端点)
回填后 outcome=NO_TRANSPORT 调用方就地失败关闭,不得再调 GET
回填后 outcome=BACKFILLED / ALREADY_PRESENT 可继续调 GET .../requirement/vehicle?kind=TRANSFER
取当前生效需求不带 kind 双需求订单上语义不确定,一律显式传

正确调用顺序(TRANSFER 消费链路):

  1. 先无条件 POST .../requirement/transfer/service-dates/backfill
  2. 按 outcome 分流:NO_TRANSPORT 就地失败;其余继续
  3. 再 GET .../requirement/vehicle?kind=TRANSFER

⚠️ 不得倒过来先 GET 判空再回填:迁移产生的 NULL 日期行会在 GET 那一步被 809007 打掉,永远走不到回填。


五、数据库行为

对调用方无感(不改任何接口契约),但内部消费方必须知道取数语义:

  • order_vehicle_requirement 新增两列:requirement_kind(需求类别)与 active_kind(活跃标识, 只在行处于活跃状态时取值,失活即清空——唯一键靠它区分「当前活跃版本」与历史版本)
  • 新增三张团期车务需求表:order_group_vehicle_requirement / order_group_vehicle_group / order_group_vehicle_group_day(团-分组-日 三层结构,本单只建表与写入口径,消费方在后续工单)
  • 无删表、无改列类型、无存量数据迁移——原计划的迁移脚本 V20260910_406(把勾了接送机的 TRAVEL 行 迁成 TRANSFER 行)已从本单移除,所以存量行不会被本单改动,这正是回填端点存在的原因

六、边界行为

错误码 触发条件 调用方该怎么处置
809000 kind 传了 TRAVEL / TRANSFER / ALL 之外的值 参数错误;ALL 仅在需求池端点(本册 1)可用,另两个读端点不收 ALL
809002 回填时该单大交通信息为空,服务日派生不出来 不是故障;回填端点会以 outcome=NO_TRANSPORT 正常返回,调用方就地关闭该条链路
809007 消费一条 TRANSFER 需求时它的 service_dates 仍为空 调用顺序错了:先调回填端点再 GET。重试 GET 不会变好

六.5、枚举

kind

值 说明
TRAVEL 行程用车
TRANSFER 接送机
ALL 仅需求池端点(本册 1)可用的过滤值,取两类;不是需求类别本身

BackfillResultEnum

值 说明
BACKFILLED 成功回填
ALREADY_PRESENT 已存在无需重复
NO_TRANSPORT 客人未填大交通

六.6、修改前后对比

维度 改前 改后
按 orderId 取需求 唯一解 必须带 kind,同订单最多 2 条活跃
需求池过滤 无 kind 概念 不传=TRAVEL,TRANSFER=接送机池,ALL=两类
变更历史 单条时间线 按 kind 分组,两类不混线
TRANSFER 服务日 不存在该类需求 服务端从大交通派生;迁移遗留空值走回填端点补

六.7、影响评估

  • 向后兼容性:部分。不传 kind 的既有调用在单需求订单上行为不变;双需求订单上必须显式传 kind
  • 消费方:hl-fleet-service 需同步适配调用顺序与 kind 传参

七、不影响范围

  • 对外 /v3/admin/** 契约(见对外分册)
  • 房务及其他非车务内部接口
  • 单订单 TRAVEL 流程(透明升级)

八、测试环境已验证

部署:同对外分册——2026-09-13 13:17:42 执行 deploy-backend.sh hl-order-service-v3 hl-fleet-service hl-gateway,三次 git sync done HEAD 均为 a82367e15(dev-v3);deploy-status.sh 复核三行 HEAD 一致、BEHIND 均为 2/N(落后 origin/dev-v3 2 个提交,均未触及本服务/依赖模块)。之后合入的 PR #7631(squash 1c8984042)为纯 src/test 改动,不影响运行时,无需重新部署。

网关隔离验证(2026-09-13,经 https://api.test.1814.love:9443):本册 4 个端点均为 /v3/internal/,按设计不经公网网关暴露。实测确认网关正确执行了这条隔离契约——4 个路径经网关请求,一律返回 HTTP 403,响应体 {"code":403,"msg":"接口不可访问"}(hl-gateway 的 JwtAuthFilter.isInternalPath 硬编码拦截 /v3/internal/,不看是否带 JWT)。这是符合本文件 "一、背景" 与 status_note 所述"网关不暴露该前缀"设计的正确行为,不算路由失败。

业务逻辑验证(绕过网关,直连测试服 hl-order-service-v3 主端口 8086,携带与真实 Feign 调用同款的 X-Internal-Token;该 token 取自 Nacos test 命名空间 hl-common-test.yml 里 internal.auth.token 的默认值 hl-internal-service-token-2026-04,测试服未见 INTERNAL_AUTH_TOKEN 环境变量覆盖,即为生效值)。直连 localhost:8086 若不带该 token,服务自身的 InternalAuthInterceptor(hl-common-web)同样返回 403 内部接口禁止外部访问,与网关层隔离是两道独立防线,均已验证生效:

# 方法 路径 HTTP code 关键字段/message 判定
1 GET /v3/internal/order/vehicle-requirements?status=PENDING&kind=TRANSFER 200 200 返回 4 条真实 TRANSFER 需求,逐条含 kind=TRANSFER 路由通、业务逻辑通
1' GET 同上,kind=BOGUS(非法值) 200 809000 用车需求类别非法:BOGUS 路由通、业务逻辑通
2 GET /v3/internal/order/orders/{orderId}/requirement-history?requirementType=VEHICLE&kind=TRANSFER(不存在orderId) 200 581007 订单不存在 路由通、业务逻辑通
3 GET /v3/internal/order/orders/{orderId}/requirement/vehicle?kind=TRANSFER(不存在orderId) 200 581007 订单不存在 路由通、业务逻辑通
4 POST /v3/internal/order/orders/{orderId}/requirement/transfer/service-dates/backfill(不存在orderId) 200 200 outcome=NOT_FOUND, requirementId=null, serviceDates=[] 路由通、业务逻辑通;见下方说明

4/4 端点直连服务均返回真实业务响应(非网关层拦截、非 404/502)。造数声明:端点 1 为纯读,未改数据;端点 2/3/4 均使用不存在的订单 ID,服务在写操作前即判定订单不存在或需求不存在而返回,未产生任何真实数据行,无需 remark 溯源标记。

补充说明(澄清正文已标注的不确定项,非新增缺陷):端点 4(回填)"错误响应"小节原本对"订单不存在"场景标注为推测复用同域既有错误码、工单正文未明确交代。实测结果是:传入不存在的订单 ID,端点不报错,而是把它当作"该订单当前无活跃 TRANSFER 需求",以 outcome=NOT_FOUND(requirementId=null)正常返回,与端点本身文档里"NOT_FOUND(无活跃 TRANSFER 需求)"的定义一致——即本端点不区分"订单不存在"与"订单存在但无活跃 TRANSFER 需求",两种情况都归为 NOT_FOUND。这与端点 2/3(会显式判定订单存在性、返 581007)行为不同,属于本端点设计如此,不是本次验证发现的新问题。


九、相关历史 PR

PR Issue 说明
#7551 #7439 本单主体,2026-09-11 squash 合并进 dev-v3(合并提交 f035b85be)
#7572 #7439 结算侧三条缺陷修复(合并提交 600430303),不触本册端点
#7595 #7439 本册 1、2 的契约靠它才真的成立(合并提交 fdebc58c4):修前 kind=ALL 抛 809000、变更历史不传 kind 静默只返 TRAVEL——实现与本文件描述不符,已订正为文件所写的口径
#7597 #7439 结算响应补 requirementKind(合并提交 bc75393fd),不触本册端点

十、相关文档

  • Issue: #7439
  • 对外分册: changelogs-v2/2026-09/13_7439_团期车务地基-用车需求分家-修改接口-管理后台.md
  • PR: #7551 / #7572 / #7595

关联 / 联系人

链接

  • Issue: #7439
  • PR: #7551(已合并,f035b85be)、#7595(已合并,fdebc58c4)

联系人

  • 后端负责人: @wx
  • 消费方: hl-fleet-service