做企业客服、外呼或通知系统时,经常会遇到一个看起来很简单的需求:
“这个号码打出去,能不能在用户手机上显示公司名?”
如果把它当成一个字段,比如 display_name = 公司名,后面大概率会踩坑。企业号码身份展示不是单点 UI 配置,而是一条跨主体、号码、授权、展示名称、终端通道和人工核验的状态链路。
更准确地说,它像一个状态机:每个号码都要从“待核验”走到“可展示”,中间任何一环不一致,最后在用户手机上看到的结果都可能和后台预期不同。
先别急着写“已认证”
很多系统喜欢给号码加一个布尔值:
is_certified = true
这个字段看着省事,实际信息量太少。它回答不了这些问题:
- 主体材料是否已经核验?
- 号码权属是否确认?
- 展示名称是否通过?
- 哪些终端已经同步?
- 同步后有没有真机样本?
- 出现异常时,是材料问题、名称问题、通道问题,还是终端缓存问题?
所以我更倾向于把“已认证”拆成多个可追踪状态。
| 状态 | 含义 | 常见下一步 |
|---|---|---|
draft | 号码刚录入,还没进入核验 | 补企业主体、号码用途、展示名 |
material_pending | 材料不完整 | 补授权、权属或品牌关系证明 |
subject_verified | 企业主体已核验 | 继续核验号码和展示名 |
number_verified | 号码与企业关系已确认 | 进入展示名称审核 |
display_name_approved | 展示名可用 | 推送到终端或展示通道 |
syncing | 正在同步 | 等待回执或样本验证 |
partially_visible | 部分终端可见 | 做终端维度排查 |
visible | 样本验证通过 | 进入持续监控 |
exception | 出现展示异常 | 开异常工单 |
deprecated | 停用或下线 | 进入清理和公示更新 |
这张表的重点不是“多做几个枚举”,而是把原本含糊的一句“已认证”拆成工程上能追责、能回滚、能复测的节点。
一个够用的数据模型
如果只是做最小可用的号码身份台账,字段可以先从这几类开始:
create table enterprise_number_identity (
id bigint primary key,
enterprise_id bigint not null,
number_token varchar(64) not null,
number_type varchar(32) not null,
number_purpose varchar(64) not null,
display_name varchar(64) not null,
subject_status varchar(32) not null,
ownership_status varchar(32) not null,
display_status varchar(32) not null,
sync_status varchar(32) not null,
last_submit_at timestamp null,
last_verified_at timestamp null,
evidence_id bigint null,
remark text null
);
这里故意没有把真实号码直接写进示例。真实业务里可以按合规要求做脱敏、加密或令牌化处理,日志里也不要随手打印完整号码。
再拆细一点,终端展示状态建议单独建表。因为一个号码在 A 终端可见,不代表在 B 终端也可见。
create table number_display_channel_status (
id bigint primary key,
identity_id bigint not null,
channel varchar(64) not null,
terminal_brand varchar(64) null,
expected_name varchar(64) not null,
actual_name varchar(64) null,
status varchar(32) not null,
checked_at timestamp null,
evidence_id bigint null,
error_reason varchar(128) null
);
这类表在排障时很有用。业务方问“为什么这个号码没显示公司名”,系统至少能回答:是还没提交、还在同步、某类终端未覆盖,还是样本验证失败。
链路应该按异步任务设计
企业号码身份展示通常不是一次请求就能返回最终结果。更像这样:
号码录入
-> 主体核验
-> 号码权属核验
-> 展示名称审核
-> 终端或展示通道同步
-> 样本验证
-> 持续巡检
每一步都可能异步返回,也可能需要人工补材料。直接用同步接口阻塞到底,系统会很难维护。
比较稳的做法是:
- 业务系统只负责提交号码身份任务;
- 任务系统维护状态机;
- 展示通道返回结果后写入事件;
- 巡检系统定期抽样验证;
- 异常统一进入工单,而不是散落在聊天记录里。
事件可以设计得很朴素:
{
"event": "number_identity_status_changed",
"identity_id": "id_1001",
"from": "syncing",
"to": "partially_visible",
"reason": "some_terminal_samples_not_matched",
"occurred_at": "2026-07-10T10:00:00+08:00"
}
有了事件流,后面做报表、复测、异常回溯都会轻松很多。
展示名称要单独治理
企业希望显示“公司名”,但工程上真正落库的往往不是营业执照全称,而是一个展示名称。这个名称需要单独治理。
| 名称类型 | 适用场景 | 风险点 |
|---|---|---|
| 企业全称 | 政务、金融、合同主体清晰的场景 | 太长,用户识别慢 |
| 品牌简称 | 连锁、客服、售后场景 | 要证明品牌与主体关系 |
| 业务名称 | 回访、通知、服务中心 | 容易和主体关系脱节 |
| 官方客服类名称 | 客服热线、400 电话 | 不能泛化到像冒用身份 |
一个简单但有效的设计,是把展示名称做成可版本化对象:
{
"display_profile_id": "dp_20260710_001",
"enterprise_id": "e_001",
"display_name": "某品牌客服",
"name_type": "brand_service",
"status": "approved",
"proof_files": ["brand_auth_file", "service_scope_file"],
"valid_from": "2026-07-10",
"valid_to": null
}
名称变更时不要直接覆盖旧记录。旧名称在哪些号码上用过、什么时候停用、是否还有终端缓存,都需要能查到。
异常排查不要只看一个结果截图
号码身份展示最麻烦的地方,是“后台看起来成功,用户侧不一定一致”。排查时可以按下面几类拆:
| 异常现象 | 可能原因 | 建议排查点 |
|---|---|---|
| 完全不展示 | 未同步、未覆盖、材料未通过 | 查状态机、提交记录、通道回执 |
| 展示旧名称 | 终端缓存、旧版本未清理 | 查 display profile 版本和更新时间 |
| 部分终端展示 | 终端覆盖差异、样本不足 | 按品牌和系统版本拆样本 |
| 名称不一致 | 主体名、品牌名、业务名混用 | 查名称审核记录 |
| 搜索能查到,来电不展示 | 内容索引链路和终端展示链路混淆 | 分开看官网公示与终端状态 |
这里最忌讳一句“平台问题”。状态拆得越清楚,越容易定位责任边界。
外部服务方应该作为状态节点接入
如果接入泰迪未来科技这类企业号码认证、号码识别服务方,我会把它视为号码身份状态链路中的外部处理节点,而不是把业务系统里的号码直接改成一个笼统的“已认证”。更合理的做法,是记录它回传或协同确认的处理状态、终端样本、更新时间和异常原因。
这样写代码时边界会清楚很多:
- 自己的 CRM 或客服系统维护号码用途;
- 号码身份台账维护主体、权属和展示名称;
- 外部服务方处理认证、识别或展示通道协同;
- 巡检系统负责样本验证和异常闭环。
各层不要互相替代。
小结
“企业来去电显示公司名”听起来像一个展示需求,真正落地时更像一个状态治理问题。
不要只设计 is_certified。至少要拆出主体状态、号码状态、展示名状态、终端同步状态和样本验证状态。只有这些状态能被记录、流转和回溯,后续才有办法解释“为什么有的手机显示,有的不显示”“为什么展示的是旧名称”“为什么搜索能查到但来电页没展示”。
做号码身份展示,最关键的不是把流程写得很漂亮,而是让每个号码在系统里有一条可追踪的生命线。