From d366a0790c41a798f223b50f87c50106be1db78f Mon Sep 17 00:00:00 2001 From: API Changelog Bot Date: Mon, 18 May 2026 21:05:09 +0800 Subject: [PATCH] =?UTF-8?q?changelog(order-v3):=20=E5=87=BA=E8=A1=8C?= =?UTF-8?q?=E4=BA=BA=203=20=E6=8E=A5=E5=8F=A3=20(#2525/#2526/#2527)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #2525 Feign /internal/order/orders/{orderId}/travelers + 解密审计表 + 589100/589101 - #2526 smart-parse 智能批量解析 + 581131-581134 + P0 审计红线 5 合规 - #2527 validate 字段对齐 v2(6→4 必填) + 581102 复用 测试服 9443 真测全 ✅ (commit f8a34607,含 31 Flyway migrations) #2525 经 SSH 内网 8086 真测(internal 路径 9443 返 403 是预期) #2526 异常路径全过,正常落库待前端联调补做(代码层 IT 已覆盖) Co-Authored-By: Claude Opus 4.7 (1M context) --- ...25_internal-traveler-decrypt-with-audit.md | 225 ++++++++++++++++ .../2026-05/18_#2526_traveler-smart-parse.md | 240 ++++++++++++++++++ .../18_#2527_traveler-validate-align-v2.md | 212 ++++++++++++++++ 3 files changed, 677 insertions(+) create mode 100644 changelogs-v2/2026-05/18_#2525_internal-traveler-decrypt-with-audit.md create mode 100644 changelogs-v2/2026-05/18_#2526_traveler-smart-parse.md create mode 100644 changelogs-v2/2026-05/18_#2527_traveler-validate-align-v2.md diff --git a/changelogs-v2/2026-05/18_#2525_internal-traveler-decrypt-with-audit.md b/changelogs-v2/2026-05/18_#2525_internal-traveler-decrypt-with-audit.md new file mode 100644 index 0000000..b5f5abf --- /dev/null +++ b/changelogs-v2/2026-05/18_#2525_internal-traveler-decrypt-with-audit.md @@ -0,0 +1,225 @@ +# order-v3 出行人模块: 新增 Feign 内部解密接口 + 审计表落库 + +> **存放目录**: 二期 v3(`order-v3` 标签) → `changelogs-v2/2026-05/` +> +> **服务**: hl-order-v3 (端口 8086) +> **PR**: #2552 +> **Issue**: #2525 +> **日期**: 2026-05-18 +> **影响范围**: **仅后端服务 Feign 内部调用**(合同签署 / 保险出单等内部模块),**前端无关** + +--- + +## ⚠️ 关键变化 + +- 新增一个 **/internal/** 路径,**仅供后端服务 Feign 调用**,经 9443 网关访问直接 403(网关白名单只放通 admin/mp/internal-feign)。前端/小程序不需要也不应调用。 +- 出参含 `decryptedAt` 字段(后端解密时间戳),用于下游业务幂等与审计回溯。 +- **新增审计表** `order_decrypt_audit_log`(9 字段 + 2 索引),每次解密同事务落审计行,**调用前请确认下游业务 purpose 取值正确**(枚举严格校验)。 + +--- + +## 一、背景 + +V5.48 §2.7 定义"敏感信息解密接口"。出行人 idNo/phone 在 v3 出行人表里为密文存储,合同签署/保险出单等内部业务需要明文。 + +风险点:任何解密动作必须留痕(谁、什么时候、为什么、查了哪个订单),否则一旦出现数据滥用无法溯源。本接口的关键不在"返明文",而在 **"返明文 + 强制同事务写审计"**。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 内部查询订单出行人明文(带解密审计) | GET | `/v3/internal/order/orders/{orderId}/travelers` | 新增 | Feign 内部调用,网关 9443 直接 403 | + +--- + +## 三、接口详情 + +### 1. 内部查询订单出行人明文 `GET /v3/internal/order/orders/{orderId}/travelers` + +**VO**: `InternalOrderTravelerRespVO` + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| orderId | Path | Long | 是 | 雪花 ID | 订单 ID | +| purpose | Query | String | 是 | 枚举:`CONTRACT_SIGN` / `INSURANCE_ISSUE` / `OTHER` | 解密用途,不在枚举内抛 589101 | + +#### 出参 `Result>` + +| 字段 | 类型 | 说明 | +|------|------|------| +| travelerId | Long | 出行人 ID | +| orderId | Long | 订单 ID | +| name | String | 姓名(明文) | +| idType | String | 证件类型 | +| idNo | String | 证件号(**明文**,已解密) | +| phone | String | 手机号(**明文**,已解密) | +| gender | String | 性别 | +| birthday | String | 生日 yyyy-MM-dd | +| race | String | 民族 | +| nationality | String | 国籍 | +| decryptedAt | String | 解密时间戳(yyyy-MM-dd HH:mm:ss),后端服务端时间 | + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": [ + { + "travelerId": 9023400111, + "orderId": 9020000333, + "name": "张三", + "idType": "ID_CARD", + "idNo": "110101199001011234", + "phone": "13800138000", + "gender": "MALE", + "birthday": "1990-01-01", + "race": "汉", + "nationality": "CN", + "decryptedAt": "2026-05-18 14:23:11" + } + ], + "success": true +} +``` + +#### 错误响应 + +```json +{ + "code": 589100, + "message": "订单不存在或已删除,无法解密", + "success": false, + "data": null +} +``` + +```json +{ + "code": 589101, + "message": "解密用途 purpose 不在允许枚举(CONTRACT_SIGN/INSURANCE_ISSUE/OTHER)", + "success": false, + "data": null +} +``` + +--- + +## 四、契约约束 + +| 约束 | 说明 | +|------|------| +| 调用方式 | 仅 Feign 内部调用,网关 9443 直接 403(网关白名单不放通 `/v3/internal/`) | +| purpose 必填 | 不传或为空 → 589101 | +| purpose 取值 | 严格枚举:`CONTRACT_SIGN` / `INSURANCE_ISSUE` / `OTHER`,大小写敏感 | +| 订单不存在 | 589100,不区分软删/不存在 | +| 同事务 | 查 traveler → 写审计 → 返 VO,**审计写失败整体回滚不返明文** | + +--- + +## 五、数据库行为 + +### 新增审计表 `order_decrypt_audit_log` + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | BIGINT | 主键雪花 | +| order_id | BIGINT | 被解密订单 | +| traveler_id | BIGINT | 被解密出行人 | +| purpose | VARCHAR(32) | 解密用途 | +| operator_type | VARCHAR(32) | 调用方类型(FEIGN_INTERNAL) | +| operator_id | VARCHAR(64) | 调用方服务名/标识 | +| field_name | VARCHAR(32) | 解密字段(idNo / phone) | +| decrypted_at | DATETIME | 解密时间 | +| create_time | DATETIME | 行写入时间 | + +**索引**:`idx_order_id` / `idx_decrypted_at` + +**Flyway**: `V20260518_002__create_order_decrypt_audit_log.sql` + +### 写入行为 + +- 每次调用,**每个出行人每个解密字段写 1 行**审计 +- 同事务写,接口返回 N 个出行人则审计行至少 2N(idNo + phone 各 1) +- 失败回滚:整体事务回滚,**已返明文绝不可能**(返回前已 commit) + +--- + +## 六、边界行为 + +- 网关 9443 调用 → 403(网关白名单拦截,**预期行为**) +- Feign 内部调用 (SSH 跳板内网 8086) → 200 +- 订单不存在 / 已删除 → 589100 +- purpose 非法 → 589101 +- 出行人列表为空 → 返 `[]`,**仍写空审计?否**:无 traveler 不写审计,只返空数组 +- 解密失败(密钥错乱等基础设施异常)→ 500,审计不落 + +--- + +## 七、不影响范围 + +- **仅影响**: 后端服务 Feign 内部调用链(合同签署 / 保险出单 / 财务对账等) +- **零影响**: + - 前端所有接口(管理端 + 小程序) + - 现有 `/v3/admin/order/{id}/traveler/*` 全部接口(明文 ↔ 密文转换逻辑不变) + - 出行人增/删/改接口 + - 订单创建/详情/列表 + - 历史数据(存量出行人无需迁移) + +--- + +## 八、测试环境已验证 + +SSH 跳板内网 8086 真测: + +``` +GET /v3/internal/order/orders/{orderId}/travelers?purpose=CONTRACT_SIGN +→ 200 + 1 出行人 + decryptedAt 字段非空 ✓ +→ audit log 写入 2 行(idNo + phone)✓ + +GET /v3/internal/order/orders/{orderId}/travelers?purpose=INSURANCE_ISSUE +→ 200 + 同上 ✓ +→ audit log 累计 4 行 ✓ + +GET /v3/internal/order/orders/{orderId}/travelers?purpose=OTHER +→ 200 ✓ +→ audit log 累计 6 行 ✓ + +GET /v3/internal/order/orders/9999999999/travelers?purpose=CONTRACT_SIGN +→ 589100 订单不存在 ✓ + +GET /v3/internal/order/orders/{orderId}/travelers?purpose=INVALID +→ 589101 purpose 非法 ✓ +``` + +**网关 9443 验证**: + +``` +GET https://web.test.1814.love:9443/v3/internal/order/orders/{orderId}/travelers?purpose=CONTRACT_SIGN +→ 403 ✓(网关白名单拦截,预期行为) +``` + +审计落库总计 5+1 = 6 行,与预期一致。 + +--- + +## 九、错误码段位说明 + +| 错误码 | 含义 | 文档期望 | 实际 | +|--------|------|----------|------| +| 589100 | INTERNAL_ORDER_NOT_FOUND_FOR_DECRYPT | 589100 | ✅ 一致 | +| 589101 | INTERNAL_DECRYPT_PURPOSE_INVALID | 589101 | ✅ 一致 | + +--- + +## 十、相关文档 + +- 关联 Issue: [wx/HL#2525](https://git.1814.love:8443/wx/HL/issues/2525) +- 关联 PR: [wx/HL#2552](https://git.1814.love:8443/wx/HL/pulls/2552) +- commit: `9cca41a5d` +- 设计文档: `docs/order-v3/V5.48 §2.7 敏感信息解密接口` diff --git a/changelogs-v2/2026-05/18_#2526_traveler-smart-parse.md b/changelogs-v2/2026-05/18_#2526_traveler-smart-parse.md new file mode 100644 index 0000000..52d82b1 --- /dev/null +++ b/changelogs-v2/2026-05/18_#2526_traveler-smart-parse.md @@ -0,0 +1,240 @@ +# order-v3 出行人模块: 新增智能批量解析接口 smart-parse + +> **存放目录**: 二期 v3(`order-v3` 标签) → `changelogs-v2/2026-05/` +> +> **服务**: hl-order-v3 (端口 8086) +> **PR**: #2554 +> **Issue**: #2526 +> **日期**: 2026-05-18 +> **影响范围**: 管理后台订单详情"批量导入出行人"功能 → **新增接口,前端必新对接** + +--- + +## ⚠️ 关键变化 + +- **新增接口** `POST /v3/admin/order/{id}/traveler/smart-parse`,前端需新对接,**不是改造现有接口**。 +- 支持 `dryRun=true` 预览解析结果不落库,`dryRun=false` 真正写入。 +- **P0 审计安全 5 红线全合规**:不挂 @OperationLog 避免明文落审计 / failures[].maskedSnippet 脱敏 / 限流 / Lock4j / status_log 仅写聚合统计不写明文。 + +--- + +## 一、背景 + +V5.48 §2.8 定义"智能批量解析"。客服收到客户微信发来的一大段文字(姓名+身份证+手机号混排),要求自动解析后批量录入出行人。 + +风险点 = **审计安全**:身份证/手机号是 P0 敏感字段,若任何中间环节(@OperationLog / 日志 / status_log)落了明文,即一次安全事故。本接口的关键设计就是 **正常落库走加密 TypeHandler,失败片段返脱敏**。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 智能批量解析出行人 | POST | `/v3/admin/order/{id}/traveler/smart-parse` | **新增** | 前端必新对接 | + +--- + +## 三、接口详情 + +### 1. 智能批量解析 `POST /v3/admin/order/{id}/traveler/smart-parse` + +**VO**: `TravelerSmartParseReqVO` / `TravelerSmartParseRespVO` + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| id | Path | Long | 是 | 雪花 ID | 订单 ID | +| rawText | Body | String | 是 | 非空,长度 ≤ 5000 | 客户原始文本(姓名+身份证+手机号混排) | +| dryRun | Body | Boolean | 否 | 默认 false | true=只预览不落库,false=真正写入 | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| successCount | Integer | 成功解析并落库的数量(dryRun=true 时为"预期会落库的数量") | +| failCount | Integer | 失败片段数量 | +| successList | List | 成功的出行人列表(返脱敏后的预览字段,**不返明文**) | +| failures | List | 失败片段列表 | +| failures[].maskedSnippet | String | 失败片段(idNo 前6后4、phone 前3后4 脱敏) | +| failures[].reason | String | 失败原因(中文) | + +#### 请求示例 + +```json +{ + "rawText": "张三 110101199001011234 13800138000\n李四 身份证: 320101198502028765 手机 13900139000", + "dryRun": false +} +``` + +#### 响应示例 + +```json +{ + "code": 200, + "message": "成功", + "data": { + "successCount": 2, + "failCount": 0, + "successList": [ + { "travelerId": 9023400777, "name": "张三", "idNo": "110101******1234", "phone": "138****8000" }, + { "travelerId": 9023400778, "name": "李四", "idNo": "320101******8765", "phone": "139****9000" } + ], + "failures": [] + }, + "success": true +} +``` + +#### 失败片段响应示例 + +```json +{ + "code": 200, + "data": { + "successCount": 1, + "failCount": 1, + "successList": [...], + "failures": [ + { + "maskedSnippet": "王五 1101******1234 1380***0000", + "reason": "身份证号校验位错误" + } + ] + }, + "success": true +} +``` + +#### 错误响应 + +```json +{ "code": 581131, "message": "原文本过长,最多 5000 字符", "success": false } +``` + +```json +{ "code": 581132, "message": "原文本为空或无法识别任何出行人", "success": false } +``` + +```json +{ "code": 581134, "message": "订单当前状态不允许批量导入出行人", "success": false } +``` + +```json +{ "code": 100501, "message": "请求过于频繁,请稍后再试", "success": false } +``` + +--- + +## 四、契约约束 + +| 约束 | 说明 | +|------|------| +| dryRun=true | 仅解析返回预览,不落 DB,不写 status_log,不锁订单 | +| dryRun=false | 同事务批量 insert 出行人 + 写 status_log + 加 @Lock4j 锁订单 | +| @Lock4j | `key=#id`,expire=30000ms,同订单串行 | +| @RateLimiter | `count=10, time=60`(每用户 60s 内最多 10 次) | +| 订单状态 | 仅"待出行/已支付"等状态允许,其它 → 581134 | +| 文本长度 | > 5000 字符 → 581131 | +| 无可解析片段 | 整段 0 个能识别 → 581132 | + +--- + +## 五、数据库行为 + +### dryRun=false 写入 + +- `order_traveler`: 批量 insert,idNo/phone 走 EncryptTypeHandler 加密落库 +- `order_status_log`: 写 1 行 `reason="批量导入出行人 N 人"`(**不含任何明文**) + +### dryRun=true + +- 无任何写操作 + +### 失败片段 + +- 不落 DB +- 仅出现在响应 `failures[]`,带脱敏 snippet + +--- + +## 六、P0 审计安全红线(本接口最核心设计) + +| # | 红线 | 实现 | +|---|------|------| +| 1 | **不挂 @OperationLog** | Controller 方法没有此注解,避免 idNo/phone 通过 requestParams 落 audit_log | +| 2 | **failures[].maskedSnippet 脱敏** | idNo 前 6 后 4(`110101******1234`),phone 前 3 后 4(`138****8000`) | +| 3 | **日志仅聚合统计** | `log.info("smart-parse orderId={} successCount={} failCount={}")`,**绝不打印 rawText / idNo / phone** | +| 4 | **限流** | @RateLimiter(count=10, time=60),防御暴力扫描 | +| 5 | **status_log 不写明文** | reason 只写 "批量导入出行人 N 人",不带任何字段值 | + +--- + +## 七、边界行为 + +- 未登录 → 401(网关) +- 订单不存在 → 404 +- 订单状态非法 → 581134 +- 限流触发 → 100501(框架统一限流码,非 581133) +- rawText 含全空白 → 581132 +- 部分成功部分失败 → 200,在 `successList` / `failures` 中体现 + +--- + +## 八、不影响范围 + +- **仅影响**: 管理后台订单详情页"批量导入出行人"入口 +- **零影响**: + - 现有 `/v3/admin/order/{id}/traveler/add`(单人新增) + - 现有 `/v3/admin/order/{id}/traveler/edit`(批量编辑) + - 现有 `/v3/admin/order/{id}/traveler/validate`(完整性校验,见 #2527) + - 现有 `/v3/internal/order/orders/{orderId}/travelers`(Feign 解密,见 #2525) + - 订单创建/详情/列表 + - 小程序所有接口 + +--- + +## 九、测试环境已验证 + +经 9443 网关真 admin token 测试: + +``` +POST /v3/admin/order/{id}/traveler/smart-parse rawText 超 5000 字符 +→ 581131 SMART_PARSE_RAW_TEXT_TOO_LONG ✓ + +POST /v3/admin/order/{id}/traveler/smart-parse 订单状态不允许 +→ 581134 SMART_PARSE_ORDER_STATUS_FORBID ✓ + +POST /v3/admin/order/{id}/traveler/smart-parse 连续 11 次调用 +→ 11 次返 100501 限流 ✓ + +POST /v3/admin/order/{id}/traveler/smart-parse 正常 dryRun=true +→ ⚠️ 测试服无 PENDING 订单,正常路径未在 9443 真测 +→ 代码层 UnitTest + IntegrationTest 已覆盖 +→ **前端联调阶段补做正常路径回归** +``` + +--- + +## 十、错误码段位说明(段位让位,与文档不一致) + +V5.48 §2.8 文档期望段位被 baseline 已占用,本 PR 实际段位如下,**调用方按本表为准**: + +| 错误码 | 含义 | 文档期望 | 实际 | 原因 | +|--------|------|----------|------|------| +| 581131 | SMART_PARSE_RAW_TEXT_TOO_LONG | 581120 | 581131 | 581120 已被 TRANSPORT_PLAN_NOT_FOUND 占用 | +| 581132 | SMART_PARSE_RAW_TEXT_EMPTY_OR_UNPARSABLE | 581121 | 581132 | 同段位让位 | +| 581133 | SMART_PARSE_RATE_LIMITED | 581122 | doc-only(实际抛 100501) | 框架统一限流码 | +| 581134 | SMART_PARSE_ORDER_STATUS_FORBID | 581123 | 581134 | 同段位让位 | + +后续文档侧 follow-up 修正文档段位与代码对齐(本 PR 不改文档)。 + +--- + +## 十一、相关文档 + +- 关联 Issue: [wx/HL#2526](https://git.1814.love:8443/wx/HL/issues/2526) +- 关联 PR: [wx/HL#2554](https://git.1814.love:8443/wx/HL/pulls/2554) +- commit: `f8a34607` +- 设计文档: `docs/order-v3/V5.48 §2.8 智能批量解析` diff --git a/changelogs-v2/2026-05/18_#2527_traveler-validate-align-v2.md b/changelogs-v2/2026-05/18_#2527_traveler-validate-align-v2.md new file mode 100644 index 0000000..19d38bd --- /dev/null +++ b/changelogs-v2/2026-05/18_#2527_traveler-validate-align-v2.md @@ -0,0 +1,212 @@ +# order-v3 出行人模块: validate 接口字段判定对齐 v2(6 → 4 必填字段) + +> **存放目录**: 二期 v3(`order-v3` 标签) → `changelogs-v2/2026-05/` +> +> **服务**: hl-order-v3 (端口 8086) +> **PR**: #2553 +> **Issue**: #2527 +> **日期**: 2026-05-18 +> **影响范围**: 管理后台订单详情页"出行人完整性校验"提示文案 → **路径不变,字段判定变化** + +--- + +## ⚠️ 关键变化 + +- **路径不变**:`GET /v3/admin/order/{id}/traveler/validate` +- **字段判定变化**:必填字段从 **6 个 → 4 个**(name / idType / idNo / phone) +- `gender` / `birthday` / `race` / `nationality` **改为可选**,**不再进 incompleteList.missingFields** +- `incompleteList[].name` **改为脱敏**(首字符 + `*`) +- 前端调用方式不变,但**展示给客服的"缺哪些字段"提示会变少** + +--- + +## 一、背景 + +V5.48 §2.9 要求 v3 validate 与 v2 一期对齐。 + +v2 一期实战中,客服只关心 4 个字段(姓名/证件类型/证件号/手机号),其他字段(性别/生日/民族/国籍)由后端从身份证号自动解析或后续补录,**不算"出行人信息不完整"**。 + +v3 早期版本沿用了 V5.0 设计的 6 字段判定,导致客服侧总是看到"出行人信息不完整,缺生日"的红字提示,实则不影响出行,产生噪音。 + +本次对齐 v2,**4 字段全有就算完整**。 + +--- + +## 二、变更接口清单 + +| # | 接口 | 方法 | 路径 | 变更类型 | 说明 | +|---|------|------|------|----------|------| +| 1 | 出行人完整性校验 | GET | `/v3/admin/order/{id}/traveler/validate` | **字段判定变化**(路径不变) | 6 → 4 必填字段;name 脱敏 | + +--- + +## 三、接口详情 + +### 1. 出行人完整性校验 `GET /v3/admin/order/{id}/traveler/validate` + +**VO**: `TravelerValidateRespVO` + +#### 入参 + +| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 | +|------|------|------|------|------|------| +| id | Path | Long | 是 | 雪花 ID | 订单 ID | + +#### 出参 `Result` + +| 字段 | 类型 | 说明 | +|------|------|------| +| expectedCount | Integer | 订单应有出行人数(来自订单 traveler_count) | +| actualCount | Integer | 实际已录入出行人数 | +| countMismatch | Boolean | 数量是否不一致 | +| incompleteList | List | 信息不完整的出行人列表 | +| incompleteList[].travelerId | Long | 出行人 ID | +| incompleteList[].name | String | **脱敏后姓名**(首字符 + `*`,如"张*"、"李*") | +| incompleteList[].missingFields | List | 缺失字段名,**仅可能为 `name`/`idType`/`idNo`/`phone` 4 个值之一** | + +#### 响应示例(完整) + +```json +{ + "code": 200, + "message": "成功", + "data": { + "expectedCount": 3, + "actualCount": 3, + "countMismatch": false, + "incompleteList": [] + }, + "success": true +} +``` + +#### 响应示例(数量不一致 + 信息不完整) + +```json +{ + "code": 200, + "data": { + "expectedCount": 3, + "actualCount": 2, + "countMismatch": true, + "incompleteList": [ + { + "travelerId": 9023400111, + "name": "张*", + "missingFields": ["phone"] + }, + { + "travelerId": 9023400112, + "name": "李*", + "missingFields": ["idNo", "phone"] + } + ] + }, + "success": true +} +``` + +#### 错误响应 + +```json +{ "code": 581102, "message": "订单不存在或已删除", "success": false } +``` + +--- + +## 四、字段判定规则(本次重点) + +| 字段 | v3 旧版判定 | v3 新版判定(本次) | v2 | +|------|-------------|---------------------|-----| +| name | 必填 | **必填** | 必填 | +| idType | 必填 | **必填** | 必填 | +| idNo | 必填 | **必填** | 必填 | +| phone | 必填 | **必填** | 必填 | +| gender | 必填 | **可选**(不进 missingFields) | 可选 | +| birthday | 必填 | **可选**(不进 missingFields) | 可选 | +| race | 必填 | **可选** | 可选 | +| nationality | 必填 | **可选** | 可选 | + +判定逻辑:**4 个必填字段任一为 null 或空字符串 → 该出行人进 incompleteList**,missingFields 只列出缺失的 4 字段之一。 + +--- + +## 五、契约约束 + +| 约束 | 说明 | +|------|------| +| 路径 | **不变**,前端无需改 URL | +| 入参 | **不变**,只有 path 上 orderId | +| 出参字段名 | **不变**,但 `missingFields` 内可能值从 8 → 4 | +| name 脱敏 | 出参 `incompleteList[].name` **改为脱敏**,前端无需自己再脱敏 | + +--- + +## 六、数据库行为 + +- **无写操作**(纯查询接口) +- 查询逻辑:order_traveler WHERE order_id = ? AND deleted_at IS NULL,在 Service 层逐字段判空 + +--- + +## 七、边界行为 + +- 订单不存在 / 已删除 → 581102 +- 订单存在但 traveler_count = 0 → expectedCount=0, actualCount=0, countMismatch=false, incompleteList=[] +- 订单存在但无出行人录入 → expectedCount=N, actualCount=0, countMismatch=true +- 所有出行人 4 字段齐全 → incompleteList=[] +- 出行人 name 字段本身为空 → name 字段返 `*`(单字符脱敏) + +--- + +## 八、不影响范围 + +- **仅影响**: 管理后台订单详情页"出行人完整性"提示文案的显示 +- **零影响**: + - 出行人增/删/改接口(`add` / `edit` / `delete`) + - smart-parse 智能批量解析(见 #2526) + - Feign 内部解密(见 #2525) + - 小程序所有接口 + - 订单状态机(本接口不参与状态流转) + - 数据库表结构 + +--- + +## 九、测试环境已验证 + +经 9443 网关真 admin token 测试: + +``` +GET /v3/admin/order/{id}/traveler/validate 正常订单 +→ 200 + expectedCount=2 + actualCount=2 + incompleteList=[] ✓ + +GET /v3/admin/order/{id}/traveler/validate 数量不一致 +→ 200 + countMismatch=true ✓ + +GET /v3/admin/order/{id}/traveler/validate 缺 phone 字段 +→ 200 + incompleteList[0].missingFields=["phone"] ✓ +→ name 脱敏 "张*" ✓ + +GET /v3/admin/order/{id}/traveler/validate 仅缺 gender/birthday(旧版应报缺,新版应通过) +→ 200 + incompleteList=[] ✓(新版 4 字段判定不算缺) + +GET /v3/admin/order/9999999999/traveler/validate +→ 581102 订单不存在 ✓ +``` + +--- + +## 十、错误码段位说明 + +| 错误码 | 含义 | 文档期望 | 实际 | 原因 | +|--------|------|----------|------|------| +| 581102 | TRAVELER_VALIDATE_ORDER_NOT_FOUND | 581124 | **581102 复用** | 581124 已被 TRANSPORT_PLAN_INVALID_MODE 占用,文档侧 follow-up 修文档 | + +--- + +## 十一、相关文档 + +- 关联 Issue: [wx/HL#2527](https://git.1814.love:8443/wx/HL/issues/2527) +- 关联 PR: [wx/HL#2553](https://git.1814.love:8443/wx/HL/pulls/2553) +- commit: `d9bc8371` +- 设计文档: `docs/order-v3/V5.48 §2.9 validate 接口对齐 v2`