# 【修改接口·管理后台】司机详情 attachments 弱类型 Map 改强类型 DriverAttachmentsVO (#2812)

> **PR**: #2818 | **服务**: hl-fleet-service | **更新时间**: 2026-05-21 15:30
>
> **存放目录**: `changelogs-v2/2026-05/`
> **关联前序**: [`21_2791_司机档案-修改接口-管理后台.md`](./21_2791_司机档案-修改接口-管理后台.md)（commit `761ca07`）

---

## ⚠️ 关键变化（30 秒速读）

**`GET /admin/fleet/drivers/{driverId}` 的响应字段 `attachments` 类型变了**：

| 项 | 改前 | 改后 |
|---|---|---|
| 类型 | `Map<string, DriverAttachmentRespVO[]>`（动态 key） | `DriverAttachmentsVO`（8 个固定字段） |
| 前端用法 | `data.attachments['id_card']` 字符串访问 | `data.attachments.idCard` 字段访问 |
| TypeScript 类型 | 没有具体字段名 | 8 个具体字段（含类型提示） |

**前序 changelog 废止内容**：[`21_2791_司机档案-修改接口-管理后台.md`](./21_2791_司机档案-修改接口-管理后台.md) **§3.2 详情接口** + **§9 业务边界** 中所有把 attachments 描述为 `Map<String, List<>>` 的部分**已废**，以本文档为准。

**前端必须改动**（不是兼容的）：所有 `data.attachments[key]` 形式的访问要改成 `data.attachments.xxx` 字段访问。

---

## 1. 接口背景

`attachments` 当前用 `Map<String, List<DriverAttachmentRespVO>>` 返回违反 HL 项目规则 [禁止弱类型返回值]：
- Swagger / TypeScript 生成的类型是 `{ [key: string]: DriverAttachmentRespVO[] }`，**没有具体字段名**
- 前端 IDE 不能自动补全
- 拼错 key 编译期不报错
- 文档无法自包含描述每个类目出参

改为强类型 `DriverAttachmentsVO`，8 个 List 字段对应 8 个固定类目（与 `DriverAttachmentCategoryEnum` 严格对齐）。

---

## 2. 变更清单

| # | 方法 | 路径 | 变更类型 | 说明 |
|---|------|------|----------|------|
| 1 | GET | `/admin/fleet/drivers/{driverId}` | **修改** | 响应字段 `attachments` 类型变化（详见 §3.1） |

仅 1 个端点受影响。其余 5 个司机档案接口（page / create / update / delete / import）**不受影响**。

---

## 3. 接口详情

### 3.1 司机详情 — `attachments` 字段类型变化

**GET** `/admin/fleet/drivers/{driverId}`

#### 改后的 `attachments` 字段结构

字段：`DriverAttachmentsVO` 对象（**不再是 Map**）。

| 字段 | 类型 | 默认 | 张数上限（来自 DriverAttachmentCategoryEnum） |
|---|---|---|---|
| `idCard` | `DriverAttachmentRespVO[]` | `[]` | 2（"换证留历史"：支持正反 2 张或新旧 2 套） |
| `driverLicense` | `DriverAttachmentRespVO[]` | `[]` | 2（换证留历史） |
| `portrait` | `DriverAttachmentRespVO[]` | `[]` | 2（换证留历史） |
| `healthCert` | `DriverAttachmentRespVO[]` | `[]` | 2 |
| `trainingCert` | `DriverAttachmentRespVO[]` | `[]` | 5 |
| `award` | `DriverAttachmentRespVO[]` | `[]` | 5 |
| `qualification` | `DriverAttachmentRespVO[]` | `[]` | 3 |
| `other` | `DriverAttachmentRespVO[]` | `[]` | 5 |

> **每个字段默认空数组**（不为 null），前端**无需判空**，直接 `.map()` 渲染即可。

`DriverAttachmentRespVO[]` 单条项字段（**不变**）：

| 字段 | 类型 | 说明 |
|---|---|---|
| `id` | string | 附件 ID（Long 序列化为 String） |
| `driverId` | string | 所属司机 ID |
| `category` | string | 类目 |
| `sortNo` | int | 同类目排序（0-based） |
| `url` | string | OSS URL |
| `mimeType` | string | 例 `image/jpeg` |

#### 改后响应示例（节选）

