自建CRM系统实践:用Spring Boot+Vue打造私有化客户管理平台
2026/9/16 21:11:21 网站建设 项目流程

今年年初我给团队定了个目标:把一直靠Excel来回传的客户资料,切换到一个真正能跑起来的CRM客户管理系统。市面上各家云CRM确实省心,但免费版限制多,数据全在别人服务器上,越用越觉得被动。最后我干脆用开源组件自己搭了一套,代号DeskcommCRM。跑通之后团队用到现在,大半年没出过严重故障,也算是一个“永久在线”的私人CRM网站了。这篇就把整个项目的设计思路、关键模块、部署步骤和踩坑过程都记录下来,给正在选CRM、想自建客户管理系统、或者已经买了飞鱼CRM却卡在员工邀请和权限配置上的朋友做个参考。

1. 项目整体设计与方案选型

1.1 先搞清楚:免费CRM和自建“私人网站”到底差在哪

很多人一开始会纠结:网上免费CRM一抓一大把,为什么还要自己搭一个私人网站来跑?这个差别不是界面上好看不好看的问题,而是底层逻辑完全不同。免费版CRM本质上是SaaS服务商给你开了一个受限账号,你用的是别人的系统、别人的数据库、别人的规则。客户数据是核心资产,如果服务商调整收费策略、功能下架或者停服,你连数据导出的窗口期都得看对方心情。

自建CRM网站的本质,是自己在云服务器上部署一套完整可用的客户管理系统。域名解析到你自己的服务器,数据库里的每一条客户记录都由你自己掌控。我选择的方案是单体应用加单机部署,尽量降低维护门槛,个人开发者和二十人以内的小团队都能Hold住。这套方案的关键指标是“数据可控”和“长期可用”,而不是追求微服务、容器编排那些高复杂度架构。

从成本上算也很直观:一台入门级云服务器一年的费用,大约只相当于同类商业CRM按年付费的三分之一到二分之一。如果按五年周期看,自建省下来的钱足够再买一台备用机做灾备。当然自建有代价,所有维护、备份、升级都得自己来,这就是“免费CRM和私人网站”之间最核心的差异:你们交换了“省事”和“掌控”。

所以在设计DeskcommCRM之初,我给自己定的原则是:功能不贪多,但客户、跟进、商机、权限、统计这些核心链路必须完整;部署不复杂,但备份、HTTPS、进程守护这些安全基础必须一开始就做好。

1.2 DeskcommCRM的功能模块与页面结构

整个系统在设计时划分为五个模块,分别是客户管理、联系人、跟进记录、商机管理和统计报表。登录后左侧菜单树一眼能看完全部功能,不堆砌没有用的按钮。每个模块解决一个明确的业务问题,这也是我在设计时最看重的:你要是塞了一堆没人用的功能,最后员工还是会回到Excel去。

  • 客户管理:维护公司/个人客户的主体信息,包括客户名称、行业、来源、等级、负责人、下次联系时间。
  • 联系人:一个客户下可以挂多个联系人,电话、微信、职务,方便做1对1跟进。
  • 跟进记录:每次电话、拜访、线上沟通都记录在案,带沟通类型标签和时间线。
  • 商机管理:把有意向但还没成交的客户放进商机阶段,从初次接触到报价、谈判、赢单,每个阶段可拖动切换。
  • 统计报表:按销售统计新增客户数、跟进次数、商机金额和赢单率,导出给管理层复盘。

技术选型上,后端用的是Spring Boot框架,前端是Vue,数据库用了MySQL,部署时配合Nginx做反向代理。这套技术栈的好处是文档多、社区活跃、招人也好招。如果你用过若依这类后台管理脚手架,会对DeskcommCRM的菜单和权限模型非常熟悉。我们复用了RBAC(基于角色的访问控制)思路,也就是给角色分配菜单和数据权限,再把员工挂到角色下。这是后面员工邀请和权限分配的理论基础。

关于页面结构,我特意把销售工作的高频操作放在两级以内。客户列表直接能看到今日待跟进、本周新增、我的客户几个筛选条件,不用翻三层菜单才找到某个客户。我始终认为,CRM项目失败的第一原因不是技术,而是员工不爱用,录入要超过十秒就会产生抵触情绪。

