做销售的朋友应该都有这种体验:纸质名片递出去一叠,真正加微信的没几个,后续对方看没看、有没有兴趣完全两眼一抹黑。我这段时间折腾并跑通了一套“智能名片 + AI雷达 + 多用户管理”三合一系统源码,它要解决的正是这个老问题——名片数字化之后,还要知道谁看了、看了多久、是否值得跟。这篇文章我会把这套系统的设计动机、核心技术选型、数据表结构、关键链路、实测踩坑以及部署上线经验完整过一遍,适合有一定开发基础、想快速落地私域获客工具的同学参考。
1. 三个模块分别解决什么问题:从一个销售场景说起
1.1 纸质名片时代的死结在哪
纸质名片的功能很简单:留下联系方式。但它存在天生的信息黑洞——你把名片递出去,对方回去之后有没有翻看、有没有扫你的微信、在哪个时间点对你产生了兴趣,你一概不知。即便对方主动加了你,你也不知道他是从哪个渠道认识你的,更不知道他当时处于什么决策阶段。
智能名片倒是把“递名片”这个动作搬到了线上,但那只是把纸质版换成了电子版,本质上还是“单向展示 + 被动等待”。真正拉开差距的,是对名片被访问之后产生的数据做二次加工。这套三合一源码里最值钱的部分,不是那个花哨的H5名片页,而是藏在后面的雷达数据流:谁来过、从哪来、停留多久、翻看了哪些板块、有没有点过电话按钮或下载资料。
我见过不少团队做私域获客,第一反应是买一套CRM,然后发现一线销售根本不愿意录数据。而智能名片天然绕过了这个难题:客户打开名片这个动作本身就在产生数据,不需要销售自己花时间登记。这也是“名片 + 雷达”组合能跑通的根本原因——数据是用户自发生的,不是被逼出来的。
1.2 把“AI雷达”拆穿来看
我第一次听到“AI雷达”这四个字,也以为是厂商在造概念。把源码翻完以后发现,它的底层逻辑并不神秘,本质上是一个“行为采集 + 规则加权 + 线索排序”的轻量分析模型。
它做的事可以拆成三层:
第一层是基础行为采集。名片页被谁打开、打开几次、停留多久、点击了哪个控件,这些事件全部通过埋点上报到后端。第二层是环境信息补全。服务端根据访客的IP、设备型号、操作系统、浏览器信息,拼出一份基础的访客画像。第三层是意向度评分。把第二层数据按照一套可调的规则映射成分数,比如访问一次加几分、停留超过一定时长加几分、反复访问再加几分。
源码里还内置了一个简单的“雷达报告”,其实就是把这三层数据聚合到一张时间轴上,告诉销售:这个客户今天下午三点看了我的名片,先看了产品页,然后点了电话按钮,停留时间接近两分钟——这个意向信号比“转发一万次名片”有用得多。
很多成品系统会把这一层渲染得极其神秘,其实核心就是一张行为分值和权重配置表。我实际把规则表直接改过几轮,效果非常直接:权重调得激进一点,销售收到的提醒就多,跟进压力也大;调得保守一点,慢热客户会被漏掉。这套源码留了规则项,这点比固化死逻辑的商用系统要好。
1.3 多用户管理的真实诉求
个人使用名片工具,一个账号就够了,所有名片和线索都是自己的。但放到团队场景里,问题就出现了:公司的名片模板要统一,销售各自名下的线索不能互看,主管需要看团队整体数据,而老板不希望普通业务员能看到全公司的客户名单。
这个源码里的多用户管理模块,解决的正是这类权限边界问题。它是按“组织-部门-成员”三层建模的,名片归属某个成员,线索默认跟随名片所有者,主管角色可以看下属成员的数据汇总,管理员负责分配额度和管理模板。没有这一层的话,这套系统充其量就是个单机工具,完全撑不起销售团队的日常运转。
从实际落地角度看,多用户管理还承担了一个容易忽略的职责:品牌资产沉淀。销售离职会把微信客户带走,但公司统一配发的智能名片、名片内嵌的官方内容、以及客户在名片上产生的行为数据,都留在系统里。这一点对管理者来说,价值甚至比“雷达”更实在。
2. 源码的技术底座:我的选型思路与模块边界
2.1 为什么选了Spring Boot + Vue这套组合
我入手这套源码的技术栈是Java后端、Vue后台、H5名片页的组合。先说明一点:市面上也有PHP原生版或者Node.js版,但我实际跑下来的感受是,Spring Boot版在数据聚合和权限控制这两个模块上写得更规范,结构更容易二次开发。
选择这套技术栈有三个很实际的理由:
第一,后端生态成熟。雷达系统每天产生的是高频行为日志,Spring Boot配合MySQL读写、Redis做热点缓存,这一套组合开发效率高,运维成本也低,几乎任何云服务器都能跑。第二,前端三端分离但共享同一套接口。名片H5页挂在客户自己的域名下,管理后台挂在另一个域名,两者通过统一的API访问后端,逻辑边界非常清晰。第三,Java技术栈招人、接手、排错都容易,团队协作的时候不会有人因为看不懂代码而卡住。
有个朋友总问,能不能换成低代码平台来做这套系统。我的回答是:名片展示可以低代码,但雷达上报需要自定义埋点,多用户权限需要精细到行级数据隔离,这两块低代码平台要么做不了,要么会收很贵的扩展费。自己拿源码改,反而是最可控的方案。
2.2 三个端分别负责什么
整套系统的代码可以拆成三块来看,理解了这个边界之后,阅读源码会顺畅很多。
第一块是名片展示端,一个移动端H5页面。它负责名片渲染、公司介绍、产品手册、在线留言、保存通讯录、一键拨号,同时还要负责向前端雷达埋点接口上报行为数据。这个H5必须轻量,因为访客大多数是在聊天软件内或者扫码后打开的,加载速度直接决定转化率。
第二块是雷达采集服务,指的是后端接口层。它接收名片页上报的访问流水,补全IP归属地、设备指纹信息,把数据写入行为日志表,同时触发意向度评分、线索去重、消息通知等后续动作。这一层是系统的核心,也是并发压力最大的一块。
第三块是管理后台,面向销售和团队管理者。销售在这里看到自己的名片列表、雷达提醒、线索详情;主管看到团队汇总数据;管理员维护组织架构、角色权限、系统配置。三块之间通过移动端埋点字段和API Token衔接,模块化程度做得好不好,直接影响后续维护的难度。
2.3 拿到源码先看这几个目录
一个陌生系统的源码,最忌讳拿到手就全盘翻读。我建议按这个顺序看:
backend/ 后端主服务(Spring Boot) ├── controller/ 接口层 ├── service/ 业务逻辑层 ├── mapper/ 数据访问层 ├── job/ 定时任务(线索归档、数据清理) ├── config/ 权限、拦截器、Redis配置 frontend/ 管理后台(Vue) mobile-h5/ 名片展示页(移动端H5) docs/ 数据库脚本、部署文档、接口文档核心先看docs里的数据库脚本,把表关系理顺;再看backend的service层,因为业务逻辑基本都在这一层;最后才是controller和前端页面。很多新人习惯从Controller开始看,结果看到一堆接口跳来跳去,半天也拼不出完整链路。
3. 数据表设计:名片、雷达、多用户三层结构的核心
3.1 名片表:一个关键字段决定了后续所有逻辑
名片表是整个系统的地基,字段设计直接影响雷达归属和多用户隔离。核心结构类似下面这样:
CREATE TABLE `card` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `card_no` varchar(32) NOT NULL COMMENT '名片唯一编号,对外分享用', `owner_member_id` bigint(20) NOT NULL COMMENT '所属成员ID', `owner_org_id` bigint(20) NOT NULL COMMENT '所属组织ID', `name` varchar(64) NOT NULL COMMENT '姓名', `title` varchar(64) DEFAULT NULL COMMENT '职位', `company` varchar(128) DEFAULT NULL, `phone` varchar(32) DEFAULT NULL, `wechat` varchar(64) DEFAULT NULL, `avatar_url` varchar(255) DEFAULT NULL, `template_id` bigint(20) DEFAULT NULL COMMENT '名片模板ID', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1启用 0停用', PRIMARY KEY (`id`), KEY `idx_owner_member` (`owner_member_id`), KEY `idx_card_no` (`card_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有两个很容易忽略的设计点。第一是owner_org_id,它不是冗余字段,而是多用户数据隔离的核心依据——后面前端查询、雷达权限过滤都要靠它。第二是card_no,对外分享链接里只暴露这个编号,不暴露数据库自增ID,避免别人遍历你的名片数据。
3.2 访问流水表:雷达数据的原始素材
访客每一次打开名片、点击按钮,都会在这张表里留下一行记录。这张表是典型的“写多读少”场景,也是后期最容易出现性能瓶颈的地方。
CREATE TABLE `visit_log` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `card_no` varchar(32) NOT NULL, `visitor_fingerprint` varchar(64) NOT NULL COMMENT '访客设备指纹', `event_type` varchar(32) NOT NULL COMMENT 'visit/click_phone/click_wechat/share/download', `ip` varchar(64) DEFAULT NULL, `ip_region` varchar(128) DEFAULT NULL COMMENT 'IP归属地', `device_info` varchar(255) DEFAULT NULL COMMENT '设备UA信息', `duration_seconds` int(11) DEFAULT '0' COMMENT '页面停留时长', `ref_source` varchar(64) DEFAULT NULL COMMENT '来源渠道:qr/chat/menu/seo', `visit_time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `idx_card_no_time` (`card_no`, `visit_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;实际运营中这张表的增长速度很快,一个十个销售的小团队,每天几千条访问流水很正常。源码里默认用的是定时任务归档,把一个月前的流水迁移到历史表,保留最近一个月的数据供雷达分析。如果不做这个归档,半年后你的MySQL表会膨胀到难以维护。
3.3 组织与成员表:多用户隔离的钥匙
多用户管理在数据表层面,就是一系列外键关联和权限约束。组织表、成员表、角色表是三个基础表,组织与成员的关系通过member表的org_id字段直接挂靠,角色通过中间表与成员关联。
最核心的是“数据可见范围”的规则,它不是靠复杂的数据库视图实现的,而是在业务查询层统一拼接条件。比如销售查自己的线索:WHERE card.owner_member_id = 当前用户ID;主管查团队线索:WHERE card.owner_org_id = 当前用户组织ID。这类行级数据权限,需要所有查询接口统一维护,漏一处就是越权漏洞。
4. 核心链路实现:从名片被打开到雷达提醒
4.1 名片页如何知道自己是谁
名片页的URL通常是这样的结构:https://yourdomain.com/card/{card_no}?channel=qr。前端拿到card_no之后,调用后端接口获取名片详情,渲染页面。这里的channel参数很关键,因为它决定了雷达报告里“来源渠道”一栏的展示。
系统里生成名片的流程是:管理员在后台创建模板、销售绑定个人信息、系统生成二维码和专属链接。二维码里除了名片编号,还会写入分享人的身份信息,这样转介绍场景下也能追溯是谁带来的访问。实测下来,这一步对线下地推特别有用,销售把二维码打印在工牌和宣传单上,扫码访问的数据直接归到对应销售名下。
4.2 埋点上报为什么用sendBeacon
移动端H5页面的埋点上报,源码里用的是navigator.sendBeacon而不是普通的fetch或XMLHttpRequest。这是有讲究的:当用户在看名片过程中突然锁屏、切走或者退出页面,普通异步请求很可能会被浏览器直接中断,导致最后一条行为记录丢失。sendBeacon的特点是即使页面正在关闭,它也能把数据完整发给服务端。
核心上报逻辑类似这样:
function reportEvent(cardNo, eventType, durationSeconds) { const payload = { card_no: cardNo, event_type: eventType, duration_seconds: durationSeconds, fingerprint: getFingerprint(), channel: getQueryParam('channel') || 'direct', ts: Date.now() }; if (navigator.sendBeacon) { navigator.sendBeacon('/api/radar/track', JSON.stringify(payload)); } else { fetch('/api/radar/track', { method: 'POST', body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json' }, keepalive: true }); } }防重复计数是埋点这里最容易被忽略的问题。同一个访客一天打开十次名片,如果系统每次都当作新线索提醒销售,那就是灾难。源码的处理方式是“设备指纹 + 固定时间窗口”双重判断:同一个指纹在60分钟内重复访问,不重复加分,只延后最后访问时间;跨天访问才继续累加意向度。这个策略比较克制,不会让销售被过多提醒轰炸。
4.3 后台线索聚合与意向度评分逻辑
后端收到埋点数据后,不是直接写库就结束的,它会经过一个评分服务,把原始行为转换成销售能直接看的“意向分”。源码里的默认规则大致是这样:
| 行为事件 | 分值 | 说明 |
|---|---|---|
| 首次访问名片 | +5 | 基础分 |
| 停留超过60秒 | +8 | 说明认真看了内容 |
| 点击电话号码 | +15 | 强意向信号 |
| 点击微信复制 | +15 | 强意向信号 |
| 重复访问(当日二次) | +10 | 主动回头 |
| 下载资料或产品手册 | +20 | 深度兴趣信号 |
这个评分逻辑放在service层,任何人拿到源码都可以直接改。我建议不要死守默认规则,先看自己的行业。我自己跑过一阵子B2B设备销售,点击资料和拨打电话的权重确实要调高;如果是快消品,反而是查看门店地址和地图导航更有价值。规则表是配置文件,支持热更新,这点很实用。
线索聚合用的是Redis配合定时落库。高频访问先写Redis,按名片分组维护“最近访客”和“今日访客”两个维度;定时任务每隔几分钟把Redis数据批量写入MySQL,同时触发通知。这样做的好处是扛得住短时间高并发,比如销售把名片发到群里,瞬间几十个人点进来,数据库不会被打垮。
4.4 数据权限:普通员工凭什么看不到别人的雷达
数据权限是这套源码里最需要小心改动的部分。它的实现方式是:所有线索查询接口在进入Controller之前,都经过一个权限拦截器,拦截器解析当前登录用户的组织ID和角色,再决定SQL查询条件。
销售端的线索列表查询,最终的SQL会强制拼接这么一段:
WHERE card.owner_member_id = #{当前用户ID}组织主管则放宽一层:
WHERE card.owner_org_id = #{当前用户所在组织ID}这个逻辑本身不复杂,难点在于新增接口时容易忘记拼接。我实测时踩过一次:后来加了一个“导出线索”的功能,开发时没带权限条件,结果普通销售直接把全公司的线索导出走了。排查半天才发现是导出接口单独写了一条不经过权限过滤的查询。所以拿到这种源码,第一件事就是把所有涉及card表的查询全部过一遍权限条件,宁多勿少。
5. 实测踩坑记录:五个让我挠头的细节
5.1 微信内置浏览器里生成海报黑屏
名片页有个常用功能是“保存名片海报”,方便销售转发到朋友圈。源码里海报是用前端Canvas绘制的,结果在PC浏览器上测试一切正常,一放到微信内置浏览器里就偶发黑屏,绘制出来的图片是黑的,或者只有文字没有背景图。
排查后发现是微信内置浏览器对Canvas的drawImage跨域图片处理策略更严格,本地测试时是本地域名,线上海报背景图和二维码图来自CDN域名,跨域没有配置crossOrigin属性,图片绘制失败但不会报错,最终产出了黑屏海报。
解决方法是两步:给所有图片标签或图片对象加上crossOrigin="anonymous",同时在CDN的响应头里配置Access-Control-Allow-Origin。后续我干脆改成了后端生成海报——前端把名片信息发给服务端,服务端用图形库渲染好图片返回,兼容性最好,也省去了前端在不同浏览器里测试的精力。
5.2 同一个访客被反复计成新线索
这是雷达系统最容易惹恼销售的问题。最开始设备指纹只取了浏览器localStorage里存的随机UUID,只要用户清了缓存、换了浏览器、或者用PC和手机分别打开一次,系统就会识别成两个新访客。销售那边看到的就是同一个人出现了七八条线索,实际意向被严重注水。
我把指纹采集改成了多因子组合:优先用聊天软件授权后的用户唯一标识;拿不到就取设备UA + 屏幕分辨率 + 时区 + 语言组成一个近似指纹,再叠加UUID。这套组合在真实场景下命中率比较高,但也要认清一个边界:任何纯前端指纹都不可能做到绝对准确,只要用户刻意伪装,你就没法完全识别。雷达数据看的是趋势和集中度,不是精确到个人的监控。
5.3 IP定位永远只能到城市
刚上线的时候我在后台看到IP归属地精确到了“某区某街道”,第一反应是这数据好强大,结果拿自己手机一测,发现它把我在公司旁边两公里外的地方标成了另一个商圈。后来才想明白,IP地理库的精度上限基本就是城市级别,“街道”这一级大多是根据运营商机房位置近似出来的,换个WiFi环境定位就漂了。
这个问题的正确应对方式,是在前端页面上标注“最近访问地区”而不是“精确位置”,避免给销售造成错误判断。源码里默认展示“省 + 市”,例如“广东省深圳市”,这个粒度比较安全。不要为了炫技强行展示街道级位置,带来的误导远大于惊喜。
5.4 保存到通讯录的vCard中文乱码
名片页上的“保存联系人”功能,在生成vCard文件时踩了编码的大坑。iOS和Android通讯录对vCard的解析标准不完全一致,Android大多能正常识别UTF-8,iOS早期版本对没有显式声明CHARSET=UTF-8的vCard文件,会把中文姓名解析成乱码。
正确的生成方式是完整声明vCard 4.0头,并把所有中文字段使用UTF-8编码:
BEGIN:VCARD VERSION:4.0 N:张;伟;;; FN:张伟 ORG:某某公司 TEL;TYPE=CELL:+8613800000000 END:VCARD同时设置下载响应头Content-Disposition: attachment; filename="card.vcf"。文件名不要用中文,很多手机浏览器处理中文文件名会出问题。这个小功能耗时不多,但销售用得频率很高,做不好会直接拉低专业感。
5.5 前后端分离部署导致后台登录失效
管理后台放在admin.example.com,后端API放在api.example.com,这是很常见的部署方式,但坑也在这一刻埋下。后台登录后前端保存了会话Cookie,浏览器请求API时发现跨域,某些情况下Cookie会被浏览器的SameSite策略拦截,导致登录状态时有时无,页面疯狂跳登录页。
排查时先看浏览器开发者工具里的请求响应头,发现接口请求没有带上会话Cookie。解决方案是在后端配置允许前端的跨域来源,并将Cookie的SameSite设置为Lax,前端的请求配置改为credentials: 'include'。注意跨域白名单要写死具体域名,不要写通配符,否则存在被第三方站点盗用接口的风险。
这一块如果是内网部署或者单域名部署,问题不会暴露,但只要前后端分离上生产,几乎必踩。
6. 部署上线与落地运营的几点补充
6.1 服务器规格别贪大,够用就好
这套系统的体量不大,最吃资源的其实是访问流水的写入和定时聚合任务。我实测过的配置是2核4G的云服务器,MySQL和Redis都跑在同一台机器上,同时支撑几十个销售、日均几万条访问流水,完全没问题。
如果销售规模上百人,再加一台1核2G的机器专门跑Redis就行。真正的瓶颈是访问日志表的增长,所以定时归档任务一定不能关。部署时把数据库和Redis的数据目录放到独立数据盘,避免系统盘满了以后整个服务瘫痪。这个细节很多人忽略,等磁盘告警才后悔。
6.2 备案、HTTPS和Nginx配置别拖到最后
国内服务器部署这套系统之前,域名和服务器准备好以后,按要求完成网站备案是绕不开的一步,否则域名解析会受限。这个过程建议在开发阶段就同步进行,不要等代码写完了才去提交,审核需要时间,白白空转。
HTTPS必须上,原因不仅是安全,还在于H5名片页会被聊天软件和社交平台大量打开,非HTTPS页面在分享时经常被拦截或者出现不明风险提示。证书申请后用Nginx一配,配置示例:
server { listen 443 ssl; server_name card.example.com; ssl_certificate /etc/nginx/cert/fullchain.pem; ssl_certificate_key /etc/nginx/cert/privkey.pem; root /var/www/mobile-h5; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }很多IP归属地不准的问题,就是忘了在Nginx层把真实的X-Forwarded-For传给后端,导致后端拿到的全是代理服务器的IP。
6.3 上线后怎么做才能发挥雷达的价值
系统部署上去不等于自动产生业绩。我观察到一个很普遍的现象:销售刚开始对“雷达提醒”很新鲜,天天盯着看,过了两周热度就降了,又回到原来那种盲打盲聊的状态。真正有效的运营方法,是把雷达数据和跟进动作绑定起来。
比如把名片二维码放到聊天签名、公众号菜单、自动回复、线下工牌和宣传单上,统一渠道来源;然后每周复盘“访问了但没跟进”的线索,逼着销售把雷达信号转化成聊天抓手。实测中一句话特别好用:“刚看您看了我们产品页,不知道是不是在了解某一款型号,我给您发份详细参数?”这种跟进响应得快,客户也不会反感,因为确实是他自己主动看的。
多用户管理模块在这里还有一个隐藏玩法:主管可以按线索意向分排名,把高分线索重新分派给跟进能力更强的销售。这套系统给了管理者一个客观依据,而不是靠感觉派单。
最后再分享一点我的个人体会。这套三合一源码最让我意外的不是功能多豪华,而是把几个小细节做对之后,对一线销售的帮助立竿见影。它的核心资产集中在三处:访问埋点的准确率、意向度评分规则的可配置性、数据权限的严密程度。你在二次开发时,把精力优先投在这三处,比加任何花哨功能都值。如果你正好也要搭建类似的名片获客系统,建议先从数据库脚本和service层的评分逻辑读起,先把数据跑通,再谈展示和交互——这是我折腾完这一整套东西之后最实在的建议。