☰
开源CRM系统源码:打造可定制的销售管理系统
2026/10/14 7:10:35 网站建设 项目流程

开源CRM系统源码全新发布,定制化销售管理系统。这句话看起来像一条标准的软件发布公告,但真正跑过业务的人都知道,销售管理从来不是一个标准化模块能解决的事。我见过不少销售团队,从十来个人的小队伍到上百号人的分公司,装了各种商业CRM之后,最后都回到了Excel和微信聊天记录。原因很简单:每个团队的销售流程、客户字段、审批逻辑、权限边界都不一样,商业软件要么改不动,要么改一次要排队等版本更新。这套源码的核心思路就是反过来——与其给你一套固定流程,不如给你一套可以自己改流程的机制。

这套系统是我基于长期服务中小销售团队的经验整理出来的,针对性解决的问题有三个:客户资源沉淀、跟进过程留痕、成交数据分析。适合三种人看:想搭建私有销售管理系统的团队负责人,接到定制化CRM项目但不想从零写起的开发者,以及想研究业务建模和权限设计的产品经理。下文会从源码结构、核心机制、部署步骤到真实定制案例逐个拆开讲,不绕弯子。

1. 为什么这套CRM非开源不可:私有化与定制化的双重需求

1.1 商业软件在“销售管理”场景里,为什么总是差一口气

先说一个我在某外贸公司遇到的真实诉求。对方的销售团队有三十多人,业务员分区域管理客户,订单要经过主管审批,回款节点和信用证条款挂钩,同一个客户可能同时存在多个在谈合同。他们之前用的是一套通用SaaS产品,功能看着齐全,但一落到实际业务就露馅了。

问题出在三个地方。第一,字段不够灵活。系统里预设的客户字段只有公司名、联系人、电话、地址这些,但外贸业务真正关心的是货代、付款方式、汇率结算币种、目标市场这类信息,在通用系统里只能塞进备注。第二,销售阶段锁死。默认的“意向客户-报价-成交”三段式对谁都不适用,有的行业要走投标,有的行业要先寄样,有的客户可能要挂账三个月。第三,权限边界太粗。通用系统通常只分管理员和普通员工,但销售管理需要的是“部门主管能看本部门全部客户、普通销售只能看自己的”这种多层控制。

这三点凑在一起,就逼着团队回到Excel。不是他们不想用系统,是系统没法跟着业务走。这也是我下决心做开源版本的原因:销售管理的变量太多了,唯一可行的方案是让每个团队都能改自己的字段、阶段和权限。

1.2 开源的真正价值:不是代码公开,而是自定义能力公开

很多人对“开源CRM”有个误解,觉得把源码放出来就是让你拿去改一改,跑起来就算完事。实际上源码公开只是第一步,真正有价值的是系统内部的扩展机制。

这套系统在设计初期就把“可定制”当作第一优先级,而不是后补功能。具体体现在四个层面:数据库层面用字段配置表支撑自定义字段,业务层面用销售管道配置表支撑阶段调整,权限层面用数据范围字段支撑多级可见性,接口层面预留了事件钩子方便二次开发。这四个层面加在一起,才算是真正把定制化能力交到了使用者手里。

另外必须实事求是地说,开源不等于零门槛。拿到源码后,你仍然需要具备基本的Java后端或前端开发能力,至少要能在本地把工程跑起来。系统适合的是有一定技术基础、愿意自己动手调整的团队,而不是指望下载即用的非技术用户。这一点请在选型前先对齐预期。

2. 源码总览:技术栈选择、模块边界与核心业务数据链

2.1 工程目录与技术选型

这套CRM的技术栈是Spring Boot 2.7.x + MyBatis-Plus + MySQL 8,前端用的是Vue 3 + Element Plus。选这套组合的原因很直接:Spring Boot生态成熟,招人容易;MyBatis-Plus写复杂查询方便,适合业务字段经常变动的场景;MySQL则是绝大多数团队已有的基础设施,不需要额外引入重型中间件。

整个仓库分成三个工程:

  • crm-server:后端服务,负责接口、权限、业务逻辑和定时任务。
  • crm-admin:管理端前端,供销售主管、管理员使用,包含客户管理、数据看板、系统配置。
  • crm-api:给外部系统对接使用的接口工程,比如从企业微信或ERP同步客户数据。

后端工程内部按模块分包,每个业务域独立成包,避免多人协作时互相踩脚:

com.example.crm ├── clue 线索管理 ├── customer 客户管理 ├── business 商机管理 ├── contract 合同管理 ├── receivable 回款管理 ├── system 用户、角色、菜单 └── common 公共工具、统一返回体

2.2 销售主线数据模型:线索到回款是怎么串起来的

很多做CRM的新手容易把表结构设计成单表大而全,一个customer表恨不得装下所有字段。这套源码里头,业务数据是按销售主线的不同阶段拆开的,核心链路是:

