两份 changelog 都按 SKILL.md 多接口模板(按接口分小节)+ 严格代码契约(不照搬文档)写: 文件 1: changelogs-v2/2026-05/21_车辆档案-修改接口-管理后台.md - 6 接口(page/detail/create/update/delete/import)全契约 - 真枚举 VehicleAttachmentCategoryEnum(6 类目 + 张数上限)+ fleet/vehicleStatus @Pattern 强校验 - 错误码:600100 + 600107-600110(与文档 v1.4 差异已显式注解)+ 共用 601001-601005 - 标注:本模块无单独 PR 改动,本次为全模块快照首推 文件 2: changelogs-v2/2026-05/21_2791_司机档案-修改接口-管理后台.md - 6 接口全契约 + 详情接口新增 relatedOrders mock 字段(PR #2796) - 真枚举 DriverAttachmentCategoryEnum(8 类目 + 张数上限,前 3 类换证留历史语义) + DriverStatusEnum / SeasonEnum / InsuranceTypeEnum @Pattern 强校验 - 4 嵌套 VO 完整字段表(DriverLicenseVO/DriverEmergencyVO/DriverInsuranceVO/DriverStatsVO) - attachments 是 Map<category, List> 按类目分组 - 错误码:600200-600205 + 601001-601005 - 5 tab 后端实现说明(season=active/pending/archived/blacklist + 全部不传) - 关联 Issue #2791 + PR #2796 - mock 警示(固定 3 条假数据,所有司机返回相同,待真实化) 校准(代码 vs 文档): - 车辆错误码段位实际避让到 600107-600110(与车型管理库 600101-600106 冲突) - 司机详情 attachments 是 Map<String, List<Item>> 按类目分组返回(不是扁平 array) - DriverImportRespVO 字段名与 VehicleImportRespVO 不同(前者 insertedCount/renewedCount/errorCount,后者 success/failed/errorRows)
19 KiB
【修改接口·管理后台】车辆档案 6 接口(全模块首次推送 changelog)
PR: 无(本次为首次推送,实质接口契约未变更,配合 §1 §3 模块 changelog 一并补齐 fleet 全档案契约) 服务: hl-fleet-service | 更新时间: 2026-05-21
存放目录:
changelogs-v2/2026-05/影响范围: 管理后台「车管 / 车辆档案」页(列表 + 详情 + 新增 + 编辑 + 软删 + Excel 批量导入)
⚠️ 关键说明
车辆档案模块 6 接口自 fleet 服务首次落地(PR #2724)以来契约未变化,本次仅作首次完整 changelog 推送,让前端 v3 项目仓库一次拿全 6 接口契约。
与车型管理库(§1 changelog)配套:车辆档案的 vehicleModelId 引用车型管理库的型号 ID,前端可在新增车辆页用 §1.5 / §1.7 拉型号下拉。
1. 接口背景
车辆档案是车队所有车辆的主数据:车牌 / 车型 / VIN / 行驶证 / 保险 / 年检 / 多类目附件(外观/内饰/保险/年检/营运证/其他 6 类)。
前端"车管 / 车辆档案"页面提供:
- 列表筛选(按车牌、车队、车型大类、状态、保险/年检到期日)
- 详情查看(含附件按类目分组)
- 新增 / 编辑(含附件 diff 语义批量提交)
- 软删(业务流水模块上线后会按"未完成派单"拒删)
- Excel 批量导入
2. 变更清单
| # | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|
| 1 | GET | /admin/fleet/vehicles/page |
首推 | 分页列表(多条件过滤) |
| 2 | GET | /admin/fleet/vehicles/{vehicleId} |
首推 | 详情(含附件) |
| 3 | POST | /admin/fleet/vehicles |
首推 | 新增(含附件批量 INSERT) |
| 4 | PUT | /admin/fleet/vehicles/{vehicleId} |
首推 | 编辑(附件 diff 语义) |
| 5 | DELETE | /admin/fleet/vehicles/{vehicleId} |
首推 | 软删 |
| 6 | POST | /admin/fleet/vehicles/import |
首推 | Excel 批量导入 |
3. 接口详情
3.1 分页查询
GET /admin/fleet/vehicles/page
- 使用场景:车管车辆档案列表页
- 认证:管理后台 JWT
- 幂等:是(只读)
Query 入参:
| 字段 | 类型 | 必填 | 默认 | 校验 | 说明 |
|---|---|---|---|---|---|
page |
int | ❌ | 1 | >= 1 |
页码 |
pageSize |
int | ❌ | 20 | 1-100 |
每页条数 |
keyword |
string | ❌ | — | — | 车牌模糊匹配 |
fleet |
string | ❌ | — | own/coopA/coopB |
车队精确过滤 |
typeKey |
string | ❌ | — | — | 车型大类 key 精确(如 suv) |
vehicleStatus |
string | ❌ | — | idle/busy/maint |
状态精确 |
insureDueBefore |
date | ❌ | — | yyyy-MM-dd | 保险到期日早于此日(含) |
inspectDueBefore |
date | ❌ | — | yyyy-MM-dd | 年检到期日早于此日(含) |
出参:Result<PageResult<VehiclePageItemRespVO>> (按 create_time DESC)
| 字段 | 类型 | 说明 |
|---|---|---|
records[].id |
string | 车辆 ID(Long 序列化为 String) |
records[].plate |
string | 车牌 |
records[].vehicleModelId |
string | 型号 ID |
records[].vehicleTypeId |
string | 大类 ID |
records[].modelName |
string | 型号名(如丰田汉兰达) |
records[].seats |
int | 座位数 |
records[].fleet |
string | 车队(own/coopA/coopB) |
records[].vehicleStatus |
string | 状态(idle/busy/maint) |
records[].insurer |
string | 保险公司 |
records[].insureDue |
date | 保险到期日 |
records[].inspectDue |
date | 年检到期日 |
records[].createTime |
datetime | 创建时间 |
total / page / pageSize |
int | 分页元数据 |
典型示例 请求:
GET /admin/fleet/vehicles/page?page=1&pageSize=20&fleet=own&insureDueBefore=2025-12-31
Authorization: Bearer <token>
(无请求体)
典型示例 响应:
{
"code": 200,
"data": {
"records": [
{
"id": "1234567890123456789",
"plate": "蒙A-88888",
"vehicleModelId": "1234567890123456001",
"vehicleTypeId": "1234567890123456002",
"modelName": "丰田汉兰达",
"seats": 7,
"fleet": "own",
"vehicleStatus": "idle",
"insurer": "中国人保财险",
"insureDue": "2025-12-31",
"inspectDue": "2025-12-31",
"createTime": "2025-05-21 14:00:00"
}
],
"total": 1,
"page": 1,
"pageSize": 20
},
"msg": "成功"
}
错误码:400 参数校验 / 401 未登录
3.2 详情
GET /admin/fleet/vehicles/{vehicleId}
- 使用场景:详情页 / 编辑前预填
- 认证:管理后台 JWT
- 幂等:是
路径入参:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
vehicleId |
Long | ✅ | 车辆 ID |
出参:Result<VehicleDetailRespVO>
包含 §3.1 全部字段 + 以下扩展:
| 字段 | 类型 | 说明 |
|---|---|---|
vin |
string | 车架号(VIN) |
regDate |
date | 注册日期 |
policyNo |
string | 保单号 |
regCertNo |
string | 行驶证号 |
regCertOwner |
string | 行驶证所有人 |
regCertUsage |
string | 使用性质 |
regCertFrontUrl |
string | 行驶证正面 OSS URL |
regCertBackUrl |
string | 行驶证副页 OSS URL |
attachments[] |
array | 附件列表(按 category + sortNo 升序) |
attachments[].id |
string | 附件 ID |
attachments[].category |
string | 类目(见 §6.1) |
attachments[].sortNo |
int | 同类目排序(0-based) |
attachments[].url |
string | OSS URL |
attachments[].mimeType |
string | 例 image/jpeg |
primaryDriverName |
string|null | 目前恒为 null(待业务流水 PR 注入司机姓名) |
updateTime |
datetime | — |
典型示例 响应:
{
"code": 200,
"data": {
"id": "1234567890123456789",
"plate": "蒙A-88888",
"vehicleModelId": "1234567890123456001",
"vehicleTypeId": "1234567890123456002",
"modelName": "丰田汉兰达",
"seats": 7,
"fleet": "own",
"vehicleStatus": "idle",
"vin": "LSVNV2182E2100001",
"regDate": "2020-01-01",
"inspectDue": "2025-12-31",
"insurer": "中国人保财险",
"policyNo": "PICC-2024-XXX",
"insureDue": "2025-12-31",
"regCertNo": "12345678",
"regCertOwner": "呼籁旅游服务有限公司",
"regCertUsage": "营运租赁",
"regCertFrontUrl": "https://oss.example.com/fleet/cert-front.jpg",
"regCertBackUrl": "https://oss.example.com/fleet/cert-back.jpg",
"attachments": [
{
"id": "1234567890123456900",
"vehicleId": "1234567890123456789",
"category": "exterior",
"sortNo": 0,
"url": "https://oss.example.com/fleet/ext-1.jpg",
"mimeType": "image/jpeg"
}
],
"primaryDriverName": null,
"createTime": "2025-05-21 14:00:00",
"updateTime": "2025-05-21 14:00:00"
},
"msg": "成功"
}
错误码:600110 车辆不存在 / 401 未登录
3.3 新增
POST /admin/fleet/vehicles
- 使用场景:新增车辆 + 附件批量提交(前端 STS 直传 OSS 后把 URL 数组放主表单一并提交)
- 认证:管理后台 JWT
- 幂等:否(重复抛 600100/600107)
Body 入参(VehicleSaveReqVO):
| 字段 | 类型 | 必填 | 校验 | 说明 |
|---|---|---|---|---|
plate |
string | ✅ | @Size(max=16) |
车牌(全局唯一) |
vehicleModelId |
Long | ✅ | — | 车型型号 ID(引用 §1 模块) |
fleet |
string | ✅ | @Pattern(own|coopA|coopB) |
车队 |
vehicleStatus |
string | ❌ | @Pattern(idle|busy|maint) |
状态,默认 idle |
vin |
string | ❌ | @Size(max=32) |
车架号(传则全局唯一) |
regDate |
date | ❌ | — | 注册日期 |
inspectDue |
date | ❌ | — | 年检到期日 |
insurer |
string | ❌ | @Size(max=64) |
保险公司 |
policyNo |
string | ❌ | @Size(max=64) |
保单号 |
insureDue |
date | ❌ | — | 保险到期日 |
regCertNo |
string | ❌ | @Size(max=64) |
行驶证号 |
regCertOwner |
string | ❌ | @Size(max=128) |
行驶证所有人 |
regCertUsage |
string | ❌ | @Size(max=32) |
使用性质 |
regCertFrontUrl |
string | ❌ | @Size(max=512) |
行驶证正面 OSS URL |
regCertBackUrl |
string | ❌ | @Size(max=512) |
行驶证副页 OSS URL |
attachments[] |
array | ❌ | — | 附件数组(见下) |
attachments[] 单项(VehicleAttachmentReqVO):
| 字段 | 类型 | 必填 | 校验 | 说明 |
|---|---|---|---|---|
id |
Long | ❌ | — | 附件 ID(新增项不传) |
category |
string | ✅ | @Size(max=32) |
类目(见 §6.1) |
url |
string | ✅ | @Size(max=512) |
OSS URL |
mimeType |
string | ✅ | @Size(max=64) |
例 image/jpeg |
sortNo |
int | ❌ | — | 同类目排序(不传时按入参顺序自动重写 0..N-1) |
出参:Result<VehicleCreateRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 新增车辆 ID |
attachmentIds[] |
array | 附件 ID 列表(按入参顺序) |
典型示例 请求:
POST /admin/fleet/vehicles
Authorization: Bearer <token>
Content-Type: application/json
{
"plate": "蒙A-88888",
"vehicleModelId": "1234567890123456001",
"fleet": "own",
"vehicleStatus": "idle",
"vin": "LSVNV2182E2100001",
"regDate": "2020-01-01",
"inspectDue": "2025-12-31",
"insurer": "中国人保财险",
"policyNo": "PICC-2024-XXX",
"insureDue": "2025-12-31",
"regCertNo": "12345678",
"regCertOwner": "呼籁旅游服务有限公司",
"regCertUsage": "营运租赁",
"regCertFrontUrl": "https://oss.example.com/fleet/cert-front.jpg",
"regCertBackUrl": "https://oss.example.com/fleet/cert-back.jpg",
"attachments": [
{ "category": "exterior", "url": "https://oss.example.com/fleet/ext-1.jpg", "mimeType": "image/jpeg" },
{ "category": "exterior", "url": "https://oss.example.com/fleet/ext-2.jpg", "mimeType": "image/jpeg" },
{ "category": "insurance", "url": "https://oss.example.com/fleet/ins.jpg", "mimeType": "image/jpeg" }
]
}
典型示例 响应:
{
"code": 200,
"data": {
"id": "1234567890123456789",
"attachmentIds": ["1234567890123456900", "1234567890123456901", "1234567890123456902"]
},
"msg": "成功"
}
异常 请求(车牌重复):
{ "code": 600100, "msg": "车牌已存在", "data": null }
异常 请求(附件类目张数超上限 exterior > 6):
{ "code": 601003, "msg": "该类目张数已达上限", "data": null }
错误码:400 参数校验 / 600100 车牌已存在 / 600107 VIN 已存在 / 600108 车型不存在 / 601002 附件类目非法 / 601003 类目张数达上限 / 401 未登录
3.4 编辑(附件 diff 语义)
PUT /admin/fleet/vehicles/{vehicleId}
- 使用场景:编辑车辆基本信息 + 附件增删改一次性提交
- 认证:管理后台 JWT
- 幂等:是
路径入参:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
vehicleId |
Long | ✅ | 车辆 ID |
Body 入参:同 §3.3(共用 VehicleSaveReqVO)
附件 diff 语义:
| 入参 attachments[] 项 | DB 中的对应行 | 行为 |
|---|---|---|
有 id + DB 命中 |
存在 | 保留(可更新 sortNo) |
无 id(新增项) |
— | INSERT |
| — | 存在但本次未传 | 软删 |
出参:Result<VehicleUpdateRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
kept |
int | 保留的附件数 |
inserted |
int | 新插入的附件数 |
softDeleted |
int | 软删的附件数 |
典型示例 响应:
{
"code": 200,
"data": { "kept": 2, "inserted": 1, "softDeleted": 1 },
"msg": "成功"
}
错误码:400 参数校验 / 600100 车牌已存在 / 600107 VIN 已存在 / 600108 车型不存在 / 600110 车辆不存在 / 601002 类目非法 / 601003 类目张数达上限 / 401 未登录
3.5 软删
DELETE /admin/fleet/vehicles/{vehicleId}
- 使用场景:下架车辆
- 认证:管理后台 JWT
- 幂等:是
路径入参:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
vehicleId |
Long | ✅ | 车辆 ID |
出参:Result<Void>(data: null)
典型示例 响应:
{ "code": 200, "data": null, "msg": "成功" }
异常 请求(车辆有未完成派单 —— 业务流水 PR 落地后才生效):
{ "code": 600109, "msg": "车辆有未完成派单,不能删除", "data": null }
错误码:600109 有未完成派单(占位,业务流水 PR 上线后生效)/ 600110 车辆不存在 / 401 未登录
3.6 Excel 批量导入
POST /admin/fleet/vehicles/import
- 使用场景:运营批量录入车辆
- 认证:管理后台 JWT
- 幂等:否(重复行按车牌冲突拒绝单行)
入参(multipart/form-data):
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
file |
file | ✅ | Excel(.xlsx / .xls)或 CSV |
表头约定(第 1 行 12 列,顺序固定):
车牌 | 车型型号ID | 车队(own/coopA/coopB) | 车架号(VIN) |
注册日期(yyyy-MM-dd) | 年检到期日(yyyy-MM-dd) |
保险公司 | 保单号 | 保险到期日(yyyy-MM-dd) |
行驶证号 | 行驶证所有人 | 使用性质
出参:Result<VehicleImportRespVO>
| 字段 | 类型 | 说明 |
|---|---|---|
total |
int | 总行数(不含表头) |
success |
int | 成功导入数 |
failed |
int | 失败行数 |
errorRows[] |
array | 失败行详情 |
errorRows[].row |
int | Excel 行号(从 2 开始,1 为表头) |
errorRows[].plate |
string | 车牌(用于定位) |
errorRows[].msg |
string | 错误信息(如"车牌已存在") |
典型示例 响应:
{
"code": 200,
"data": {
"total": 10,
"success": 8,
"failed": 2,
"errorRows": [
{ "row": 3, "plate": "蒙A-88888", "msg": "车牌已存在" },
{ "row": 7, "plate": "蒙A-99999", "msg": "车型型号 ID 不存在" }
]
},
"msg": "成功"
}
错误码:400 文件解析失败 / 401 未登录
6. 枚举 / 数据字典
6.1 附件类目(category,VehicleAttachmentCategoryEnum)
所属字段:VehicleAttachmentReqVO.category / VehicleAttachmentRespVO.category | 类型:String | 必填:✅
真枚举(后端硬编码,含同类目张数上限):
| 值 | 中文 | 张数上限 |
|---|---|---|
exterior |
车辆外观 | 6 |
interior |
车辆内饰 | 3 |
insurance |
保险单据 | 2 |
inspection |
年检单据 | 2 |
operation_license |
营运证 | 2 |
other |
其他附件 | 5 |
超出上限触发 601003 "该类目张数已达上限"。
6.2 车队(fleet)
所属字段:VehicleSaveReqVO.fleet / VehiclePageReqVO.fleet / VehiclePageItemRespVO.fleet | 类型:String | 必填:✅(新增时)
@Pattern 强校验枚举:
| 值 | 含义 |
|---|---|
own |
自营车辆 |
coopA |
合作车队 A |
coopB |
合作车队 B |
6.3 车辆状态(vehicleStatus)
所属字段:同 fleet | 类型:String | 必填:❌(默认 idle)
@Pattern 强校验枚举:
| 值 | 含义 |
|---|---|
idle |
空闲 |
busy |
在途 |
maint |
维修中 |
7. 错误码(全模块汇总)
| code | 含义 | 触发条件 | 涉及接口 |
|---|---|---|---|
| 400 | 参数校验失败 | 字段缺失/长度/枚举值不合法/MIME 非法 | 所有写入接口 |
| 401 | 未登录 | JWT 无效或缺失 | 全部 6 接口 |
| 600100 | 车牌已存在 | plate 全局唯一约束 |
§3.3 §3.4 |
| 600107 | VIN 已存在 | vin 全局唯一约束 |
§3.3 §3.4 |
| 600108 | 车型不存在 | vehicleModelId 无效或软删 |
§3.3 §3.4 |
| 600109 | 有未完成派单,不能删除 | 业务流水 PR 上线后才触发(目前占位) | §3.5 |
| 600110 | 车辆不存在 | vehicleId 无效或软删 |
§3.2 §3.4 §3.5 |
| 601001 | 实体不存在 | 附件关联的主车辆不存在 | §3.3 §3.4 |
| 601002 | 附件类目非法 | category 不在枚举内 |
§3.3 §3.4 |
| 601003 | 该类目张数已达上限 | 超出 §6.1 张数上限 | §3.3 §3.4 |
| 601004 | 文件超出大小限制 | 应用层 + OSS HEAD 校验 | §3.6(导入),§3.3/§3.4 主体不直接校验 |
| 601005 | 文件类型非法 | MIME 不在白名单 | §3.3 §3.4 |
错误码段位 600100-600110 归属车辆档案;601001-601005 归属附件管理(车辆/司机共用)。
注意:与文档 v1.4 的差异 —— 文档原计划 VIN 用 600101 / 车型不存在 600102 / 派单冲突 600103,但因这 3 个段位被车型管理库占用(见 §1 changelog),实际代码避让到 600107/600108/600109。本 changelog 反映代码实际值。
9. 业务边界
- ✅ 附件 diff 语义统一:§3.3 新增时所有项无 id;§3.4 编辑时按
id命中度决定保留/插入/软删 - ✅ sortNo 自动重写:附件
sortNo不传时按入参顺序自动写 0..N-1(同 category 内 0-based) - ✅ 行驶证 OSS URL 留主表:
regCertFrontUrl/regCertBackUrl是主表字段(不走附件表),与附件分离 - ✅ 唯一约束:
plate/vin全局唯一(违反抛 600100 / 600107) - ✅ 车队 / 状态强枚举:
fleetvehicleStatus走@Pattern校验,违反返 400 - ⚠️ primaryDriverName 占位:§3.2 详情返
primaryDriverName: null,待业务流水 PR 上线后注入司机姓名 - ⚠️ 派单删除保护占位:§3.5 错误码 600109 在业务流水 PR 上线后才会真触发
11. 影响评估
- 是否破坏向后兼容:否(接口契约自首次落地以来未变更)
- 前端是否必须同步上线:否(本次仅补发 changelog,无代码改动)
- 影响已有数据:无
12. 注意事项
- 分页参数名:
page/pageSize(不是 pageNo) - Long 主键序列化:
id/vehicleModelId/vehicleTypeId/attachments[].id等 Long 字段 JSON 返回为 String,前端不要当 Number 解析 - 错误码不连续:600100 / 600107-600110(中间 600101-600106 是车型管理库的段位),见 §7 注解
- 附件 URL 不可改:§3.4 编辑时
attachments[].url不可改(后端忽略),只能保留 / 新增 / 软删 primaryDriverName当前恒为 null:业务流水 PR 上线前前端不要展示该字段或处理为"未指派"
13. 关联 / 联系人
13.1 链接
- API 文档:
docs/order-v3/api/API-SPEC-FLEET-V1.5.html§2 车辆档案 - DB 文档:
docs/order-v3/database/DATABASE-SCHEMA-FLEET-V1.5.html§2.3 / §2.6 - 同期相关 changelog:
- 车型管理库 9 接口(
21_2785_车型管理库-新增接口-管理后台.md) - 司机档案 6 接口(
21_2791_司机档案-修改接口-管理后台.md)
- 车型管理库 9 接口(
13.2 联系人
- 后端负责人: @yst(腰苏图)
- 前端对接(管理后台): 待指派