ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSM体育器材租借管理系统:从架构到落地的完整实践

SSM体育器材租借管理系统:从架构到落地的完整实践

简介:面向Java毕业设计场景的SSM体育器材租借管理系统,采用Spring+SpringMVC+MyBatis框架与B/S模式,配合MySQL数据库实现管理员、普通用户、器材信息、租借记录、留言板等核心模块。后台业务覆盖用户管理、器材管理、租借记录与留言回复等典型功能,结构清晰,适合作为Java Web课程设计或毕业设计的参考蓝本。压缩包共714个文件,约28.37MB,涵盖jsp源码、java类、jar依赖库、sql脚本及xml配置等类型:页面与业务代码负责功能实现,jar包提供依赖支持,sql脚本可直接建库,xml配置含数据库连接与框架参数,另附docx论文便于撰写文档。目前已有437人学习下载,资源内含完整项目源码、数据库脚本及论文说明,并提示MySQL版本兼容调整与连接配置修改位置,可快速搭建运行环境并二次开发。无论是需要参考完整SSM项目结构,还是想了解租借业务流程,都能从中获取可落地的实战经验。

1. 拿到「java毕业设计 065ssm体育器材租借管理系统」这类压缩包,里面基本就是一个完整的 SSM 项目:Java 后端、MySQL 数据库脚本、一份可直接打印的论文文档。它面向两类人:正在选题的本科生,和要快速把管理后台跑起来做二次开发的程序员。系统要管的业务并不复杂——器材有库存,用户能借,借了要还,后台要记录谁在什么时候借走了什么。真正值钱的是,SSM 三层架构把「增删改查 + 业务状态流转 + 后台管理」串成了闭环,同一个包里能把 Controller、Service、Mapper、数据库表结构和论文写法一次看全。我的建议是:先别急着换主题皮肤,第一步是把它跑起来,用真实数据走一遍借还流程,再谈改造。

2. 拆开 SSM 管理系统的三层骨架:搭建选型、建库建表、配置清单

2.1 SSM 为什么是毕设后台的「标准答案」:三层分工与前后端边界

SSM 不是一种独立框架,而是 Spring、SpringMVC、MyBatis 三件套的合称。在体育器材租借管理系统里,职责可以切成三条流水线:Spring 负责对象创建和依赖注入,SpringMVC 负责接收 HTTP 请求并把请求转给对应的业务方法,MyBatis 负责把 Java 对象和 MySQL 表结构互相转换。前端表单提交过来,SpringMVC 接住,交给 Spring 管理的 Service 对象处理,Service 再用 MyBatis 的 Mapper 去查改数据库。这套分工决定了你写代码时的大致位置:Controller 只做参数接收和结果返回,Service 写业务规则,Mapper 只管 SQL。

跑项目时最常打交道的是下面这些注解,我把它们按出现频率整理成表:

注解所属框架在租借系统里的典型位置
@ControllerSpringMVC器材管理、借阅管理的请求入口类上
@ServiceSpring借阅业务逻辑实现类上
@AutowiredSpringController 注入 Service,Service 注入 Mapper
@TransactionalSpring租借、归还这类需要同时改多张表的方法上
@ParamMyBatisMapper 接口方法参数与 SQL 里的 #{} 对应
@ResponseBodySpringMVC返回 JSON 给前端 Ajax 调用

有个细节容易被新手跳过去:SSM 项目里的 JSP 页面和静态资源并不归 SpringMVC 直接管,前后端是通过「表单提交 + Ajax 拿 JSON」两种方式混合协作的。页面上点「借出」,前端先$.ajax把器材 ID 和用户 ID 发到后端/borrow/add,后端返回 JSON,前端再根据 success 字段弹提示或刷新表格。理清这个边界后,改代码时就不会满世界找「按钮在哪里绑定」。那一堆「SSM 常用注解」面试题背后,其实问的也是这套边界:谁处理请求、谁写业务、谁管 SQL。只要你能在自己的项目里说清楚「一条借用记录从点击到入库经过了哪几个类」,答辩基本就稳了。

