
简介这是一套面向高校计算机相关专业毕业设计的JavaEE客户关系管理系统CRM完整项目资料适合正在准备毕设或需要CRM实战案例的开发者参考。系统围绕企业客户信息管理展开划分为营销管理、客户管理、服务管理、统计报表与基础数据维护五大模块可帮助理解企业级信息系统的业务分层与实现思路。压缩包共847个文件约15.06MB涵盖74个Java源码、23个JSP页面、156个HTML、132个CSS与119个JS等前端资源以及36个JAR依赖、34个XML配置、6个数据库文件与1个SQL脚本并附完整项目代码、数据库及配套报告。内容预览可见客户、销售机会、用户、客户服务、数据字典、客户流失等多个控制器与业务实现类结构完整。目前已有181人学习适合作为毕设选题参考、课程设计模板或JavaEE综合练习素材。1. 从一堆 .class 文件说起这套 JavaEE CRM 到底能不能跑起来如果你是从搜索引擎点进来的大概率手里已经攥着一个压缩包解压后看到一堆CustomerController.class、SaleChanceController.class、UserController.class这样的字节码文件外加一个数据库脚本和一份配套报告。第一反应通常是这玩意儿能跑吗源码在哪我该从哪下手这套基于 JavaEE 的客户关系管理系统简称 CRM核心就是围绕企业客户信息做全生命周期管理。它拆成五大模块营销管理、客户管理、服务管理、统计报表、基础数据维护。落到代码层面你能看到CustomerController管客户档案SaleChanceController管销售机会CusDevPlanController管客户开发计划CustomerLossController管客户流失DataDicController管数据字典ContactController管联系人。这是一套典型的 JavaEE 分层架构Controller 层暴露接口Service 层CustomerServiceImpl处理业务实体类Customer承载数据。它适合谁计算机毕业设计选题卡在“基于 Java 的毕业设计”上的同学或者需要一套能讲清楚 MVC 分层、能演示增删改查和报表导出的参考项目的人。不适合谁想直接拿去做生产级 SaaS 的人——这套代码的定位是教学和毕设演示并发和权限粒度都偏基础。下面我把这套资源从环境配置到模块拆解再到常见翻车点完整走一遍。2. 环境搭建与工程导入从 JDK 到 Tomcat 的完整链路2.1 为什么这套项目对 JDK 和 Tomcat 版本敏感JavaEE 项目最玄学的地方就是版本。这套 CRM 用的是经典 Servlet JSP JDBC 组合没有上 Spring Boot所以它对 JDK 和 Tomcat 的版本匹配要求比现代项目更直接。常见做法是 JDK 8 配 Tomcat 8.5 或 9.0这是最稳的组合。如果你用 JDK 17 去跑javax.servlet包名在 Jakarta EE 9 之后已经改成jakarta.servlet编译期就会报package javax.servlet does not exist。我一般会先确认三件事JDK 版本、Tomcat 版本、IDE 的编译级别。三者不一致后面全是血泪经验。具体检查命令# 确认 JDK 版本输出应为 1.8.x java -version # 确认 JAVA_HOME 指向 JDK 8 而非 JRE echo $JAVA_HOME # 确认 Tomcat 版本8.5 或 9.0 均可 catalina.sh version逻辑说明java -version看运行时版本JAVA_HOME必须指向 JDK 而不是 JRE否则编译 JSP 时会找不到tools.jar。Tomcat 的catalina.sh version能同时输出 Tomcat 版本和它实际使用的 JVM 版本这一步能提前发现“IDE 里配了 JDK 8 但 Tomcat 启动时用了系统默认 JDK 17”的错位问题。参数说明如果你机器上有多个 JDK不要改系统全局变量直接在 Tomcat 的setenv.shLinux或setenv.batWindows里写JAVA_HOME和JRE_HOME这样只影响这个 Tomcat 实例不污染其他项目。2.2 在 IDE 里导入项目并配置 Artifact拿到源码包后先看目录结构。典型结构是src放 Java 源码WebContent或web放 JSP 和WEB-INFlib放 jar 包。导入步骤# 假设项目根目录为 crm-javaee # 1. 确认 web 目录下有 WEB-INF/web.xml ls crm-javaee/WebContent/WEB-INF/web.xml # 2. 确认 lib 目录下的 jar 包完整 ls crm-javaee/WebContent/WEB-INF/lib/ # 3. 数据库脚本通常命名为 crm.sql 或 db.sql find crm-javaee -name *.sql逻辑说明web.xml是 JavaEE 项目的部署描述符没有它 Tomcat 无法识别 Servlet 映射。lib目录下的 jar 包决定了 JDBC 驱动、连接池、JSON 处理等能力是否齐全。find命令定位 SQL 脚本因为数据库结构必须和实体类字段一一对应缺一张表后面就是Table doesnt exist。参数说明在 Eclipse 里导入时选 “Existing Projects into Workspace”如果项目没有.project文件就选 “Dynamic Web Project” 手动建再覆盖。在 IntelliJ IDEA 里选 “New Module from Existing Sources”然后手动指定 Web 资源目录和web.xml路径。关键是把WEB-INF/lib加入模块依赖否则编译期不报错、运行期ClassNotFoundException。提示导入后先别急着启动 Tomcat先执行一次mvn dependency:tree如果有 pom.xml或手动检查 lib 目录确认mysql-connector-java版本和你的 MySQL 服务端版本匹配。5.x 驱动配 MySQL 5.78.x 驱动配 MySQL 8.0混用会报时区或 SSL 错误。3. 五大模块的代码结构与数据库映射拆解3.1 Controller 层到 Service 层的调用链这套项目的分层很清晰Controller 接收请求Service 处理业务DAO 或 ServiceImpl 直接操作数据库。以客户管理为例CustomerController里通常有list、add、update、delete四个方法对应 JSP 页面上的操作按钮。CustomerServiceImpl里则是具体的 JDBC 或连接池调用。// CustomerController.java 片段示意 public class CustomerController extends HttpServlet { private CustomerService customerService new CustomerServiceImpl(); Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action req.getParameter(action); if (list.equals(action)) { ListCustomer list customerService.findAll(); req.setAttribute(customerList, list); req.getRequestDispatcher(/customer/list.jsp).forward(req, resp); } } }逻辑说明doGet里通过action参数区分操作类型这是 JavaEE 时代典型的“一个 Servlet 管一个模块”写法。customerService.findAll()返回ListCustomer然后setAttribute把数据塞进请求域转发到 JSP 渲染。这里的关键是CustomerServiceImpl必须正确关闭数据库连接否则连接池很快耗尽。参数说明action的取值要和 JSP 页面里的链接或表单隐藏域一致常见值是list、toAdd、add、toEdit、edit、delete。如果你改了 Controller 里的判断逻辑记得同步改 JSP否则会出现“点了没反应”或 404。3.2 数据库表与实体类的字段对应关系数据库脚本里通常有customer、sale_chance、cus_dev_plan、customer_loss、data_dic、contact等表。实体类Customer的字段必须和customer表的列名对应或者通过 MyBatis 的resultMap手动映射。常见做法是用下划线转驼峰比如customer_name对应customerName。表名对应实体类关键字段所属模块customerCustomercustomer_id, customer_name, phone客户管理sale_chanceSaleChancechance_id, customer_id, status营销管理cus_dev_planCusDevPlanplan_id, chance_id, plan_date营销管理customer_lossCustomerLossloss_id, customer_id, reason服务管理data_dicDataDicdic_id, dic_type, dic_value基础数据contactContactcontact_id, customer_id, name客户管理逻辑说明这张表是你排查“字段找不到”问题的索引。如果 JSP 页面显示空白先查 SQL 查询是否返回了数据再查实体类字段名和表列名是否一致。sale_chance和cus_dev_plan通过chance_id关联customer_loss通过customer_id关联客户表这些外键关系决定了统计报表能否正确聚合。参数说明data_dic是数据字典表dic_type区分字典类别如客户等级、销售机会状态dic_value是具体值。基础数据维护模块就是对这个表做增删改查。如果你要扩展新的下拉选项往这张表插数据比改代码更快。3.3 统计报表模块的数据聚合逻辑统计报表模块通常不单独建表而是对customer、sale_chance、customer_loss做聚合查询。常见指标包括客户总数、本月新增客户、销售机会转化率、客户流失率。SQL 大致长这样-- 按月份统计新增客户数 SELECT DATE_FORMAT(create_time, %Y-%m) AS month, COUNT(*) AS new_count FROM customer GROUP BY DATE_FORMAT(create_time, %Y-%m) ORDER BY month DESC; -- 统计各状态销售机会数量 SELECT status, COUNT(*) AS cnt FROM sale_chance GROUP BY status;逻辑说明第一条 SQL 用DATE_FORMAT把创建时间截断到月份然后GROUP BY聚合。第二条按状态分组用于饼图或柱状图的数据源。这些查询结果通常由 Controller 塞进 request 域JSP 里用 JSTL 循环渲染成表格或传给前端图表库。参数说明create_time字段的类型必须是datetime或timestamp如果是varchar存日期字符串DATE_FORMAT会返回 NULL。status字段的取值要和data_dic里定义的字典值一致否则统计结果会出现“未知状态”的脏数据。4. 避坑排查从 404 到乱码的五个高频翻车现场4.1 启动后访问 404web.xml 映射与上下文路径现象Tomcat 启动无报错但浏览器访问http://localhost:8080/crm/customer?actionlist返回 404。原因web.xml里 Servlet 的url-pattern和实际访问路径不匹配或者项目的 Context Path 不是crm。常见情况是 IDE 部署时默认用了项目名作为上下文但web.xml里映射的是/customer而你访问时多写或少写了层级。解决先看 Tomcat 启动日志里的Deployment of web application archive那一行确认 Context Path。然后检查web.xml里url-pattern的值。如果是注解方式WebServlet(/customer)确认注解没被注释掉。最后在浏览器里用http://localhost:8080/上下文路径/url-pattern逐段拼接测试。4.2 数据库连接失败驱动版本与连接串参数现象页面报Cannot create PoolableConnectionFactory或Communications link failure。原因MySQL 驱动版本和数据库版本不匹配或者连接串缺少useSSLfalse、serverTimezoneUTC等参数。MySQL 8.0 默认要求 SSL旧驱动不支持。解决确认lib下的mysql-connector-java版本。5.1.x 用jdbc:mysql://localhost:3306/crm8.0.x 用jdbc:mysql://localhost:3306/crm?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue。改完连接串后重启 Tomcat不要只刷新页面。4.3 中文乱码请求编码与数据库字符集现象表单提交的中文客户名在数据库里变成???或者 JSP 页面显示乱码。原因三个环节都可能出问题——JSP 页面编码、请求体编码、数据库表和连接字符集。JavaEE 默认用 ISO-8859-1 解析请求参数。解决JSP 页面头部加% page contentTypetext/html;charsetUTF-8 %。在 Controller 里取参数前调用req.setCharacterEncoding(UTF-8)。数据库连接串加characterEncodingutf8。建表时用DEFAULT CHARSETutf8mb4。四个地方统一成 UTF-8缺一个就翻车。4.4 连接池耗尽Connection 未关闭的连锁反应现象系统运行一段时间后所有数据库操作卡死日志里出现Wait timeout for connection。原因CustomerServiceImpl或其他 Service 实现里Connection、PreparedStatement、ResultSet没有在finally块里关闭。每次请求泄漏一个连接池子很快见底。解决检查所有 DAO 方法确保finally { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); }。如果用了连接池如 Druid 或 DBCP确认配置文件里的maxActive不是默认的 8适当调到 20 到 50。改完用SHOW PROCESSLIST观察 MySQL 侧的连接数是否稳定。4.5 报表数据为空时间字段类型与查询条件现象统计报表页面显示“暂无数据”但数据库里明明有记录。原因create_time字段存的是varchar类型或者查询条件里的日期格式和数据库里的格式不一致。比如数据库存2024-01-15 10:30:00查询条件写2024-01-15BETWEEN比较时字符串匹配失败。解决把时间字段改成datetime类型。查询条件用DATE(create_time) 2024-01-15或create_time 2024-01-15 00:00:00 AND create_time 2024-01-16 00:00:00。在 Controller 里打印最终执行的 SQL确认参数拼接正确。5. 进阶技巧用反射批量校验实体字段与表结构一致性5.1 为什么需要自动校验字段映射这套 CRM 有六张核心表、六个实体类字段加起来几十个。手动比对Customer.java的字段和customer表的列名不仅费时而且改一处漏一处。我一般会写一个一次性校验工具用 JDBC 的ResultSetMetaData拿到表的所有列名再用反射拿到实体类的所有字段名两边做差集。import java.lang.reflect.Field; import java.sql.*; public class SchemaChecker { public static void main(String[] args) throws Exception { String url jdbc:mysql://localhost:3306/crm?useSSLfalseserverTimezoneAsia/Shanghai; Connection conn DriverManager.getConnection(url, root, password); // 表名 - 实体类全限定名 String[][] pairs { {customer, com.crm.entity.Customer}, {sale_chance, com.crm.entity.SaleChance}, {cus_dev_plan, com.crm.entity.CusDevPlan} }; for (String[] pair : pairs) { String table pair[0]; Class? clazz Class.forName(pair[1]); // 获取表列名 Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT * FROM table LIMIT 0); ResultSetMetaData meta rs.getMetaData(); java.util.SetString columns new java.util.HashSet(); for (int i 1; i meta.getColumnCount(); i) { columns.add(meta.getColumnName(i).toLowerCase()); } // 获取实体字段名下划线转驼峰 java.util.SetString fields new java.util.HashSet(); for (Field f : clazz.getDeclaredFields()) { fields.add(f.getName().replaceAll(([A-Z]), _$1).toLowerCase()); } // 差集 columns.removeAll(fields); if (!columns.isEmpty()) { System.out.println(table 表中以下列在实体类中无对应字段: columns); } rs.close(); stmt.close(); } conn.close(); } }逻辑说明ResultSetMetaData是 JDBC 提供的元数据接口getColumnName返回真实列名。反射部分用getDeclaredFields拿到实体类声明的所有字段然后用正则把驼峰转下划线和列名对齐。removeAll之后剩下的就是“表里有但实体类没有”的列这些列在查询时会被忽略但插入时如果设了NOT NULL就会报错。参数说明url里的数据库名、用户名、密码换成你自己的。pairs数组按需扩展把六张表都加进去。这个工具不需要集成到项目里单独跑一次把输出结果对照着改实体类或改表结构即可。跑完删掉别留在生产代码里。5.2 用日志级别定位 SQL 执行慢的问题统计报表模块在数据量大了之后会变慢。我一般会在 JDBC 连接串上加loggerLevelDEBUGMySQL 驱动自带或者在 Service 层手动打时间戳long start System.currentTimeMillis(); ListCustomer list customerService.findAll(); long cost System.currentTimeMillis() - start; if (cost 500) { System.out.println(findAll 耗时 cost ms检查索引); }逻辑说明500ms 是我个人的告警阈值超过就说明要么缺索引要么查询没走索引。customer表的customer_name和phone如果经常作为查询条件加普通索引。sale_chance的customer_id加外键索引。加完用EXPLAIN确认type不是ALL。参数说明System.currentTimeMillis()是最轻量的计时方式不需要引入任何依赖。如果你用 Log4j 或 SLF4J改成logger.debug并配好级别避免在生产环境刷屏。从那以后我每次拿到一套 JavaEE 源码包都强制先跑一遍字段校验、再确认 JDK 和 Tomcat 版本、最后把连接串参数补齐这三步走完再启动能省掉八成以上的“启动即翻车”。希望帮到你。本文还有配套的精品资源点击获取