29 KiB
schema, ticket, title, consumer, author, change_type, backend_status, gateway_status, frontend_status, frontend_owner, frontend_ref, target_release, verified_at, status_note, updated_at, base
| schema | ticket | title | consumer | author | change_type | backend_status | gateway_status | frontend_status | frontend_owner | frontend_ref | target_release | verified_at | status_note | updated_at | base |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| hl-changelog/v2 | 6436 | 供应商敏感字段完整回显与主类型 | admin | lc(GIT) | 修改接口 | deployed | verified | verified | mmg | 5957c58c | v2.1 | 2026-08-27 | PR #6478、补充 PR #6483/#6486 已合并 dev-v3,最终提交 aa735fb8 已由 Deploy Panel 任务 7a48028c 发布 TEST;真实 TEST 身份已验证完整值、权限投影、失败零写入及测试数据清理。 | 2026-08-27 | dev-v3 |
🔧 供应商敏感字段完整回显与主类型
供应商管理接口不再用掩码替代已授权管理员需要处理的业务原值,并补齐唯一主类型和注册账户摘要。证明附件仍受独立权限保护,不能因为本次完整值调整而绕过授权或同步读取审计。
一、背景
此前详情、账户和审批记录混用了原值、掩码与历史密文回退,草稿类型也缺少稳定的主类型回显,导致管理端无法可靠编辑、审核或回填表单。本次统一以下消费口径:
- 通过既有供应商读取权限后,税号、法人证件、电话、证照号和银行账号返回完整业务值。
proofFileUrls继续要求supplier:account:proof:read,且只有同步读取审计成功后才返回;无权限时字段不序列化。types[].isPrimary与根对象primaryTypeCode、primaryTypeName成为主类型权威回显。*Mask字段保留兼容但已废弃,兼容期内与对应完整值字段同值;新代码应使用无Mask的字段。
二、变更接口清单
| # | 接口 | 方法 | 路径 | 变更类型 | 说明 |
|---|---|---|---|---|---|
| 1 | 查询供应商基本信息 | GET | /admin/supplier/items/{supplierId}/basic-info/view |
响应修改 | 返回完整主体、联系人、资质字段及主类型 |
| 2 | 查询供应商账户信息 | GET | /admin/supplier/items/{supplierId}/account-info/list |
响应修改 | 返回可读账户完整账号,按权限决定附件字段 |
| 3 | 查询收款账户详情 | GET | /admin/supplier/bank-accounts/{accountId}/view |
响应修改 | 返回完整账号,按权限决定附件字段 |
| 4 | 查询供应商审批记录 | GET | /admin/supplier/items/{supplierId}/approval-records/page |
请求与响应修改 | 增加目标筛选及完整前后值可用性 |
| 5 | 创建供应商注册草稿 | POST | /admin/supplier/items/add |
响应修改 | initialAccounts 回显完整账号摘要 |
| 6 | 更新供应商 | PUT | /admin/supplier/items/{supplierId}/update |
响应修改 | 稳定回显主类型及完整初始账号摘要 |
| 7 | 提交供应商注册 | POST | /admin/supplier/items/{supplierId}/submit |
响应修改 | 审批结果增加完整初始账号摘要 |
三、接口详情
1. 查询供应商基本信息 GET /admin/supplier/items/{supplierId}/basic-info/view
VO: SupplierBasicInfoRespVO
使用场景
供应商详情和编辑表单初始化。管理端可直接使用完整字段,并用主类型字段初始化单选控件。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
supplierId |
String | 供应商 ID |
tax_no |
String | 完整主体证件号,JSON 名保持既有口径 |
legalRepresentativeIdNo |
String/null | 完整法人居民身份证号 |
legalRepresentativeIdCardFrontUrl / legalRepresentativeIdCardBackUrl |
String/null | 完整永久文件地址 |
contactPhone |
String/null | 完整公司联系电话 |
contacts[].contactPhone |
String | 完整联系人电话 |
qualifications[].certNo |
String/null | 完整证照编号 |
types[].isPrimary |
Boolean | 当前类型是否为主类型 |
primaryTypeCode / primaryTypeName |
String/null | 主类型值和展示名称;无类型草稿为 null |
legalRepresentativeIdNoMask / contactPhoneMask |
String/null | 废弃兼容别名,与完整值字段同值 |
请求示例
GET /admin/supplier/items/2092800000000000001/basic-info/view
Authorization: Bearer <admin-token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "2092800000000000001",
"fullName": "示例旅行服务有限公司",
"tax_no": "91350211M000100Y46",
"legalRepresentativeIdNo": "11010519491231002X",
"legalRepresentativeIdNoMask": "11010519491231002X",
"legalRepresentativeIdCardFrontUrl": "https://files.example.com/supplier/front.jpg",
"legalRepresentativeIdCardBackUrl": "https://files.example.com/supplier/back.jpg",
"contactPhone": "13800138000",
"contactPhoneMask": "13800138000",
"types": [{"typeCode": "HOTEL", "typeName": "酒店", "isPrimary": true}],
"primaryTypeCode": "HOTEL",
"primaryTypeName": "酒店",
"contacts": [{"contactName": "示例联系人", "contactPhone": "13900139000", "contactPhoneMask": "13900139000"}],
"qualifications": [{"qualType": "BUSINESS_LICENSE", "certNo": "LIC-2026-001", "certNoMask": "LIC-2026-001"}],
"updateTime": "2026-08-27 11:30:00"
}
}
空数据 / 降级响应
无类型草稿返回 types: []、primaryTypeCode: null、primaryTypeName: null。联系人或资质为空时返回空数组;历史停用类型的名称无法从字典解析时,名称回退为类型值,不阻断详情。
错误响应
{
"code": 395001,
"message": "供应商不存在",
"data": null,
"success": false
}
业务边界
- 要求既有
supplier:view和数据范围校验,登录态不能替代业务权限。 - 普通应用日志、异常和 Trace 不记录上述完整值。
*Mask仅用于旧客户端兼容,新代码不得继续依赖掩码语义。
2. 查询供应商账户信息 GET /admin/supplier/items/{supplierId}/account-info/list
VO: SupplierAccountInfoRespVO
使用场景
在供应商结算信息区域展示已进入 PENDING、ACTIVE 或 DISABLED 的管理端可读账户。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
supplierId |
String | 供应商 ID |
bankAccounts |
Array | 可读账户,默认账户优先 |
bankAccounts[].accountNo |
String | 完整银行账号 |
bankAccounts[].accountNoMask |
String | 废弃兼容别名,与 accountNo 同值 |
bankAccounts[].proofFileUrls |
Array | 有独立权限且审计成功时出现;无权限时整个字段省略 |
bankAccounts[].status |
String | PENDING、ACTIVE 或 DISABLED |
请求示例
GET /admin/supplier/items/2092800000000000001/account-info/list
Authorization: Bearer <admin-token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "2092800000000000001",
"bankAccounts": [{
"accountId": "2092800000000000011",
"accountName": "示例旅行服务有限公司",
"bankName": "示例银行",
"accountNo": "6222021234567890",
"accountNoMask": "6222021234567890",
"proofFileUrls": ["https://files.example.com/supplier/account-proof.pdf"],
"status": "ACTIVE",
"isDefault": "YES"
}],
"updateTime": "2026-08-27 11:31:00"
}
}
空数据 / 降级响应
仅有 DRAFT 账户或没有账户时返回 bankAccounts: []。无 supplier:account:proof:read 时账号仍为完整值,但每个账户均省略 proofFileUrls,不是返回空数组。
错误响应
证明附件同步审计不可用时失败关闭,不返回任何附件:
{
"code": 395039,
"message": "暂时无法校验资源,请稍后重试",
"data": null,
"success": false
}
业务边界
- 要求
supplier:view;附件另需supplier:account:proof:read。 - 每次获准的附件读取都会先同步写安全审计,失败时整个请求失败。
- DRAFT 初始账户不会出现在该读取接口中,应使用创建/更新响应的
initialAccounts展示草稿摘要。
3. 查询收款账户详情 GET /admin/supplier/bank-accounts/{accountId}/view
VO: SupplierBankAccountDetailRespVO
使用场景
查看单个已进入可读状态的收款账户完整信息。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
accountId |
Path | String | 是 | 正整数 ID 字符串 | 账户 ID |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
accountId |
String | 账户 ID |
accountName |
String | 收款户名 |
accountNo |
String | 完整银行账号 |
accountNoMask |
String | 废弃兼容别名,与完整账号同值 |
proofFileUrls |
Array | 有独立权限且同步审计成功时出现,否则省略 |
status / isDefault |
String | 账户状态与默认标记 |
请求示例
GET /admin/supplier/bank-accounts/2092800000000000011/view
Authorization: Bearer <admin-token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"accountId": "2092800000000000011",
"accountName": "示例旅行服务有限公司",
"accountType": "CORPORATE",
"bankName": "示例银行",
"accountNo": "6222021234567890",
"accountNoMask": "6222021234567890",
"proofFileUrls": ["https://files.example.com/supplier/account-proof.pdf"],
"settleMode": "PREPAY",
"invoiceType": "NONE",
"status": "ACTIVE",
"isDefault": "YES",
"updateTime": "2026-08-27 11:31:00"
}
}
空数据 / 降级响应
无附件权限时响应省略 proofFileUrls;DRAFT、已删除或不存在的账户按不可读处理,不返回草稿详情。
错误响应
{
"code": 395001,
"message": "供应商不存在",
"data": null,
"success": false
}
业务边界
- 完整账号沿用基础查看权限,证明附件使用独立权限和同步审计。
- 账户必须属于未删除供应商且状态为 PENDING、ACTIVE 或 DISABLED。
- 无附件权限时后端读取阶段即排除附件字段,不是先读取后隐藏。
4. 查询供应商审批记录 GET /admin/supplier/items/{supplierId}/approval-records/page
VO: SupplierApprovalRecordPageReqVO / PageResult<SupplierApprovalRecordRespVO>
使用场景
统一查看供应商主体与收款账户的变更历史,并区分完整新记录和不可恢复的历史掩码记录。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
page / limit |
Query | Integer | 否 | 正整数,limit 不超过 200 |
分页参数 |
approvalLogId |
Query | String | 否 | 正整数 ID 字符串 | 精确筛选审批 |
operationType |
Query | String | 否 | CREATE/UPDATE/ENABLE/DISABLE/DELETE |
操作类型 |
targetType |
Query | String | 否 | SUPPLIER/ACCOUNT |
本次增加的目标筛选 |
fieldName / status |
Query | String | 否 | 当前接口枚举 | 字段或状态筛选 |
from / to |
Query | String | 否 | 必须成对,yyyy-MM-dd HH:mm:ss |
时间范围 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
records[].targetType |
String | SUPPLIER 或 ACCOUNT |
records[].targetId |
String/null | 主体记录为 null,账户记录为账户 ID |
records[].oldValue / newValue |
String/null | 完整中文业务摘要,附件仍按独立权限投影 |
records[].valueAvailability |
String | FULL 或 LEGACY_MASKED_UNRECOVERABLE |
records[].oldValueMasked / newValueMasked |
String/null | 废弃兼容别名 |
total / page / pageSize |
Integer | 分页元数据 |
请求示例
GET /admin/supplier/items/2092800000000000001/approval-records/page?page=1&limit=20&targetType=ACCOUNT
Authorization: Bearer <admin-token>
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"records": [{
"supplierId": "2092800000000000001",
"operationType": "ENABLE",
"targetType": "ACCOUNT",
"targetId": "2092800000000000011",
"oldValue": "账户状态:待审批;收款账号:6222021234567890",
"newValue": "账户状态:已生效;收款账号:6222021234567890",
"valueAvailability": "FULL",
"status": "已生效",
"operatorName": "测试管理员",
"operatorRole": "超级管理员",
"createTime": "2026-08-27 11:32:00"
}],
"total": 1,
"page": 1,
"pageSize": 20
}
}
空数据 / 降级响应
无匹配记录返回 records: [] 和 total: 0。历史记录只保存掩码且无法恢复原值时,valueAvailability 返回 LEGACY_MASKED_UNRECOVERABLE,不会猜测或拼造完整值。
错误响应
{
"code": 400,
"message": "目标类型仅支持SUPPLIER或ACCOUNT",
"data": null,
"success": false
}
业务边界
- 要求
supplier:approval:read及供应商数据范围权限。 - 证明附件只有独立权限存在且同步审计成功时才进入完整摘要。
- 分页内主体记录和账户记录按创建时间、记录 ID 稳定排序。
5. 创建供应商注册草稿 POST /admin/supplier/items/add
VO: SupplierDraftSaveReqVO / SupplierWriteRespVO
使用场景
创建供应商草稿并可同时保存一个初始收款账户;成功响应直接回显该账户的完整账号摘要。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
fullName |
Body | String | 是 | 非空白,最长 500 字符 | 供应商全称 |
taxNo |
Body | String | 是 | 6 至 64 字符 | 主体证件号 |
types |
Body | Array | 否 | 草稿可为空,非空不得重复 | 供应商类型,首项成为主类型 |
mainCooperation |
Body | String | 是 | 非空白 | 主要合作内容 |
initialAccounts |
Body | Array | 否 | 最多 1 项 | 初始收款账户完整输入 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
supplierId |
String | 新供应商 ID |
status |
String | DRAFT |
initialAccounts[].accountId |
String | 初始账户 ID |
initialAccounts[].accountNo |
String | 完整银行账号 |
initialAccounts[].accountNoMask |
String | 废弃兼容别名,与完整账号同值 |
initialAccounts[].status |
String | 创建时为 DRAFT |
updateTime |
String | 并发版本时间 |
请求示例
{
"fullName": "示例旅行服务有限公司",
"taxNo": "91350211M000100Y46",
"types": [{"typeCode": "HOTEL"}],
"mainCooperation": "酒店资源合作",
"initialAccounts": [{
"accountType": "CORPORATE",
"bankName": "示例银行",
"accountNo": "6222021234567890",
"proofFileUrls": ["https://files.example.com/supplier/account-proof.pdf"],
"settleMode": "PREPAY",
"invoiceType": "NONE"
}]
}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "2092800000000000001",
"supplierNo": null,
"status": "DRAFT",
"onboardingStage": "PROFILE_DRAFT",
"initialAccounts": [{
"accountId": "2092800000000000011",
"accountNo": "6222021234567890",
"accountNoMask": "6222021234567890",
"status": "DRAFT"
}],
"updateTime": "2026-08-27 11:30:00"
}
}
空数据 / 降级响应
省略 initialAccounts 或传空数组时成功创建并返回 initialAccounts: []。草稿可传 types: [],此时详情的主类型字段为 null。
错误响应
{
"code": 395002,
"message": "无权执行该供应商写操作",
"data": null,
"success": false
}
业务边界
- 仅可信
FINANCE、SUPER_ADMIN且拥有supplier:create的身份可写;ADMIN明确拒绝。 - 初始账户最多一项,完整请求原子成功或失败,失败不留下主体或子项。
- 新写入和新审计使用完整业务值,但普通日志、异常、Trace 和跨服务消息不得携带这些值。
6. 更新供应商 PUT /admin/supplier/items/{supplierId}/update
VO: SupplierUpdateReqVO / SupplierWriteRespVO
使用场景
增量更新供应商草稿或可变字段,并获得稳定的主类型与初始账户摘要回显。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 目标供应商 |
types |
Body | Array | 否 | 省略表示不改;空数组表示清空草稿类型 | 类型完整快照 |
changeReason |
Body | String | 是 | 非空白 | 变更原因 |
expectedUpdateTime |
Body | String | 是 | yyyy-MM-dd HH:mm:ss |
乐观并发版本 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
supplierId / status |
String | 供应商 ID 和当前状态 |
initialAccounts[].accountNo |
String | 既有初始账户完整账号 |
initialAccounts[].accountNoMask |
String | 废弃兼容别名 |
updateTime |
String | 更新后的并发版本 |
请求示例
{
"types": [{"typeCode": "HOTEL", "isPrimary": true}],
"changeReason": "调整主合作类型",
"expectedUpdateTime": "2026-08-27 11:30:00"
}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"supplierId": "2092800000000000001",
"status": "DRAFT",
"onboardingStage": "PROFILE_DRAFT",
"initialAccounts": [{
"accountId": "2092800000000000011",
"accountNo": "6222021234567890",
"accountNoMask": "6222021234567890",
"status": "DRAFT"
}],
"updateTime": "2026-08-27 11:35:00"
}
}
空数据 / 降级响应
省略 types 保持现有类型;草稿传 types: [] 会清空类型并在详情返回 null 主类型。响应没有初始账户时固定返回空数组。
错误响应
{
"code": 395014,
"message": "数据已被他人修改,请刷新后重试",
"data": null,
"success": false
}
业务边界
- 仅可信
FINANCE、SUPER_ADMIN且拥有supplier:update的身份可写。 - 非空类型集合稳定保留且恰有一个主类型;提交审批时仍要求至少一个类型。
- 并发版本、状态或权限不满足时失败且零写入。
7. 提交供应商注册 POST /admin/supplier/items/{supplierId}/submit
VO: SupplierSubmitReqVO / SupplierApprovalCommandRespVO
使用场景
提交完整注册表单并进入审批;审批响应直接携带本次注册初始账户的完整摘要。
入参
| 字段 | 位置 | 类型 | 必填 | 约束 | 说明 |
|---|---|---|---|---|---|
supplierId |
Path | String | 是 | 正整数 ID 字符串 | 草稿供应商 |
fullName / taxNo |
Body | String | 是 | 完整注册表单约束 | 主体信息 |
types |
Body | Array | 是 | 至少一项且不得重复 | 第一项为主类型 |
initialAccounts |
Body | Array | 否 | 最多一项;省略可保留既有草稿账户 | 初始账户完整快照 |
expectedUpdateTime |
Body | String | 是 | 必须等于当前版本 | 并发围栏 |
出参
| 字段 | 类型 | 说明 |
|---|---|---|
approvalLogId / requestNo |
String | 审批记录与幂等请求号 |
provider / approvalStatus / syncStatus |
String | 审批通道、结果与本地应用状态 |
initialAccounts[].accountId |
String | 本次注册关联账户 ID |
initialAccounts[].accountNo |
String | 完整银行账号 |
initialAccounts[].accountNoMask |
String | 废弃兼容别名 |
initialAccounts[].status |
String | 审批通过后为 ACTIVE |
请求示例
{
"fullName": "示例旅行服务有限公司",
"taxNo": "91350211M000100Y46",
"types": [{"typeCode": "HOTEL"}],
"mainCooperation": "酒店资源合作",
"licenseImageUrl": "https://files.example.com/supplier/license.jpg",
"qualifications": [{
"qualType": "BUSINESS_LICENSE",
"certNo": "LIC-2026-001",
"imageUrl": "https://files.example.com/supplier/license.jpg",
"permanentValid": true
}],
"expectedUpdateTime": "2026-08-27 11:35:00"
}
响应示例
{
"code": 200,
"message": "成功",
"success": true,
"data": {
"approvalLogId": "2092800000000000021",
"requestNo": "SUP-REQ-EXAMPLE",
"provider": "LOCAL_AUTO",
"approvalStatus": "APPROVED",
"syncStatus": "APPLIED",
"initialAccounts": [{
"accountId": "2092800000000000011",
"accountNo": "6222021234567890",
"accountNoMask": "6222021234567890",
"status": "ACTIVE"
}],
"submittedAt": "2026-08-27 11:36:00",
"finishedAt": "2026-08-27 11:36:00"
}
}
空数据 / 降级响应
没有初始账户时返回 initialAccounts: []。提交仍必须包含非空类型和当前完整注册资料,不因草稿阶段允许空类型而放宽。
错误响应
{
"code": 395008,
"message": "请至少选择一个类型并设置唯一主类型",
"data": null,
"success": false
}
业务边界
- 仅可信
FINANCE、SUPER_ADMIN且同时拥有更新和提交权限的身份可执行。 - 审批准备、结果应用、主体状态、账户状态和完整审计保持原有事务与幂等语义。
- 权限、状态、摘要或并发围栏失败时不允许部分写入。
四、契约约束与正确调用方式
| 场景 | 正确处理 |
|---|---|
| 主类型绑定 | 使用 primaryTypeCode;类型列表使用 types[].isPrimary,不要自行取第一项猜测 |
| 完整字段 | 使用 tax_no、legalRepresentativeIdNo、contactPhone、certNo、accountNo |
| 废弃别名 | legalRepresentativeIdNoMask、contactPhoneMask、certNoMask、accountNoMask 仅作旧客户端兼容 |
| 证明附件无权限 | proofFileUrls 字段不存在;不要把缺字段当接口异常或空附件 |
| 草稿账户 | 从写响应 initialAccounts 获取;账户读取接口只返回 PENDING/ACTIVE/DISABLED |
| 业务失败 | HTTP 状态之外必须检查 code、success、message 和 data |
管理端不得把业务请求体中的身份或角色作为授权依据,也不得缓存其他管理员读取到的完整敏感值供当前会话复用。
五、数据库行为
| 外部动作 | 可观察结果 |
|---|---|
| 创建/更新供应商 | 主体、联系人、资质、类型和初始账户在同一业务事务中成功或失败 |
| 注册提交 | 审批结果成功应用后主体和初始账户进入可读生效状态,响应返回完整账户摘要 |
| 新增或变更敏感值 | 后续授权读取返回与提交一致的完整业务值,唯一冲突或非法值整次失败 |
| 失败请求 | 未认证、无权、缺参、非法状态、并发冲突和审计失败均不留下部分业务写入 |
| 历史数据 | 可恢复历史值继续读取;只剩不可逆掩码的审批历史显式标记为不可恢复 |
这些是接口可观察行为;调用方不依赖具体存储结构,也不应自行维护明文/密文兼容状态。
六、边界行为
- 未登录经 Gateway 返回
401。 ADMIN可在既有查看权限与数据范围内读取完整业务值,但写入返回395002。FINANCE、SUPER_ADMIN仍需同时拥有对应平台权限才能写,角色名称本身不是唯一授权条件。- 附件独立权限不足时字段省略;同步审计失败时请求失败,不降级泄露附件。
- 列表
limit=201、缺少必填字段或非法状态返回业务失败,并保持零写入。 - Snowflake ID 继续按字符串处理;时间格式继续为
yyyy-MM-dd HH:mm:ss。
六.5、枚举 / 数据字典
targetType / valueAvailability
| 字段 | 值 | 中文与说明 |
|---|---|---|
targetType |
SUPPLIER |
供应商主体变更,targetId 为 null |
targetType |
ACCOUNT |
收款账户变更,targetId 为账户 ID |
valueAvailability |
FULL |
完整业务前后值可用 |
valueAvailability |
LEGACY_MASKED_UNRECOVERABLE |
历史只剩不可逆掩码,不推测原值 |
账户可读状态
| 值 | 中文 | 说明 |
|---|---|---|
PENDING |
待审批 | 账户可在管理端读取 |
ACTIVE |
已生效 | 正常可用账户 |
DISABLED |
已停用 | 保留只读历史信息 |
DRAFT |
草稿 | 不进入账户列表和账户详情 |
六.6、修改前后对比
字段级对比
| 字段 | 改前 | 改后 |
|---|---|---|
| 税号、法人证件号、电话、证照号 | 主要返回掩码或混合口径 | 授权读取返回完整值 |
accountNo |
可能为空或仅依赖 accountNoMask |
返回完整账号 |
*Mask |
表示掩码 | 废弃兼容别名,暂与完整值同值 |
proofFileUrls |
权限语义分散 | 独立权限 + 同步审计;无权时字段省略 |
types[].isPrimary |
主类型回显不稳定 | 明确 Boolean 标记 |
primaryTypeCode/Name |
不存在 | 根对象直接返回,空类型草稿为 null |
initialAccounts |
写/提交响应摘要不完整 | 返回账户 ID、完整账号和状态 |
| 审批记录 | 主体与账户口径分散 | 统一目标类型、目标 ID、完整值和可用性 |
行为级对比
| 行为 | 改前 | 改后 |
|---|---|---|
| 编辑页初始化 | 可能需要用掩码字段或猜测主类型 | 可直接绑定完整值与权威主类型 |
| 附件读取 | 容易把空值与无权混淆 | 无权省略字段,审计失败整体失败 |
| 草稿账户展示 | 读取接口与草稿状态语义不清 | 写响应展示草稿摘要,读取接口只展示可读状态 |
| 历史审批 | 无法区分完整值与不可逆掩码 | 通过 valueAvailability 明确区分 |
六.7、影响评估
- 是否破坏向后兼容: 否。既有字段名保留,废弃别名在兼容窗口内继续返回。
- 前端是否必须同步上线: 建议尽快。应切换到无
Mask字段、接入主类型字段,并正确处理proofFileUrls缺失。 - 前端 workaround 清理点: 删除自行猜主类型、对
accountNoMask二次掩码、把附件缺字段强制转空数组等兼容逻辑。 - 敏感展示责任: 完整值只在已授权业务页面按最小必要范围展示,不写入前端日志、埋点、错误上报或持久缓存。
七、不影响范围
- 仅影响: 管理后台供应商基本信息、结算账户、注册提交与审批记录消费契约。
- 零影响:
- 不新增或修改 Gateway 路由。
- 不修改角色、菜单、按钮或数据范围定义。
- 不修改小程序、C 端、订单、产品、车队接口。
- 不修改文件上传接口、Redis、MQ 或跨服务 DTO。
- 不允许 DRAFT 账户通过账户查询接口提前暴露。
八、测试环境已验证
最终后端提交 aa735fb8531a3463b2b460965245e07ed8697775 已通过 Deploy Panel 任务 7a48028c 发布 hl-resource-service 双实例,真实 TEST 身份经 Gateway 验证:
历史兼容:完整值与数据库一致,税号关键字搜索命中 ✓
SUPER_ADMIN:创建草稿、详情完整值、注册审批、账号及附件审计读取 ✓
ADMIN:详情与完整账号可读,proofFileUrls 省略,写入拒绝 395002 ✓
未认证 401、缺参 400、limit=201 为 400、非法状态 400 ✓
新写主体/联系人/资质/账户及新审计均为完整值,失败请求零写入 ✓
合成供应商完成精确清理:17 张相关表检查、业务残留 0、失败标记残留 0 ✓
验收会话主动失效,敏感读取操作审计按审计规则保留 ✓
获批真实账号没有 FINANCE 角色,因此没有伪造该身份;FINANCE 与 SUPER_ADMIN 的服务端写权限同构由后端自动化测试覆盖,真实 TEST 写入使用 SUPER_ADMIN,并用真实 ADMIN 验证拒绝路径。
九、相关历史 PR
| PR | Issue | 说明 | 是否仍有效 |
|---|---|---|---|
| #6478 | #6436 | 完整字段、主类型、权限、迁移与兼容读写 | ✅ 主交付 |
| #6483 | #6481 | 修复 TEST 排序规则下回填精确比较 | ✅ 补充修复 |
| #6486 | #6484 | 支持历史 Unicode 税号回填 | ✅ 补充修复 |
十、相关文档
- 主工单 #6436
- 主 PR #6478
- 补充工单 #6481 / PR #6483
- 补充工单 #6484 / PR #6486
- 后端详细 API 说明:
docs/supplier/API-CHANGE-6436.html
关联 / 联系人
链接
联系人
- 后端负责人: @lc