资讯动态

基于SSM的客户资料管理系统设计与实现:Spring+SpringMVC+MyBatis完整源码解析

发布时间:2026/10/27 15:19:38 来源:尧图企业网站定制
简介SSMSpringSpringMVCMyBatis是Java Web领域最经典的轻量级分层开发组合之一它将请求接收、业务处理与数据持久化拆分为清晰的三层架构。理解这一原理有助于快速掌握企业级项目的基本组织方式。基于此架构的客户资料管理系统往往涵盖用户登录、客户档案管理、跟进记录与权限控制等核心需求配合数据库脚本与项目源码可显著降低毕业设计与工程练手的起步门槛。在实际部署中涉及Maven环境配置、MySQL脚本导入、Tomcat运行、中文编码及静态资源拦截等常见问题围绕这些环节的排查与优化能帮助开发者从“跑通CRUD”迈向“理解分层设计与安全边界”。这是一套基于SSM的客户资料管理系统完整方案从表结构设计到代码实现、再到答辩加分项适合计算机专业毕业设计选型及初级开发者参考。1. 客户资料管理系统为什么还值得用 SSM 做毕设选型与三个现实理由每年毕业季都有大量同学卡在系统选型这一步客户资料管理系统这种题目看似简单真下手才发现入口很多有 Spring Boot MyBatis Plus 的有 SSM 传统三层结构的甚至还有前端用 Vue 后端用 Node 的。那标题里写的基于 SSM 的客户资料管理系统【项目源码数据库脚本】到底好在哪一句话说清楚它是一个把 Java 后端最经典的 Spring SpringMVC MyBatis 三件套串起来的完整 CRUD 业务系统自带源码和数据库脚本目标用户是准备做毕设的计算机相关专业学生以及想补一套标准三层架构作为练手项目的在职初级开发。选它的理由也很现实。其一是答辩友好SSM 是教材和面试里出现频率最高的 Java Web 组合评审老师看到这个技术栈会觉得你基本功扎实不用解释太多。其二是复杂度适中客户资料管理涉及用户登录、客户信息增删改查、分页搜索、数据统计规模刚好处在能展示完整思路又不至于写不完的区间。其三是源码加数据库脚本的交付形式意味着你不用从零建表省掉最枯燥的起步阶段可以把精力放在跑通流程和准备答辩问题上。这篇文章就按系统拆解→环境搭建→代码实现→数据库脚本→踩坑排查→加分优化的顺序把这个标题背后的方案完整讲清楚你要是手里已经有一套源码按章节对照补课就行要是还没开始做也能照着我给的路径自己复现一套。2. 拆解 SSM 客户资料管理系统的功能清单与表设计登录、客户档案、跟进记录这三块怎么落地2.1 先搞清楚客户资料管理到底管哪些数据人员角色与数据流向很多同学拿到标题就开始搜代码其实第一步应该想清楚这个系统给谁用、每天在操作什么。一套标准的客户资料管理系统用户角色至少分两类管理员和普通业务员。管理员负责维护系统里的用户账号、查看全部客户数据业务员负责录入自己名下的客户、更新跟进状态、记录每次沟通内容。少数版本还会加一个部门经理角色能看到下属业务员的客户列表这可做可不做看你的源码包里有没有。数据流向概括起来就是业务员登录后在客户档案模块新增一条客户记录填写公司名称、联系人、电话、地址、所属行业、客户等级后续每次电话或拜访后在跟进记录模块追加一条沟通纪要客户成交了就把客户状态从跟进中改成已成交。管理员不定时登录后台看客户总量、按行业或来源统计分布必要时把某条数据导成 Excel 存档。这个链路不复杂但它把 SSM 三层架构里每一层该干什么都覆盖到了Controller 接收请求、Service 处理业务规则、Mapper 操作数据库。2.2 核心表结构用户表、客户表、跟进记录表的设计思路与字段说明数据库脚本通常包含至少三张核心表sys_user用户表、customer客户表、follow_record跟进记录表有的项目还会加 customer_type客户类型字典表和 login_log登录日志表用来丰富功能。下面是我按常见毕设源码的建表习惯整理的一个字段集你可以拿它跟你的数据库脚本做对照。-- 用户表 CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT MD5加密后的密码, real_name VARCHAR(50) COMMENT 真实姓名, role_id INT DEFAULT 2 COMMENT 角色1管理员 2业务员, status TINYINT DEFAULT 1 COMMENT 账号状态1正常 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 客户表 CREATE TABLE customer ( id INT PRIMARY KEY AUTO_INCREMENT, customer_name VARCHAR(100) NOT NULL COMMENT 公司/客户名称, contact_person VARCHAR(50) COMMENT 联系人, contact_phone VARCHAR(20) COMMENT 联系电话, email VARCHAR(100) COMMENT 邮箱, address VARCHAR(200) COMMENT 地址, industry VARCHAR(50) COMMENT 所属行业, customer_level TINYINT COMMENT 客户等级1高 2中 3低, status TINYINT DEFAULT 1 COMMENT 客户状态1跟进中 2已成交 3已流失, owner_id INT COMMENT 归属业务员ID关联sys_user.id, remark VARCHAR(500) COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); -- 跟进记录表 CREATE TABLE follow_record ( id INT PRIMARY KEY AUTO_INCREMENT, customer_id INT NOT NULL COMMENT 客户ID, user_id INT NOT NULL COMMENT 跟进人ID, content VARCHAR(1000) COMMENT 跟进内容, next_plan VARCHAR(500) COMMENT 下次跟进计划, follow_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 跟进时间 );这段建表脚本的精髓在 customer 表的设计上owner_id 用外键关联到 sys_user 表这样每一条客户数据都能找到归属的业务员管理员按人筛选客户时只需要一条 where 条件status 字段用数字表示状态而不是存中文是为了后续做统计分析时方便 group by。注意客户等级和行业如果做成字典表后续加选项就不用改表结构直接在字典表里插一行但代价是查询时要多一次 join毕设项目数据量小直接用字符串字段也说得通。2.3 页面功能与三层架构的对应关系登录拦截、客户列表、新增编辑、删除的请求流转搞清楚了表和角色下一步是看请求在 SSM 框架里怎么流转。以新增客户这个动作为例完整链路是这样的浏览器提交表单到 CustomerController 的 addCustomer 方法Controller 接收参数并封装成 Customer 对象Service 层先校验客户名称是否为空、电话格式是否合法再调用 CustomerMapper 的 insert 方法写库MyBatis 执行 SQL 后把受影响行数返回Service 判断结果Controller 再跳转回客户列表页并携带提示信息。这里有个常见理解误区很多同学以为 SSM 就是三个框架拼在一起写代码时把 SQL 直接写在 Controller 里这是不对的。SSM 的分层价值在于——业务规则写在 Service 里可以复用SQL 写在 Mapper 的 XML 文件里方便单独调优Controller 只负责参数接收和页面跳转。你的毕设源码如果不是这么分的建议你重构一下因为答辩时老师很爱问为什么要分三层你要是答不上来会比较尴尬。2.4 用一张表看清你的源码包结构mapper 层、service 层、controller 层各自该放什么判断一套 SSM 客户管理系统源码质量好坏最快的方法是打开源码包看目录结构。合格的工程一般长这样src/main/java/com/example/crm ├── controller # SpringMVC 控制器接收请求、返回视图 ├── service # 业务接口 实现类处理业务逻辑 ├── dao/mapper # MyBatis 的 Mapper 接口 ├── entity/pojo # 实体类对应数据库表 ├── interceptor # 登录拦截器做权限校验 └── util # 工具类如 MD5 加密、分页封装 src/main/resources ├── mybatis # MyBatis 的 XML 映射文件 ├── spring # Spring 和 SpringMVC 的配置文件 └── jdbc.properties # 数据库连接配置 src/main/webapp ├── static # 静态资源 js/css/images └── WEB-INF/jsp # JSP 视图页面如果你的源码包跟这个结构有出入问题一般不大重点是检查三件事第一mybatis 的 XML 文件里 SQL 是否都写了参数占位符而不是字符串拼接防止 SQL 注入是答辩安全问题的标准考点第二Service 层是否有事务注解没有的话多表操作容易造成数据不一致第三web.xml 里 DispatcherServlet 的映射路径是 / 还是 *.do这直接决定静态资源能不能被正常访问。这三处没问题工程地基就稳了。3. 用 Spring SpringMVC MyBatis 把客户 CRUD 跑通最小可运行配置与三步验证3.1 环境版本怎么选JDK 8 Maven Tomcat 8/9 的最稳组合拿到源码第一步是先配环境这一步翻车率奇高。常见的错误是 JDK 版本装得太新比如用 JDK 17 跑老项目编译时直接报错因为老版本 Spring 对高版本 JDK 的字节码格式支持不好。SSM 项目通常诞生在 JDK 8 时代老老实实装 JDK 8 是最稳的。Maven 用 3.6 左右的版本就好Tomcat 选 8.5 或 9.0这三个版本组合是被验证过无数次的不会吵架的组合。开发工具方面IDEA 需要安装 Lombok 插件——大多数毕设源码的实体类都会用 Lombok 的 Data 注解来免写 getter/setter不装插件会报编译错误。另外注意导入工程时要用从现有源码导入 Maven 项目的方式而不是直接 Open 文件夹否则 Maven 依赖不会自动下载。导入后先让 Maven 把依赖拉完确认 pom.xml 里没有红色报错再进行下一步。3.2 数据库连接配置jdbc.properties 里改这五个参数附驱动版本对照数据库脚本到手之后第一步是修改 jdbc.properties 里的连接参数。这个文件一般在 src/main/resources 目录下内容大致如下jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/crm_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456 jdbc.maxActive50这里有两个坑必须注意。第一个坑是 MySQL 驱动版本和数据库版本的匹配问题如果本机装的是 MySQL 5.7 及以下用 com.mysql.jdbc.Driver 没问题如果是 MySQL 8.0驱动类要改成 com.mysql.cj.jdbc.Driver而且 pom.xml 里 mysql-connector-java 的版本要升级到 8.x否则会报Loading class is disallowed之类的连接异常。第二个坑是 serverTimezone 参数MySQL 8.0 默认时区跟本地不一致连接时容易报错加上 Asia/Shanghai 基本能消掉这个报错。3.3 跑通客户列表的最小链路Controller、Service、Mapper 三层代码逐行注释配置改好后找一个最简单的功能把整条链路验证通。我建议先跑客户列表查询因为它不需要表单提交也不涉及复杂参数。下面是一整套最小代码你对照着看业务是怎么在层与层之间走的。// 实体类对应 customer 表 Data public class Customer { private Integer id; private String customerName; private String contactPerson; private String contactPhone; private String industry; private Integer status; private Integer ownerId; }// Mapper 接口 public interface CustomerMapper { // 查询所有客户参数是当前登录用户ID管理员传0表示查全部 ListCustomer selectCustomerList(Param(ownerId) Integer ownerId); }!-- MyBatis 映射文件 CustomerMapper.xml -- select idselectCustomerList resultTypecom.example.crm.entity.Customer SELECT * FROM customer where if testownerId ! null and ownerId ! 0 AND owner_id #{ownerId} /if /where ORDER BY create_time DESC /select// Service 接口 实现类 public interface CustomerService { ListCustomer getCustomerList(Integer ownerId); } Service public class CustomerServiceImpl implements CustomerService { Autowired private CustomerMapper customerMapper; Override public ListCustomer getCustomerList(Integer ownerId) { // 业务规则管理员传0查全部业务员只能看自己的 return customerMapper.selectCustomerList(ownerId); } }// Controller Controller RequestMapping(/customer) public class CustomerController { Autowired private CustomerService customerService; RequestMapping(/list) public String list(RequestParam(value ownerId, defaultValue 0) Integer ownerId, Model model) { // 调用 service 层拿数据放进 Model交给 JSP 渲染 model.addAttribute(customerList, customerService.getCustomerList(ownerId)); return customer/list; } }这段链路最值得琢磨的地方是 Service 层那条业务规则管理员和业务员看到的客户范围不同。很多毕设源码的列表查询是直接查出全部数据的答辩时老师问一句业务员怎么能看到别人的客户答不上来就露怯了。加了这层判断之后系统权限逻辑就有了代码也谈不上复杂。MyBatis 的where标签会自动处理条件拼接ownerId 传 0 时不会生成无效的 where 条件这是比手动拼接 SQL 更安全的写法。3.4 用 Postman 或浏览器验证接口看返回视图还是 JSON、状态码 200 不代表业务成功代码写完怎么验证学到的第一个技巧是分清楚请求到达了和业务成功了是两回事。启动 Tomcat 后浏览器访问http://localhost:8080/crm/customer/list如果看到的是客户列表页面说明链路是通的。这时候优先用 Postman 做几个关键测试第一未登录状态下直接访问这个地址应该被拦截器重定向到登录页——这验证登录拦截是否生效第二用一个业务员账号登录后访问列表数据里不应该包含 owner_id 不是你自己的客户第三故意传一个不存在的 ownerId 值看是否报 500 错误这能暴露 SQL 或空指针问题。一个容易忽略的细节是如果 Controller 方法返回值是 String 且工程配置了视图解析器那么返回的 customer/list 会被解析成/WEB-INF/jsp/customer/list.jsp。如果访问时出现 404先确认 JSP 文件是不是放对了目录。相反如果源码里有 ResponseBody 注解那返回的就是 JSON 数据一般用于 Ajax 异步刷新页面两者别搞混了。4. 数据库脚本怎么导入与验证MySQL 命令行和 Navicat 两种方式、初始化数据的作用4.1 拿到 .sql 脚本后先做三件事看编码、看库名、看数据量数据库脚本是整个项目的地基文件很多同学上来就用 Navicat 双击运行跑出一堆红字就慌了。按照血泪经验正确顺序是先打开脚本文件看三样东西。第一看文件编码用记事本或 IDEA 打开右下角显示的是 UTF-8 还是 GBK。脚本里如果有中文注释或初始化数据编码不对会导致导入后中文全部变成乱码后面页面显示中文肯定出问题。第二看库名脚本里一般会有 CREATE DATABASE 语句确认你要建的库叫 crm_db 还是别的如果跟 jdbc.properties 里的 URL 不一致连接必然报错。第三看数据量脚本有多大数据量决定导入耗时和导入方式——几十行的初始化数据直接运行就好好几万条模拟数据就需要命令行导入图形界面容易超时。4.2 命令行导入脚本mysql -u root -p 的完整操作与参数说明超时问题怎么处理命令行导入是最常见也最可靠的方式适合任何大小的脚本。Windows 上打开 cmd 或 Git Bash进入 MySQL 的 bin 目录后执行mysql -u root -p /path/to/crm_db.sql按提示输入 root 密码后MySQL 会逐条执行脚本里的 SQL 语句执行成功时没有任何回显有报错才会在终端打出来。这个命令的原理是-u指定用户、-p表示需要密码、把文件内容重定向给 mysql 客户端执行。注意路径中如果有中文或空格用双引号包住整个路径否则会报找不到文件。如果你的脚本是几十 MB 的大文件命令行导入可能卡住或报 lost connection 错误。解决方法是调整 MySQL 的 timeout 参数导入前先在当前会话里执行一句SET SESSION wait_timeout 600; SET SESSION max_allowed_packet 128 * 1024 * 1024;这两条命令把会话级的等待超时改成 600 秒把最大允许的数据包调大到 128MB大部分超时问题都能解决。注意 SESSION 级别的设置只对当前连接生效不影响 MySQL 全局配置。4.3 用 Navicat 导入的两种路径直接运行 SQL 文件 vs 新建数据库后导入Navicat 导入的操作更直观但有几个典型问题容易被忽视。第一种方式是直接右键数据库选择运行 SQL 文件然后选中你的 .sql 脚本。这种方式要求你的连接已经有对应的数据库存在如果脚本里有 CREATE DATABASE 语句且你连接的账号有建库权限也可以直接运行整个脚本。第二种方式是自己先手工新建一个数据库字符集选 utf8mb4然后右键这个新库选择运行 SQL 文件。我推荐第二种因为库是自己建的后续排查比较不容易搞混。Navicat 导入时有一个常见坑脚本文件如果太大Navicat 默认的读取缓冲区可能不够会报out of memory或invalid SQL syntax。解决办法是用命令行导入或者把脚本拆成小块再导。另外导入完成后务必刷新一下数据库列表看表是否真的建出来了有时候 Navicat 缓存没刷新会误以为导入失败。4.4 初始化数据的作用一条管理员账号怎么写、admin/123456 这种默认账号背后的加密逻辑脚本里的 INSERT 语句一般是整个数据库脚本最容易看明白的部分但也别跳过。它通常包含两类数据一是字典数据比如客户等级的定义、行业分类的选项二是测试账号一般会有一个管理员账号和一两个业务员账号。以管理员账号为例脚本里往往是这样一条记录INSERT INTO sys_user (username, password, real_name, role_id, status) VALUES (admin, e10adc3949ba59abbe56e057f20f883e, 系统管理员, 1, 1);密码那一串是123456的 MD5 加密结果。为什么初始化数据存的是密文而不是明文因为登录接口的校验逻辑是先把用户输入的密码用同样的算法加密再跟库里比对——如果库里存的是明文那加了密也白加。你启动系统后不管是用 admin/123456 还是 admin/admin 登录都取决于源码里登录校验用的是什么加密算法MD5 是绝大多数 SSM 项目的默认选择个别会加一个固定盐值做二次混淆。你要是想改登录密码不要直接在数据库里改翻工具类里有没有 PasswordUtil 之类的加密方法跑一下否则改了也登录不上。4.5 验证表的关联关系owner_id、customer_id 这些外键字段怎么检查是否对齐表导入完之后别急着启动项目花五分钟检查外键逻辑是否对齐。检查方式就是执行几条 SQL-- 检查每个客户的归属人是否都存在于用户表 SELECT c.customer_name, c.owner_id, u.username FROM customer c LEFT JOIN sys_user u ON c.owner_id u.id WHERE u.id IS NULL;如果这条 SQL 查出了结果说明存在孤儿客户——客户指向了一个不存在的用户。这种脏数据会导致页面在显示归属人姓名时 NPE 报错。同理可以用类似的方式检查 follow_record 表的 customer_id 是否都能在 customer 表里找到对应记录。为什么要做这个检查因为很多网上下载的源码脚本里作者为了模拟数据随便写了一些 ID没有严格对齐外键。你不在导入阶段排查后面列表页或者详情页一旦做关联查询就会在答辩演示时当场翻车。5. SSM 客户资料管理系统常见问题与避坑从 404 到中文乱码的 5 个血泪排查点5.1 启动 Tomcat 后访问项目直接 404web.xml 里 DispatcherServlet 映射路径在作怪现象项目能正常编译启动Tomcat 也显示部署成功但浏览器访问http://localhost:8080/项目名/返回 404。原因这个现象十有八九是 web.xml 里 DispatcherServlet 的url-pattern配置不对。很多源码用的是*.do这种后缀匹配那你访问/customer/list这种不带 .do 的地址就必然 404正确地址应该是/customer/list.do。反过来如果配置的是/那你起的地址就得是/customer/list。还有一种情况是 web.xml 里 load-on-startup 配置导致 Spring 容器没有初始化成功ContextLoaderListener 报错这种一般会在 Tomcat 日志里找到 ClassNotFoundException 之类的线索。解决先看 web.xml 的 url-pattern 是哪种风格再决定访问用什么路径。如果要排查最快的办法是打开 Tomcat 的 catalina.out 或 IDEA 控制台日志拉到最底部看有没有Deployed字样然后把报错的关键行复制下来搜一搜。5.2 页面能打开但 JS/CSS 全部失效静态资源被 SpringMVC 拦截的经典症状现象页面长得奇丑无比完全没有样式控制台报一堆 404路径指向 /static/css 之类的静态资源。原因这是 SpringMVC 配置里的经典问题。当 DispatcherServlet 的 url-pattern 配置为/时它会接管所有请求包括静态资源如果 spring-mvc.xml 里没有配置mvc:resources映射那静态资源就全部找不到了。而如果用*.do后缀匹配反而不会有这个问题因为静态资源的路径不带 .do 后缀不会被 DispatcherServlet 拦走。解决在 spring-mvc.xml 里加上静态资源映射mvc:resources mapping/static/** location/static/ /如果你不想让静态资源路径里带 /static 前缀也可以改成mvc:annotation-driven /结合mvc:default-servlet-handler /让容器默认的 Servlet 来处理静态文件。这两种方案的差别是前者你可以在 JSP 里按/static/css/style.css引用后者你直接按/css/style.css引用就行看源码里原来是怎么引用的别改了引用路径反而找不到。5.3 新增客户时中文写入数据库变成问号连接参数和表字符集双重检查现象页面上填写的客户名称是某某科技有限公司保存成功后去数据库看变成了一串问号。原因这个问题的根源是编码链路上的某个环节用了 latin1 或 GBK。最常见的因素有三处数据库表的字符集不是 utf8mb4、JDBC 连接 URL 里没带 characterEncodingutf8、JSP 页面本身的 contentType 没设置 UTF-8。三处断一环中文就会在不同环节被转成错误编码。解决先执行一条 SQL 检查表的字符集SHOW TABLE STATUS LIKE customer;看到 Collation 不是 utf8mb4_* 开头用这条命令改掉ALTER TABLE customer CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;然后检查 jdbc.properties 的 URL 是否带 characterEncodingutf8最后在 web.xml 里配置 Spring 的 CharacterEncodingFilter把所有请求和响应的编码强制成 UTF-8。这三个动作做完中文乱码问题基本就绝迹了。5.4 登录成功后跳转不进去、一直回到登录页Session 存值 key 没对上现象账号密码都正确登录接口也提示成功但跳转到首页后马上被拦截器重定向回 login 页面。原因说明登录拦截器认为你没有登录。常见的坑是登录成功后往 Session 里放的 key 是user而拦截器里取的是loginUser或者userId对不上自然判定未登录。还有一种是拦截器取了 session 的值后判断方式是 null但你存进去的是一个 Integer 类型的 0 值不判空直接判等逻辑就会出问题。解决打开 Interceptor 类和登录 Controller找出 setAttribute 和 getAttribute 的 key 到底是什么——这种肉眼比对往往一眼就能看出不一致。还有一个隐蔽问题是 Map 和 Model 的使用混淆SpringMVC 中往 session 里存值要用session.setAttribute()而在 Controller 方法参数里加 HttpSession 再调用别直接在 Model 上 setAttribute 以为能存进 session那是两码事。5.5 分页查询翻到第二页数据为空MyBatis 分页参数没有持续传递现象第一页显示 10 条客户数据点击下一页后页面空白或者数据缺失地址栏里的 pageNum 参数变了但结果没变。原因翻页时查询条件丢失是最普遍的现象。第一次请求带了 keyword 和 pageNum 两个参数第二次点击只传了 pageNum 没传 keyword导致第二页的数据是在完全不同的查询条件下查出来的。如果你用的是 PageHelper 这类分页插件还有个特有的坑PageHelper 的分页参数是绑定在当前线程的 ThreadLocal 上的如果查询前有一步走了缓存或者执行了别的 SQL分页参数可能被消费掉。解决查看分页的 SQL 是不是在 Service 层里紧跟第一条查询——PageHelper 只对第一条紧接着的查询生效。翻页时要在 Controller 方法里把 keyword 参数继续传给前端模板用${keyword}拼在分页链接的请求参数里。答辩演示时翻页功能是高频操作这个不修好基本上演示必翻车。6. 把客户管理系统做出差异化Excel 导入导出、登录日志与验收前的验证清单6.1 给客户资料加一个 Excel 导入导出用 POI 封装一个工具类的实操答辩评分里功能完整性占相当比重而客户资料批量导入导出是很容易出彩又不难实现的功能。常见做法是用 Apache POI 库操作 Excel 文件在 pom.xml 里引入 poi 和 poi-ooxml 两个依赖然后自己封装一个工具类。核心代码大概是这样的// 导出客户列表为 Excel .xls 格式 public static void exportCustomerList(ListCustomer list, HttpServletResponse response) { HSSFWorkbook workbook new HSSFWorkbook(); HSSFSheet sheet workbook.createSheet(客户列表); // 创建表头 String[] headers {客户名称, 联系人, 电话, 行业, 状态}; HSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { headerRow.createCell(i).setCellValue(headers[i]); } // 填充数据 for (int i 0; i list.size(); i) { HSSFRow row sheet.createRow(i 1); Customer c list.get(i); row.createCell(0).setCellValue(c.getCustomerName()); row.createCell(1).setCellValue(c.getContactPerson()); row.createCell(2).setCellValue(c.getContactPhone()); row.createCell(3).setCellValue(c.getIndustry()); row.createCell(4).setCellValue(c.getStatus() 2 ? 已成交 : 跟进中); } // 设置响应头让浏览器以文件下载方式打开 response.setContentType(application/vnd.ms-excel); response.setHeader(Content-Disposition, attachment; filenamecustomer_list.xls); workbook.write(response.getOutputStream()); workbook.close(); }这段代码的注意点有两个第一HSSFWorkbook 对应的是 Excel 97-2003 的 .xls 格式数据量超过 65535 行会爆掉毕设系统客户量一般到不了这个数所以够用要是想支持新版 .xlsx 格式得上 XSSFWorkbook但代码基本一样。第二Content-Disposition 里的 filename 如果是中文某些浏览器会乱码最好用 URLEncoder 编码一下。导入功能就是反向操作用 WorkbookFactory 创建一个 workbook遍历每个 Sheet 的行和单元格把数据封装成 Customer 对象然后批量插入注意处理格式异常和空行的跳过逻辑。6.2 增加一张登录日志表记录用户名、登录时间、IP 地址让你的项目多一个加分项答辩时评委最常问的问题之一是你做了哪些数据层面的设计如果你只有一个单表的增删改查聊起来会很单薄。加一张登录日志表是个低成本高回报的选择在用户登录成功的 Controller 方法里把用户名、登录时间写进一张 login_log 表管理员页面做一次最近登录记录列表展示即可。具体的操作是在登录方法里获取 HttpServletRequest 的 remoteAddr 得到客户端 IP然后调用 LoginLogService 插入一条记录。表结构大概就是 id、username、login_time、ip_address、login_status 五个字段。这个功能的业务价值非常直观——能看到谁在什么时候登录过系统这个场景在答辩陈述时可以自然讲出来系统上线后如果有人恶意尝试登录管理员可以通过日志发现问题。不用做得多复杂两张表、两个方法、一个列表页就成型了。6.3 答辩前必做的功能验证清单登录拦截、权限边界、空数据页面、重复提交最后给你一份自查清单每个都是踩过的坑建议一项项验证完再去答辩。第一项是未登录拦截退出登录后直接在浏览器输入/customer/list地址应该被弹回登录页。第二项是业务员权限用业务员账号登录后把你的 owner_id 改成别人的 ID再查列表看是不是只能看到自己的数据数据库里单独插一条 owner_id 为别人的数据页面上不应该显示。第三项是空数据处理把某张表 truncate 清空后访问对应页面页面应该展示暂无数据而不是白屏或报错。第四项是重复提交快速连续点两下新增按钮数据库里不应该出现两条一模一样的客户记录这个可以用前端按钮 disable 解决也可以在 Service 层根据客户名加唯一校验。6.4 一个实效技巧把常用 SQL 的执行计划看一下答辩时能多说两句优化很多人的项目接口在数据量小的时候秒开但答辩被问到如果客户数据量上百万怎么优化就卡壳。提前做一步准备在数据库执行一条EXPLAIN SELECT * FROM customer WHERE owner_id 1就能看到 MySQL 是怎么查的如果 type 列是 ALL说明是全表扫描你就可以回答可以通过给 owner_id 建索引优化。做这一步不需要真的建索引你只需要能说出来这个思路。同理MyBatis 里尽量用 resultType 而不是循环查单条也可以在综合查询的场景下自然带出。这套项目做完你会发现 SSM 真正的价值不在于技术多新而在于它逼你把分层的思路理清楚。我的个人习惯是每拿到一套陌生源码先跑通最小链路再逐层注释梳理逻辑最后留出两天时间专门做权限和编码层面的加固。望这个方案能帮你把毕设做扎实希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