# 【新增接口·管理后台+司机H5】司机自助 H5「已发放链接管理」5 端点——列表/导出/作废/重发短信/重新生成

> 服务:hl-fleet-service(8087/8187) + hl-user-service(8081/8181) | 分支:dev-v3 | PR:#3777+#3778(补丁) | 工单:#3775 | 已部署测试服并 13 项 E2E 实测通过(2026-06-12)
> 背景:API §4.6 此前未实现——车管发出去的 H5 录入链接看不到、不能作废、不能重发。本次补齐完整生命周期管理,同时把链接 URL 收口 Nacos、H5 init 补状态机出参。
> 对应原型:车管控制台「链接管理」页。

## ⚠️ 关键说明(前端动作)

1. **管理后台新增「已发放链接管理」页面**(5 个新端点,见 §1-§5),路径前缀 `/admin/fleet/h5/tokens`。
2. **链接格式变了**:所有生成的 H5 链接统一为 `https://hr.1814.love/driver-intake?t={token}`(参数名 **`?t=`**,与短信模板一致;原硬编码 `h5.hl-fleet.com/onboard?token=` 作废)。**H5 前端从页面 URL 读 `t` 参数**后调 init。
3. **H5 init(`GET /app/h5/driver-onboard/init`)出参新增 4 字段**(司机 H5 需按 `tokenState` 渲染失效文案,见 §6)。
4. **§4.1 生成链接出参新增 `pendingId`**(后续作废/重发/重新生成都以 pendingId 为操作键)。
5. **测试服公网已可直达 H5 接口**:nginx 已补 `/app/` 代理(此前 `/app/h5/*` 经 9443 会落到 SPA 页面)。
6. **已知外部阻塞(不影响接口联调)**:重发短信链路已全通(事件→通知中心→阿里云真实调用,send_log 落档),但阿里云模板 SMS_507390235 的 token 变量类型(链接参数)容不下 32 位 token 被拒发——**需 wx 在阿里云控制台调整模板变量规范后过审**,代码侧零改动。name 为空拒发已后端兜底(空→「师傅」)。
7. 60 秒内对同一链接重复点「重发」:接口返回成功但通知中心幂等拒发第二条(防轰炸),属预期。

## 1. 已发放链接列表(分页)

`GET /admin/fleet/h5/tokens?mode=&status=&keyword=&page=1&pageSize=20`

| 入参 | 必填 | 说明 |
|---|---|---|
| mode | 否 | new=新招 / renew=续签 |
| status | 否 | 派生 6 态:`VOIDED`已作废/`APPROVED`已通过/`REJECTED`已驳回/`SUBMITTED`已提交/`EXPIRED`已过期/`VALID_UNSUBMITTED`有效·未提交 |
| keyword | 否 | 模糊匹配 姓名 OR token(**不支持手机号**——加密存储无法模糊) |

实测出参(行):

```json
{
  "pendingId": "2065414570750455809",      // 操作键(字符串防精度丢失)
  "mode": "new", "modeLabel": "新招",
  "token": "182ee9dd884e46d8a628bb98a5695252",
  "name": "", "phone": null,                // 手机脱敏返回(138****2345),未填为 null
  "createTime": "2026-06-12T20:45:29", "expireAt": "2026-06-19T20:45:29",
  "submittedAt": null, "reviewStatus": "pending",
  "status": "VALID_UNSUBMITTED", "statusLabel": "有效·未提交",
  "url": "https://hr.1814.love/driver-intake?t=182ee9dd884e46d8a628bb98a5695252"  // 直接复制发司机
}
```

## 2. 导出 CSV

`GET /admin/fleet/h5/tokens/export?mode=&status=&keyword=`(同条件,不分页)

返回 `text/csv;charset=UTF-8` 文件流(带 UTF-8 BOM,Excel 直开不乱码),列:`类型,token,姓名,手机(脱敏),生成时间,有效期至,状态,审核状态`,文件名 `h5_tokens_{时间戳}.csv`。前端 blob 下载即可。

## 3. 作废链接

`POST /admin/fleet/h5/tokens/{pendingId}/void` body(可选):`{ "reason": "司机信息有误" }`

- 仅「已通过」不可作废(**600304**);其余状态(含已驳回/已提交)均可作废;重复作废幂等返回成功
- 作废后司机端立即失效:H5 init 返 `tokenState=voided`、提交/OCR 被 600304 硬拦(实测联动通过)

## 4. 重发短信

`POST /admin/fleet/h5/tokens/{pendingId}/resend` body(可选):`{ "phone": "13808612345" }`(覆盖收件号,生成时没填手机的链接由此补)

- 仅「有效·未提交」「已驳回」可重发,否则 **600308**;入参与档案手机号均空 → **600307**
- **不刷新有效期**(要延长走重新生成)
- 出参仅 `{ "maskedPhone": "138****2345" }`——短信异步发出,**接口成功≠短信已到**,到达结果看通知中心发送日志

## 5. 重新生成

`POST /admin/fleet/h5/tokens/{pendingId}/regenerate`(无 body)

- 「已提交」「已通过」不可重新生成(**600309**);其余(含已过期/已作废)可
- 旧链接自动作废(审计留痕),返回**全新** pendingId/token/url/expireAt/qrCodeUrl(出参同 §4.1 生成)
- **新链接不自动发短信**——车管复制 url 或对新 pendingId 点重发

## 6. 司机 H5 init 出参新增 4 字段(H5 前端)

`GET /app/h5/driver-onboard/init?token=`

| 字段 | 说明 |
|---|---|
| tokenState | `fresh`首次可填/`editable`已提交待审可覆盖重提/`rejected_editable`已驳回可改重提/`expired`已过期/`voided`已作废/`consumed`已通过(终态)/`invalid`无效 |
| reviewStatus | pending / approved / rejected |
| rejectReason | 驳回原因(驳回相关态才有值,指导司机改哪) |
| lastSubmittedAt | 上次提交时间(已提交过才有值) |

- `tokenValid` 保留向后兼容(= tokenState ∈ fresh/editable/rejected_editable);**修了一个旧 bug**:审核通过后司机再打开,旧实现误返 tokenValid=true(填完 8 步提交才被拦),现在 init 即返 false + `consumed`
- H5 请按 tokenState 渲染失效文案(已作废/已过期/已通过 区分展示)

## 7. 错误码速览

| code | message |
|---|---|
| 600304 | 链接已作废(作废后的 H5 提交/OCR 也返此码) |
| 600307 | 无可用手机号 |
| 600308 | 仅有效未提交或已驳回的链接可重发 |
| 600309 | 已提交或已通过的链接不可重新生成 |
| 600402 | 待审核记录不存在 |
