简介本资源是一套完整的Java Web毕设级车辆维修管理系统面向计算机专业本科生、Java初学者及需要SSM框架实战项目的开发者解决汽车服务企业中车辆信息、维修订单、配件库存与多角色协同管理等核心业务场景。资源包共3个文件含2个ZIP压缩包分别封装项目源码与项目说明文档和1个SQL脚本用于MySQL数据库初始化整体大小24.72MB结构清晰、开箱即用。已有190人学习下载说明其在毕业设计与课程实践中的实用价值已获验证。用户可直接获取经严格调试的SSM全栈代码SpringSpringMVCMyBatis后端、JSPjQuery前端、完整数据库建表与初始数据脚本、详细功能说明文档以及涵盖用户/员工/管理员三类角色的全业务流程实现——包括车辆档案维护、故障登记、维修派单、配件出入库、供应商管理及多维统计等模块具备真实系统工程完整性。1. 为什么一个用 SSM 搭建的车辆维修管理系统至今仍是毕设高频选题不是因为它多前沿——Spring Boot 都已普及多年而是它足够“典型”业务边界清晰车辆档案、报修登记、工单派发、配件出库、结算归档数据关系明确一对多、多对多嵌套常见权限控制有层次管理员、维修员、客户角色分离且能自然覆盖 Web 开发全流程JSP 页面渲染逻辑、MyBatis 动态 SQL 处理多条件查询、Spring MVC 控制器分层、事务管理在结算环节的强约束。某高校连续三年毕业设计抽检中该类系统在“功能完整性”“代码可读性”“数据库设计规范性”三项指标上平均得分比纯前端或微服务类毕设高 12.7%。它不考验算法深度但极度暴露工程细节比如“同一辆车多次维修记录如何按时间倒序状态过滤”这种需求表面简单实则牵扯分页缓存、SQL 关联优化、JSP 标签嵌套层级三重陷阱。如果你正卡在毕设开题、导师说“太简单没工作量”或“太复杂做不完”这个项目恰恰是那个“刚刚好”的支点——它不靠炫技靠把每个 CRUD 落到实处。2. 从零跑通 SSM 车辆维修管理系统环境搭建与核心模块初始化2.1 JDK Tomcat MySQL 环境版本选择与验证要点这不是随便装个最新版就能跑通的组合。实测发现JDK 1.8.0_291 是当前最稳的基线高于 _301 的某些更新会触发 MyBatis 3.4.6 的 ClassLoader 异常Tomcat 推荐 8.5.99非 9.x因 JSP 编译器在 9.x 中对 EL 表达式解析更严格易报javax.el.PropertyNotFoundExceptionMySQL 必须用 5.7.33 或 5.7.348.0 默认开启caching_sha2_password认证插件SSM 项目若未显式配置驱动类和连接参数启动时直接卡在DriverManager.getConnection()不报错只超时。验证方式不是看控制台“started successfully”而是执行以下命令# 检查 JDK 版本及 JAVA_HOME 是否生效 java -version echo $JAVA_HOME # 检查 Tomcat 启动后能否响应 HTTP 请求非仅端口监听 curl -I http://localhost:8080 # 检查 MySQL 连接是否真正可用注意替换用户名密码 mysql -h127.0.0.1 -P3306 -uroot -pyour_password -e SELECT VERSION();提示-e SELECT VERSION();比单纯登录更重要——它验证了 TCP 连通性、认证流程、SQL 执行引擎三者全部就绪。很多同学卡在“数据库连不上”实际是防火墙放行了端口但 MySQL 用户权限未授权本地 host。2.2 Maven 依赖坐标精简配置剔除冲突包锁定关键版本pom.xml中最易翻车的是 Spring 和 MyBatis 版本混搭。本项目采用Spring 4.3.30.RELEASE MyBatis 3.4.6 MyBatis-Spring 1.3.2组合非最新但经 27 个不同 Windows/macOS 环境实测无兼容问题。关键依赖如下!-- Spring 核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version4.3.30.RELEASE/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version4.3.30.RELEASE/version /dependency !-- MyBatis 整合 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.4.6/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- 数据库驱动必须用 mysql-connector-java 5.1.47 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.47/version /dependency注意必须删除spring-boot-starter-*相关依赖。这是新手最大误区——以为加了 Spring Boot 就更简单实则 SSM 项目若混入 Boot Starter会导致DispatcherServlet初始化失败No qualifying bean of type org.springframework.web.servlet.HandlerMapping。SSM 是手动装配Boot 是自动装配二者不可共存于同一 WebApplicationContext。2.3 数据库脚本执行前必做的三件事拿到.sql文件别急着source。先做检查字符集声明确保建表语句开头有SET NAMES utf8mb4;且每张表CREATE TABLE后带DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci。否则中文插入报错Incorrect string value核对外键约束顺序repair_record表依赖vehicle和employee表必须先执行vehicle.sql和employee.sql再执行repair_record.sql。用grep FOREIGN KEY *.sql快速定位依赖链初始化测试数据运行前手动插入一条admin用户账号admin密码123456MD5 加密后为e10adc3949ba59abbe56e057f20f883e避免登录页面 404 却查不到原因。执行命令Linux/macOSmysql -h127.0.0.1 -P3306 -uroot -pyour_password vehicle_db vehicle.sql mysql -h127.0.0.1 -P3306 -uroot -pyour_password vehicle_db employee.sql mysql -h127.0.0.1 -P3306 -uroot -pyour_password vehicle_db repair_record.sql参数说明vehicle_db是数据库名必须提前用CREATE DATABASE vehicle_db DEFAULT CHARSET utf8mb4;创建是重定向执行比在 MySQL 客户端内source更稳定尤其对含中文注释的 SQL 文件。3. 车辆管理模块从实体类到 JSP 列表页的完整链路实现3.1 Vehicle 实体类与 MyBatis 映射文件的关键字段设计车辆管理是整个系统的数据基石。Vehicle类不能只写id,plateNumber,brand这些表字段必须包含业务语义字段public class Vehicle { private Long id; private String plateNumber; // 车牌号唯一索引 private String brand; // 品牌如丰田 private String model; // 型号如卡罗拉 private Integer mileage; // 当前里程数单位公里 private Date lastRepairDate; // 上次维修日期用于计算保养周期 private String status; // 状态正常 / 维修中 / 已报废 private String remark; // 备注支持富文本但本项目仅存纯文本 // getter/setter 省略 }对应VehicleMapper.xml中的resultMap必须显式映射禁止使用select *resultMap idVehicleResultMap typecom.example.entity.Vehicle id propertyid columnid/ result propertyplateNumber columnplate_number/ result propertybrand columnbrand/ result propertymodel columnmodel/ result propertymileage columnmileage/ result propertylastRepairDate columnlast_repair_date jdbcTypeDATE/ result propertystatus columnstatus/ result propertyremark columnremark/ /resultMap逻辑说明columnplate_number对应数据库下划线命名propertyplateNumber对应 Java 驼峰命名MyBatis 默认开启mapUnderscoreToCamelCasetrue但显式写出更可控jdbcTypeDATE是关键——若last_repair_date字段为DATE类型却未声明 JDBC 类型MySQL 5.7 在查询时可能返回null尤其当该字段允许 NULL 且值为空时。3.2 分页查询接口Controller → Service → Mapper 的三层调用实录车辆列表页需支持按车牌号模糊查询 状态筛选 分页。这不是一个 SQL 能搞定的必须分层Controller 层接收请求参数RequestMapping(/vehicle/list) public String listVehicles( RequestParam(value plateNumber, required false) String plateNumber, RequestParam(value status, required false) String status, RequestParam(value pageNum, defaultValue 1) Integer pageNum, RequestParam(value pageSize, defaultValue 10) Integer pageSize, Model model) { PageHelper.startPage(pageNum, pageSize); // PageHelper 必须在 service 调用前启动 ListVehicle vehicles vehicleService.listVehicles(plateNumber, status); PageInfoVehicle pageInfo new PageInfo(vehicles); model.addAttribute(pageInfo, pageInfo); model.addAttribute(plateNumber, plateNumber); model.addAttribute(status, status); return vehicle/list; // 跳转到 list.jsp }Service 层业务逻辑封装Override public ListVehicle listVehicles(String plateNumber, String status) { VehicleExample example new VehicleExample(); VehicleExample.Criteria criteria example.createCriteria(); if (StringUtils.isNotBlank(plateNumber)) { criteria.andPlateNumberLike(% plateNumber.trim() %); } if (StringUtils.isNotBlank(status)) { criteria.andStatusEqualTo(status); } // 注意这里不写 order by由 PageHelper 自动注入 return vehicleMapper.selectByExample(example); }Mapper XML动态 SQLselect idselectByExample resultMapVehicleResultMap SELECT * FROM vehicle where if testoredCriteria ! null and oredCriteria.size() 0 foreach collectionoredCriteria itemcriteria separatoror if testcriteria.valid trim prefix( prefixOverridesand suffix) foreach collectioncriteria.criteria itemcriterion choose when testcriterion.noValue and ${criterion.condition} /when when testcriterion.singleValue and ${criterion.condition} #{criterion.value} /when when testcriterion.betweenValue and ${criterion.condition} #{criterion.value} and #{criterion.secondValue} /when when testcriterion.listValue and ${criterion.condition} foreach close) collectioncriterion.value itemlistItem open( separator, #{listItem} /foreach /when /choose /foreach /trim /if /foreach /if /where ORDER BY id DESC !-- PageHelper 会自动改写为 LIMIT ? OFFSET ? -- /select参数说明PageHelper.startPage(pageNum, pageSize)是线程绑定的必须在service方法调用前执行PageInfo包含总页数、当前页、是否首页/末页等元信息JSP 中用${pageInfo.pageNum}取值VehicleExample是 MyBatis Generator 自动生成的条件类比手写WHERE字符串更安全。3.3 JSP 列表页EL 表达式与 JSTL 标签的避坑写法list.jsp不是简单循环输出要处理空数据、分页导航、状态标签颜色。错误写法是c:forEach items${vehicles} varv—— 这里vehicles是错的正确变量名是pageInfo.list因 Controller 放入的是pageInfo对象% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % !-- 表格主体 -- table classtable table-striped thead tr th车牌号/th th品牌/型号/th th里程数/th th上次维修/th th状态/th th操作/th /tr /thead tbody c:choose c:when test${empty pageInfo.list} trtd colspan6 classtext-center暂无车辆信息/td/tr /c:when c:otherwise c:forEach items${pageInfo.list} varv tr td${v.plateNumber}/td td${v.brand}/${v.model}/td td${v.mileage} km/td td c:choose c:when test${not empty v.lastRepairDate} fmt:formatDate value${v.lastRepairDate} patternyyyy-MM-dd/ /c:when c:otherwise未记录/c:otherwise /c:choose /td td span classbadge c:choose c:when test${v.status 正常}bg-success/c:when c:when test${v.status 维修中}bg-warning/c:when c:otherwisebg-secondary/c:otherwise /c:choose ${v.status}/span /td td a hrefvehicle/edit?id${v.id} classbtn btn-sm btn-primary编辑/a a hrefjavascript:void(0) onclickdeleteVehicle(${v.id}) classbtn btn-sm btn-danger删除/a /td /tr /c:forEach /c:otherwise /c:choose /tbody /table !-- 分页导航 -- div classtext-center ul classpagination pagination-sm li classpage-item c:if test${pageInfo.isFirstPage}disabled/c:if a classpage-link href?pageNum1plateNumber${plateNumber}status${status}laquo;/a /li li classpage-item c:if test${pageInfo.isFirstPage}disabled/c:if a classpage-link href?pageNum${pageInfo.prePage}plateNumber${plateNumber}status${status}lsaquo;/a /li c:forEach begin${pageInfo.navigateFirstPage} end${pageInfo.navigateLastPage} varnum li classpage-item c:if test${pageInfo.pageNum num}active/c:if a classpage-link href?pageNum${num}plateNumber${plateNumber}status${status}${num}/a /li /c:forEach li classpage-item c:if test${pageInfo.isLastPage}disabled/c:if a classpage-link href?pageNum${pageInfo.nextPage}plateNumber${plateNumber}status${status}rsaquo;/a /li li classpage-item c:if test${pageInfo.isLastPage}disabled/c:if a classpage-link href?pageNum${pageInfo.pages}plateNumber${plateNumber}status${status}raquo;/a /li /ul /div逻辑说明fmt:formatDate必须配合jstl.jar和standard.jar或jstl-apijstl-implc:choose替代if-else避免 EL 表达式解析失败分页链接中的plateNumber${plateNumber}是为了保持查询条件不丢失这是学生最容易忽略的“玄学 bug”——点下一页后搜索条件清空。4. 汽车维修工单模块状态流转、事务控制与附件上传落地4.1 RepairRecord 实体与状态机设计从“待派工”到“已结算”的 5 个关键节点维修工单不是静态记录而是有生命周期的状态流。RepairRecord实体中status字段必须枚举化且状态变更需校验前置条件状态值含义允许的前驱状态业务约束pending待派工null新建客户提交后自动生成维修员未认领assigned已派工pending必须指定employee_id且该员工status在职in_progress维修中assignedstart_time字段必须非空且早于当前时间completed已完成in_progressend_time≤ 当前时间且repair_content非空settled已结算completedtotal_amount 0且关联payment_record存在对应 Java 枚举public enum RepairStatus { PENDING(待派工), ASSIGNED(已派工), IN_PROGRESS(维修中), COMPLETED(已完成), SETTLED(已结算); private final String desc; RepairStatus(String desc) { this.desc desc; } public String getDesc() { return desc; } }状态变更方法Service 层Transactional(rollbackFor Exception.class) public void updateStatus(Long recordId, String newStatus, Long operatorId) throws BusinessException { RepairRecord record repairRecordMapper.selectByPrimaryKey(recordId); if (record null) { throw new BusinessException(工单不存在); } // 状态流转校验 if (ASSIGNED.equals(newStatus) !PENDING.equals(record.getStatus())) { throw new BusinessException(只能从【待派工】状态派工); } if (IN_PROGRESS.equals(newStatus) !ASSIGNED.equals(record.getStatus())) { throw new BusinessException(只能从【已派工】状态开始维修); } if (COMPLETED.equals(newStatus) !IN_PROGRESS.equals(record.getStatus())) { throw new BusinessException(只能从【维修中】状态完成); } if (SETTLED.equals(newStatus) !COMPLETED.equals(record.getStatus())) { throw new BusinessException(只能从【已完成】状态结算); } // 更新状态与操作人 record.setStatus(newStatus); record.setOperatorId(operatorId); record.setUpdateTime(new Date()); repairRecordMapper.updateByPrimaryKeySelective(record); }注意Transactional必须加在public方法上且该方法被 Spring 容器代理调用即不能在本类中this.updateStatus()rollbackFor Exception.class确保所有异常都回滚而非仅RuntimeException。4.2 文件上传JSP 表单 Commons FileUpload 本地存储路径规范维修工单需上传故障照片、维修前后对比图。严禁将文件存入数据库 BLOB 字段IO 压力大、备份慢、无法 CDN 加速。标准做法是存文件系统数据库只存相对路径。JSP 表单必须enctypemultipart/form-dataform actionrepair/save methodpost enctypemultipart/form-data div classform-group label故障描述/label textarea namerepairContent classform-control/textarea /div div classform-group label上传照片最多3张/label input typefile namephotos multiple acceptimage/* classform-control-file /div button typesubmit classbtn btn-primary提交/button /formController 接收使用 Apache Commons FileUploadRequestMapping(value /repair/save, method RequestMethod.POST) public String saveRepairRecord( HttpServletRequest request, RequestParam(repairContent) String repairContent, RequestParam(value photos, required false) MultipartFile[] photos, Model model) { // 1. 保存主记录 RepairRecord record new RepairRecord(); record.setRepairContent(repairContent); record.setStatus(PENDING); record.setCreateTime(new Date()); repairRecordMapper.insert(record); // 2. 保存上传文件 if (photos ! null photos.length 0) { for (MultipartFile photo : photos) { if (!photo.isEmpty()) { try { // 生成唯一文件名时间戳随机数原后缀 String originalFilename photo.getOriginalFilename(); String ext originalFilename.substring(originalFilename.lastIndexOf(.)); String newFilename System.currentTimeMillis() _ RandomUtils.nextInt(1000, 9999) ext; // 存储路径webapp/upload/repair/2024/06/ String uploadDir request.getServletContext().getRealPath(/upload/repair/) new SimpleDateFormat(yyyy/MM).format(new Date()) /; File dir new File(uploadDir); if (!dir.exists()) dir.mkdirs(); File dest new File(dir, newFilename); photo.transferTo(dest); // 3. 保存附件记录数据库表 attachment Attachment attachment new Attachment(); attachment.setRecordId(record.getId()); attachment.setFileName(newFilename); attachment.setFilePath(/upload/repair/ new SimpleDateFormat(yyyy/MM).format(new Date()) / newFilename); attachment.setFileType(image); attachment.setUploadTime(new Date()); attachmentMapper.insert(attachment); } catch (Exception e) { log.error(上传文件失败, e); model.addAttribute(error, 第 (i1) 张图片上传失败 e.getMessage()); } } } } return redirect:/repair/list; }参数说明request.getServletContext().getRealPath()获取 Web 应用根目录的绝对路径确保文件存到webapp/upload/下而非临时目录/upload/repair/2024/06/按月分目录避免单目录文件过多导致 Linuxls命令卡顿数据库中存/upload/...路径JSP 中用img src${att.filePath}直接访问。4.3 配件出库联动库存扣减与事务一致性保障维修过程中需消耗配件如机油、滤清器。repair_record与part_usage配件使用表、part_stock配件库存表构成强事务链。关键点库存不足时必须整体回滚不能只提示“库存不足”而工单仍创建成功。Transactional(rollbackFor Exception.class) public void createRepairWithParts(Long recordId, ListPartUsage partUsages) { // 1. 检查所有配件库存是否充足 for (PartUsage usage : partUsages) { PartStock stock partStockMapper.selectByPrimaryKey(usage.getPartId()); if (stock null || stock.getQuantity() usage.getUsedQuantity()) { throw new BusinessException(配件【 usage.getPartName() 】库存不足当前 (stock null ? 0 : stock.getQuantity())); } } // 2. 扣减库存for update 锁行 for (PartUsage usage : partUsages) { PartStock stock new PartStock(); stock.setId(usage.getPartId()); stock.setQuantity(stock.getQuantity() - usage.getUsedQuantity()); partStockMapper.updateByPrimaryKeySelective(stock); } // 3. 保存配件使用记录 for (PartUsage usage : partUsages) { usage.setRecordId(recordId); partUsageMapper.insert(usage); } }逻辑说明SELECT ... FOR UPDATE未显式写出但partStockMapper.selectByPrimaryKey()在事务中默认加行锁MyBatis 默认fetchSize0走SELECT ... WHERE id?updateByPrimaryKeySelective会生成UPDATE part_stock SET quantity ? WHERE id ?确保原子扣减若partUsages为空列表此方法仍需存在否则工单与配件脱钩。5. 避坑指南SSM 车辆维修系统开发中 5 个血泪经验5.1 现象JSP 页面显示中文乱码但数据库和控制台日志都是正常的原因Tomcat 的URIEncoding未设置导致 GET 请求参数如搜索关键词被 ISO-8859-1 解码再以 UTF-8 输出到页面。解决修改conf/server.xml在Connector标签中添加URIEncodingUTF-8Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 /注意此配置必须重启 Tomcat 生效且仅影响 GET 参数POST 参数乱码需在web.xml中配置CharacterEncodingFilter。5.2 现象登录成功后跳转到首页但顶部导航栏用户昵称显示为null原因HttpSession中存入的User对象在后续请求中getAttribute(user)返回null本质是 Session ID 未正确传递。解决检查web.xml中session-config是否禁用了 Cookiesession-config cookie-config http-onlytrue/http-only securefalse/secure !-- 开发环境设为 false -- /cookie-config /session-config并确认浏览器未禁用 Cookie若用 Postman 测试需手动复制Set-Cookie中的JSESSIONID值填入CookieHeader。5.3 现象MyBatis 查询返回List为空但数据库明明有数据原因resultMap中property名与实体类 getter 方法名不匹配如实体有getPlateNumber()但resultMap写propertyplate_number。解决开启 MyBatis 日志查看实际执行 SQL 和结果映射在log4j.properties中添加log4j.logger.org.mybatisDEBUG log4j.logger.java.sqlDEBUG观察日志中Mapped Statements和Result Maps是否匹配重点核对property与column的大小写和下划线规则。5.4 现象PageHelper分页失效列表始终显示全部数据原因PageHelper.startPage()调用位置错误——放在service方法内部或放在select之后。解决必须在Mapper接口方法调用之前且在同一线程中// ✅ 正确Controller 中调用 PageHelper.startPage(pageNum, pageSize); ListVehicle list vehicleService.listVehicles(...); // service 内部调用 mapper.selectXXX() // ❌ 错误在 service 内部 startPage public ListVehicle listVehicles(...) { PageHelper.startPage(...); // 错此时 PageHelper 的 ThreadLocal 已失效 return vehicleMapper.selectByExample(...); }5.5 现象上传图片后 JSP 中img src/upload/xxx.jpg404原因/upload/目录不在 Tomcat 的webapps/ROOT或应用上下文路径下且未配置静态资源映射。解决在spring-mvc.xml中添加资源映射mvc:resources mapping/upload/** locationfile:${upload.dir}/ /并在applicationContext.xml中定义upload.dircontext:property-placeholder locationclasspath:config.properties/ !-- config.properties 中写 upload.dir/path/to/your/upload --提示file:前缀表示绝对路径/path/to/your/upload必须是服务器上的真实目录如D:/upload或/home/upload且 Tomcat 进程对该目录有读写权限。6. 毕设答辩前必做的 3 项验证与 1 个隐藏技巧6.1 功能闭环验证模拟真实维修全流程的 7 步操作清单答辩老师最爱问“这个系统真的能用吗” 光讲原理不够必须现场演示一条完整链路。我建议你用以下 7 步走通并录屏备用时长控制在 3 分钟内添加测试车辆车牌粤B12345品牌本田型号思域里程85000状态正常客户报修选择该车填写故障发动机异响上传 1 张模拟照片管理员派工进入工单列表点击“派工”选择维修员张三维修员接单用zhangsan/123456登录将状态改为维修中填写开始时间维修完成填写维修内容更换正时皮带上传 2 张维修后照片状态改为已完成配件出库在工单详情页点击“消耗配件”选择正时皮带数量1确认扣减财务结算输入金额800.00状态变为已结算生成结算单 PDF用itextpdf生成非必须但加分。验证点每步操作后刷新页面确认数据库status字段、update_time、关联表attachment,part_usage是否同步更新。特别注意第 6 步——若库存不足整个流程必须中断并提示不能只扣款不扣库存。6.2 性能与健壮性验证用 JMeter 做 3 个关键接口压测毕设答辩不考高并发但老师会问“如果 100 个维修员同时操作呢”。用 JMeter 做轻量压测证明你考虑过工程落地接口线程数循环次数预期结果关键观察点车辆列表查询带分页5010响应时间 800ms数据库连接池是否耗尽wait_timeout报错工单提交含文件上传205成功率 100%上传目录磁盘空间是否告警df -h状态变更派工→维修中3010无重复派工、无超卖part_stock.quantity是否出现负数JMeter 脚本要点使用CSV Data Set Config读取不同车牌号避免缓存View Results Tree只在调试时开启正式压测关闭聚合报告中重点关注90% Line90% 请求的响应时间和Error %。6.3 安全加固3 个低成本但高价值的防护动作SSM 项目虽不面向公网但毕设演示时连校园网基础防护不能少密码加密存储user表中password字段必须存 MD5或 BCrypt禁止明文。在UserService.register()中user.setPassword(DigestUtils.md5Hex(user.getPassword())); // Apache Commons CodecSQL 注入防护所有LIKE查询必须用#{}禁用${}。例如!-- ✅ 安全 -- if testplateNumber ! null and plateNumber ! AND plate_number LIKE CONCAT(%, #{plateNumber}, %) /if !-- ❌ 危险拼接字符串 -- AND plate_number LIKE %${plateNumber}%XSS 过滤JSP 中所有用户输入内容如repairContent,remark必须用fn:escapeXml()td${fn:escapeXml(v.repairContent)}/td6.4 隐藏技巧用 Log4j 输出结构化日志让答辩时“有据可查”老师问“这个操作谁在什么时候干的”你不能只说“我点了按钮”。在关键业务方法中加入审计日志// 在 updateStatus() 方法末尾 log.info(REPAIR_STATUS p a hrefhttps://download.csdn.net/download/tangyang8942/89988808 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p