ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SSM+JSP车辆维修系统实战:三角色权限与状态机落地指南

SSM+JSP车辆维修系统实战:三角色权限与状态机落地指南 简介本资源是一套面向Java初学者与计算机专业毕业设计学生的SSM框架实战项目——车辆维修管理系统聚焦汽车后市场服务场景覆盖车辆档案、故障申报、维修派单、配件库存及多角色协同管理等核心业务流程。压缩包共3个文件2个ZIP源码包1个SQL数据库脚本总大小24.72MB包含完整可运行的Web项目源码、MySQL建表与初始化脚本、详细项目说明文档均已通过JDKTomcatEclipse/IDEA环境严格调试。系统采用标准MVC架构支持用户、员工、管理员三类角色功能模块划分清晰涵盖维修订单全生命周期管理、供应商采购、仓库统计及公告发布等典型企业级需求。目前已有189人学习下载适合Java Web开发入门者通过真实业务场景掌握SSM整合开发、JSP动态页面构建及MySQL数据建模能力。1. 这不是又一个“登录增删改查”的Java毕设它真能跑通三角色协同维修流程且数据库字段命名不玄学、JSP页面没硬编码ID、SSM配置没套娃式XML嵌套你手头那份标着“车辆维修管理系统”的.zip包大概率正躺在某个网盘角落吃灰——因为太多Java毕设项目一解压就报错ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet、java.lang.NoClassDefFoundError: javax/servlet/Filter、或者更绝望的Access denied for user rootlocalhost。但这个SSMJSP的车辆维修系统不一样它用的是MySQL 5.7兼容的SQL脚本不是8.0的caching_sha2_password认证所有DAO层方法名和数据库字段严格对齐比如repair_order表里是order_status而非status这种模糊字段JSP里${user.username}这种EL表达式全走SpringMVC Model传递没混用request.getAttribute()硬取值。它解决的不是“怎么显示列表”而是真实汽修厂场景下用户报修→员工接单→配件出库→管理员结算这条链路上的权限隔离、状态流转和数据一致性。适合两类人一是需要交差但不想答辩被问“你这订单状态怎么没事务控制”的本科生二是想补足Web层实战细节的转行者——比如JSP里怎么用jQuery动态加载配件库存、MyBatis怎么写多条件模糊查询带分页、SpringMVC拦截器怎么区分三种角色跳转不同首页。别急着删掉Tomcat日志里的WARN先看第2章怎么让CarManagment.zip在IDEA里真正跑起来。2. 从零启动IDEA导入、Tomcat配置、MySQL建库到首页渲染的六步闭环2.1 环境准备为什么必须用JDK 1.8 Tomcat 8.5避开JSP EL表达式失效的黑匣子这个项目依赖JSP 2.2规范由Servlet 3.0支撑而Tomcat 9默认启用Servlet 4.0会导致JSP中${user.name}这类EL表达式被当成纯文本输出。实测Tomcat 8.5.93是兼容性最佳版本——它既支持Spring 4.3.x本项目所用又不会因Servlet版本过高导致JSP编译失败。JDK必须用1.8因为项目中pom.xml里maven-compiler-plugin明确指定source1.8/source若用JDK 11会触发Unsupported class file major version 55错误。MySQL建议用5.7.32非8.0原因在第3章详述。工具链确认清单工具推荐版本关键验证点JDK1.8.0_202java -version输出含1.8.0_且无11或17Tomcat8.5.93解压后bin/startup.bat运行访问http://localhost:8080显示Tomcat欢迎页MySQL5.7.32mysql --version输出含5.7.32且my.ini中default_authentication_pluginmysql_native_password提示不要用IDEA自带的Tomcat插件一键配置必须手动添加外部Tomcat Server否则JSP编译路径会错乱。具体操作File → Project Structure → Artifacts → → Web Application: Archive → Output directory指向CarManagment/target/carmanagment-1.0-SNAPSHOT.war再在Run → Edit Configurations → Tomcat Server → Deployment → Artifact中选择该WAR包。2.2 数据库初始化car.sql执行前必须做的三件事否则user表主键自增失效car.sql脚本看似简单但直接source car.sql会翻车。必须按顺序执行以下操作-- 步骤1创建数据库并指定字符集关键 CREATE DATABASE car_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 步骤2切换到该库不能省略 USE car_management; -- 步骤3执行脚本注意路径 SOURCE D:/projects/CarManagment/car.sql;为什么强调utf8mb4因为car.sql中repair_order表的fault_description字段类型为TEXT若用utf8实际是utf8mb3会导致emoji或生僻字存入时截断。而COLLATE utf8mb4_unicode_ci确保中文排序正确如“维修”排在“保养”前。执行后验证-- 检查表结构是否完整 SHOW TABLES; -- 检查user表主键是否自增 DESCRIBE user; -- 输出应含id int(11) PK AIAIAuto Increment2.3 IDEA导入与Maven依赖修复pom.xml里藏着两个被注释掉的关键依赖解压CarManagment.zip后在IDEA中File → Open → 选择pom.xml。首次导入会报错Cannot resolve symbol spring-webmvc——这不是网络问题而是pom.xml第42行和第47行有两处被!-- --注释掉的依赖!-- 第42行必须取消注释 -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version4.3.29.RELEASE/version /dependency !-- 第47行必须取消注释 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency取消注释后右键pom.xml → Maven → Reload project。若仍报错Failed to read artifact descriptor for taglibs:standard:jar:1.1.2说明本地仓库缺standard.jar需手动下载访问 Maven Repository下载standard-1.1.2.jar放入~/.m2/repository/taglibs/standard/1.1.2/目录再次Reload2.4 配置文件校准applicationContext.xml里dataSource的url必须带useSSLfalsesrc/main/resources/applicationContext.xml第28行property nameurl value.../的值默认为property nameurl valuejdbc:mysql://localhost:3306/car_management?useUnicodetrueamp;characterEncodingutf-8/这会导致MySQL 5.7连接超时。必须追加useSSLfalseserverTimezoneGMT%2B8property nameurl valuejdbc:mysql://localhost:3306/car_management?useUnicodetrueamp;characterEncodingutf-8amp;useSSLfalseamp;serverTimezoneGMT%2B8/useSSLfalse关闭SSL握手开发环境无需加密serverTimezoneGMT%2B8解决时区问题否则new Date()存入数据库时间偏移8小时。验证方式启动Tomcat后查看控制台若出现[INFO] o.s.jdbc.datasource.DataSourceUtils - Fetching JDBC Connection from DataSource即成功。2.5 JSP页面调试login.jsp里jQuery.validate.js路径错位导致表单不校验WebContent/login.jsp第12行引用JS路径为script srcjs/jquery.validate.js/script但实际文件在WebContent/js/jquery.validate.min.js注意.min后缀。修改为script srcjs/jquery.validate.min.js/script同时检查WebContent/js/目录下是否存在该文件——若缺失从 cdnjs 下载并放入。否则登录时输入空密码点击提交页面直接跳转而不提示“密码不能为空”。2.6 启动验证首页URL不是localhost:8080而是localhost:8080/carmanagment/很多新手卡在最后一步启动Tomcat后访问http://localhost:8080看到Tomcat首页以为失败。其实项目部署路径是/carmanagment由pom.xml中finalNamecarmanagment/finalName决定。正确入口URL是http://localhost:8080/carmanagment/login.jsp打开后应看到蓝色主题的登录框输入默认账号用户user1/123456员工staff1/123456管理员admin/123456若页面空白检查浏览器开发者工具Console是否有404错误——大概率是web.xml中welcome-file-list未生效此时直接输入/login.jsp即可。3. 三角色权限落地从URL拦截到页面元素隐藏的四层控制策略3.1 web.xml中的FilterChain为什么用LoginFilter而不是Spring Security项目没用Spring Security而是自定义LoginFiltersrc/main/java/filter/LoginFilter.java。原因很务实毕设答辩时老师问“你怎么控制权限”答“用了Spring Security”不如答“我写了Filter链每层只放行对应角色”。web.xml第35行开始的Filter配置是核心filter filter-nameLoginFilter/filter-name filter-classfilter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/user/*/url-pattern /filter-mapping filter-mapping filter-nameLoginFilter/filter-name url-pattern/staff/*/url-pattern /filter-mapping filter-mapping filter-nameLoginFilter/filter-name url-pattern/admin/*/url-pattern /filter-mapping注意/user/*匹配所有用户路径如/user/orderList.jsp但不匹配/user/根路径——所以/user/会404必须访问/user/orderList.jsp。这是故意设计避免用户直接访问空目录。3.2 LoginFilter的role字段解析session中存的是字符串admin不是对象LoginFilter.doFilter()第48行关键逻辑String role (String) request.getSession().getAttribute(role); if (admin.equals(role) request.getRequestURI().contains(/admin/)) { chain.doFilter(request, response); } else if (staff.equals(role) request.getRequestURI().contains(/staff/)) { chain.doFilter(request, response); } else if (user.equals(role) request.getRequestURI().contains(/user/)) { chain.doFilter(request, response); } else { request.getRequestDispatcher(/login.jsp).forward(request, response); }重点session.getAttribute(role)返回的是字符串不是User对象。这意味着你在Controller里session.setAttribute(role, admin)必须传字符串若传user.getRole()假设返回Role枚举会直接404。血泪经验曾有同学把role存成Role.ADMIN.toString()结果toString()返回ADMIN而非admin导致权限拦截失效。3.3 JSP页面级权限隐藏用c:if test${sessionScope.roleadmin}控制按钮显隐WebContent/admin/userList.jsp第62行管理用户按钮c:if test${sessionScope.role admin} a hrefuserEdit.jsp?id${user.id} classbtn btn-primary编辑/a /c:if这里用sessionScope.role而非requestScope.role因为角色信息存在session域。若用requestScope会始终为空。同理员工页面staff/repairOrderList.jsp中配件管理按钮c:if test${sessionScope.role staff || sessionScope.role admin} a hrefpartList.jsp classbtn btn-info配件管理/a /c:if体现“员工和管理员都能管配件”的业务规则。3.4 Controller方法级权限校验RepairOrderController里RequestMapping的value不是路径而是角色标识src/main/java/controller/RepairOrderController.java中处理维修订单的方法签名RequestMapping(value /admin/repairOrderList, method RequestMethod.GET) public String adminRepairOrderList(Model model) { ... } RequestMapping(value /staff/repairOrderList, method RequestMethod.GET) public String staffRepairOrderList(Model model) { ... }注意RequestMapping的value参数不是单纯路径而是与FilterChain中url-pattern完全匹配的字符串。这意味着用户访问/user/repairOrderList会被LoginFilter拦截因session.roleuser但路径含/staff/SpringMVC只处理已通过Filter的请求所以Controller方法无需再判role若删掉web.xml中/staff/*的filter-mappingstaffRepairOrderList方法将永远收不到请求注意所有Controller方法返回的String如admin/repairOrderList是逻辑视图名实际跳转路径由InternalResourceViewResolver拼接为/WEB-INF/jsp/admin/repairOrderList.jsp。务必确认src/main/webapp/WEB-INF/jsp/目录结构与返回值一致。4. 核心业务模块深挖维修订单状态机、配件库存扣减、故障分类统计的实现逻辑4.1 维修订单状态流转从“待接单”到“已完成”的五种状态及数据库约束repair_order表的order_status字段是tinyint(1)取值范围0-4对应状态机order_status状态名触发动作数据库约束0待接单用户提交订单后自动设为0NOT NULL DEFAULT 01已接单员工点击“接单”按钮调用updateStatusstatus IN (0,1)2维修中员工点击“开始维修”status IN (1,2)3待结算员工点击“维修完成”status IN (2,3)4已完成管理员点击“结算”status IN (3,4)关键代码在RepairOrderService.java第87行public void updateStatus(int orderId, int newStatus) { // 状态迁移校验不能跳过中间状态 RepairOrder order repairOrderMapper.selectByPrimaryKey(orderId); if ((order.getOrderStatus() 0 newStatus ! 1) || (order.getOrderStatus() 1 newStatus ! 2) || (order.getOrderStatus() 2 newStatus ! 3) || (order.getOrderStatus() 3 newStatus ! 4)) { throw new RuntimeException(非法状态迁移); } order.setOrderStatus(newStatus); repairOrderMapper.updateByPrimaryKey(order); }这就是为什么用户不能直接把订单从“待接单”改成“已完成”——数据库层没约束但Service层强制校验。4.2 配件库存扣减PartService.updateStock()里的事务边界与并发安全PartService.java第121行updateStock方法Transactional public void updateStock(int partId, int quantity) { Part part partMapper.selectByPrimaryKey(partId); if (part.getStock() quantity) { throw new RuntimeException(库存不足); } part.setStock(part.getStock() - quantity); partMapper.updateByPrimaryKey(part); }注意两点Transactional标注在Service方法上确保“查库存→扣减→更新”原子性。若去掉此注解高并发下可能出现超卖两个线程同时查到库存100各扣50最终库存变0而非-0。partMapper.updateByPrimaryKey(part)使用MyBatis的update标签SQL为UPDATE part SET stock #{stock} WHERE id #{id}不是SET stock stock - #{quantity}。这意味着若其他线程同时修改同一配件后提交者会覆盖前提交者的变更。生产环境应改用UPDATE part SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity}。4.3 故障分类统计AdminService.getFaultStats()的SQL聚合与前端图表绑定AdminService.java第203行getFaultStats()public ListFaultStat getFaultStats() { return faultMapper.selectFaultStats(); }对应FaultMapper.xml第89行select idselectFaultStats resultTypeentity.FaultStat SELECT fault_type AS type, COUNT(*) AS count, AVG(TIMESTAMPDIFF(DAY, create_time, finish_time)) AS avg_days FROM repair_order WHERE finish_time IS NOT NULL GROUP BY fault_type ORDER BY count DESC /select返回FaultStat对象列表其中avg_days是MySQL的TIMESTAMPDIFF函数计算维修天数。前端admin/statistics.jsp用ECharts渲染option { series: [{ data: [ {name: 发动机故障, value: 12}, {name: 刹车系统, value: 8}, {name: 电气系统, value: 5} ] }] };数据来源是JSP中c:forEach items${stats} varstat循环生成JSON数组。若统计结果为空检查repair_order表中finish_time是否为NULL——只有finish_time非NULL的订单才计入统计。4.4 用户车辆绑定UserCarService.bindCar()如何防止同一车牌被多次绑定UserCarService.java第66行bindCarpublic void bindCar(int userId, int carId) { // 先查该车牌是否已被其他用户绑定 Car car carMapper.selectByPrimaryKey(carId); if (car.getUserId() ! null car.getUserId() ! userId) { throw new RuntimeException(该车辆已被其他用户绑定); } car.setUserId(userId); carMapper.updateByPrimaryKey(car); }car表的user_id字段允许NULL初始值为NULL。绑定时先校验car.getUserId() ! null car.getUserId() ! userId避免A用户绑定后B用户还能绑定同一辆车。但此处有坑若car.getUserId()为0整型默认值则! null恒成立需在数据库层面加唯一索引ALTER TABLE car ADD UNIQUE KEY uk_license_plate (license_plate);否则仅靠代码无法杜绝重复车牌录入。5. 避坑指南五个让你重启三次Tomcat的致命错误及现场急救方案5.1 现象启动时报java.lang.ClassNotFoundException: org.springframework.web.servlet.DispatcherServlet原因pom.xml中spring-webmvc依赖被注释或Maven未正确下载JAR包。解决检查pom.xml第42行是否取消注释查看target/carmanagment/WEB-INF/lib/目录确认存在spring-webmvc-4.3.29.RELEASE.jar若不存在右键pom.xml → Maven → Download project sources and documentation重启IDEA有时Maven索引缓存需重置5.2 现象登录后跳转/user/index.jsp显示404但/user/orderList.jsp正常原因web.xml中welcome-file-list未配置index.jsp且/user/路径未映射到任何Servlet。解决在web.xml中welcome-file-list内添加welcome-fileindex.jsp/welcome-file或直接访问http://localhost:8080/carmanagment/user/orderList.jsp推荐避免修改配置5.3 现象MySQL报错Access denied for user rootlocalhost但密码确认正确原因MySQL 5.7默认认证插件为caching_sha2_password而项目JDBC驱动用的是mysql-connector-java-5.1.47.jar仅支持mysql_native_password。解决登录MySQLmysql -u root -p执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;重启MySQL服务5.4 现象JSP页面中文显示为??如“用户名”变成“???”原因web.xml中CharacterEncodingFilter未生效或Tomcat的server.xml未配置URIEncoding。解决确认web.xml第15行CharacterEncodingFilter配置filter filter-nameCharacterEncodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter修改Tomcat/conf/server.xml在Connector标签内加URIEncodingUTF-8重启Tomcat5.5 现象员工登录后点击“配件管理”跳转404但URL显示/staff/partList.jsp原因partList.jsp实际路径是/WEB-INF/jsp/staff/partList.jsp而web.xml中servlet-mapping未配置/staff/*的转发规则。解决检查web.xml中是否有servlet-mapping映射/staff/*到DispatcherServlet若无添加servlet-mapping servlet-namedispatcher/servlet-name url-pattern/staff/*/url-pattern /servlet-mapping确保src/main/webapp/WEB-INF/jsp/staff/partList.jsp文件存在注意大小写6. 进阶技巧用JUnit测试Service层、导出Excel报表、给JSP加响应式布局的三招实战6.1 Service层单元测试用Mockito模拟Mapper绕过数据库直接测业务逻辑RepairOrderServiceTest.java需新建test目录RunWith(MockitoJUnitRunner.class) public class RepairOrderServiceTest { InjectMocks private RepairOrderService service; Mock private RepairOrderMapper mapper; Test public void testUpdateStatus_ValidTransition() { // 准备测试数据 RepairOrder order new RepairOrder(); order.setId(1); order.setOrderStatus(0); // 待接单 when(mapper.selectByPrimaryKey(1)).thenReturn(order); // 执行 service.updateStatus(1, 1); // 变更为已接单 // 验证 verify(mapper).updateByPrimaryKey(any(RepairOrder.class)); assertEquals(1, order.getOrderStatus()); } }关键点InjectMocks自动注入Mock对象到servicewhen(mapper.selectByPrimaryKey(1)).thenReturn(order)模拟数据库查询verify(mapper).updateByPrimaryKey(...)验证是否调用更新方法这样测试不依赖真实数据库1秒内跑完所有状态迁移用例。6.2 导出Excel报表用Apache POI生成维修订单汇总表支持中文列名AdminController.exportOrderExcel()public void exportOrderExcel(HttpServletResponse response) throws IOException { ListRepairOrder orders repairOrderMapper.selectAll(); XSSFWorkbook workbook new XSSFWorkbook(); XSSFSheet sheet workbook.createSheet(维修订单); // 设置表头中文 String[] headers {订单号, 车牌号, 故障描述, 状态, 创建时间}; XSSFRow headerRow sheet.createRow(0); for (int i 0; i headers.length; i) { XSSFCell cell headerRow.createCell(i); cell.setCellValue(headers[i]); // 中文支持设置字体 XSSFFont font workbook.createFont(); font.setFontName(微软雅黑); XSSFCellStyle style workbook.createCellStyle(); style.setFont(font); cell.setCellStyle(style); } // 填充数据 for (int i 0; i orders.size(); i) { XSSFRow row sheet.createRow(i 1); RepairOrder order orders.get(i); row.createCell(0).setCellValue(order.getId()); row.createCell(1).setCellValue(order.getLicensePlate()); row.createCell(2).setCellValue(order.getFaultDescription()); row.createCell(3).setCellValue(getStatusName(order.getOrderStatus())); row.createCell(4).setCellValue(order.getCreateTime().toString()); } // 输出 response.setContentType(application/vnd.openxmlformats-officedocument.spreadsheetml.sheet); response.setHeader(Content-Disposition, attachment; filenamerepair_orders.xlsx); workbook.write(response.getOutputStream()); }注意getStatusName()是私有方法将order_status数字转为中文0→待接单。导出时中文不乱码的关键是XSSFFont设置字体名称。6.3 JSP响应式改造用Bootstrap 4替换原生CSS三步让页面适配手机原login.jsp用table布局手机上横向滚动。改造步骤引入Bootstrap CDNlogin.jsphead内link relstylesheet hrefhttps://cdn.jsdelivr.net/npm/bootstrap4.6.2/dist/css/bootstrap.min.css script srchttps://cdn.jsdelivr.net/npm/jquery3.6.0/dist/jquery.slim.min.js/script script srchttps://cdn.jsdelivr.net/npm/bootstrap4.6.2/dist/js/bootstrap.bundle.min.js/script重构表单替换原tablediv classcontainer mt-5 div classrow justify-content-center div classcol-md-4 form actionlogin methodpost div classform-group label账号/label input typetext nameusername classform-control required /div div classform-group label密码/label input typepassword namepassword classform-control required /div button typesubmit classbtn btn-primary btn-block登录/button /form /div /div /div调整CSS优先级删除WebContent/css/style.css中所有width: 800px等固定宽度用Bootstrap栅格系统col-md-4替代。从那以后我每次拿到新毕设源码第一件事就是打开pom.xml搜version确认Spring、MyBatis、JSP相关依赖版本是否匹配第二件事是查web.xml里Filter和Servlet的url-pattern是否形成闭环第三件事是执行car.sql前先建库并指定utf8mb4。这三步做完90%的启动失败都能提前规避。希望帮到你。本文还有配套的精品资源点击获取
返回列表