2. 环境准备与部署要点

2.1 服务器选型与让系统“永久在线”的最小配置

要让CRM网站“永久在线”,说白了就是有一台不宕机、不欠费、进程常驻的服务器。我的建议是初期不要过度配置,但也不能买最低配。实测下来,2核4G内存、带宽5M的云服务器,跑DeskcommCRM这种单体应用加MySQL数据库,支撑二十个以内的日常活跃用户是够用的。CPU在日常操作时占用一般不超过30%,高峰期也就在70%左右晃。

存储方面建议单独挂一块数据盘,系统盘和数据盘分开。万一系统盘出问题,数据盘上的MySQL数据文件还在,恢复只是时间问题。我当前用的是40G数据盘,客户数和跟进记录加起来大概几万条,目前用了不到10G,留足余量。

域名和HTTPS是另一个“在线感”的重要来源。访问一个裸IP地址和访问一个带绿色锁标的域名,心理体验完全不同。域名解析做好之后,用Certbot申请免费的SSL证书,配置到Nginx上。到这里,系统的网络层才算闭环。

端口规划也有讲究。常规做法是80和443给Nginx,8080给后端应用,3306给数据库。但数据库端口不应该直接暴露在公网,这个我放到安全加固章节细说。Nginx负责接收外部请求并转发给后端,前端静态资源直接由Nginx托管,这样能减少跨域问题,也方便统一处理HTTPS证书。

2.2 数据库设计与初始化要点

数据库是CRM的核心,表结构设计直接影响后续开发的效率和数据统计的准确性。我梳理了五张核心表:customers(客户表)、contacts(联系人表)、follow_records(跟进记录表)、opportunities(商机表)、users(用户表),外加roles和user_roles做权限关联。

为了让大家看得直观,我摘录客户表的建表语句核心部分:

CREATE TABLE `customers` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(128) NOT NULL COMMENT '客户名称', `industry` varchar(64) DEFAULT '' COMMENT '所属行业', `source` varchar(32) DEFAULT '手动录入' COMMENT '客户来源', `level` tinyint DEFAULT 3 COMMENT '客户等级 1-5', `owner_id` bigint DEFAULT NULL COMMENT '负责人ID', `next_follow_time` datetime DEFAULT NULL COMMENT '下次跟进时间', `status` tinyint DEFAULT 1 COMMENT '1启用 0停用', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_owner_id` (`owner_id`), KEY `idx_next_follow_time` (`next_follow_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户表';

有两个设计细节值得说。一是owner_id上必须有索引,因为客户列表最常见的查询是按负责人过滤;二是next_follow_time也要加索引,因为“今日待跟进”本质上就是一条where next_follow_time <= 今天且status = 1的查询。索引建对了,几万条数据下查询都是毫秒级。

初始化脚本我放在项目根目录的database/init.sql里,里面包含建表语句、初始管理员账号和基础字典数据。第一次安装时只需要执行一次。后续的表结构变更,我坚持用增量脚本的方式管理,每次升级都新增一个带日期后缀的SQL文件,而不是直接修改init.sql。这样老客户升级时能清楚知道哪些脚本已经跑过,哪些还没有。

3. 实操过程与核心环节实现

3.1 从零部署:让CRM网站真正跑起来的关键步骤

这里把从一台裸机到能访问的完整过程捋一遍,照着操作基本能复现。我的服务器Linux版本是Ubuntu 22.04,其他发行版也大同小异。

第一步安装基础环境。后端需要JDK 17和Maven,前端需要Node.js 16以上用来构建,数据库需要MySQL 8.0。

sudo apt update sudo apt install -y openjdk-17-jdk maven mysql-server nginx

Node.js的安装推荐用nvm管理版本,避免系统自带源里的版本太旧。装完之后分别验证java -version和node -v能看到版本号,说明基础环境就绪。

第二步初始化数据库。先启动MySQL并创建数据库和专用账号,不建议直接用root账号连接业务应用:

sudo systemctl enable --now mysql mysql -u root -p

