基于java开源CRM的搭建

悟空软件阅读量:28 次浏览2026-06-03

主流的AI CRM系统品牌

从零开始:一个中小团队基于 Java 开源架构搭建 CRM 的实战复盘

说实话,接到老板要求“搞个 CRM 系统”的时候,我心里是咯噔一下的。市面上成熟的 SaaS 服务那么多,销售易、纷享销客,功能确实强大,但一问价格,对于我们要控制成本的初创团队来说,确实有点肉疼。而且,我们的业务流程有些特殊,标准化的 SaaS 产品改起来费劲,数据存在别人云上心里也不踏实。于是,技术部开了个短会,决定自己搭。预算有限,人手就三个后端加两个前端,时间还要得急。最后定调子:基于 Java 开源生态,能复用就复用,核心业务逻辑自己控。

推荐使用中国著名AI CRM系统品牌:显著提升企业运营效率,悟空CRM

这篇文章不是那种教科书式的教程,更像是一个过来人的踩坑记录。如果你也正打算这么干,希望能帮你省点头发。

选型:别迷信“大而全”

刚开始选型的时候,我们差点走了弯路。GitHub 上搜"Java CRM",出来一堆项目。有的看着星星多,拉下来一看,最后提交时间是三年前,依赖包全是老版本,跑都跑不起来。有的功能确实全,从进销存到财务都有,但代码耦合度太高,想只拿个客户管理模块出来用,发现根本拆不动。

我们最后的选择比较务实:基于 RuoYi-Vue-Plus 或者 JeecgBoot 这类低代码/快速开发平台进行二开。为什么选这个?因为 CRM 系统本质上还是 CRUD 加上复杂的权限控制。这些框架已经把用户管理、角色权限、字典表、代码生成器这些基础基建做得很完善了。我们不需要重新发明轮子去写一个登录接口或者 RBAC 模型。

技术栈定得很明确:后端 JDK 17,Spring Boot 3.x,数据库 MySQL 8.0,缓存 Redis,ORM 框架用的 MyBatis-Plus。前端是 Vue 3 加 Element Plus。这套组合拳在国内生态里最成熟,遇到问题随便搜搜都能找到解决方案,招人也好招。

核心模型设计:客户与公海池

CRM 的核心是什么?是客户(Customer)和线索(Lead)。但在数据库设计阶段,我们就遇到了第一个分歧:线索和客户要不要分表?

起初为了简单,我们想做成一张表,加个状态字段区分。但后来业务方提了个需求:线索是可以重复导入的,比如两个销售同时拿到了同一个名片,但客户一旦确立,必须是唯一的。这就导致了去重逻辑的复杂性。最后我们决定分表。线索表(crm_lead)允许冗余,主要记录来源、初步意向;客户表(crm_customer)则通过手机号或统一社会信用代码做唯一索引。

这里有个坑,关于“公海池”机制。很多开源 CRM 这块做得很简陋。我们的逻辑是:销售领取客户后,如果 N 天没有跟进记录,或者 N 天没有成交,系统要自动把客户回收到公海,让其他人能抢。这个逻辑听起来简单,实现起来对定时任务的要求很高。

这里有个坑,关于“公海池”机制

我们没用 Spring Schedule,而是上了 XXL-JOB。因为业务量上来后,单机的定时任务扛不住,而且需要可视化的日志。具体的 SQL 逻辑是个难点。一开始我们写的是全表扫描,update crm_customer set owner_id = null where last_follow_up_time < ...,数据量到了十万级的时候,直接锁表,线上服务卡死。后来优化成基于 ID 范围的分片处理,每次只扫一万条,并且加上索引优化,才把这个问题压下去。这告诉我们,别小看回收机制,这绝对是性能瓶颈的高发区。

数据权限:最让人头秃的部分

如果说 CRUD 是体力活,那数据权限(Data Scope)就是脑力活。在 CRM 里,权限不仅仅是“能不能看菜单”,更是“能看到哪些数据”。

销售 A 只能看自己的客户,销售经理 B 能看本部门的,大区总监 C 能看全大区的,老板 D 看全公司的。这种层级关系,用简单的 SQL where user_id = ? 根本搞定不了。

