ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于SSM框架的实验室计算机故障报修系统开发实战

2026/9/28 18:39:16 拓冰建站 浏览量
基于SSM框架的实验室计算机故障报修系统开发实战 1. 项目背景与核心痛点解析1.1 实验室设备管理到底难在哪去年我们学院做设备盘点的时候光统计机房里那些“带病运行”的电脑就花了两天时间——有的学生上课时发现机器开不了机直接在QQ群里喊了一嗓子就没了下文有的老师遇到投影仪故障填了纸质报修单之后一周没人处理更头疼的是几位维修师傅每天在几个实验楼之间来回跑手边攒了一叠皱巴巴的报修单根本分不清哪些已经修完、哪些还在等配件。这台java_ssm36实验室计算机故障报修系统就是冲着这些痛点去的。它本质上是一个基于 SSM 框架、用 Java 写的 Web 应用核心解决三件事让师生能在线上快速提交设备故障、让维修人员能按优先级排队处理工单、让管理员随时掌握全校设备的健康状态。如果你正在做 Java 课程设计或者毕业设计这个项目是非常经典的 SSM 入门级课设麻雀虽小五脏俱全覆盖了用户登录、权限控制、增删改查、文件上传、状态流转这些 Web 开发的核心技能。我最初拿到这个题目的时候也想过要不要上 Spring Boot 加 MyBatis-Plus毕竟现在企业里用那套更多一些。但后来还是决定用传统的 SSM 手搭一遍。原因也很实际课程设计的时间窗口大概只有三到四周SSM 这种老老实实写 XML 映射的方式反而能帮你把 SQL、事务、Bean 管理这些底层逻辑吃透后面再上手 Spring Boot 就会轻松很多。1.2 SSM 框架在这个项目里各司其职先说清楚 SSM 是哪三个东西Spring 管对象SpringMVC 管请求分发MyBatis 管数据库操作。这三个框架组合起来正好对应一个请求从浏览器发起到数据库往返的完整链路。以“学生提交故障报修单”这个最核心的动作为例浏览器把表单数据 POST 到服务器SpringMVC 的 DispatcherServlet 先拦截到这个请求通过 HandlerMapping 找到对应的 Controller 方法Controller 里通过 Spring 注入的 Service 对象去调用业务逻辑Service 里再用 MyBatis 的 Mapper 接口去执行 SQL 语句。整个过程看起来层次分明但初期搭框架的时候容易栽跟头——很多人把注解、配置文件、jar 包这三件事混在一起搞最后报错报得一头雾水。提示SSM 的核心思想就是分层解耦。Controller 不直接碰数据库Service 不写 SQL每个类只关心自己那一层的事情这也是后面排查 bug 时的救命稻草。这个系统里我格外看重“状态流转”这件事。报修单不是一张静态的表单它从学生提交那一刻开始就进入了一条生命周期待审核、待接单、维修中、已完成、已驳回。如果只是简单地把状态字段存成一个字符串那么在管理端做筛选的时候就很容易出乱子。所以我在设计阶段就把状态机画清楚每种状态有哪些合法跳转路径都写在了文档里后面写代码的时候思路就非常顺。2. 整体设计思路与数据库建模2.1 三种角色与业务流程的流转关系一个高校机房通常涉及三类人学生、实验员、还有维护外包的工程师。这个系统里我对应设计了三种角色普通用户学生端、维修人员工程师端、系统管理员实验员端。没做页面级的精细权限控制而是在拦截器里按角色做了三级跳转既满足课设要求又不会把自己搞得太累。先看学生端流程登录后能看到“我的报修”列表点“新增报修”会弹出一个表单需要填写故障电脑的资产编号机房每台机器都有贴标签、故障类型开机黑屏、网络不通、软件崩溃、硬件异响等、以及详细的故障描述。提交之后这条单子就进入了“待审核”状态。学生端还能对已完成的工单进行满意度评分方便管理员事后统计维修质量。再来看维修端流程维修人员登录后的首页是“待接单工单池”按提交时间倒序排列每张单子会明显标记出紧急程度和报修时长。点击“接单”后工单变成“维修中”此时维修人员可以看到学生的联系方式以及故障现场的照片如果有的话。维修完成后提交维修结果工单转入“待确认”学生确认或管理员代确认后就变成了“已完成”。管理员端的职责则贯穿全流程可以审核并驳回不合理的报修单、可以把工单重指派给其他维修人员、可以管理设备台账和场地信息、还可以在首页看到几个关键统计数字——今日新增工单、待处理工单、本月完成率。最后这个统计卡片实现起来也不难几条带 GROUP BY 的 SQL 就搞定了。2.2 四张核心表和字段设计中的取舍数据库我用的 MySQL 5.7一共设计了四张表tb_user用户表、tb_device设备表、tb_repair_order报修单表、tb_repair_log维修日志表。有人会问为什么维修日志要单开一张表直接往报修单里塞几个字段不行吗答案是不行——一次维修过程中往往有多次操作记录比如“维修人员接单”“工程师申请延期”“更换电源适配器”每次操作都应该是独立的一行记录否则你事后根本没法复盘这台机器经历过什么。用户表的设计比较常规id、username、password我用了 MD5 加盐虽然现在看加盐强度一般但应对课程设计够了、real_name、role1为普通用户、2为维修人员、3为管理员、phone、create_time。设备表稍微有点讲究id、asset_code 是唯一的资产编号这个字段加了唯一索引因为后面报修时全凭它来确定是哪台机器device_name 只描述“01号机房第3排第5座”不具体到品牌型号因为精确的品牌型号放在单独的配置表里维护成本太高了对课设来说属于过度设计。报修单表是整个系统的核心字段比较多id、user_id提交人、device_id设备、room_name机房位置、fault_type故障类型、description故障描述、photo_url现场照片、status1待审、2待接、3维修中、4已完成、5已驳回、assignee_id指派的维修人员、create_time、accept_time、finish_time。设计时我特别把“提交时间”和“完成时间”分成了两个字段因为后面算平均处理时长的时候直接拿这两个时间戳做差就能得到精确数据不需要额外写逻辑。维修日志表的字段是id、order_id、operator_id、action_type、action_desc、create_time。action_type 我用数字枚举1接单、2开始维修、3补充说明、4完成这样后续做统计和分析非常方便。2.3 为什么状态字段必须用数字枚举我不建议直接在数据库里把状态存成“待审核”“维修中”这种中文文字。原因有两个稳定性差——你很难保证所有写入的地方都使用完全一致的中文一旦有人写成“等待审核”或者“待修理”筛选和统计就全乱了可扩展性差——万一以后要加一个“已退回重报”状态你得改数据库里已存的历史数据。我在代码里建了一个OrderStatusEnum枚举类把状态值、状态描述、可跳转的下一个状态都集中定义在一个类里面。比如“待审核”对应的数字是1允许跳转到2待接和5驳回“维修中”对应的数字是3只允许跳转到4已完成。这样做的好处是Controller 里根本不需要到处散落 magic number所有状态判断都从一个来源取代码可读性和维护性都提升不少。这个设计和安全登录一样属于“一开始可能体会不到等你改需求的时候就真香了”的做法。我见过不少改版项目就是因为状态散落各处最终改了这里漏了那里上线之后出现一堆脏数据。3. 核心功能实现与技术难点详解3.1 登录拦截与角色权限控制的实现思路SSM 项目里做权限控制最朴素的方式就是写一个LoginInterceptor实现 SpringMVC 的HandlerInterceptor接口。拦下来的请求先检查 session 里有没有loginUser没有就重定向到登录页。有了登录态之后再比对请求路径前缀比如/student/**开头的只允许角色为1的用户访问就通过否则跳转到 403 页面。有的人图省事只在 Controller 方法里判断一下if (role ! 1) return error这样也能用但拦截器方式更符合 Web 分层设计思想——权限属于横切关注点不应该散落在业务代码里。Spring AOP 其实也能做切面式权限控制但在课程设计这个层面拦截器足够简洁直观面试官问到的时候你也能讲清楚两者之间的区别。登录时我埋了一个小细节把查到的用户信息存进 session 之后又额外放了一份到request域里。很多同学在 JSP 页面上通过${sessionScope.userName}取用户昵称这样写十次八次倒也没问题但一旦页面被转发而非重定向session 域里的变量在部分容器下会出现延迟失效的坑。直接全部走 request 域取值配合ModelAndView返回视图逻辑上更稳妥。3.2 故障报修单提交与现场图片上传的完整链路报修单提交是整套系统的高频操作我把它分成两步走先上传图片再插入业务数据。图片上传的 Controller 方法接收MultipartFile参数设置大小为 5MB 上限保存路径是项目下的upload/repair/{yyyyMMdd}/文件夹文件名用“时间戳加原始后缀”拼接这样绝大概率不会重名。保存图片之后把存储路径返回给前端前端拿到这个路径后再连同表单数据一起提交后端插入tb_repair_order表。可能有人会问为什么不一次性提交图片和表单一起处理这样确实能少一次请求但图片上传是先要写磁盘的如果事务回滚了磁盘上的文件不会跟着回滚——把图片上传和业务入库拆开至少能保证脏数据不会太脏。表单校验方面我在前端用了简单的 jQuery 校验在后端 Controller 里也做了一遍非空校验。前端校验是为了提升用户体验后端校验才是真正的安全防线这个意识从课程设计阶段就要养成。比如故障描述字段我限制它最多 200 字如果后端不校验长度一个恶意用户直接 POST 超长字符串数据库这行记录就会变得异常庞大。3.3 维修接单与状态流转的事务控制“维修人员点击接单”这个操作虽然看起来只是一次更新但背后涉及两次写操作把报修单的状态从“待接单”改成“维修中”同时往维修日志表里插入一条接单记录。这两个动作必须放在同一个事务里也就是要么都成功要么都失败——如果只改了状态而日志没插上后面做审计的时候就找不到谁接的单。在 Spring 里这个需求用Transactional注解就能解决。给 Service 层的方法加上这个注解Spring 会为这个方法开启一个数据库事务方法体内抛出RuntimeException时自动回滚。但有一个常见的坑Transactional默认只在抛出RuntimeException或Error时回滚如果你 catch 住了异常没有继续抛事务就形同虚设。我刚开始做的时候也犯过这个错后来养成了习惯——在 catch 块里把异常重新包装成自定义业务异常再抛出让事务管理机制真正生效。另一个需要小心的点是状态流转时要防止“跳状态”。比如学生刚提交的单子别人是不能直接把它置为“已完成”的。我在 Service 层写了一个公用的changeOrderStatus(orderId, targetStatus)方法内部先去查当前状态再对照枚举里定义的可跳转集合判断是否合法。这比在 Controller 里到处 if else 要集中得多同事后来 review 我的代码时特意夸了这一点。4. 完整实操过程记录4.1 开发环境准备与工程目录结构先交代我的环境配置JDK 1.8SSM 用 8 是最稳妥的如果电脑上装了 17记得在 IDEA 里把 Project Structure 的 SDK 和 language level 都改成 8否则编译会报错Maven 3.6.3用来管理依赖Tomcat 8.5部署环境IDEA 2021.1开发工具。数据库连接池我用了 Druid 1.1.20数据库版本 MySQL 5.7。工程结构遵循 Maven 的标准约定java_ssm36_lab_repair ├── pom.xml ├── src/main/java │ ├── com.xxx.controller // 控制层UserController、RepairOrderController 等 │ ├── com.xxx.service // 业务层接口 实现 │ ├── com.xxx.mapper // MyBatis 持久层接口 │ ├── com.xxx.pojo // 实体类User、Device、RepairOrder │ ├── com.xxx.interceptor // 登录拦截器 │ └── com.xxx.util // 工具类图片上传、常量枚举 ├── src/main/resources │ ├── spring-mvc.xml // SpringMVC 配置 │ ├── spring-mybatis.xml // Spring MyBatis 整合配置 │ ├── jdbc.properties // 数据库连接信息 │ └── mybatis-config.xml // MyBatis 全局配置 └── src/main/webapp ├── WEB-INF/web.xml // Web 部署描述文件 ├── static // css、js、图片资源 └── jsp // 视图页面login.jsp、order_list.jsp 等pom.xml里我重点加入的依赖有spring-webmvc 5.0.8、mybatis 3.4.6、mybatis-spring 1.3.2、mysql-connector-java 5.1.46、druid 1.1.20、jstl 1.2、jackson-databind用于 JSON 数据交互。版本之间要匹配比如 mybatis-spring 老版本在高版本 Spring 下会报警告但不影响运行为了少踩坑我统一使用了 5.0.x 系列。4.2 数据库脚本与连接池配置我直接给出建表脚本的核心部分你在自己机器上执行时根据实际需求修改即可。有一点经验之谈字符集一定要用utf8mb4而不是utf8否则用户提交的故障描述里如果连 emoji 表情都存不下到时候写入直接报错。CREATE TABLE tb_user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT MD5加盐密码, real_name VARCHAR(50) DEFAULT NULL, role INT NOT NULL DEFAULT 1 COMMENT 1学生 2维修 3管理员, phone VARCHAR(20) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;连接池配置我就直接贴了jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/lab_repair?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你的密码 jdbc.initialSize5 jdbc.maxActive20这里有个坑——serverTimezoneAsia/Shanghai这一段必不可少。MySQL 5.7 与 JDBC 驱动之间存在时区差异不加这个参数你在插入时间字段时会发现数据库存储的时间比当前时间少了 8 小时那是因为驱动默认用了 UTC 时区。我第一版跑起来的时候看到这个偏差愣了好一会儿才排查出来。4.3 关键代码实现报修列表分页与条件筛选列表展示是系统里使用频率最高的页面我不建议把数据全查出来返回前端数据量小还好说一旦积累到一个学期的工单页面就会明显卡顿。所以在 RepairOrderController 里我使用了 PageHelper 插件做分页查询。PageHelper 的用法很简单先调用PageHelper.startPage(pageNum, pageSize)紧接着执行 Mapper 的查询方法再拿到PageInfo对象就有分页数据了。public String list(Model model, RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, String status, String keyword) { PageHelper.startPage(pageNum, pageSize); ListRepairOrderVO list repairOrderService.queryOrderByCondition(status, keyword); PageInfoRepairOrderVO pageInfo new PageInfo(list); model.addAttribute(pageInfo, pageInfo); return order_list; }条件筛选我是放在 Mapper 的 XML 里用动态 SQL 实现的。MyBatis 的where和if标签在这里就派上了用场如果 status 为空就不拼接状态条件keyword 为空就不进行模糊搜索。这一块如果前端还希望保留查询条件那还得注意一个问题翻页时把查询条件传回后端。我自己写的分页标签里把 status 和 keyword 都作为参数拼到了页码链接后面否则用户翻到第二页就会莫名其妙丢掉筛选条件体验会很差。维修人员移动端操作的时候要传 JSON 数据加快表单提交效率我记得在“维修完成”那个接口用ResponseBody配合MapString,Object返回结果。这里踩过一个小坑返回 JSON 前一定要把null字段处理掉或转成空字符串否则前端 JS 拿到null以后如果直接用点操作符访问其属性整个页面渲染就会断裂。5. 常见问题与排查技巧实录5.1 前端 JSP 页面循环标签用不了这个问题发生概率极高基本每个第一次上手 SSM 的人都会碰到。JSP 页面里想用c:forEach遍历报修单列表结果启动 Tomcat 之后页面直接报错“Unknown tag (c:forEach)”。很多人会怀疑是不是 JSTL 的 jar 包没导入其实不是问题是 JSP 页面顶部没有加 taglib 指令。解决方案是在每个用到 JSTL 标签的 JSP 顶部加上% taglib urihttp://java.sun.com/jsp/jstl/core prefixc %只要加了这一行c:forEach、c:if这些标签就能正常用了。注意 uri 里的版本号可以不写也可写成jakarta.tags.core新容器样式但如果你用的是老式 Tomcat 8还是老老实实用http://java.sun.com/jsp/jstl/core。小心不要漏掉 web.xml 里的 servlet 2.5 版本声明某些情况下老版本的 web.xml 头会导致 JSP 页面解析 JSTL 时行为不一致。5.2 表单重复提交导致生成多条报修单这个问题是我自己测出来的在“新增报修”页面快速点击两次提交按钮最终数据库里出现了两条一模一样的工单。这属于典型的重复提交问题虽然课程设计里不强制要求解决但面试官问到“你怎么防止重复提交”时这个项目经历就是你最好的答案。我当时用了最简单方案前端提交后立即将按钮设置为 disabled 状态并在后端用 session 令牌机制双重保证。具体做法进入新增页面时生成一个随机 token 存到 session 中表单提交时带着这个 token后端处理器校验 token 和 session 中的值一致就执行操作并清空 session不为空就拒绝处理。这样即使前端按钮失效恶意刷新也无法产生第二单。5.3 上传图片中文文件名导致页面乱码有用户传了一张叫“主机电源烧毁.jpg”的图片保存到服务器后数据库里存的 URL 是正确的中文但前台页面用这个 URL 显示图片时却变成了一片空白。排查后发现问题在于 URL 编码Tomcat 默认对 URL 中的非 ASCII 字符处理不太友好中文文件名在浏览器请求时会被编码成百分号加字节的形式如果后端没有对应的解码逻辑就会 404。解决方式有两种要么在上传时对文件名做处理统一转换成 UUID 加后缀从根源上杜绝中文字符要么在 Tomcat 的server.xml的Connector节点加上URIEncodingUTF-8属性。我自己实践中是两件事都做了——上传时重命名文件、Connector 里同时配置 URIEncoding双保险。5.4 SQL 注入测试与 MyBatis #{} 的正确使用曾经有位同学为了图方便在 Mapper XML 中直接拼接 SQL 字符串写了${keyword}结果页面一输入 OR 11 --就把整张表的数据都查出来了。这个例子不做实验不知道有多吓人。MyBatis 写 SQL 时一定要用#{}占位符它是预编译语句天然防注入${}是字符串替换除非是动态排序列名否则绝对不要用。排查这个问题的方式也很简单把所有 Mapper XML 文件全局搜索一遍${开头的表达式凡是出现在条件值位置的全部改成#{}。顺便说一句正确地使用#{}而不是{}也是面试中的高频八股问题你在这个项目里真正理解了它的原理讲出来就会比其他候选人深入得多。5.5 时间日期在前后端之间的时区差前面提到了 JDBC 连接的时区问题这里再补充一个前端展示格式的坑。数据库里存的是DATETIME类型后端 Java 对象里是java.util.Date转换成 JSON 输出后前端显示成了2024-05-11T15:30:00.00000:00这种带时区的长字符串完全不符合用户阅读习惯。我用的解决方案是在 Jackson 配置里做全局日期格式化mvc:annotation-driven mvc:message-converters bean classorg.springframework.http.converter.json.MappingJackson2HttpMessageConverter property nameobjectMapper bean classcom.fasterxml.jackson.databind.ObjectMapper property namedateFormat bean classjava.text.SimpleDateFormat constructor-arg valueyyyy-MM-dd HH:mm:ss/ /bean /property /bean /property /bean /mvc:message-converters /mvc:annotation-driven另外一个隐藏问题Java 实体类的时间字段如果用java.util.Date和数据库 DATETIME 映射时 MyBatis 会自动处理但如果你用了LocalDateTimeJava 8 之后更推荐的类型要注意 MyBatis 和数据库驱动版本是否支持老版本的 connector 是不支持 JSR-310 时间类型的。我的做法是课设阶段继续用java.util.Date简单省事不出错。6. 写在最后的几点个人心得6.1 从课设项目到面试谈资的经验转化这个项目做完以后我在准备 Java 岗位面试时发现一个很有意思的现象面试官对项目本身的业务细节反而不怎么追问更关注的是你写每一行代码时候的思考。比如“为什么状态字段用数字而不是字符串”“为什么图片上传和业务入库要分成两步”“为什么权限用拦截器而不用 AOP”。这些问题在这个项目里每一个都能给出具体、有依据的回答远比背八股文来得扎实。如果你时间充裕强烈建议在课设基础上再加两个小功能一个是站内消息通知——维修完成后给提交人发一条通知这就要用到消息表设计和定时任务轮询另一个是导出维修报表可以用poi生成 Excel把每月各机房的维修次数统计出来。这两个功能都不复杂但往简历上写的时候“消息通知”和“数据导出”这两个关键词的分量会明显不同。6.2 开发习惯上的一些真诚建议再分享两个我踩过几次坑之后养成的习惯。第一个是动手写代码前一定先把 MySQL 库表建好并填入几条测试数据千万不要边写边建表——代码里用到的字段名如果频繁改名反复折腾会让你怀疑人生。第二个是每个接口写完以后立刻用 Postman 测一遍边界条件比如不传参数、传超长字符串、传不存在的 id不要只测正常路径。积累这些测试经验的好处是答辩时老师随便输入一个异常值你的系统都能给出友好提示这一下就能和其他人拉开差距。如果你准备把这个项目部署到服务器上注意两点数据库连接改用本机 IP 的权限账号不要用 root 裸奔Tomcat 版本和管理员密码都要修改默认配置但这些都是后话了。希望这篇经验分享对正在做 SSM 课设或者想入坑 Java Web 开发的朋友有帮助。项目本身不难但每一个容易踩坑的细节我都尽量写清楚了按着这个流程走下来你不仅能跑通一个完整的报修系统还能在这个过程中真正把 SSM 的技术脉络摸透这就比单为了交作业有意义得多。