From ded9289d984fda8c1a29d0565ec651611d57fb9b Mon Sep 17 00:00:00 2001 From: lc Date: Tue, 25 Aug 2026 12:39:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=95=86=E6=95=8F=E6=84=9F?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E6=A0=A1=E9=AA=8C=E6=94=B6=E7=B4=A7=E4=BA=A4?= =?UTF-8?q?=E4=BB=98=EF=BC=88#6312=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...�感字段与数值格式校验收紧-修改接口-管理后台.md | 454 ++++++++++++++++++ 1 file changed, 454 insertions(+) create mode 100644 changelogs-v2/2026-08/25_6312_供应商敏感字段与数值格式校验收紧-修改接口-管理后台.md diff --git a/changelogs-v2/2026-08/25_6312_供应商敏感字段与数值格式校验收紧-修改接口-管理后台.md b/changelogs-v2/2026-08/25_6312_供应商敏感字段与数值格式校验收紧-修改接口-管理后台.md new file mode 100644 index 00000000..56f2d977 --- /dev/null +++ b/changelogs-v2/2026-08/25_6312_供应商敏感字段与数值格式校验收紧-修改接口-管理后台.md @@ -0,0 +1,454 @@ +--- +schema: "hl-changelog/v2" +ticket: "6312" +title: "供应商敏感字段与数值格式校验收紧" +consumer: "admin" +author: "lc(GIT)" +change_type: "修改接口" +backend_status: "deployed" +gateway_status: "verified" +frontend_status: "pending" +frontend_owner: "" +frontend_ref: "" +target_release: "" +verified_at: "" +status_note: "PR #6322 已合并 dev-v3,合并提交 52a61a88 已由部署任务 b5d27d2b 发布到 TEST。供应商新增、修改、注册提交和独立新增收款账户现统一校验税号、电话、证照编号、银行账号、税率、金额、评分、枚举及业务 ID;接口路径、字段名和成功响应结构不变。" +updated_at: "2026-08-25" +base: "dev-v3" +--- + +# 供应商敏感字段与数值格式校验收紧 + +供应商新增、修改、注册提交和独立新增收款账户的输入契约现统一收紧。管理端必须在提交前按本文校验税号、电话、证照编号、银行账号、税率、金额、评分和枚举,并将供应商子表的 `Long` ID 作为规范正整数字符串发送。 + +本次没有新增、删除或重命名字段,也没有改变成功响应结构;但以前可能被接受的错误格式现在会返回参数错误,因此属于请求兼容性收紧。 + +## 🔧 关键变化 + +| 项目 | 修改前 | 修改后 | +|---|---|---| +| 敏感字段格式 | 部分入口仅校验非空或宽松长度,Controller 与 Service 口径可能不一致 | Controller 与 Service 使用同一业务口径,绕过 Controller 也不能写入非法值 | +| 银行账号 | 展示格式和规范化后的数字长度缺少完整门禁 | 去除空格、连字符后必须为 8 至 32 位数字 | +| 税率、金额、评分 | 个别入口可进入后续流程后才失败 | 请求入口即校验范围和小数位 | +| 子表业务 ID | `null`、数字令牌、零或溢出值可能被错误解释为新增或类型错误 | 只有字段省略表示新增;显式 ID 必须是规范正整数字符串 | +| 枚举 | DTO 与直接 Service 调用的口径可能不一致 | 人员规模、账户、结算、发票、合同类型和合同状态均双层校验 | + +业务失败可能仍返回 HTTP 200。管理端必须同时判断统一响应中的 `code`、`success`、`message` 和 `data`。 + +## 变更接口清单 + +| # | 方法 | 路径 | 权限 | 变化 | +|---:|---|---|---|---| +| 1 | POST | `/admin/supplier/items/add` | `supplier:create` | 收紧主体、联系人、资质和初始账户输入 | +| 2 | PUT | `/admin/supplier/items/{supplierId}/update` | `supplier:update` | 收紧主体、子表 ID、合同和评价输入 | +| 3 | POST | `/admin/supplier/items/{supplierId}/submit` | `supplier:update` + `supplier:approval:submit` | 完整注册表单复用与新增一致的字段规则 | +| 4 | POST | `/admin/supplier/items/{supplierId}/bank-accounts/add` | `supplier:account:manage` + `supplier:approval:submit` | 独立账户复用初始账户的账号、结算和开票规则 | + +## 通用字段规则 + +### 主体、联系人和资质 + +| 字段 | JSON 类型 | 空值语义 | 当前约束 | +|---|---|---|---| +| `taxNo` | String | 新增、提交必填;修改时省略表示不改 | 原始文本 6 至 64 位,只允许字母、数字、空格和连字符;服务端移除空格、连字符并转大写后仍须为 6 至 64 位字母或数字;可识别的统一社会信用代码、居民身份证同时校验日期或校验位 | +| `contactPhone` | String / null | 公司电话可省略 | 原始文本 7 至 20 位;去除空格、括号、连字符及 `+86`/`0086` 后,须为中国大陆手机号、带 `0` 的座机或 400/800 服务号 | +| `contacts[].contactPhone` | String | 新增、完整提交的联系人必填;修改既有联系人时省略表示不改 | 与公司电话使用同一规范化和号码类型规则 | +| `qualifications[].certNo` | String / null | 草稿可空;提交是否必填继续按资质规则判断 | 最大 128 个 UTF-8 字节;超过长度直接拒绝,读取仍只返回脱敏值 | +| `staffScale` | String / null | 省略表示不改或不设置 | 仅允许 `LT50`、`R50_200`、`R200_500`、`GT500`,区分大小写 | + +电话示例: + +| 输入 | 结果 | +|---|---| +| `+86 138-0013-8000` | 接受,按境内手机号规范化 | +| `010-12345678` | 接受,按境内座机规范化 | +| `1234567` | 拒绝,消息为 `公司联系电话格式不合法` 或 `联系人电话格式不合法` | +| `13800ABC000` | 拒绝,电话不能包含字母 | + +### 收款账户、结算和开票 + +`initialAccounts[]` 与独立账户接口 `accounts[]` 使用同一规则: + +| 字段 | 必填 | 当前约束 | +|---|---|---| +| `accountType` | 是 | `CORPORATE`(对公)或 `PERSONAL`(对私) | +| `bankName` | 是 | 非空,最大 500 字符 | +| `bankBranch` | 否 | 最大 500 字符 | +| `accountNo` | 是 | 只允许数字、空格和连字符;去除展示分隔符后必须为 8 至 32 位数字 | +| `settleMode` | 否 | `PREPAY`、`MONTHLY`、`SINGLE` | +| `accountPeriod` | 条件必填 | 只有 `MONTHLY` 必须填写;其他结算方式必须为空 | +| `invoiceType` | 否 | `SPECIAL`、`NORMAL`、`NONE` | +| `taxRate` | 条件必填 | `SPECIAL`/`NORMAL` 必填,`NONE` 必须为空;格式为 `0%` 至 `100%`,最多两位小数 | + +合法税率包括 `0%`、`6%`、`6.5%`、`6.50%`、`100%`、`100.00%`。`5.123%`、`100.01%`、缺少 `%` 或负数均拒绝。 + +### 合同、评价和枚举 + +| 字段 | 当前约束 | +|---|---| +| `contracts[].amount` | `0` 至 `9999999999.99`,最多两位小数 | +| `contracts[].contractType` | `FRAME`、`SINGLE_TRIP`、`PURCHASE` | +| `contracts[].status` | `DRAFT`、`ACTIVE`、`EXPIRED` | +| `evaluations[].score` | `0` 至 `5`,最多两位小数 | + +枚举均区分大小写。空值是否表示“不修改”继续由对应修改字段语义决定;显式提交未知值一律拒绝。 + +### 路径和子表业务 ID + +路径中的 `{supplierId}` 必须大于 0。JSON 中以下可选子表 ID 使用同一严格规则: + +- `contacts[].contactId` +- `qualifications[].qualificationId` +- `contracts[].contractId` +- `evaluations[].evaluationId` +- `evaluations[].orderId` +- `evaluations[].resourceId` +- `evaluations[].evaluator` + +| 发送值 | 结果 | +|---|---| +| 字段省略 | 接受,表示新增子项或未建立可选关联 | +| `"1"` 至 `"9223372036854775807"` | 接受,必须是 1 至 19 位且在 Java `Long` 正整数范围内 | +| `null`、`""`、`"0"`、`"-1"` | 拒绝 | +| `"01"`、`" 1"` | 拒绝,不允许前导零或空白 | +| `1` | 拒绝,数字 JSON 令牌不能替代字符串 | +| `"9223372036854775808"` 或 20 位以上数字 | 拒绝,超出范围或长度 | + +管理端更新已有子项时,应原样回传详情接口返回的字符串 ID;新增子项必须省略 ID 字段,不能传 `null`。 + +## 接口详情 + +### 1. 创建供应商草稿 `POST /admin/supplier/items/add` + +请求头: + +```text +Authorization: Bearer +Content-Type: application/json +``` + +典型合法请求: + +```json +{ + "fullName": "示例旅行服务有限公司", + "shortName": "示例旅行", + "taxNo": "91350211M000100Y46", + "contactPhone": "010-12345678", + "staffScale": "R50_200", + "mainCooperation": "住宿与地接服务", + "types": [ + { "typeCode": "HOTEL" } + ], + "contacts": [ + { + "contactName": "示例联系人", + "contactPhone": "+86 138-0013-8000", + "contactRole": "contentBus", + "isPrimary": true + } + ], + "initialAccounts": [ + { + "accountType": "CORPORATE", + "bankName": "示例银行", + "accountNo": "6222 0212 3456 7890", + "settleMode": "SINGLE", + "invoiceType": "SPECIAL", + "taxRate": "6.00%" + } + ] +} +``` + +成功响应结构不变: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "supplierId": "2092000000000000001", + "supplierNo": null, + "status": "DRAFT", + "onboardingStage": "PROFILE_DRAFT", + "initialAccounts": [ + { + "accountId": "2092000000000000002", + "accountNoMask": "6222********7890", + "status": "DRAFT" + } + ], + "updateTime": "2026-08-25 12:35:00" + } +} +``` + +无效税号、电话、证照编号或初始账户会在供应商、联系人、账户和审批写入前失败。 + +### 2. 修改供应商 `PUT /admin/supplier/items/{supplierId}/update` + +请求头: + +```text +Authorization: Bearer +Content-Type: application/json +``` + +更新已有联系人时,`contactId` 必须使用字符串;新增联系人则省略该字段: + +```json +{ + "contactPhone": "+86 138-0013-8000", + "staffScale": "R200_500", + "contacts": [ + { + "contactId": "2092000000000000010", + "contactPhone": "010-87654321", + "expectedUpdateTime": "2026-08-25 12:35:00" + }, + { + "contactName": "新增联系人", + "contactPhone": "13900139000", + "contactRole": "contentMoney", + "isPrimary": false + } + ], + "contracts": [ + { + "contractName": "年度合作框架", + "contractType": "FRAME", + "amount": 1000000.50, + "status": "DRAFT" + } + ], + "evaluations": [ + { + "orderId": "2092000000000000020", + "score": 4.75, + "content": "履约正常" + } + ], + "changeReason": "更新联系方式与合作资料", + "expectedUpdateTime": "2026-08-25 12:35:00" +} +``` + +成功响应仍为 `Result`: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "supplierId": "2092000000000000001", + "supplierNo": null, + "status": "DRAFT", + "onboardingStage": "PROFILE_DRAFT", + "initialAccounts": [], + "updateTime": "2026-08-25 12:40:00" + } +} +``` + +任一子项 ID、金额、评分或枚举非法时,整个更新失败,既有主体与子表快照不变。 + +### 3. 提交供应商注册 `POST /admin/supplier/items/{supplierId}/submit` + +请求头: + +```text +Authorization: Bearer +Content-Type: application/json +``` + +提交接口接收完整注册表单,主体、联系人、资质和 `initialAccounts` 复用创建接口的全部规则,并要求 `expectedUpdateTime`: + +```json +{ + "fullName": "示例旅行服务有限公司", + "taxNo": "91350211M000100Y46", + "contactPhone": "010-12345678", + "mainCooperation": "住宿与地接服务", + "types": [ + { "typeCode": "HOTEL" } + ], + "contacts": [ + { + "contactName": "示例联系人", + "contactPhone": "13800138000", + "contactRole": "contentBus", + "isPrimary": true + } + ], + "qualifications": [ + { + "qualType": "BUSINESS_LICENSE", + "certNo": "LIC-2026-000001", + "imageUrl": "https://files.example.com/supplier/license.png" + } + ], + "expectedUpdateTime": "2026-08-25 12:40:00" +} +``` + +成功响应结构不变: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": { + "approvalLogId": "2092000000000000030", + "requestNo": "SUPPLIER-PROFILE-EXAMPLE", + "provider": "LOCAL_AUTO", + "approvalStatus": "APPROVED", + "spNo": null, + "spStatus": null, + "syncStatus": "APPLIED", + "submittedAt": "2026-08-25 12:41:00", + "finishedAt": "2026-08-25 12:41:00" + } +} +``` + +格式错误会在创建审批事实前失败;必备资质、并发版本、重复主体和状态机规则保持既有语义。 + +### 4. 独立新增收款账户 `POST /admin/supplier/items/{supplierId}/bank-accounts/add` + +请求头: + +```text +Authorization: Bearer +Content-Type: application/json +``` + +请求示例: + +```json +{ + "accounts": [ + { + "accountType": "CORPORATE", + "bankName": "示例银行", + "bankBranch": "示例支行", + "accountNo": "6222-0212-3456-7890", + "settleMode": "MONTHLY", + "accountPeriod": "月结30天", + "invoiceType": "NORMAL", + "taxRate": "6%", + "proofFileUrls": [ + "https://files.example.com/supplier/account-proof.png" + ] + } + ] +} +``` + +成功响应仍为按请求顺序返回的数组: + +```json +{ + "code": 200, + "message": "成功", + "success": true, + "data": [ + { + "accountId": "2092000000000000040", + "approvalLogId": "2092000000000000041", + "requestNo": "SUPPLIER-ACCOUNT-EXAMPLE", + "provider": "LOCAL_AUTO", + "approvalStatus": "APPROVED", + "spNo": null, + "spStatus": null, + "syncStatus": "APPLIED", + "accountStatus": "ACTIVE", + "isDefault": "NO", + "submittedAt": "2026-08-25 12:45:00", + "finishedAt": "2026-08-25 12:45:00" + } + ] +} +``` + +账号、结算或开票组合非法时整批在账户和审批写入前失败。供应商尚未完成注册时,合法格式仍返回既有业务错误 `395010`(`请先完成供应商注册`)。 + +## 典型错误响应 + +字段格式失败统一使用业务码 `400`,例如: + +```json +{ + "code": 400, + "message": "收款账号规范化后必须为8到32位数字", + "success": false, + "data": null +} +``` + +| 场景 | `code` | 典型消息 | +|---|---:|---| +| `{supplierId}` 为 0 | `400` | `供应商ID必须为正数` | +| 路径 ID 超出 `Long` 范围 | `400` | `参数 supplierId 格式错误,请检查后重试` | +| 可识别的统一社会信用代码校验位错误 | `400` | `主体证件号的统一社会信用代码校验位不合法` | +| 公司或联系人电话不符合境内号码规则 | `400` | `公司联系电话格式不合法` / `联系人电话格式不合法` | +| 子表 ID 显式为 `null`、空、数字令牌、零或溢出 | `400` | `请求数据格式错误:字段 [...] 格式错误` | +| 账号规范化后少于 8 位或超过 32 位 | `400` | `收款账号规范化后必须为8到32位数字` | +| 税率超过 100% 或多于两位小数 | `400` | `税率必须为0%至100%且最多两位小数` | +| 合同金额超过范围或多于两位小数 | `400` | `合同金额最多10位整数和2位小数` | +| 评价分数超过 5 或多于两位小数 | `400` | `评分不能大于5` / `评分最多1位整数和2位小数` | +| 未认证 | `401` | 统一认证失败;HTTP 状态可能仍为 200 | + +本次没有新增业务错误码;既有权限、供应商不存在、未注册、必备资质、乐观锁和审批状态错误继续使用原错误码。 + +## 管理端接入事项 + +1. 新建、编辑和提交表单按本文在前端同步限制长度、数字格式、范围、小数位和枚举,避免用户提交后才看到后端错误。 +2. 银行账号输入可保留空格或连字符用于展示,但提交前应确认去除分隔符后为 8 至 32 位数字;不要使用 JavaScript `Number` 保存账号。 +3. 税率必须保留 `%`;根据 `invoiceType` 联动必填和清空,根据 `settleMode` 联动 `accountPeriod`。 +4. 所有子表业务 ID 使用字符串保存和提交。新增项省略 ID,不能传 `null`;禁止用 `Number(...)` 转换雪花 ID。 +5. 合同金额和评分使用十进制定点输入,分别限制两位小数和对应范围。 +6. 接口失败时保留用户表单,展示后端 `message`;不能只判断 HTTP 状态。 + +## 影响评估与未变化范围 + +- **是否破坏向后兼容**:对合法请求兼容;对以前可能被接受的非法格式不兼容。 +- **前端是否需要同步**:需要。请求字段和响应结构未变,但表单校验、ID 序列化及结算/开票联动应对齐。 +- **历史数据**:不迁移、不批量重写;存量数据读取不受影响,下次显式修改相关字段时按新规则校验。 +- **权限与状态机**:不变,仍使用既有管理端角色、平台权限、乐观版本、聚合锁、幂等、审批、审计和软删除规则。 +- **基础设施**:无数据库 migration、配置、Redis Key、MQ Topic、Feign 或 Gateway 路由变化。 +- **响应与脱敏**:成功响应结构不变;税号、电话、证照号和账号读取仍只返回脱敏值。 + +## 验证证据 + +- 自动化:12 个供应商聚焦测试类全部通过;Resource 及依赖模块全量 2018 项测试零失败、零错误,38 项仓库既有条件跳过,13 个 Maven 模块构建成功。 +- 独立审查:合并提交的补丁等价、字段边界、Controller/Service 入口一致性和失败零副作用测试均通过,未发现确定性缺口。 +- TEST 部署:部署任务 `b5d27d2b` 成功,`hl-resource-service` 的 8182、8082 双实例依次启动;服务器运行提交确认包含合并提交 `52a61a88c`,Nacos 两实例均为 healthy、enabled。 +- 真实 Gateway:使用真实有效 SUPER_ADMIN 登录完成 35 项验收,覆盖未认证、路径 ID、税号、公司电话、联系人电话、证照长度、初始/独立账户、税率、子表 ID、合同金额、评价分数、枚举、合法创建、脱敏读取、合法更新和业务状态失败。 +- 零写入:合法草稿建立后的数据库快照为 `1` 个主体、`1` 个类型、`1` 个联系人、`1` 个初始账户、`0` 个审批;全部失败用例执行后快照完全一致。 +- 清理:合成草稿通过既有删除接口成功清理,活动主体、类型、联系人、账户、评价和审批均为 0;按审计要求保留 3 条主体变更事实和 2 条账户变更事实,不含真实个人或企业数据。 +- 运行健康:验收后 8082、8182 仍健康,Gateway 可达,两实例本次启动日志 `ERROR=0`;未触发文件、配置或 MQ 副作用,未生成需要清理的重复确认令牌。 + +## 撤回 + +1. 从最新 `dev-v3` 创建回退分支,执行 `git revert -m 1 --no-edit 52a61a88c906395d5cafd72cbda2390ad31a02a3`,经新的独立 PR 合入。 +2. 重新构建并滚动部署 `hl-resource-service`,确认部署提交不再包含本工单变更。 +3. 无数据库 migration、数据改写、配置、Redis、MQ 或跨服务契约变更,不需要执行 DDL、DML、缓存清理、消息补偿或配置恢复;现有业务和审计数据保持不变。 +4. 回退后接口路径、字段名和成功响应结构仍不变,但输入接受范围恢复为本工单前的宽松口径;管理端若已上线严格校验可继续保持,避免重新接受明显非法值。 +5. 经 Gateway 重跑合法新增、修改、提交、独立账户、未认证、失败零写入和脱敏读取用例,并确认 Nacos 双实例健康、服务日志无新增错误。 +6. 若撤回本 Changelog,以新的文档提交删除本文件或标记撤回,不重写已发布历史。 + +## 关联 / 联系人 + +### 链接 + +- **Issue**: [#6312](https://git.1814.love:8443/wx/HL/issues/6312) +- **PR**: [#6322](https://git.1814.love:8443/wx/HL/pulls/6322) +- **合并提交**: [52a61a88c](https://git.1814.love:8443/wx/HL/commit/52a61a88c906395d5cafd72cbda2390ad31a02a3) + +### 联系人 + +- **后端负责人**: @lc