You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

32 lines
4.2 KiB

9 hours ago
---
status: accepted
---
# 联系人列表侧增强与全局脱敏口径(contact-graph 票 04)
## Context
customer-contact-graph effort 票 04([spec](../../.scratch/customer-contact-graph/spec.md) §2.4/§3.2):A4-4-2 客户详情 §6.1/6.2/6.3 列表侧四动作——电话明文查看(reveal)+ 埋点、快速添加多行、页内批量编辑、批量导出——以及**全局脱敏口径**(map 拍板:所有人默认脱敏,明文只经 reveal 通道)。票 03 图谱域已落地(节点出参 `phoneMasked` 独立字段),本票把列表侧拉齐同一口径。票面早期文案与 spec 冻结版冲突处(导出范围、埋点查询端点)以 spec 为准。
## Considered Options
- **脱敏范围**:A(仅列表出参)vs **B(选中):列表出参 + 编辑回显保护 + 导出/port 同口径**——`ContactPhoneMasker.isMaskedForm`(含 `*`)让编辑提交「detail 回显的脱敏串」= 未改号,服务端跳过 phone 更新保留库内明文(`validatePhone` 同步跳过脱敏形态);否则每次编辑都会把 `138****1111` 写进库。C(连 checkPhone 可见命中也脱敏)否——D26 查重软提示既有契约,命中行同权限内本可 reveal,无新增泄露面,脱敏反而破坏提示有用性。
- **quickAdd 批语义**:A(原子批)vs **B(选中):部分成功**——合法行落库、非法行进失败明细(行号按提交下标)。原子批 + 查重拦截组合会让「补交失败行」死锁:重提行撞查重永远失败;原型流程是失败行留在编辑器改完只重提失败行。空行跳过不计成功不记失败;任一行成功即 bump 图谱版本 + 批量一条 oplog。
- **batchEdit 批语义**:**原子批(票面明说「首个错误行不提交其他行」)**——预校验全过才应用,任一行失败零落库 + 失败明细;越权/不存在是安全问题直接 67008 不进行级明细(防探测同码,同基座)。
- **导出范围**:票面「当前页勾选导出」vs **spec 全量(选中)**——票 01 冻结契约优先,票面旧文案作废;电话列同列表脱敏口径,不因导出而放开。xlsx 响应流直出 + `OpLogPort.recordBatch` 结果文件留档 90 天(对齐 LeadPool 导出先例,A7-4)。
- **埋点查询端点**:票面「给管理侧 or 仅留档按 spec」——**取后者:只写不管理**(spec D10:append-only 审计,无查询页、无管理端点,e2e 直查表断言);写入失败自吞不阻塞 reveal 主流程(审计增强,非业务前置)。
- **错误码**:67021(quickAdd 整批不可用)/ 67022(batchEdit 整批不可用)顺延——67017 已被战略协议占用(ADR-0037 连锁),行级错误走 200 结果明细不占用错误码。
## Decision
1. `customer_contact_reveal_log` 埋点表(customer_id/contact_id/operator_id/operator_name 快照/op_time/ip)+ `ContactRevealLogWriter` 薄组件;reveal 返回明文并记埋点,失败自吞。
2. 脱敏统一走 `ContactPhoneMasker`:`mask`(前3+****+后4,短号全遮)出参五处(detail/listByCustomer/page/search + 导出行),`isMaskedForm` 入参保护(batchEdit 与基座 update 共用语义)。
3. quickAdd/batchEdit 独立 `ICustomerContactEnhanceService`(不塞 CRUD 基座),字典联动复用 `DictQueryService.listTree/listEnabledItems`(ADR-0015 读契约);职务四列联动 + D1 档位初始值(level_source=1)在 quickAdd 落库时固化,batchEdit 按 D2(跟随重算/锁定不动)。
4. 端点形态 ADR-0017:`GET /reveal?id=`、`POST /quickAdd`、`POST /batchEdit`(indexed 表单绑定 `rows[0].xxx`)、`POST /export?customerId=`;深 tag「A4 客户管理/客户详情/联系人」。
## Consequences
- 库内不可能出现含 `*` 的 phone(isMaskedForm 拦截一切脱敏回显串写入);reveal 成为明文唯一出口,其埋点表即审计线索。
- ContactBrief(客户目录 port)与图谱节点 `phoneMasked` 自动继承同口径(前者透传 listByCustomer,后者票 03 已独立字段)——脱敏口径变更只需动 Masker 单点。
- 后续票 07 E2E 起服冒烟(掩码→reveal→直查 reveal_log 断言)承接票面验收的 HTTP 断言部分;单测已等价覆盖服务层语义。