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.

123 lines
8.4 KiB

5 days ago
# 04 — 新增/编辑客户的查重校验 + 企查查工商回填 seam
Type: research
5 days ago
Status: resolved
5 days ago
Blocked by: 02
## 问题
定新增/编辑客户表单的**查重校验规则**(内建在新增流程、写死不做后台可配)+ **企查查外部服务契约 seam**(调用点/回填范围/异常/精确查重触发)。**A4-6 独立查重页 + 客户合并不做**(chart grill D6)。
## 一手证据(蓝湖缓存 txt)
- `a4-3-3-1_新增客户信息.txt` — 主页面 §3.2 客户名称与自动查重 / §3.3 企业查询与工商信息回填 / §3.4 客户重复校验 / §3.5 联系人明细表 / §3.6 工商信息
- `a4-3-3-2_编辑客户信息.txt` — 编辑页复用新增结构 + 有 seam
## 已知事实(原型明文,节选)
- **自动查重(防抖)**:客户名称停止输入→防抖异步查 CRM 相似客户;仅名称输入框下方展示相似列表;只提示、不阻止;无权限的匹配脱敏为「存在匹配记录」不显 name/编号/负责人。
- **企业查询**:`查询企业`→调外部工商服务→候选列表→单选回填。首次选择只回填**当前为空且属于允许范围**的工商字段(工商名称/信用代码/法代/成立时间/注册资本/经营范围/人员规模/年产值/工商注册地址)。已填字段再次选择必须弹「受影响字段确认」再覆盖;空值不覆盖原值。**不覆盖**:客户类型/行业/所属地区/关系星级/负责人/部门/协同人/人脉关系单位/联系人/备注。
- **精确查重触发**:选择企业并回填后立即用统一社会信用代码执行精确查重;保存时再执行一次终查。
- **保存时校验**(服务端最终查重):
- 统一社会信用代码在**全部未删除客户**中必须唯一 → 完全相同**硬拦禁止创建**(含无权查看时不显 name/编号,只提示阻断)。
- 客户名称相似但信用代码不同 → 弹「客户相似确认」弹窗,用户可"仍要创建";"仍要创建"后**服务端必须再查一次**(防并发/防中间被人创建同信用代码)。
- **联系人明细表内联查重**:同表格内联系电话相同→禁止提交定位重复行;无电话按「客户+姓名+职务」提示疑似重复;全库按电话查重(对已存在联系人)。→ 联系人查重口径归本票,联系人 CRUD 建模归票 06。
- **父子客户防环**:`是否子客户=是`时父客户必填;父客户不得选择自身及其下级 → 服务端必须校验不得形成循环。
- **人脉关系单位**:标签集(单个≤50字,同 name 不重复),非关联 CRM 客户。
## 待拍板点
1. **相似客户判定口径**(阈值/算法)——原型说「使用 CRM 后端统一查重服务,不在前端固化阈值」。本票拍板:走什么算法(简单 Trigram / MySQL 全文索引 / 编辑距离 / N-gram 分词)?先给最简可行方案。
2. **企查查外部服务契约 seam**:不深挖服务实现,只定接口(`CustomerCompanyLookupPort`?)+ 请求(name)/响应(候选列表带9项工商字段)/异常语义(无结果/失败/超时);实现可 mock。
3. **信用代码格式校验**:18 位数字或字母组合(原型明说,正则拍死)。
4. **成立时间不得晚于当前日期** 前置校验。
5. **并发建客户**(同信用代码并发提交):DB 唯一约束兜底 + 乐观返回「重复」错误;先查后写做双保险但不依赖它。
6. **相似客户提示的脱敏**:无权限用户命中他人客户时的返回负载最小化(不带 name/编号/负责人/联系方式),只回「命中数量+提示语」。
## Answer
5 days ago
**Status: resolved**
### A. 范围(定稿)
-**做**:新增/编辑客户时的**内建查重校验** + **企查查外部服务契约 seam**
-**不做**:A4-6 独立查重页 + 客户合并(chart D6);「客户查重设置」可配规则(chart D4);企查查服务内部实现(只定契约)。
### B. 查重校验(内建,写死在新增/编辑流程)
#### B1. 三层查重(定稿)
| 层 | 时机 | 规则 | 结果 |
|---|---|---|---|
| L1 自动相似提示 | 客户名称**停输防抖**(~300ms) | 名称模糊匹配全部未删除客户 | 只提示不阻止;无权命中的脱敏为「存在匹配记录」 |
| L2 保存时精确查重 | 点击确认提交 | ① 信用代码全局唯一(硬拦)② 名称相似(弹窗确认) | 信用代码同 → 禁止;名称似 → 弹窗「仍要创建/返回修改」 |
| L3 终查(并发兜底) | 「仍要创建」后、写库前 | 再查一次信用代码 + 名称 | 仍无冲突才写库;DB UNIQUE 兜底 |
#### B2. 相似判定算法(⚠ 挂号,需拍板)
原型明说「使用 CRM 后端统一查重服务,不在前端固化阈值」。**我的推荐(最简可行)**:用 **MySQL 内置 `FULLTEXT` 索引 + `MATCH ... AGAINST` 自然语言模式**,或更简单先上 **`LIKE '%name%'` + 编辑距离(Levenshtein) 后置过滤**。
- **推荐方案**:`customer_name` 建 FULLTEXT 索引,`MATCH(customer_name) AGAINST(?) ` 取 topN 候选,再用 **Levenshtein 相似度 ≥ 0.7** 判定「相似」,信用代码精确相等判定「重复」。首版阈值 0.7 写死常量(不做后台可配,chart D4)。
- **备选**:NGram 分词(MySQL ngram parser)+ Jaccard 相似度。中文企业名短,FULLTEXT 默认分词对中文不友好,**ngram parser(token_size=2)更合适**——**最终推荐走 ngram parser**。
- **统一查重服务**:抽 `CustomerDedupService`(crm-customer 内),后续 A4-6 若重启可复用;本期只被新增/编辑流程调用。
#### B3. 信用代码校验(定稿)
- 格式:`^[0-9A-Z]{18}$`(18 位数字或大写字母,原型「18 位数字或字母组合」)。
- 唯一:`uk_unified_credit_code` 唯一索引(票 02 已建),**NULL 不参与唯一约束**(未填时允许)。
- 精确相等 → 硬拦禁止创建;命中无权查看的客户只提示「系统已存在该企业」,不显 name/编号/负责人。
#### B4. 联系人内联查重(定稿,回填票 06)
- 同表格内**电话相同两行** → 禁止提交并定位重复行。
- 无电话 → 按「客户 + 姓名 + 职务」提示疑似重复(只提示)。
- 全库按电话精确查重(对已存在联系人)→ 相同禁止新建。
### C. 企查查外部服务契约 seam(定稿)
#### C1. 端口(不做实现,留 mock)
```java
// crm-customer 侧出站端口
public interface CompanyLookupPort {
/** 按企业名查询工商候选列表 */
List<CompanyCandidate> lookup(String companyName);
}
record CompanyCandidate(
String bizName, String unifiedCreditCode, String legalRepresentative,
LocalDate establishedDate, String registeredCapital, String businessScope,
String staffSize, String annualRevenue, String address
) {}
```
#### C2. 回填范围(定稿,原型 §3.3 明文)
- **允许回填字段(9 项)**:工商名称 / 统一社会信用代码 / 法定代表人 / 成立时间 / 注册资本 / 经营范围 / 人员规模 / 年产值 / 工商注册地址。
- **只回填当前为空的字段**(首次选择);已填字段再次选择必须先弹「受影响字段确认」才覆盖;服务返回空值不覆盖原值。
- **永不覆盖**:客户类型 / 行业 / 所属地区 / 关系星级 / 客户星级 / 销售负责人 / 销售部门 / 协同人 / 人脉关系单位 / 联系人 / 备注。
- 回填后**立即用信用代码执行精确查重**(B1 L2 联动)。
#### C3. 异常语义(定稿)
| 场景 | 行为 |
|---|---|
| 无结果 | 保留表单,提示「未查询到匹配企业」,可手工填写 |
| 服务失败/超时 | 保留表单,提示重试或手工填写;**不阻断手工提交** |
| 请求乱序 | 只处理最后一次有效请求,旧结果不覆盖当前表单 |
### D. 编辑客户(定稿,对称新增)
- 编辑页复用新增表单结构 + 查重校验;编辑时**排除自身**出查重范围(改名字不和自己撞)。
- 历史停用枚举可回显不可重选(原型公共规则)。
### E. 回填 / 下游
- **票 02**:`unified_credit_code` 唯一索引 + `customer_name` FULLTEXT(ngram) 索引(A5 索引表补一条 `idx_name_fulltext`)。
- **票 06**:联系人电话查重(B4)。
- **票 09**(导入):复用 `CustomerDedupService` 做导入查重。
### F. ⚠ 挂号(需拍板)
1. **相似判定算法**:ngram parser + 相似度阈值 0.7(推荐)vs 更简单 LIKE+Levenshtein。你选哪个?