进入MySQL命令行后执行:

CREATE DATABASE deskcomm_crm DEFAULT CHARACTER SET utf8mb4; CREATE USER 'deskcomm'@'localhost' IDENTIFIED BY '换成强密码'; GRANT ALL PRIVILEGES ON deskcomm_crm.* TO 'deskcomm'@'localhost'; FLUSH PRIVILEGES;

然后导入项目里的初始化脚本:

mysql -u deskcomm -p deskcomm_crm < database/init.sql

第三步构建应用。后端项目是标准的Maven工程,在项目根目录执行:

mvn clean package -DskipTests

构建成功后target目录下会生成deskcomm-crm.jar。建议把jar放到/opt/deskcomm目录下,后续维护都围绕这个目录展开。

第四步配置Nginx。前端构建完成后,将dist目录里的静态文件拷贝到/var/www/deskcomm,然后在Nginx配置里写一个server块:

server { listen 80; server_name crm.example.com; location / { root /var/www/deskcomm; index index.html; try_files $uri $uri/ /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,登录日志和风控数据就不可靠。try_files那行是给Vue路由用的,防止前端页面刷新后出现404。

第五步用systemd守护后端进程。在/etc/systemd/system/deskcomm.service文件里写入:

[Unit] Description=Deskcomm CRM Application After=network.target mysql.service [Service] User=deskcomm WorkingDirectory=/opt/deskcomm ExecStart=/usr/bin/java -Xms512m -Xmx1024m -jar /opt/deskcomm/deskcomm-crm.jar Restart=always RestartSec=5 [Install] WantedBy=multi-user.target

然后执行:

sudo systemctl daemon-reload sudo systemctl enable --now deskcomm

Restart=always的意思是即使进程意外退出,systemd也会在5秒后自动拉起。这一行是“永久在线”最朴素的保障。到这里,访问http://crm.example.com应该能看到登录页,用初始化脚本里的管理员账号就能登录。

说实话,我第一次部署时没有用systemd,后端进程一断开SSH就没了,页面虽然能访问,但接口全部超时。后来改成systemd托管之后,才真正解决了“人走了服务还在”的问题。

3.2 团队协作关键:员工创建、邀请与权限分配

之前总有朋友买了一套飞鱼CRM,却卡在“怎么邀请员工”这一步。其实不管是飞鱼CRM还是自建的DeskcommCRM,团队协作配置的本质都是RBAC的三件事:建用户、配角色、给权限。菜单入口可能不同,逻辑是通用的。

在DeskcommCRM里,邀请员工走的是这样一个流程:管理员在团队管理页面点击“新增员工”,填写员工姓名、手机号和初始密码。系统支持两种方式让员工进入系统:一种是直接告知账号密码,适合封闭内网环境;另一种是生成邀请链接,员工点击后自行设置密码,适合有域名的在线环境。两种方式底层都是往users表插入一条状态为“待激活”的记录,区别只在于激活方式。

权限分配我设计了四类角色,用一个表格能看得很清楚:

角色客户查看范围跟进权限商机管理系统设置
管理员全部客户新增/编辑/删除全部
销售主管本部门客户新增/编辑全部不可
普通销售仅本人客户新增/编辑仅本人不可
只读访客仅被分配客户只读只读不可

这里我要特别提醒一点:表结构里必须有一个字段标识客户的“归属人”也就是owner_id,普通销售登录后查询客户时,SQL语句里强制追加owner_id = 当前用户ID这一条件。如果漏了这个过滤条件,就会出现所谓的“越权漏洞”,明明菜单上只能看到我的客户,实际请求接口却能把全公司客户拉出来。权限不只是控制前端按钮的显示,后端接口必须做同样的校验。

另一个容易忽略的点是“客户转移”。销售离职或调岗时,名下客户需要一键转移给接手人。我在客户列表的操作菜单里放了一个“转移负责人”,转移时同时更新customers表和open商机表的owner_id,确保客户和历史商机不会出现“孤儿数据”。这个功能在团队超过三个人的时候几乎是必用的。

3.3 客户数据导入与去重策略

初始上线时我们手里有Excel里几千条历史客户,一条条手动录入不现实。我做了批量导入功能:下载Excel模板,按模板填写,上传后系统逐行校验。

校验规则包括:客户名称必填、手机号格式校验、行业字段必须匹配字典值。任何一行不合法,系统都会生成错误报告,标出错误原因,比如“第3行手机号格式不正确”。校验通过的批次可以预览,确认无误后再正式入库。

去重策略是重点。我选了三种处理方式让管理员选择:跳过重复、覆盖重复、新建记录。判断重复的依据是客户名称加手机号双字段匹配。这三种策略听起来简单,实际使用中要格外谨慎,尤其是“覆盖重复”——如果之前的客户已经录入了大量跟进记录,覆盖会把历史记录冲掉。所以我做了个保护:如果待覆盖的客户名下已有跟进记录,系统会强制拒绝覆盖并提示先合并或手动处理。

在这件事上我的体会是:上线初期就把存量数据清洗干净,远比你上线三个月后再做数据治理省力得多。我们在导入前专门花了两个晚上整理Excel,把明显重复的合并掉,把“张总”“李哥”这种不规范名称改成完整公司名,系统上线后的统计报表才能反映真实业务。

4. 常见问题与排查技巧实录

4.1 部署期高频报错:从连接失败到白屏

自建系统最耗时间的往往不是功能开发,而是排查部署过程的各种问题,这里把遇到的高频问题做个速查。

数据库连接失败是最常见的启动报错。启动jar后立刻报Communications link failure,八成是MySQL没有启动或者账号密码不对。用systemctl status mysql看一下服务状态,再用命令行手动连接一次测试账号。network连不上,大概率是账号授权了localhost但应用配置里写的是127.0.0.1,两者在某些MySQL配置下会被视作不同的host,解决办法是统一使用127.0.0.1。

端口不通是第二大类。页面能打开但接口全部超时,用curl http://127.0.0.1:8080/api/health在本机测一下,再对比从外部curl域名接口。如果本机通、外部不通,检查云服务商的安全组和服务器firewalld规则,需要放行80和443端口。

白屏问题前端的常见原因有两个。一个是静态资源路径不对,页面F12会看到一堆资源请求404,那是dist目录的base路径没配好;另一个是前端路由刷新404,对应我在Nginx配置里写的那行try_files。这两个问题都有清晰的控制台报错,定位起来不算难。

进程反复崩溃也遇到过。表现是systemd服务起来几秒钟就被杀死,journalctl -u deskcomm -f看日志,最后发现是服务器只有1G内存,Java堆配置了1024m以后直接OOM。后来我降低到-Xms256m -Xmx512m,再把swap开出来,问题就解决了。所以部署前先free -h看一下内存余量,别让JVM配置超过实际物理内存。

4.2 使用期性能瓶颈与数据权限边界

上线之后系统慢,首先被怀疑的往往是服务器性能,但大部分时候问题出在SQL查询和索引上。客户列表每次刷新都全表扫描,是因为where条件里的字段没有索引或者用了like '%关键字%'。优化方法是给高频筛选字段建组合索引,比如(owner_id, next_follow_time),并把模糊查询尽量改成前缀匹配。

还有一个隐蔽的性能坑是列表页默认查询“所有客户”而不是“我的客户”。普通销售可能无所谓,但主管和管理员打开全公司客户列表,数据量一旦上万,不做分页的话一次查询能把数据库拖垮。我强制给所有列表查询加了PageHelper分页,默认每页20条,并且禁止不带任何条件的全量导出,导出必须选择时间范围。

并发修改是团队使用中另一个容易出问题的环节。两个销售同时打开同一条客户的跟进记录,A提交了记录,B再提交时如果不小心,会把A写的内容覆盖掉。我的处理方式是在跟进记录保存时带上updated_at作为乐观锁条件,update语句里加and updated_at = 上次读取的时间。影响行数为0就说明记录已被别人改过,前端会提示“记录已更新,请刷新后重试”。

数据权限的边界问题前面提过,后端接口必须强制附加owner过滤。这里还要补一个细节:SQL里不要只依赖拼接的字符串条件,而是要用参数化查询。否则万一以后接入外部系统,查询参数没有校验,可能在不知不觉中把全表数据暴露出去。安全无小事,尤其是客户手机号这种敏感信息。

4.3 备份恢复实战:让数据永远丢不了

“永久在线”的另一个隐藏要求是数据不能丢。我见过太多人搭好了系统却从不备份,等到误删数据才追悔莫及。DeskcommCRM上线第一天我就配了自动备份,这里把脚本分享出来。

在/opt/deskcomm/backup.sh写入:

#!/bin/bash BACKUP_DIR="/data/backups/mysql" DATE=$(date +%Y%m%d_%H%M%S) mkdir -p $BACKUP_DIR mysqldump -udeskcomm -p'数据库密码' deskcomm_crm | gzip > $BACKUP_DIR/deskcomm_$DATE.sql.gz find $BACKUP_DIR -type f -mtime +30 -delete

然后加入crontab,每天凌晨3点执行:

0 3 * * * /bin/bash /opt/deskcomm/backup.sh

保留最近30天的备份,超过的自动清理。备份文件除了在本地数据盘,我还会每天同步一份到对象存储,防止整台服务器故障导致数据全丢。

备份的价值要通过恢复演练来验证。我每季度会挑一天把备份恢复到一台临时机器上,确认数据能完整加载、管理员账号能登录。别等到真出问题时才第一次尝试恢复,到那时如果发现备份文件损坏或脚本没跑,就已经来不及了。

5. 日常运维与安全加固

5.1 安全加固:别让你的CRM变成“公开数据库”

CRM里有客户手机号、微信、商业往来记录,安全性要求比一般内容网站高得多。我做了几项基础加固,成本极低但收益很大。

第一,MySQL端口不绑定公网。my.cnf里设置bind-address = 127.0.0.1,这样外部连接3306端口直接被拒。所有数据库访问只走本机的后端应用。

第二,管理员账号开启两步验证。我实现了一个基于TOTP的动态验证码功能,管理员登录时除了密码还要填手机上的验证码。这一步能拦住绝大多数因为密码泄露导致的入侵。

第三,定期更换密码,并强制员工不要使用公司名加123这类弱口令。我在用户表里存密码时用的是BCrypt加密,不是明文,哪怕数据库备份泄露,密码也不会轻易暴露。

第四,登录接口加简易限流。同一个IP在一分钟内连续输错5次密码,锁定15分钟。这个措施能有效防止暴力破解。实现方式也很简单,用ConcurrentHashMap记录失败次数和时间戳,到点自动清零。

5.2 系统更新与后续扩展方向

自建系统的更新升级要控制节奏。我现在的惯例是先在测试环境跑一遍增量脚本和构建流程,没问题再操作生产环境。升级前必须做一次完整备份,升级后立刻检查核心链路:登录、客户查询、跟进保存、报表导出。这四件事没问题,基本就可以放心让团队使用了。

DeskcommCRM目前还是一个单体Web应用,后续的扩展方向我梳理了几个。一是对接企业微信和钉钉,客户新增或跟进超期时自动发通知到对应销售的手机,让提醒从Web端延伸到IM端。二是开发轻量移动端H5,销售在外面拜访客户时直接用手机拍照上传跟进记录,比现在打开电脑方便得多。三是提供API接口,方便后续和其他系统比如财务系统、工单系统打通,避免数据在多个系统之间靠人工搬运。

每个扩展方向我都评估过ROI再动手。做CRM最忌讳的是为了做功能而做功能,用户的真实痛点永远是:录得快、查得到、盯得紧、丢不了。其他都是锦上添花。

最后再分享一个小技巧:把系统所有关键操作都写入操作日志,包括谁在什么时候删了哪条客户记录、谁导出了客户列表。员工知道操作有迹可循之后,删库跑路也好、偷偷倒卖数据也好,都会在事前被威慑。日志的二级备份也重要,我把它和数据库备份一起同步到对象存储,占用空间不大,关键时刻却可能比数据库还有价值。自建CRM这条路一旦走通,你收获的不只是一套系统,更是对数据和业务的理解。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询