2.2 建库建表:器材、用户、借阅记录三张核心表的字段设计与状态流转

管理系统基本都是「表驱动」的,页面表单只是表的另一种长相。体育器材租借最少要三张表:器材表保存「有什么可借的」和「还剩多少」;用户表保存借东西的人和后台管理员;借阅记录表保存「谁在什么时间借走了哪件器材、还了没有」。核心 DDL 我整理在下面,这份结构我在多个毕设项目里见过,字段大同小异。

CREATE DATABASE IF NOT EXISTS ss_equip DEFAULT CHARACTER SET utf8mb4; USE ss_equip; CREATE TABLE equipment ( id INT PRIMARY KEY AUTO_INCREMENT, category VARCHAR(50) NOT NULL COMMENT '器材分类,如球类/健身/户外', name VARCHAR(100) NOT NULL COMMENT '器材名称', specs VARCHAR(200) DEFAULT NULL COMMENT '规格型号,如5号篮球', total_stock INT NOT NULL DEFAULT 0 COMMENT '总库存', stock INT NOT NULL DEFAULT 0 COMMENT '当前可借库存', status TINYINT NOT NULL DEFAULT 1 COMMENT '1可借 0下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) COMMENT '体育器材表'; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL COMMENT '建议存MD5或BCrypt', real_name VARCHAR(50) DEFAULT NULL, phone VARCHAR(20) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 1 COMMENT '1管理员 2普通用户' ) COMMENT '用户表'; CREATE TABLE borrow_record ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, equipment_id INT NOT NULL, borrow_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '借出时间', due_time DATETIME NOT NULL COMMENT '应还时间', return_time DATETIME DEFAULT NULL COMMENT '实际归还时间,NULL表示未还', status TINYINT NOT NULL DEFAULT 0 COMMENT '0借出中 1已归还 2逾期未还', FOREIGN KEY (user_id) REFERENCES sys_user(id), FOREIGN KEY (equipment_id) REFERENCES equipment(id) ) COMMENT '借阅记录表';

这里有两个字段设计很关键。第一个是「总库存 total_stock」和「当前可借库存 stock」分开:总库存代表采购或录入时的数量,stock 代表设备当前还能不能借,归还时只回补 stock 不碰 total_stock,后期对账能直接看出损耗。第二个是借阅记录的 status 只存 0/1/2 三个数值,逾期状态不要靠「当前时间 > due_time」现场算——未还且已超期的记录要定时任务批量改成 2,界面查询才快。很多毕设只做「借出/归还」两个状态,答辩时被问到「器材丢了怎么办、逾期怎么区分」就容易卡壳,把状态机和字段先想清楚,后面写代码只是赋值的问题。

外键要不要建?我建议在学号级的毕设里建上,论文里能写「通过外键保证引用完整性」;但如果你的库要反复导入导出,外键反而碍事。折中方案是:只保留 user_id 和 equipment_id 字段,逻辑外键在 Java 代码里维护,SQL 里不写 FOREIGN KEY。上面这段 SQL 我写了外键,属于「提交论文时更安全」的写法。

2.3 pom.xml 与项目结构:一个能跑起来的 SSM 项目必须具备的配置

SSM 项目跑不起来,八成的锅在依赖。最少必要的依赖是 spring-webmvc、mybatis、mybatis-spring、mysql-connector-java、jstl、servlet-api,以及编译期常用的 lombok。下面这份 pom 片段是精简后的骨干,保留版本属性和最关键依赖:

<properties> <spring.version>5.2.22.RELEASE</spring.version> <mybatis.version>3.5.9</mybatis.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.29</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <scope>provided</scope> </dependency> </dependencies>

版本选择上有一条血泪经验:Spring 5.2 配 MyBatis 3.5、MySQL 8 驱动,这组搭配被踩过的坑最多,也最好搜到答案。把 spring-mybatis.xml 里的数据源、SqlSessionFactory、MapperScannerConfigurer 三个配置核对好,项目才算「接线」成功。

提示:pom 里如果 spring-web 和 spring-webmvc 版本不一致,先别乱改,把它们统一到同一个父版本下再看启动日志。

一个标准 SSM 项目的目录结构通常是:

src/main/java/com/xxx/equipment ├── controller # 接收请求,返回ModelAndView或JSON ├── service # 业务接口 ├── service/impl # 业务实现,加@Transactional ├── mapper # MyBatis的Mapper接口 ├── entity # 和表对应的实体类,通常用Lombok省掉getter/setter └── util # 分页等杂项工具 src/main/resources ├── jdbc.properties # 数据库连接参数 ├── spring-mybatis.xml # 数据源、事务、Mapper扫描 └── spring-mvc.xml # 包扫描、视图解析器、静态资源放行 webapp/WEB-INF/web.xml # 启动入口,加载Spring容器和DispatcherServlet

拿到压缩包后,先按这张目录图检查一次——如果发现源码里缺了 jdbc.properties,那基本是被人手工清理过,得自己补;缺了某个 Mapper XML,项目照常编译但一调用就报「Invalid bound statement」。定位这类问题的最快办法在第 5 章展开,这里先记住「四个配置文件一个 pom」是 SSM 的地基。

3. 在 IDEA 里跑通这套源码:从解压 zip 到浏览器看到登录页

3.1 环境版本对照表:不吵架的 JDK、MySQL、Tomcat、Maven 组合

这类毕设压缩包最常见的形态是:代码是两年前写的,注释里写着 JDK 1.8,现在机器上装的是 JDK 17。环境选错是最先翻车的位置,我推荐的组合如下:

组件推荐版本不推荐的原因
JDK1.8(8u201+)JDK 11 跑 Spring 5 没问题,JDK 17 会遇到 Lombok 旧版不兼容
Maven3.6.33.9 也能跑,但镜像源配置不对会卡在下载依赖
MySQL5.7 或 8.0MySQL 8 要带驱动 8.x,且连接串必须有 serverTimezone
Tomcat8.5 或 9.0Tomcat 10 用了 jakarta.* 命名空间,旧 SSM 代码编译不过
IDEA2021 以上任意版本太老的 IDEA 内置 Tomcat 集成方式变化大
数据库工具Navicat / DataGrip / dbx只要能执行 SQL 和看表结构都行

一个最容易忽略的点:Tomcat 9 还是 8.5 其实都行,但 Tomcat 10 是分水岭——它把 javax.servlet 换成了 jakarta.servlet,2019 年之前的 SSM 代码几乎没法直接在 Tomcat 10 里编译。如果电脑里只有 Tomcat 10,去下个 Tomcat 9 zip 解压就能用,不要和 IDEA 的嵌入式 Tomcat 较劲。JDK 也一样,装一个 JDK 8 专门给这个项目用,其他项目继续用新版 JDK 互不干扰,这是最省事的隔离方案。

3.2 导入数据库:建库、执行 SQL、改 jdbc.properties 三步走

先看数据库脚本长什么样。很多压缩包里会放ssm_equip.sql、db_equip.sql这类文件,用文本编辑器打开确认它到底只是多条 INSERT,还是包含 CREATE TABLE。处理方法分两种:脚本只有建表语句,就自己先建库再执行;脚本里已经写了CREATE DATABASE,那就一路执行到底。命令行导入是最稳的,不依赖任何图形工具,在 bash 里这么干:

mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS ss_equip DEFAULT CHARACTER SET utf8mb4;" mysql -u root -p ss_equip < ssm_equip.sql mysql -u root -p -e "SELECT id, username, role FROM ss_equip.sys_user;"

第一条命令把 ss_equip 库建好;第二条把 zip 里的 SQL 文件导入;第三条检查用户表里有没有初始账号。执行完之后,不要急着启动 Tomcat,先去改资源文件。SSM 项目的数据库连接通常写在src/main/resources/jdbc.properties,至少要长这样:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ss_equip?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=你的数据库密码

这里说明一下三个参数的用途:serverTimezone是 MySQL 8 驱动强制要求的,不写就报时区异常;characterEncoding=utf8保证中文写入不乱码;useUnicode和前者是配套关系,通常一起出现。改完后做一件事:用数据库工具重新连一次,确认用户名密码能通过。密码里带特殊字符时,记得在 properties 里做转义,常见坑是有&没转义导致 URL 被截断。我一般先连上数据库再启动项目,能把「数据库连接不上」和「项目启动失败」两类问题隔离。

补充一个后来被证明很实用的习惯:首次导入后立刻在数据库工具里把表清单和 ER 图截图存到论文素材里,写「数据库设计」章节时直接能用,不用答辩前补画。如果你用的是 IDEA 自带 Database 面板,它支持把当前库的表结构导出成 SQL 脚本,这个功能在做备份时也很好用。

3.3 部署到 Tomcat 并启动:Artifact 配置与启动失败点

IDEA 跑 SSM 项目没有统一的「绿色按钮」,本质是「把项目打包成 war exploded 目录,交给本地 Tomcat 执行」。常见做法是:项目右键 Open Module Settings,在 Artifacts 里添加 Web Application: Exploded,然后运行配置里选 Tomcat Server -> Local,Deployment 页把该 Artifact 加上,Application context 填/ss_equip。如果压缩包里自带.idea目录,直接用 IDEA Open 打开后运行配置可能还在,但多数网上下载的源码都会清掉本机配置,手动加一次更可靠。

启动时如果 IDEA 日志卡在类似Waiting for changelog lock而不是打印 Tomcat 启动日志,多半是运行配置里的 Tomcat 端口和旧实例冲突。把 HTTP port 从 8080 换成 8088,或者杀掉残留进程。启动成功的标志是出现INFO: Server startup in [xxx] milliseconds,而不是控制台刷了几行 Spring 日志就算成功。访问路径是http://localhost:8080/ss_equip/,看到登录页后用初始账号登录,常见初始账号是 admin/admin123 或 admin/123456,如果登录失败,回数据库查 sys_user 表的 password 字段,看是不是 MD5 加密过的密文。

3.4 最小验收路径:从新增器材到归还,一条演示闭环

登录进去之后,我建议按「新增器材 → 用户借出 → 用户归还 → 查看库存回补」这条线走一遍,目的不是为了玩,而是确认这个包真正能用。具体操作是:在器材管理页面点新增,名称填「测试篮球」、总库存填 3;到借阅页面选一个普通用户、选这件器材,借出后去器材列表看 stock 变成 2;再走归还流程,确认 stock 变回 3,记录状态变成「已归还」。

如果每个节点都正常,说明这份源码的核心链路是通的,后面的功能改造可以放心动刀;如果某一环点了没反应,打开浏览器 F12 看 Network 面板,接口是 404 还是 500,直接决定你该查 SpringMVC 路由还是查 SQL。带毕设这些年我的经验是,至少留出一小时把这条最小闭环走两遍,第二遍故意乱点(比如借出数量超过库存),很多平时测不出来的校验漏洞都在这一遍暴露出来。

4. 核心业务代码在哪里:租借、归还、库存扣减的正确改法

4.1 租借一次器材:Controller 到 Mapper 的调用链与事务边界

真正的租借动作通常在BorrowServiceImpl#borrow里,常见的请求路径是/borrow/add或/rent/add。下面这段是一个简化的 Service 实现,几乎每个 SSM 租借系统都有同样的逻辑骨架:

@Service public class BorrowServiceImpl implements BorrowService { @Autowired private BorrowRecordMapper borrowRecordMapper; @Autowired private EquipmentMapper equipmentMapper; @Override @Transactional(rollbackFor = Exception.class) public boolean borrow(Integer equipmentId, Integer userId) { Equipment eq = equipmentMapper.selectById(equipmentId); // 1. 校验器材存在并且可借状态 if (eq == null || eq.getStatus() != 1) { throw new BusinessException("器材不存在或已下架"); } // 2. 校验库存 if (eq.getStock() == null || eq.getStock() < 1) { throw new BusinessException("库存不足"); } // 3. 扣库存,写借阅记录 equipmentMapper.decreaseStock(equipmentId); BorrowRecord record = new BorrowRecord(); record.setUserId(userId); record.setEquipmentId(equipmentId); record.setDueTime(DateUtil.addDays(new Date(), 3)); record.setStatus(0); borrowRecordMapper.insert(record); return true; } }

对应 Mapper XML 里的核心 SQL 只有两段,一段是扣库存,另一段是插入记录:

<update id="decreaseStock"> UPDATE equipment SET stock = stock - 1 WHERE id = #{equipmentId} AND stock > 0 </update> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> INSERT INTO borrow_record (user_id, equipment_id, borrow_time, due_time, status) VALUES (#{userId}, #{equipmentId}, NOW(), #{dueTime}, #{status}) </insert>

我在上面特意用了「先 select 校验再 update 扣减」的两段写法,它在答辩里是加分项:@Transactional 保证借阅记录和扣库存要么都成功要么都失败,不会出现「器材扣了但记录没写」的半截数据。真正要跟评委讲清楚的点是:这里的校验只是业务规则校验,并发场景下两个人同时借最后一件器材时,select 出来的库存可能都是 1,此时真正兜底的是WHERE stock > 0这个条件——update 影响行数为 0 时,方法就抛业务异常,库存不会变成负数。很多系统的脏数据都是因为把这句话写成了SET stock = stock - 1 WHERE id = ...,少了库存判断条件。

4.2 归还时的状态机:逾期判断、库存回补与记录闭环

归还接口的逻辑和借出反过来:先根据记录 ID 查出借阅记录,确认它确实处于「借出中」状态,接着把器材库存 +1,再把记录的状态改成「已归还」并写入 return_time。这个流程同样是一个 @Transactional 方法,我在里面额外加了逾期判断:

@Override @Transactional(rollbackFor = Exception.class) public boolean returnEquipment(Integer recordId) { BorrowRecord record = borrowRecordMapper.selectById(recordId); if (record == null || record.getStatus() != 0) { throw new BusinessException("记录不存在或已归还"); } // 先回补库存再更新记录 equipmentMapper.increaseStock(record.getEquipmentId()); // 逾期则把状态置为2,否则置为1 int newStatus = System.currentTimeMillis() > record.getDueTime().getTime() ? 2 : 1; borrowRecordMapper.updateStatus(recordId, newStatus, new Date()); return true; }
<update id="increaseStock"> UPDATE equipment SET stock = stock + 1 WHERE id = #{equipmentId} </update> <update id="updateStatus"> UPDATE borrow_record SET status = #{status}, return_time = #{returnTime} WHERE id = #{recordId} </update>

这里要提醒一句:千万不要在归还动作里顺手把 total_stock 也 +1,否则对账时会发现库存比采购数还多。total_stock 只在后台采购或录入时变。updateStatus 里的WHERE id = #{recordId}看似多余,但配上status != 0的 Service 校验,能防止前端重复提交表单造成两次归还——第一次把状态改成 1,第二次进来 record.getStatus() 已经是 1,直接抛异常,记录不会翻倍。这种「业务校验 + 条件更新」双保险,是代码里最值得讲的健壮性设计。

4.3 并发扣库存:用条件更新代替「先查再改」,避免负数库存

器材租借的并发量通常不高,但「库存变成负数」仍然是我在毕设代码里见过最多的问题之一。根因是很多同学的 Service 是「先 select 库存,判断 >0,再 update 减一」,却没有加锁也没有 WHERE 条件。两个学生同时点击借出,都读到库存 1,各自判断通过,最后都执行 update,库存就成了 -1。解决办法在 4.1 已经埋了一处,这里单独讲透三种方案。

第一种:条件更新,也就是UPDATE equipment SET stock = stock - 1 WHERE id = ? AND stock > 0。MyBatis 的 update 返回影响行数,返回 0 就认为是库存不足,直接中断业务。这个方案最简单,适合毕设和低并发业务。

第二种:使用SELECT ... FOR UPDATE,在事务里先把器材行锁住,等扣减完成再提交,其他事务只能排队等着。能保证严格的串行,代价是数据库锁等待变长。对租借系统来说完全够用但没必要大规模用,只在高价值的器材(比如几万块的测功仪)上单独加。

第三种:在实体类加乐观锁字段 version,每次 update 时SET version = version + 1 AND version = #{version}。适合「业务里既有库存又有其他字段要一起改」的场景,比如同时修改器材单价和库存。写进论文时,用「我采用乐观锁避免超借」这个说法,评委一听就知道你理解并发问题。

再给一个排查思路:如果decreaseStock返回影响行数一直为 0,先别怀疑并发,查两条——器材表 status 是否为 1,以及 stock 字段是否被其他脚本改过。租借系统里 90% 的「借不出去」其实都是数据问题不是代码问题。

5. SSM 毕设项目跑不起来的排查清单:我踩过的 5 类坑

很多 SSM 项目跑不起来时,错误栈往往并不可怕,真正可怕的是看起来没报错但页面 404 或样式全丢。我习惯把问题按「启动前、启动时、运行时」三个阶段切分:启动前查配置文件,启动时看 Tomcat 日志,运行时用浏览器 F12 的 Network 面板判断 500/404 属于后端还是前端。下面的 5 类坑按出现频率排序,每一项都写现象、原因和解决,可以直接当排查手册用。

阶段常见报错优先排查
启动前ClassNotFoundException 驱动jdbc.properties 驱动类、版本
启动时Port already in use端口占用、重复 Tomcat
启动时找不到符号 getXxxLombok 与 JDK 版本
运行时页面样式全丢spring-mvc.xml 静态资源
运行时中文乱码数据库字符集、连接串

5.1 MySQL 8 驱动连接失败:时区、驱动类、SSL 警告连环炸

现象:启动时抛Public Key Retrieval is not allowed或The server time zone value is unrecognized,或者干脆报ClassNotFoundException: com.mysql.jdbc.Driver。

原因:旧代码里写的是com.mysql.jdbc.Driver,MySQL 8 已经改名成com.mysql.cj.jdbc.Driver;而且 8.0 的驱动对时区有强校验,不指定就报错;allowPublicKeyRetrieval 没开,连接时会卡在秘钥交换。

解决:把 driver-class 改成com.mysql.cj.jdbc.Driver,在 jdbc.url 上补serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true。这三个参数一次到位,可以省下至少一小时的排查时间。另外注意,如果改完还是报密码认证失败,去 MySQL 里ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码';重设一下认证方式,MySQL 8 默认的 caching_sha2_password 和旧驱动不兼容。

5.2 Lombok 和 JDK 版本不对付:getter/setter 全部失踪

现象:代码里到处是eq.getStock(),编译报「找不到符号 getStock」,但实体类里明明没写 getter。

原因:Lombok 是在编译期生成方法的,旧版 Lombok 不认识新 JDK 的字节码结构。JDK 17 配 Lombok 1.16.x 必炸,这不是代码写错。

解决:优先把 Lombok 升到 1.18.30 或更新,或者直接用 JDK 8 跑。IDEA 里还要确认安装了 Lombok 插件并勾选 Annotation Processing。如果不想依赖 Lombok,花 20 分钟用 IDE 的 Generate 功能把 getter/setter 生成出来,再删掉 pom 里的依赖,这是最保底、最不玄学的做法。很多学长给的代码其实没开 Lombok,但实体类里用了@Data,这时候优先怀疑依赖版本。

5.3 静态资源 404:登录页打开了但 JS/CSS 全没加载

现象:页面能打开,但样式全丢,F12 里一堆 .css 文件 404。

原因:DispatcherServlet 把/拦截了,把静态资源当成 Controller 路由处理。SpringMVC 默认只会处理带后缀的请求,但配置成/后所有请求都进框架。

解决:在 spring-mvc.xml 里放行静态资源:

<mvc:default-servlet-handler/> <mvc:resources mapping="/static/**" location="/static/"/>

同时检查页面引入路径,JSP 里最好先定义${pageContext.request.contextPath},再用它拼/static/css/...全路径,防止部署路径一改样式全丢。如果加了<mvc:default-servlet-handler/>后 Controller 反而不生效,多半是<mvc:annotation-driven/>没配对,把三行配置同时写进去。

5.4 端口被占用 / Tomcat 重复启动

现象:IDEA 启动没报错,但浏览器打不开;第二次启动直接Port 8080 was already in use。

原因:上次的 Tomcat 进程还挂在后台,或者本机其他服务占了 8080 端口。

解决:Windows 用netstat -ano | findstr 8080查 PID 再 kill;macOS/Linux 用lsof -i:8080。在 IDEA 运行配置里把 HTTP port 改成 8081 或 8088,能避开很多缓存性冲突。还有一个翻车细节:war exploded 部署时 Tomcat 的 Application context 和访问地址不一致,地址栏 404 时先看运行配置里 context 到底写的什么,而不是改代码。

5.5 中文乱码:从 SQL 导入那一刻就已经决定

现象:器材名称在数据库工具里正常,网页上全是问号或乱码。

原因:数据库或表用了 latin1 字符集,或者 jdbc.url 没有characterEncoding=utf8。最隐蔽的是 SQL 文件本身是 UTF-8 编码,但导入工具按 GBK 解析,中文注释和初始数据一起变成乱码写进表里。

解决:建库时指定DEFAULT CHARACTER SET utf8mb4;如果库已建好,用ALTER TABLE equipment CONVERT TO CHARACTER SET utf8mb4;转换。jdbc.url 里的useUnicode=true&characterEncoding=utf8和数据库实际字符集必须一致——只改一个不改另一个时,表现就是「时好时坏」。导入 SQL 时,数据库工具的导入对话框里通常有编码选项,看一眼是不是 UTF-8,这步比事后刷数据省事得多。

6. 答辩与二次开发:把系统演示做成可信的闭环

答辩现场最容易翻车的不是代码 bug,而是「讲不清数据从哪来到哪去」。我习惯在答辩前把演示脚本写成固定顺序,每个动作都注一句「这一步对应哪张表哪个字段变化」。第一遍演示正常路径:登录、新增器材、借出、归还、看库存回补;第二遍演示异常路径:故意借超过库存的器材,等系统弹出「库存不足」,这时候顺势讲出「我在 Service 里做了库存校验,SQL 里也加了 stock > 0 的条件,这是双保险」。这两遍跑下来,只要引导得当,评委基本没有余力再去抠边角。

如果你想给系统加差异化功能,我最推荐先加「押金规则」和「器材预约」。押金额可以在 sys_user 表加 deposit 字段,借出时校验余额,归还后再释放;预约功能则是在 borrow_record 前加一张 appointment 表,把「想借」和「借走」分两步。这两个改动都不大,但答辩故事能讲完整:从单纯记录变成带约束的业务闭环。再加一个展示用的小功能,比如用一条 SQL 统计本月最热门的五件器材:

SELECT e.name, COUNT(*) AS borrow_cnt FROM borrow_record r JOIN equipment e ON r.equipment_id = e.id WHERE MONTH(r.borrow_time) = MONTH(CURDATE()) AND YEAR(r.borrow_time) = YEAR(CURDATE()) GROUP BY e.name ORDER BY borrow_cnt DESC LIMIT 5;

把这条查询放到管理首页,做成图表或简单的 Top 5 表格,比在多张增删改查页面里反复横跳更能回答「你这个系统到底有什么用」。我自己的习惯是一条代码主线走到底:先把基本流程跑通,再按「需求分析、数据库设计、实现、测试」把论文重写一遍,写完发现整篇论文和项目天然对得上。带毕设这几年我见过太多人在最后一天改库存逻辑改崩,所以我的建议始终是:提前两周完成全部功能,留一周只做演示和讲稿排练。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进