feat(order): 出行人智能批量导入 API @mmg
这个提交包含在:
父节点
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): 部署测试服后补
|
||||||
正在加载...
x
在新工单中引用
屏蔽一个用户