```json
{
  "code": 200,
  "data": {
    "id": "1234567890123456789",
    "name": "张三",
    "...其他字段保持不变...": "...",
    "attachments": {
      "idCard": [
        { "id": "...", "driverId": "...", "category": "id_card", "sortNo": 0, "url": "https://oss/.../id-front.jpg", "mimeType": "image/jpeg" },
        { "id": "...", "driverId": "...", "category": "id_card", "sortNo": 1, "url": "https://oss/.../id-back.jpg", "mimeType": "image/jpeg" }
      ],
      "driverLicense": [
        { "id": "...", "driverId": "...", "category": "driver_license", "sortNo": 0, "url": "https://oss/.../license.jpg", "mimeType": "image/jpeg" }
      ],
      "portrait": [],
      "healthCert": [],
      "trainingCert": [],
      "award": [],
      "qualification": [],
      "other": []
    },
    "relatedOrders": [...]
  },
  "msg": "成功"
}
```

---

## 6. 枚举 / 数据字典

无新增枚举。`category` 字段语义和取值与前序 changelog [`21_2791_司机档案-修改接口-管理后台.md`](./21_2791_司机档案-修改接口-管理后台.md) §6.1 一致（8 个值），唯一差异是 **API 响应不再以 category 字符串作为 key**，而是用 VO 字段名（驼峰）。

类目 ↔ VO 字段对应关系：

| DB / category 字段值 | VO 字段（驼峰） |
|---|---|
| `id_card` | `idCard` |
| `driver_license` | `driverLicense` |
| `portrait` | `portrait` |
| `health_cert` | `healthCert` |
| `training_cert` | `trainingCert` |
| `award` | `award` |
| `qualification` | `qualification` |
| `other` | `other` |

注意单条 `DriverAttachmentRespVO.category` 仍然是 `id_card` / `driver_license` 等下划线值（不变）。改的是**外层分组的 key 形式**。

---

## 7. 错误码

无变化。

---

## 10. 修改前后对比

### 10.1 字段级对比

| 字段 | 改前 | 改后 |
|---|---|---|
| `attachments` 类型 | `Map<string, DriverAttachmentRespVO[]>` | `DriverAttachmentsVO`（8 个固定 List 字段） |
| `attachments` key 形式 | DB 下划线值（`id_card` / `driver_license` / `portrait` / ...） | VO 驼峰字段（`idCard` / `driverLicense` / `portrait` / ...） |
| 类目缺失 | 该 key 不在 Map 里 | 该字段是空数组 `[]` |

### 10.2 前端代码对照

```typescript
// 改前
const idCardAttachments = data.attachments['id_card'] ?? [];
const licenseAttachments = data.attachments['driver_license'] ?? [];

// 改后
const idCardAttachments = data.attachments.idCard;     // 不用判空
const licenseAttachments = data.attachments.driverLicense;
```

---

## 11. 影响评估

- **是否破坏向后兼容**：⚠️ **是**（字段类型变了，前端必须改）
- **前端是否必须同步上线**：**是**（老前端按 Map 解析会拿到 undefined）
- **影响其他后端服务**：无（DriverDetailRespVO 仅 admin 接口返回，不走 Feign）
- **影响已有数据**：无（仅 API 序列化形式变化，DB 无变）

---

## 12. 注意事项

- **前端 workaround 清理点**：
  - 改前如果前端有 `const map = data.attachments; const idCard = map['id_card'] ?? [];` 之类 workaround，**改成 `data.attachments.idCard`** 即可
  - 改前如果前端硬编码遍历类目（如 `for (const key of ['id_card', 'driver_license'])`），**改成直接读 VO 字段**
  - 改前如果前端有 `if (map['portrait'])` 判空逻辑，**直接删** —— 强类型 VO 字段必非 null（默认空数组）
- **前序 changelog 描述废止**：[`21_2791_司机档案-修改接口-管理后台.md`](./21_2791_司机档案-修改接口-管理后台.md) 已发出的 attachments Map 描述以本文档为准，前序 changelog **不会修改**（changelog 只追加不回写历史）

---

## 13. 关联 / 联系人

### 13.1 链接

- **Issue**: [#2812](https://git.1814.love:8443/wx/HL/issues/2812)
- **PR**: [#2818](https://git.1814.love:8443/wx/HL/pulls/2818)
- **Merge commit**: [`8c340550`](https://git.1814.love:8443/wx/HL/commit/8c340550656bdee3ee2243b98d694b818474aeba)
- **前序 changelog（attachments Map 描述废）**: [`21_2791_司机档案-修改接口-管理后台.md`](./21_2791_司机档案-修改接口-管理后台.md)
- **规则**: HL `MEMORY.md` → `feedback_no-map-return-type.md` （禁止弱类型返回值）

### 13.2 联系人

- **后端负责人**: @yst（腰苏图）
- **前端对接（管理后台）**: 待指派
