hl-api-changelog/changelogs-v2/2026-05/21_车辆档案-修改接口-管理后台.md
yaosutu 761ca0742d 新增 车辆档案 6 接口 + 司机档案 6 接口 全模块快照 changelog(fleet 模块批量补发)
两份 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)
2026-05-21 15:33:02 +08:00

554 行
19 KiB
Markdown

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

# 【修改接口·管理后台】车辆档案 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 | 车辆 IDLong 序列化为 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>
(无请求体)
```
**典型示例 响应**
```json
{
"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 | — |
**典型示例 响应**
```json
{
"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<Long> | 附件 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" }
]
}
```
**典型示例 响应**
```json
{
"code": 200,
"data": {
"id": "1234567890123456789",
"attachmentIds": ["1234567890123456900", "1234567890123456901", "1234567890123456902"]
},
"msg": "成功"
}
```
**异常 请求**(车牌重复):
```json
{ "code": 600100, "msg": "车牌已存在", "data": null }
```
**异常 请求**(附件类目张数超上限 `exterior` > 6
```json
{ "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 | 软删的附件数 |
**典型示例 响应**
```json
{
"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`
**典型示例 响应**
```json
{ "code": 200, "data": null, "msg": "成功" }
```
**异常 请求**(车辆有未完成派单 —— **业务流水 PR 落地后才生效**
```json
{ "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 | 错误信息(如"车牌已存在" |
**典型示例 响应**
```json
{
"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
-**车队 / 状态强枚举**`fleet` `vehicleStatus``@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`](./21_2785_车型管理库-新增接口-管理后台.md)
- 司机档案 6 接口([`21_2791_司机档案-修改接口-管理后台.md`](./21_2791_司机档案-修改接口-管理后台.md)
### 13.2 联系人
- **后端负责人**: @yst(腰苏图)
- **前端对接(管理后台)**: 待指派