线索 → 客户 → 商机 → 合同 → 回款

用五张核心表来承载这条链路:

表名作用关键关联
crm_clue线索,未确认的潜在客户跟进后转为客户
crm_customer客户,完整档案归属销售、归属部门
crm_business商机,在谈的销售机会关联客户,记录当前阶段
crm_contract合同,成交形成的契约关联客户和商机
crm_receivable回款,应收与实收记录关联合同

这样拆的好处是,每个阶段可以独立配置字段和权限,不会因为“客户阶段变了”导致数据结构大改。比如某公司只需要管理到客户和合同,不需要复杂回款,那回款模块可以直接不启用,业务无损。

2.3 给没接触过CRM源码的读者的快速阅读路线

如果你第一次接触这类工程,建议按三个层次去读。第一层先看common和system模块,理解统一返回体、分页参数和用户权限体系,这是全局基础。第二层读业务模块的Mapper和Service接口,理解每条业务链路的数据流向。第三层再看前端页面如何用自定义渲染模式展示动态字段,这是定制化的关键体现。

不要一上来就追着看某一个复杂功能,比如合同审批流程。CRM系统真正难的是数据权限和字段扩展机制,它们的代码分散在框架层里,先把这两个机制读明白,再看业务代码就会觉得处处都是通的。

3. 定制化销售管理的三个旋钮:字段、销售管道与数据权限

3.1 自定义字段:为什么业务表敢不预埋几十个字段

做销售管理系统,最常见的一个定制需求就是加字段。有的加“客户来源渠道”,有的加“预计成交月份”,有的要“是否通过代理商”。如果每个字段都直接改表结构,系统迭代几次就会有一堆冗余列,升级维护非常难受。

这套系统的设计思路是:核心字段落在业务表本身,拓展字段走动态键值对。

具体来说,系统里有两张基础配置表。crm_field_config保存字段定义,比如字段名称、类型、是否必填、所属业务对象;crm_field_value保存具体的字段值,通过object_type和object_id关联到某条业务记录。前端页面根据crm_field_config的配置动态渲染表单控件,不需要为每个新字段写死代码。

这个机制的优点很直接:加一个下拉框字段,只需要往配置表里插一条记录,再在字典表维护选项,前后端代码都不用动。缺点是用键值对查询性能会差一些,所以系统做了折中:常用的筛选字段可以映射到业务表的冗余列上,低频字段才走动态值表。

3.2 销售管道阶段配置:每个团队的漏斗都不一样

销售管道(Pipeline)是最能体现销售管理特点的功能。同一套系统,外贸公司的阶段可能是“询盘-寄样-报价-回签-生产-回款”,软件服务商可能是“初步沟通-演示-方案-商务谈判-签约-交付”,两者天差地别。

如果管道阶段写死在代码里,系统就废了一半。这套源码把管道阶段做成独立表crm_pipeline_stage,包含所属业务对象、阶段名称、排序值、是否初始阶段、是否终态阶段几个字段。管理员在后台配置好阶段顺序后,前端商机页面的看板会按顺序渲染成多列,拖拽商机卡片切换阶段就是更新阶段字段和排序值。

这里有两个细节值得注意。第一个是“终态阶段”设计,比如“赢单”和“流失”,这种阶段不再允许拖拽回之前的阶段,避免销售误操作把已成交客户拉回洽谈中。第二个是“每个阶段可配置必填字段”,比如进入报价阶段必须填写报价金额,否则无法推进。这两个机制保证了管道不只是视觉上的便利,还能约束业务流程。

3.3 数据权限:谁的客户、谁的合同、谁看全部

销售管理系统里权限控制做不好,轻则内斗,重则客户资源泄露。但权限又不能简单粗暴地只分管理员和普通用户,必须支持按数据范围隔离。

这套系统的权限模型是“角色 + 数据范围”两级结构。

角色控制菜单和操作按钮,比如能否导出客户列表、能否删除合同、能否审批高额折扣。数据范围控制查询的行级可见性,支持三个档位:

  • 仅本人:只能看到自己创建或分配给自己的记录。
  • 本部门:能看到本部门所有人名下的记录。
  • 全部:系统管理员或销售总监级别的可见范围。

实现在代码层面并不复杂,核心是在Mapper查询前注入一段数据权限拼接逻辑:

public PageResult<Customer> page(CustomerQuery query, LoginUser user) { // 根据当前用户角色构建数据权限 SQL 片段 String scopeSql = buildDataScopeSql(user); query.setDataScopeSql(scopeSql); return customerMapper.selectPage(query); }

这里的关键不是SQL拼接本身,而是所有涉及客户、商机、合同数据的查询接口都必须经过统一的数据权限拦截,不能存在漏网之鱼。这套源码在所有业务Mapper上都加了切面校验,防止开发者二次开发时新增的接口绕过权限控制。

4. 从源码到跑通:环境准备、初始化脚本与首个业务页面

4.1 环境准备清单

如果你打算把系统跑起来体验一遍,先准备好这些基础环境:

  • JDK 1.8或17,推荐17,新特性支持更好。
  • Maven 3.6以上,用于构建后端。
  • MySQL 8.0,注意字符集要选utf8mb4。
  • Node.js 16以上,用于前端构建。
  • 一个Redis实例,系统用Redis做登录会话和缓存。

源码里提供了docker-compose.yml,如果本机装了Docker,可以直接一键拉起Redis和MySQL:

version: '3' services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: crm ports: - "3306:3306" redis: image: redis:7-alpine ports: - "6379:6379"

4.2 初始化数据库与演示数据

后端工程里自带sql目录,里面分三个脚本:schema.sql建库建表,init_data.sql插入基础字典和权限菜单,demo_data.sql插入演示客户和商机数据。

初始化命令很简单:

mysql -u root -p crm < sql/schema.sql mysql -u root -p crm < sql/init_data.sql mysql -u root -p crm < sql/demo_data.sql

跑完脚本后,系统会有一个默认的管理员账号。登录后第一件事不是去录客户,而是先到系统配置里的“字段配置”和“管道配置”页面,按自己团队的业务调整字段和阶段。

4.3 配置文件中容易遗漏的三个地方

本地跑通通常会遇到几个配置问题,我直接列出最常见的三个。

第一是数据库连接串的时区参数。MySQL 8默认时区可能和本机不一致,导致时间字段差8小时,连接串里一定要带上:

spring: datasource: url: jdbc:mysql://localhost:3306/crm?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai

第二是Redis密码配置。如果本机Redis没设密码,要把redis.password留空,否则启动时报认证失败,排查半天发现是配置文件里写了默认密码。

第三是文件上传路径。客户附件和合同扫描件上传会写到本地磁盘,配置文件里的upload.path默认是相对路径,部署时建议改成绝对路径,否则重启后路径可能错乱。

5. 实战定制一例:线索自动分配与逾期跟进提醒的完整落地

5.1 业务规则拆解

讲完了基础机制,用一个真实场景串一遍定制开发的完整流程。某销售团队的需求是这样的:市场部录入的线索需要自动分配给业务员,分配原则有两个,线索所属行业优先匹配擅长该行业的销售;如果没有匹配项,则分配给当前未处理线索最少的销售。另外,任何线索超过48小时没有被跟进,系统要自动给负责人发送提醒。

这个需求看起来不复杂,但涉及三个环节:分配策略、定时扫描、通知触发。

5.2 后端实现:分配策略与定时扫描

先写分配服务。核心方法是根据线索的行业字段和销售负载情况选择一个归属人:

@Service public class ClueDispatchService { public Long pickOwner(Clue clue) { // 优先按行业匹配销售 List<User> candidates = userMapper.findByIndustryAndRole(clue.getIndustry(), "SALES"); if (candidates.isEmpty()) { candidates = userMapper.findByRole("SALES"); } // 按未处理线索数量升序排序,取最少的一位 candidates.sort(Comparator.comparingInt(u -> u.getPendingClueCount())); return candidates.get(0).getId(); } }

注意这里没有在Service里直接new一个List再排序,而是从Mapper层就查出候选人员,再结合未处理线索数做排序,避免一次查出全公司几千个用户造成内存压力。

接下来是定时扫描任务。系统里用Spring自带的@Scheduled注解实现,不需要额外引入xxl-job这类分布式调度组件:

@Component public class ClueFollowUpJob { @Scheduled(cron = "0 0/30 * * * ?") public void scanOverdueClues() { List<Clue> overdue = clueMapper.findOverdue(48); for (Clue clue : overdue) { notifyService.sendRemind(clue.getOwnerId(), clue); } } }

这里扫描频率设成每30分钟一次,不是每分钟。原因很简单:销售跟进提醒不是支付系统,不需要秒级实时,半小时的延迟完全可接受,反而能降低数据库压力。

5.3 前端通知与测试路径

提醒通知做到前端时,要注意“系统内通知”和“企微/钉钉通知”是两条链路。系统内通知通过站内信表存一条记录,用户在消息中心能看到;外部通知走接口钩子,由各团队自己配置。源码默认实现了站内信和邮件通知,企微和钉钉的适配器留了接口,二次开发时按接口规范实现即可。

测试这个功能也有讲究。建议直接用演示数据中的线索记录改行业字段测试分配逻辑,再手工把系统时间相关的数据库记录改成48小时之前,验证定时任务是否准时触发。不要为了测试真的等48小时,那样太浪费时间。

6. 二次开发的红线:哪些改动能安全落地,哪些改动会拖垮升级

6.1 直接改表还是加扩展表

很多团队拿到源码后,第一反应是“我要给业务表加一个字段”。这个动作要分情况讨论。

如果只是新增一个常规字段,优先用系统自带的自定义字段机制,通过后台配置而不是改表。如果确实需要字段参与复杂计算或查询条件,才考虑给业务表加冗余列。加了列之后,必须同时更新MyBatis的实体类和Mapper文件,并在数据库迁移脚本里记录变更,否则换环境部署时这张表的结构就对不上。

最忌讳的做法是直接修改多张业务表的结构用于存放临时业务数据。比如为了记录某个审批备注,在customer表加三个字段。这种操作短期能用,长期会把核心业务表搞得臃肿无比,还会影响缓存、索引和数据迁移。

6.2 用事件钩子扩展业务,而不是到处散弹式改代码

这套系统内部预留了事件发布机制。客户创建、商机阶段变更、合同审批通过等关键动作都会触发一个领域事件。做二次开发时,如果你要给“客户创建后自动发送欢迎邮件”这样的需求,正确做法是监听客户创建事件,而不是去CustomerServiceImpl的save方法里横插一段逻辑。

事件监听的样板代码是这样的:

@Component public class CustomerCreatedListener { @EventListener public void onCustomerCreated(CustomerCreatedEvent event) { Customer customer = event.getCustomer(); // 发送欢迎邮件或初始化子资源 emailService.sendWelcome(customer.getContactEmail()); } }

好处很明显:核心业务代码没有被污染,问题定位方便,后续升级源码时,自定义的逻辑独立在监听器里,不会和主流程冲突。

6.3 分支管理与升级预留

源码发布后,官方还会继续迭代。团队如果做了深度定制,最怕升级时合并冲突。

我的建议是,维护一个长期分支,将定制代码隔离在独立模块包里,其中包含自定义事件监听器、自定义Controller和自定义Service,尽量避免修改核心模块类。升级时,只需要把新版本的代码合并到长期分支,再检查几个扩展点(表结构变更、接口路径变更、权限配置变更)是否影响自定义代码,就可以保持兼容。

7. 上线前的检查清单:性能、接口安全与备份策略

7.1 客户列表越用越慢:索引和分页是第一道关

任何一个CRM系统跑三个月后,最大的痛点基本都是客户列表查询变慢,原因是客户数据量增长后,普通的全表模糊查询开始吃力。上线前必须确认几类查询走了索引:

  • 按归属人查询客户,是在owner_id上建索引。
  • 按部门权限查询,是在dept_id上加索引。
  • 商机列表按阶段筛选,在current_stage字段上建索引。
  • 排序字段updated_at也要纳入索引设计。

同时,前端列表一定要用分页,不要为了图快一次查一万条。如果确实有导出需求,导出的数据走异步任务生成文件,而不是由浏览器同步请求拉全量。

7.2 接口权限校验不能只靠前端隐藏

这是最容易被二次开发忽略又最容易出事的地方。前端菜单隐藏一个按钮,并不代表接口不能被直接调用。任何查看、修改、删除客户数据的接口,在Controller层之前一定要经过数据权限校验。

系统统一在AuthenticationInterceptor里做登录校验,在DataScopeInterceptor里做数据范围校验。后者的逻辑是:解析当前用户的角色,匹配数据范围档位,然后把过滤条件拼进查询SQL。如果某个新增的接口没有加@DataScope注解,系统会默认按“仅本人”处理,保证安全兜底。

此外,导出接口和批量删除接口要额外做操作日志记录。销售管理系统的审计需求很强,客户信息一旦被导出或删除是不能悄无声息的。

7.3 数据备份与灰度上线

最后是上线策略。CRM系统涉及销售数据,必须每天做数据库备份。备份方案优先级如下:

  • 全量备份:每天凌晨执行一次mysqldump,保留最近7天。
  • binlog增量备份:开启MySQL的binlog,用于恢复到任意时间点。
  • 备份文件异地存放:防止机房故障导致备份也一起丢失。

在用的团队规模大于50人时,建议先在一个销售小组试运行两周,确认业务人员反馈的字段、阶段、权限问题处理完毕后,再全公司铺开。销售团队对系统的容忍度很低,第一次印象不好以后再推动就难了。

按照我在这类系统上摸爬滚打的经验,源码本身不是最值钱的,值钱的是你能基于它快速构造出贴合自己团队的销售管理流程。开源的玩法从来不是下载即终点,而是把它当成一个可以反复打磨的地基。如果你准备拿这套源码做二次开发,先花两天时间把自定义字段和数据权限这两块机制读透,再动手改业务需求,能省下后面大量返工时间。这套系统的源码会持续更新,但每一次更新我们都会优先保证扩展兼容性,尽量让你手上已经做过的定制不因升级而作废。

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

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

立即咨询