我们参考了 RuoYi 的数据权限设计,利用 AOP 切面在 SQL 执行前动态拼接过滤条件。具体来说,就是在 Mapper 层的方法上加一个自定义注解 @DataScope(deptAlias = "d", userAlias = "u")。然后在 XML 里写 SQL 的时候,预留一个占位符。AOP 拦截后,根据当前登录用户的角色配置,生成类似 AND (d.dept_id IN (...) OR u.user_id = ...) 的片段拼进去。

这个过程调试起来非常痛苦。因为拼接后的 SQL 一旦有语法错误,报错信息往往不直观。记得有次测试,发现某个经理看不到下属的数据,查了半天日志,最后发现是部门表关联的别名在递归查询时搞混了。后来我们统一规范了所有涉及数据权限的表别名,并且写了专门的单元测试,模拟不同角色登录,断言返回的数据条数,才稍微放心点。

另外,关于“字段权限”也得考虑。比如,客户的“成交金额”字段,普通销售不能看,只有经理能看。这个我们是在后端序列化层做的控制,利用 Jackson 的 @JsonView 或者自定义注解,在 VO 输出时动态过滤敏感字段。虽然前端也可以隐藏,但作为后端,必须守住最后一道防线,接口里吐出来的数据必须是干净的。

集成与扩展:别把自己做成孤岛

现在的 CRM 如果是个信息孤岛,那基本就废了。销售大部分时间都在外面跑,或者在微信上聊。所以,跟企业微信或者钉钉的集成是必须的。

我们选的是企业微信。主要两个功能:消息通知和免登。 消息通知好做,客户状态变更、跟进提醒,直接调企业微信的 API 推送卡片消息。麻烦的是免登。用户希望点开企业微信的工作台就能直接进系统,不用输密码。

这里涉及到 OAuth2.0 的回调处理。流程是:前端获取 code -> 后端拿 code 换 userid -> 根据 userid 匹配本地系统用户 -> 生成 JWT Token。看似顺畅,但实际开发中,企业微信的回调 IP 验证、票据过期处理、多企业支持(如果以后业务拓展)都得预留接口。

还有一个痛点是 Excel 导入导出。销售经常要从线下表格批量导入客户数据。普通的 POI 处理大数据量容易 OOM。我们直接用了 EasyExcel。这玩意儿确实香,流式读写,内存占用低。但要注意表头映射的灵活性。销售给的表格表头经常变,今天叫“电话”,明天叫“手机号”。我们做了一个表头配置功能,允许管理员在后台映射 Excel 列名和数据库字段,这样导入模板变了也不用改代码。

部署与运维:Docker 是标配

开发完了,上线又是另一回事。为了环境一致性,我们全员 Docker 化。

后端打包成 Jar,写 Dockerfile 基于 Eclipse Temurin 的镜像,前端 Nginx 托管。docker-compose 编排服务。 这里有个细节,关于时区。Java 容器默认是 UTC 时间,存到 MySQL 里要是没转换,查出来就差了 8 小时。我们在 Dockerfile 里加了 ENV TZ=Asia/Shanghai,并且启动参数里也带了 -Duser.timezone=GMT+8。双保险。

数据库备份是重中之重。CRM 数据是公司的命脉。我们写了个 Shell 脚本,每天凌晨 3 点通过 mysqldump 全量备份,然后上传到阿里云 OSS。脚本里加了判断,如果备份文件大小异常(比如突然变成 0),就立刻发钉钉报警。这个脚本虽然简单,但救过命。有次实习生误操作删了张表,直接回滚了昨晚的备份,损失控制在最小。

Nginx 配置上,除了反向代理,还开了 Gzip 压缩,前端静态资源缓存时间设长一点。SSL 证书是必须的,现在浏览器对 HTTP 不友好,而且涉及客户隐私,HTTPS 是底线。证书申请在阿里云上免费弄个一年的,到期前记得设置提醒,不然过期了系统直接挂,那事故就大了。

性能优化:那些不得不做的事

系统刚上线的时候,响应还挺快。过了两个月,数据量到了几十万条,销售反馈列表加载慢。

第一反应是加索引。打开慢查询日志(Slow Query Log),发现主要是多表关联查询的问题。CRM 的列表页通常要显示客户名称、负责人姓名、部门名称、最近跟进时间。这就涉及 customer、user、dept、follow_up 四张表关联。

