feat(order): 出行人智能批量导入 API @mmg

这个提交包含在:
wx 2026-05-06 15:21:25 +08:00
父节点 0db446c409
当前提交 07adbc47c3

查看文件

@ -0,0 +1,101 @@
# [feat] 订单管理端「出行人智能批量导入」接口
- **日期**: 2026-05-06
- **PR**: wx/HL #1714 (Closes #1713)
- **后端**: wx (Claude)
- **前端 @**: mmg
---
## 接口
```
POST {GATEWAY}/admin/order-traveler/smart-parse
Header: Authorization: Bearer <admin token>
Body:
{
"rawText": "张三 350101199001011231 13800138000\n李四 110101199201021237 13900139000"
}
```
## 响应结构
```jsonc
{
"code": 200,
"data": {
"totalLines": 2,
"successCount": 2,
"failCount": 0,
"travelers": [
{
"lineNo": 1,
"name": "张三",
"idCardType": "ID_CARD",
"idCardTypeLabel": "身份证",
"idCardNo": "350101199001011231",
"phone": "13800138000",
"gender": "1", // "1"男 / "2"女 / null(身份证解析失败)
"birthday": "1990-01-01", // yyyy-MM-dd 字符串
"travelerType": "ADULT",
"travelerTypeLabel": "成人" // 中文必返非 null
}
],
"errors": [
// 每个失败行: { lineNo, raw(脱敏), reason(中文) }
]
}
}
```
## 关键约束
- **顺序无关识别**: 每行任意顺序写姓名/身份证/手机号都能正确归位;18 位身份证→`idCardNo` 自动派生 `birthday/gender/travelerType`,11 位 1 开头→`phone`,其他→`name`
- **容错**: 表头跳过(三 token AND)、行首序号剥离(`1./1、/1)`)、BOM/零宽字符 strip、半角/全角/制表符空格切分
- **校验**: 18 位 GB 11643-1999 校验码(不支持 15 位旧版)
- **字段对齐**: ParsedTraveler 100% 对齐 `AdminOrderTravelerAddReqVO`,可直接 `formData = parsedTraveler` 一键回填
- **`birthday` 序列化为 `"yyyy-MM-dd"` 字符串**(已 @JsonFormat,前端 DatePicker 直接吃)
- **`*Label` 必返中文非 null**(P0 #1679,字典失败 throw 不兜底)
- **后端不直接落库**: 仅解析返回,前端拿到结果回填出行人表格(可编辑),用户确认后调原 `POST /admin/order/{orderId}/travelers` 入库
## 限制
| 限制 | 值 | 错误码 |
|------|-----|--------|
| 单次行数 | ≤ 100 | `SMART_PARSE_LINE_LIMIT_EXCEEDED` (504003) |
| 单次文本 | ≤ 20000 字符 | `SMART_PARSE_TEXT_TOO_LONG` (504004) + @Size 兜底 |
| 单行长度 | ≤ 500 字符 | 行内失败,reason=单行超长 |
| 限流 | 每用户 10 次 / 分钟 | `@RateLimiter` |
| 字典服务 | 失败 throw | `DICT_SERVICE_UNAVAILABLE` (504005) |
## 失败原因(中文)
- `身份证号校验失败` — 18 位但末位校验码错
- `不支持 15 位身份证,请用 18 位`
- `缺少身份证号` / `缺少姓名` / `未识别到姓名`
- `本行检测到多个身份证号,已取第一个` (warning)
- `身份证号在本次导入内重复` (warning,双胞胎/同名场景不强制去重)
- `单行超长(>500),疑似格式错误`
- `姓名含非法字符`
## 前端 UI 落点(@mmg)
参考用户提供的截图("快捷导入"弹窗):
- 入口: 订单详情 → 出行人信息 Tab → 「添加出行人」旁加「智能导入」按钮
- 弹窗: 多行 Textarea + 提示文案「导入要求:一人一行 姓名 身份证 手机号码之间用空格隔开」
- 确定 → 调本接口 → 拿 `travelers[]` 回填出行人表格(每行一条,可编辑)
- `errors[]` 高亮失败行,允许用户在原文本框修正后再次提交
- `successCount=0` 时弹「全部解析失败,请检查格式」
- `errors[i].lineNo` 是已剔除空行/表头/序号干扰后的数据行序号(非物理行号)
## 兼容性
- 仅新增接口,无 DDL / 无字典数据变更 / 无 Nacos 配置变更
- 复用现有 `AdminOrderTravelerController` 的 add 接口入库,流程不变
- 限流走项目自研 `@RateLimiter`,Redis 挂时自动放行(降级)
## 测试覆盖
- 单测: Service 49 + Controller MockMvc 9 + PiiMaskUtil 18 + IdCardChecksumUtil 13 = **89/89 全绿**
- CR: 9.6/10 通过(P0 红线 @OperationLog AOP 移除防 PII 落审计)
- 真 round-trip(网关/真容器/真限流/真 Feign): 部署测试服后补