简介:面向毕业设计及Java Web初学者的CRM客户关系管理系统完整项目,基于Struts2、Hibernate3、Spring4框架整合开发,覆盖客户信息管理、订单管理、财务管理、产品管理、部门与岗位管理、数据回收站及权限管理等核心模块,适合课程设计、毕业设计参考,也可作为企业信息化项目入门演示。压缩包共13个文件,包含项目源码、MySQL数据库SQL脚本、3个MP4演示视频、1份答辩PPT、1份Word实现文档、1份txt说明文档及5张PNG运行截图,整体大小83.23MB,按源码、数据库、视频和文档分类,便于快速查找。已有2175人学习下载。内容包含MyEclipse与Tomcat7下的配置、部署与启动流程视频演示,以及客户、订单、财务等主要功能的操作录屏;SQL脚本可快速建表,源码导入后可直接运行,配套答辩PPT和Word实现文档能有效支撑毕业设计汇报、论文撰写与二次开发,整体实用性较强。
1. 基于Java的CRM客户关系管理系统到底是什么:从一份课程设计源码到可落地的客户管理方案
如果你在找的是一套「基于Java语言CRM客户关系管理系统(源码+视频+论坛+数据库)」这样的资源,多半是手里有一份Java课程设计或毕业设计要交,或者公司里想快速搭一个内部客户管理后台。这类项目在高校里出现频率极高,题目叫法很多:客户信息管理系统、客户关系管理系统、CRM系统,核心都是同一件事——用Java把客户资料、跟进记录、商机状态和简单统计报表管起来。它的价值在于:不是让你从零写框架层代码,而是把精力放到业务实现上,跑通一条从数据库表到页面展示的完整链路,并理解Java Web项目在真实环境里是怎么组织的。
这套方案适合两类人:一类是Java初学者,想通过一个完整项目把前端、后端、数据库串起来;另一类是刚入职的小团队,需要一套能快速改造的内部CRM原型,用来管理销售线索和客户跟进记录。它能帮你解决的最直接问题是:客户信息散落在Excel、微信和邮件里,谁也说不清某个客户现在推进到哪一步、上次沟通是什么时候。下面的内容我会按技术选型、数据建模、功能实现、环境部署、踩坑记录和进阶改造的顺序,把这个项目讲透,并给出可以直接复制运行的代码和参数说明。
2. 选型与数据建模:先想清楚这套CRM用什么框架、几张表
2.1 Java技术栈怎么选:SSH、SSM还是Spring Boot
很多课程设计源码默认是SSM(Spring + Spring MVC + MyBatis)结构,但如果你现在要新写,或者要把旧源码改造,建议直接选Spring Boot + MyBatis。Spring Boot内置Tomcat、自动配置数据源、简化打包方式,能省掉大量XML配置。SSH(Struts2 + Spring + Hibernate)已经很少用在CRM方向,Struts2的配置繁琐,Hibernate的级联操作在客户-联系人这种关系上反而容易踩坑。Spring MVC也不会被淘汰,但Spring Boot在课程设计和中小企业内部系统里都更常见。
| 对比项 | SSH | SSM | Spring Boot + MyBatis |
|---|---|---|---|
| 配置复杂度 | 高,struts.xml、applicationContext.xml | 中,spring-mvc.xml、spring-mybatis.xml | 低,application.yml为主 |
| 部署方式 | 打war丢Tomcat | 打war丢Tomcat | 打jar直接运行或war部署 |
| 学习资料量 | 少,逐步被淘汰 | 多,现有源码普遍用这个 | 最多,适合改造和调试 |
| 数据库访问 | Hibernate,自动维护SQL | MyBatis,手写SQL | MyBatis,手写SQL |
| 适合场景 | 早期遗留项目 | 课程设计主选 | 小团队内部系统首选 |
如果你拿到的是SSM源码,不要急着重写,先看它的pom.xml里依赖版本,把Spring和MyBatis版本往上调,再观察JDBC驱动是否匹配MySQL 8.x。MySQL 8.0以上需要com.mysql.cj.jdbc.Driver,老项目里写的是com.mysql.jdbc.Driver,这一步不改,数据库连接必失败。
2.2 数据库表设计:客户、联系人、跟进记录、商机、用户角色
CRM的业务核心是「客户」和「跟进」,表设计不应太花哨。最简可运行版本至少要有五张表:
sys_user:系统用户,登录CRM后台的账号customer:客户主表,存公司名、状态、来源、所属销售contact:联系人表,客户下面的具体人,电话、职位follow_record:跟进记录表,每次沟通的时间、内容、下次跟进时间business_opportunity:商机表,一个客户可以多个商机,记录金额、阶段
CREATE TABLE `customer` ( `id` int(11) NOT NULL AUTO_INCREMENT, `customer_name` varchar(128) NOT NULL COMMENT '客户名称', `industry` varchar(64) DEFAULT NULL COMMENT '所属行业', `source` varchar(32) DEFAULT NULL COMMENT '线索来源', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1未成交 2已成交 3已流失', `owner_user_id` int(11) NOT NULL COMMENT '所属销售ID', `phone` varchar(32) DEFAULT NULL COMMENT '联系电话', `address` varchar(255) DEFAULT NULL COMMENT '地址', `remark` text COMMENT '备注', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_owner_user_id` (`owner_user_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户主表';注意owner_user_id要建索引,后面「我负责的客户」列表全走这个字段。status用tinyint而不是字符串,统计时按数字分组效率更高,显示名由代码映射,避免中文直接入库。phone不建唯一索引,因为同一客户可能留多个电话,强行唯一会把正常数据挡在门外。
跟进记录表要单独存每次记录,而不是在customer里放一个「最近跟进内容」字段。一个客户会被销售跟进几十次,每次都要有独立时间线。next_time字段专门存下次跟进时间,这决定了首页待办提醒怎么做。
CREATE TABLE `follow_record` ( `id` int(11) NOT NULL AUTO_INCREMENT, `customer_id` int(11) NOT NULL COMMENT '客户ID', `content` text NOT NULL COMMENT '跟进内容', `follow_type` varchar(16) DEFAULT '电话' COMMENT '跟进方式:电话/拜访/微信/邮件', `next_time` datetime DEFAULT NULL COMMENT '下次跟进时间', `create_user_id` int(11) NOT NULL, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_customer_id` (`customer_id`), KEY `idx_next_time` (`next_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户跟进记录';idx_next_time这个索引非常关键。CRM首页的「今日待跟进」查询是WHERE next_time BETWEEN 今天零点 AND 明天零点,没有索引,数据量到几万条时这个查询会拖慢整个系统。很多课程设计源码没建这个索引,因为你跑测试数据几百条看不出来,一旦真实使用就翻车。我一般会在初始化脚本里把索引提前建好,省得后面再改。
2.3 为什么商城/进销存逻辑不能直接搬进CRM
有些同学想把客户、订单、产品、库存一起做进去,觉得这样功能才「完整」。但CRM的核心不是记录交易流水,而是管理「从线索到成交」的过程。你需要的状态是跟进中 / 已成交 / 已流失,而不是待发货 / 已支付。如果把进销存的字段强行塞进来,一旦客户提出「我还没准备好报价,先继续跟进」,你的状态机就要为这种中间态打补丁,最后代码里到处都是if else判断状态组合,维护成本直线上升。
我建议第一版只做四件事:客户列表、详情与新增;跟进记录的时间线;待办提醒;按销售维度的简单统计。这个范围足够撑起一个CRM的骨架。等你把客户量跑起来,再考虑增加订单、合同、产品库,那时你的表结构边界仍然是清晰的。这也是为什么我会在后面的进阶章节里单独讲「课程设计改成可上线系统要补什么」,而不是一开始就把功能堆满。
3. 核心功能怎么实现:客户、跟进与报表的三段式落地
3.1 客户管理模块:增删改查与去重逻辑
大多数课程设计源码把增删改查写在Controller里,Service层一个方法直接掉Mapper,这样代码短,交作业没问题,但改起来很痛。我建议按分层写,Controller只做参数接收和返回,Service做业务判断,Mapper只写SQL。一个典型的客户新增逻辑应该包含「同一用户下是否已有同名客户」的判断,否则销售手一抖就录出重复客户。
@Service public class CustomerServiceImpl implements CustomerService { @Autowired private CustomerMapper customerMapper; @Transactional(rollbackFor = Exception.class) public int createCustomer(Customer customer) { // 校验:同一个归属人下,客户名称不能重复 Customer exists = customerMapper.selectByNameAndOwner( customer.getCustomerName(), customer.getOwnerUserId()); if (exists != null) { throw new BusinessException("该用户名下已存在同名客户,请确认是否重复录入"); } // 补默认值:状态未成交,来源可空 if (customer.getStatus() == null) { customer.setStatus(1); } if (customer.getSource() == null || customer.getSource().isEmpty()) { customer.setSource("手动录入"); } return customerMapper.insertSelective(customer); } }这里用了@Transactional,说明新增客户不是单条insert完事。实际项目中,新增客户往往要同时初始化一条跟进记录,比如「创建客户,首次跟进:初次电话沟通,未接通」。如果只insert客户表,后面统计时会发现客户没有第一条跟进,时间线数据不完整。insertSelective是MyBatis Generator生成的通用方法,只insert非空字段,好处是phone、address这些可空字段不用前端每次传一堆空串。但要注意:insertSelective不会填充数据库默认值以外的字段,前端没传source时,你必须像上面代码里那样手动补,否则后续报表的「来源分布」会统计出一堆空值。
3.2 跟进记录与商机推进:时间线和状态机是关键
跟进记录的业务逻辑比客户增删改查复杂,因为每次新增跟进都可能触发商机状态变化。比如客户当前是「初步沟通」,今天销售确认了对方有预算并且采购周期在三个月内,商机阶段就要推进到「方案报价」。这个状态流转如果散落在多个地方,后面加一个「今日待跟进」功能时,你很难判断每条记录的next_time来自哪里。
public void addFollowRecord(FollowRecord record, int opportunityStage) { // 1. 插入跟进记录 followRecordMapper.insertSelective(record); // 2. 更新客户表里的最近跟进时间(冗余字段,方便列表页展示) Customer customer = new Customer(); customer.setId(record.getCustomerId()); customer.setLastFollowTime(record.getCreateTime()); customerMapper.updateByPrimaryKeySelective(customer); // 3. 如果传了商机阶段,更新该客户的主商机阶段 if (opportunityStage > 0) { BusinessOpportunity opp = opportunityMapper.selectMainByCustomerId(record.getCustomerId()); if (opp != null) { opp.setStage(opportunityStage); opportunityMapper.updateByPrimaryKeySelective(opp); } } }参数说明:record.getCreateTime()最好在Service里使用new Date(),不要直接信任前端传来的时间,因为前端时钟可能不准,会导致「下一分钟该跟进哪条客户」的排序失效。lastFollowTime字段是冗余设计,它的值永远等于最近一条跟进记录的create_time,如果没有这个字段,客户列表页要显示「最近跟进时间」就得对follow_record表做子查询,数据量一大就慢。商机阶段我用数字表示,0为未录入,1为初步沟通,2为需求确认,3为方案报价,4为商务谈判,5为成交。这样在报表里GROUP BY stage非常方便,不需要做字符串匹配。
3.3 统计报表:用SQL聚合代替Java内存计算
课程设计里最容易出现的问题,是销售统计用Java代码先把所有客户查出来,再在内存里循环count。客户量五百条内看不出问题,五千条以上页面会卡几秒。正确做法是让MySQL做聚合,Java只负责展示结果。统计「各销售的新增客户数」只需要一条SQL,配合MyBatis的ResultMap映射到DTO。
@Mapper public interface ReportMapper { List<SaleReportVO> countNewCustomerBySale(@Param("startTime") String startTime, @Param("endTime") String endTime); }<select id="countNewCustomerBySale" resultType="com.crm.vo.SaleReportVO"> SELECT u.real_name AS saleName, COUNT(c.id) AS customerCount, SUM(CASE WHEN c.status = 2 THEN 1 ELSE 0 END) AS dealCustomerCount FROM sys_user u LEFT JOIN customer c ON c.owner_user_id = u.id AND c.create_time >= #{startTime} AND c.create_time < #{endTime} GROUP BY u.id, u.real_name ORDER BY customerCount DESC </select>注意这里用了LEFT JOIN,不是INNER JOIN。因为有的销售这个月一个客户都没谈下,也要出现在报表里,数量显示0。CASE WHEN的统计方式只会在这次的LEFT JOIN结果集里生效,不会影响统计范围。时间参数用>=和<而不是between,是因为between包含右边界,统计「2025-01-01 00:00:00 到 2025-01-31 23:59:59」时,如果数据不小心存了2025-01-31 23:59:59.500,between会漏掉。虽然这种情况很少,但作为一个排坑习惯,我统一用半开区间。
4. 把源码跑起来:环境准备、数据库初始化和部署路径
4.1 环境清单:JDK、MySQL、Maven 的版本匹配问题
拿到源码包,第一步不是打开IDE跑代码,而是先对一遍环境版本。Java CRM最稳妥的组合是JDK 8 + Maven 3.6.x + MySQL 5.7。如果你用的是JDK 11或17,注意Spring Boot 2.x可以,Spring Boot 3.x需要JDK 17,而且老源码里可能用了javax.servlet,Spring Boot 3改成jakarta.servlet,这会导致大量import报错。所以先看pom.xml里的spring-boot-starter-parent版本再定JDK,不要盲目装新版。
MySQL的版本也影响很大。MySQL 5.7的默认排序规则是utf8_general_ci,MySQL 8.0推荐utf8mb4_general_ci,如果你导入的SQL脚本里写了ENGINE=InnoDB DEFAULT CHARSET=utf8,在8.0下没问题,但在5.7下需要确认字符集。数据库连接驱动也要匹配:MySQL 5.7用com.mysql.jdbc.Driver,MySQL 8.0必须用com.mysql.cj.jdbc.Driver,并且JDBC URL要加上serverTimezone=Asia/Shanghai,否则插入时间数据时会报The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized。这个报错几乎人人都会遇到,现在看到这种乱码说明时区没配。
4.2 初始化数据库:执行SQL脚本的正确姿势
源码包里带数据库目录,通常是一个.sql文件或文件夹里多个.sql。不要直接在Navicat里整个文件右键执行——如果脚本里有CREATE DATABASE和USE语句可以,但很多课程设计的脚本只写了建表语句,不写CREATE DATABASE,你会把表建到默认连接库里去,后面连数据库时发现表不存在。我建议先手动创建数据库:
mysql -uroot -pCREATE DATABASE IF NOT EXISTS crm_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE crm_db; SOURCE /path/to/crm_init.sql;SOURCE命令是MySQL客户端的正确导入方式,比在Navicat里拖文件更不容易出错。如果crm_init.sql里已经包含了DROP TABLE IF EXISTS,它会覆盖旧数据;如果你在跑现有数据,千万不要直接执行这个脚本,里面可能有DELETE FROM语句清空生产数据。万一是这种脚本,又只想保留已有客户数据,你先备份:
mysqldump -uroot -p crm_db > crm_backup_$(date +%F).sql这条命令导出整个库,备份文件名带当天日期,防止覆盖。我见过有同学没备份就执行初始脚本,回头发现客户表数据全没了。这不是技术问题,是操作习惯问题。
4.3 修改连接配置并启动:本地跑通的最小步骤
项目里数据库连接配置通常在application.properties或application.yml,也可能在jdbc.properties。打开后修改四项:spring.datasource.url、username、password、driver-class-name。如果你用的是Spring Boot 2.0以上,driver可以不写,Spring Boot会自动推断,但老项目建议显式写好。
spring.datasource.url=jdbc:mysql://localhost:3306/crm_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver说明:useSSL=false很重要,MySQL 8.0默认尝试SSL连接,本地开发没有配置SSL证书会警告,甚至报Public Key Retrieval is not allowed。加上这个参数后,本地启动不会因为SSL握手报错。characterEncoding=utf8是写中文不变成问号的前提。serverTimezone必须带,前面说过时区问题。如果原来配置里写的characterEncoding=utf-8,中间的连字符在MySQL驱动里也能识别,但规范写法是去掉连字符。
启动类如果找不到,找src/main/java下带有@SpringBootApplication注解的类。右键运行后,看到Started CrmApplication in xx seconds表示成功。如果用的还是SSM项目,需要配Tomcat,直接部署war包,启动控制台出现Deploying web application archive字样后再等几秒访问页面。
4.4 验证登录与核心链路:不要只看页面打开
登录成功后,先做一个完整的业务闭环验证:新增一个客户,添加一条跟进记录,然后到「商机管理」里确认状态变化,最后看报表页有没有统计到刚才的数据。很多源码演示时只打开登录页输入账号密码,却从没验证过新增客户后报表是否更新。我一般按下面几条路径走:
| 验证步骤 | 预期结果 | 容易忽略的问题 |
|---|---|---|
| 打开登录页,输入管理员账号 | 进入首页,显示今日待办 | 记住密码功能是否依赖JS,浏览器禁用JS后是否报错 |
| 新增客户,保存 | 客户列表出现该记录,详情可打开 | 手机字段格式校验是否在前端和后端都做了 |
| 给该客户添加跟进记录 | 客户详情时间线新增一条,下次跟进日期生效 | 下次跟进提醒是否用的是next_time字段 |
| 修改商机阶段 | 客户状态从「跟进中」变成「已报价」 | 状态变化后,筛选列表是否及时刷新 |
| 到报表页看新增客户统计 | 本月新增数量包含刚录入的客户 | 时间范围是否按当前月计算,跨年是否正常 |
同时打开浏览器开发者工具(F12)的Network面板,观察每个请求返回的状态码。如果是404,说明路由路径不对;如果是500,看后端控制台堆栈。常见的是请求路径和Controller里的@RequestMapping不匹配,很多课程设计把前端路径写死在页面里,你改了Controller路径就一定要改前端。
5. 避坑常见问题排查:让这套CRM从能跑到稳定跑
5.1 现象:启动直接报 Failed to configure a DataSource
启动后控制台出现类似Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured,页面访问直接白屏。原因是Spring Boot启动时尝试自动装配数据源,但配置里spring.datasource.url没有被读到。解决步骤:先检查application.properties是否被Maven打包进classes目录,再确认配置文件名是不是application.yml,注意yaml缩进。还有一个常见坑:配置里写了多套数据源,比如datasource.master和datasource.slave,Spring Boot无法识别。
# 错误写法:自定义了前缀,Spring Boot不认识 my.datasource.url=jdbc:mysql://localhost:3306/crm_db my.datasource.username=root # 正确写法:标准前缀 spring.datasource.url=jdbc:mysql://localhost:3306/crm_db spring.datasource.username=root如果配置没问题,检查pom.xml是否引入了spring-boot-starter-jdbc或mybatis-spring-boot-starter。有时候pom.xml里只加了mysql-connector-java,没有加starter,Spring Boot不会自动创建数据源Bean,报错内容就是这个。加上starter后清理Maven缓存重新导入。
5.2 现象:页面显示中文乱码,数据库里存的是问号
数据库表结构用的是utf8mb4,连接URL也写了characterEncoding=utf8,但新增客户后,用户名和地址变成???。这是因为MySQL连接器在读取和写入时,必须同时确认客户端字符集和服务器字符集。检查两处:第一,MySQL配置文件my.cnf里[mysqld]区的character-set-server=utf8mb4和[client]区的default-character-set=utf8mb4;第二,Java代码里接收POST请求时,Spring Boot已经默认UTF-8,但如果你用Servlet Filter手动设置了编码,可能覆盖成GBK。排查时先执行:
SHOW VARIABLES LIKE '%character%';看到character_set_server不是utf8mb4,就按上面改配置重启MySQL。如果服务器字符集正确,再检查项目里是否写了request.setCharacterEncoding("UTF-8"),有些老代码在Filter里写死成GBK了,删掉那行即可。
5.3 现象:登录后会话失效,或者一直跳到登录页
课程设计常见的会话管理是HttpSession存用户对象,每次请求前判断session.getAttribute("user")是否为空。出现「登录成功但没两分钟又跳回登录页」,大概率是session超时时间设置太短,或者项目设置了sessionCookiePath不对导致浏览器没存住Cookie。检查application.properties里的server.servlet.session.timeout和server.servlet.session.cookie.path。另外Spring Boot默认的session失效时间是30分钟,如果你在测试时喝完一杯咖啡回来再操作,跳转登录页是正常现象,不是Bug。
如果用的是Spring Security,还要看csrf配置是否开启。老项目把CSRF开启后,前端表单没有带_csrftoken,POST提交时被拦截返回403,表现也是跳到登录页或报错。课程设计一般建议关闭CSRF演示,但上线前必须打开。这里我建议保留CSRF,然后在前端所有POST表单加隐藏域,避免后面补安全功能时大面积返工。
5.4 现象:客户列表分页失效,或者下一页查不出数据
很多课程设计用手写pageNum和pageSize传给SQL的LIMIT参数,前端页码从1开始,后端把页码减1。常见翻车是:第一页正常,第二页开始每页重复第一页的记录。原因是后端忘了写LIMIT offset, size,只写了LIMIT size,导致每页都从0开始。代码里要这样写:
int offset = (pageNum - 1) * pageSize; return customerMapper.selectPage(offset, pageSize);<select id="selectPage" resultType="com.crm.entity.Customer"> SELECT * FROM customer WHERE owner_user_id = #{ownerUserId} ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} </select>另一个坑:ORDER BY create_time DESC在大量数据时,如果create_time没有索引,MySQL会先做文件排序,然后才LIMIT取数,数据量大时第一页都要几百毫秒。解决方法是给create_time建索引,或者改成ORDER BY id DESC,主键索引天然有序。如果要求按客户名称排序,可以先查customer_name再ORDER BY,但utf8mb4的排序规则可能导致中文排序不是你以为的拼音顺序,这是MySQL比较规则决定的,课程设计里不必执着,直接按id排序最省事。
5.5 现象:部署到服务器后图片和上传文件丢失
本地启动时上传客户头像或附件存在项目目录,重启服务后文件还在;一旦打成jar或war部署到服务器,重启后发现文件丢了。这是因为你把文件写到了项目内部路径,比如src/main/resources/static/upload,项目重新部署时整个目录被替换。正确做法是配置一个外部磁盘路径:
# 本地开发用 crm.upload-path=/data/crm/upload/ # 项目里存相对路径到数据库 crm.upload-suffix=/uploads/再写一个配置类映射静态资源:
@Configuration public class WebConfig implements WebMvcConfigurer { @Value("${crm.upload-path}") private String uploadPath; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/uploads/**") .addResourceLocations("file:" + uploadPath); } }这样上传的文件存在/data/crm/upload/,数据库里存的访问路径是/uploads/xxx.jpg,浏览器访问由Spring Boot把/uploads/**映射到磁盘目录。服务器部署时只要确保该目录存在,而且Tomcat的运行用户(比如tomcat)有读写权限。注意addResourceLocations必须以file:开头,并保证路径末尾有斜杠,否则映射失效。本地开发时配置成crm.upload-path=./upload/,重启不会丢,但也建议尽快改成固定绝对路径,避免IDE切换工作目录后找不到文件。
6. 进阶:把课程设计改成能长期使用的CRM,还要做这三件事
第一件事,补操作日志和权限控制。现在的系统只有登录和增删改查,销售误删一条客户记录没人能追溯。我建议在数据库加一张oper_log表,用Spring Boot的HandlerInterceptor或AOP在Controller层统一记录谁在什么时间操作了哪个客户。AOP写法可能对初学者有点门槛,先写拦截器也行。权限控制至少分管理员和普通销售,销售只能看自己和本部门的客户,管理员可以看全部。否则公司里销售互相看到对方客户,轻则撞单,重则离职带走客户资料。课程设计里通常只有role字段,但没有任何判断,这一步一定补上。
第二件事,做数据备份和恢复演练。数据库文件是这个业务系统唯一的价值,客户资料丢了就是真的丢了。我不会只依赖MySQL自带的导出,而是写一个每天凌晨执行的定时任务,mysqldump加--single-transaction备份,同时保留最近7天备份。恢复也是备份的一部分,我每月做一次恢复测试,把备份文件导到一个临时数据库,看表和行数是否一致。如果你问我什么叫「稳定运行」,能扛住一次误删还能找回数据,比页面响应快三秒重要得多。
第三件事,考虑把项目改成前后端分离。现在这套基于模板引擎的方案,页面和服务端代码耦合在一起,前端想给客户做一个自助查进度界面,很难下手。我建议先把后端接口按REST风格拆好,比如/api/customer返回JSON,再用一个简单Vue前端单独部署。这一步不是我为了追新框架,而是你日后面试聊这个项目时,能说清楚接口设计和职责边界,比单纯说「我用Spring Boot做了个CRM」更有说服力。接口文档可以用Swagger自动生成,省去手动维护的麻烦。
这三件事做完,你这套系统才算从「能演示的课程设计」真正变成「能用的内部工具」。我以前带实习生时,最喜欢看他们能不能主动提出加操作日志,不是因为这功能多难,而是这反映了有没有把「数据安全」当回事。希望大家在复现这个项目的时候,也能早一点意识到这点。希望帮到你。
本文还有配套的精品资源,点击获取