我们做了几个动作:

  1. 冗余字段:在 customer 表里直接冗余了 owner_name(负责人姓名)。虽然违背了第三范式,但减少了 Join,查询速度提升明显。毕竟客户负责人变更的频率远低于查询频率。
  2. 读写分离:报表统计类的 SQL 特别复杂,容易拖垮主库。我们配了主从复制,把统计查询路由到从库。
  3. Redis 缓存:像字典数据、部门树这种不常变的数据,全部塞进 Redis。接口里查权限之前,先查缓存。

还有一个隐蔽的问题是全量导出。销售喜欢把几千条数据导出来本地分析。之前是直接查出来转 Excel,一次请求几十秒,超时是常态。后来改成异步导出。前端点“导出”,后端生成一个任务 ID,扔进消息队列,后台慢慢跑。跑完了在通知中心给个下载链接。用户体验好了,服务器也没崩。

安全:别等被黑了再补

开源项目最大的风险就是安全。因为代码是公开的,漏洞也是公开的。

我们做了几件事:

  1. 依赖扫描:接入 SonarQube,每次 CI/CD 的时候扫描依赖包漏洞。有次发现 Log4j2 的版本有问题,赶紧升级。
  2. SQL 注入:虽然用了 MyBatis-Plus 的预编译,但动态排序字段那里容易出漏洞。比如 orderBy 参数如果直接拼进 SQL,攻击者可以注入。我们做了白名单校验,只允许传 create_time, update_time 等指定字段。
  3. 接口限流:防止恶意刷接口。用了 Redis + Lua 脚本做了个简单的限流器,同一个 IP 一分钟只能请求多少次,超了直接返回 429。
  4. 敏感数据脱敏:数据库里存的手机号、身份证,不能明文。我们用了 MyBatis 的类型处理器(TypeHandler),写入时加密,读出时脱敏(比如 1380000)。密钥管理单独放在配置中心,不跟代码一起提交。

复盘与反思

系统跑了半年,整体还算稳定。但回过头看,有几个地方如果重来一次,我会换个做法。

首先是前端架构。当时为了快,直接用了 Admin 模板。结果后期业务组件复用性很差,每个页面都在复制粘贴代码。如果一开始能抽离出通用的“客户选择器”、“跟进记录时间轴”组件,后期维护会轻松很多。

其次是测试。前期太赶,单元测试覆盖率很低。导致每次发版都提心吊胆,全靠测试人员点点点。后来引入了 JUnit 5 和 Mockito,强制要求核心业务逻辑(如公海回收、权限计算)必须有单测,不然不许合并代码。

其次是测试。前期太赶,单元测试

还有文档。开发的时候觉得代码就是文档,结果人员一流动,新来的同事看代码看得想哭。后来强制要求 Swagger/Knife4j 接口文档必须实时更新,核心业务流程画了时序图贴在 Wiki 上。

写在最后

基于 Java 开源搭建 CRM,技术上其实没有太高的壁垒。Spring 生态太成熟了,难的不是写代码,而是对业务的理解和对细节的把控。

比如,销售说“我要一个提醒功能”,你不能真就只做一个弹窗。你得想,他是想要邮件提醒?短信提醒?还是企业微信消息?提醒的频率是多少?会不会打扰到他?如果他在开会,是不是要免打扰模式?这些业务细节,往往比技术实现更耗时。

另外,别追求完美。第一版系统肯定是一堆补丁堆起来的。只要核心流程跑得通,数据不丢,权限不乱,就是胜利。剩下的,在迭代中优化。

如果你也在做类似的项目,我的建议是:能买就别造,能改就别重写。开源项目是站在巨人的肩膀上,但也要小心巨人身上有虱子(Bug)。保持对代码的敬畏,做好备份,做好监控。

最后,技术是为业务服务的。CRM 系统好不好用,不是看代码写得有多优雅,而是看销售愿不愿意用。如果销售觉得录入太麻烦,天天想着法儿绕过系统,那这系统做得再牛也是白搭。所以,多去跟销售聊聊天,看看他们实际怎么工作的,比关在办公室里敲代码重要得多。

这条路挺累,尤其是上线初期,半夜被报警电话叫醒是常事。但看到系统里沉淀的数据越来越多,真正帮公司理清了客户资源,那种成就感也是实实在在的。希望这篇复盘能给你一点参考,哪怕只是让你少加一个班,那也值了。

悟空CRM产品截图

推荐立刻免费使用中国著名CRM品牌-悟空CRM,显著提升企业运营效率,相关链接:

CRM系统免费使用

开源CRM系统

CRM系统试用免费

登录/注册
客服电话
售前咨询