SSM+JSP供应链管理系统实战:架构、部署与业务落地全解析

发布时间:2026/10/26 7:35:48
SSM+JSP供应链管理系统实战:架构、部署与业务落地全解析 简介面向百货零售行业的供应链管理系统后端采用SSMSpringSpringMVCMyBatis架构前端结合Vue与JSP覆盖供应商管理、商品管理、采购入库、销售出库、库存盘点等核心业务实现供应链信息化与智能化管理。系统要求JDK1.8、Tomcat7、MySQL5.7运行环境适合Java Web开发者深入学习框架整合也可直接作为毕业设计或课程设计项目使用。压缩包内共924个文件以Java源码、JSP页面、JS脚本、CSS样式及PNG/GIF图片资源为主同时附有SQL数据库脚本和项目功能介绍文档整体大小约9.97MB目录结构按功能模块划分便于快速定位与阅读。已有3524人学习下载项目经过严格调试确保可以稳定运行。借助完整源码、数据库脚本和设计文档读者可掌握SSM框架整合、Vue/JSP前端协作以及供应链业务流程设计对提升实战能力有明确参考价值。1. 百货中心供应链管理系统到底是什么一个SSMJSP项目包能帮你解决什么很多朋友拿到一个百货中心供应链管理系统的zip包第一反应是先导入数据库脚本再点启动结果页面要么白屏要么登录后采购单列表直接报错。这类以SSMSpringSpringMVCMyBatis为后端、JSP做页面渲染的供应链系统业务范围很固定供应商维护、商品资料、采购申请与审核、入库管理、库存台账。它真正能帮你解决的是两件事一是给毕业设计提供一个能完整演示业务流的后台二是把SSM的请求链路彻底打通——从JSP表单到Controller、Service、Mapper最后落到MySQL。适合谁对SSM框架有零散了解、但没有完整做过一个业务系统的开发者。别一上来就改功能先走通这条链路再谈二次开发。2. 先把SSM供应链系统的骨架立起来SpringMVC、MyBatis与JSP的分工很多二手甚至三手项目跑不起来不是代码写错了而是连框架各自管什么、配置文件在哪都没弄清。供应链管理系统看起来模块很多其实结构高度统一Controller接收页面请求Service处理采购审核这类业务规则Mapper访问数据库JSP只负责渲染结果。这一章先把分层讲透再给一套能直接套用的最小配置。2.1 三个框架各自管什么别再用“搞不清配置”的方式去读项目Spring在项目里是容器所有Service、Mapper实例都由它创建并注入。SpringMVC是Web层边界DispatcherServlet接收所有请求通过HandlerMapping找到Controller对应方法Controller本身不写业务逻辑只负责收参数、调Service、返回视图名。MyBatis把SQL和Java代码分开Mapper接口里写方法mapper.xml里写SQL运行期由MyBatis生成代理实现。JSP是视图层用EL表达式和JSTL渲染数据。这套分层在供应链系统里的价值是改页面不用动Java代码改数据库字段时不必在Controller里到处找SQL。新手最容易翻车的地方也在这——Service里明明写了注入却因为漏扫包导致启动报NullPointerExceptionMapper接口和XML的namespace不一致运行时直接提示“Invalid bound statement”。所以读项目的第一步永远是看配置不是看代码。2.2 最小可运行的applicationContext.xml与spring-mvc.xml配置模板以常见的Maven Web项目为例Spring主配置放在src/main/resources/applicationContext.xml。它要管数据源、SQL工厂、Mapper扫描和事务。?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:contexthttp://www.springframework.org/schema/context xmlns:txhttp://www.springframework.org/schema/tx xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd !-- 扫描Service、DAO等组件排除Controller是因为Web层交给SpringMVC管理 -- context:component-scan base-packagecom.supplychain context:exclude-filter typeannotation expressionorg.springframework.stereotype.Controller/ /context:component-scan bean iddataSource classorg.apache.commons.dbcp2.BasicDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.supplychain.dao/ /bean tx:annotation-driven transaction-managertxManager/ bean idtxManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean /beans这个文件的四个关键点component-scan必须排除Controller否则Spring容器和SpringMVC容器重复管理Bean事务和注入会出现诡异问题mapperLocations指向resources/mapper下的所有XML路径写错时MyBatis不会启动报错只在第一次调用Mapper才失败MapperScannerConfigurer负责把接口代理注册为Spring Bean这样Service里Autowired直接注入即可tx:annotation-driven开启注解事务业务方法上的Transactional才会生效。再配spring-mvc.xml放在src/main/resources/spring-mvc.xml由DispatcherServlet加载?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:contexthttp://www.springframework.org/schema/context xmlns:mvchttp://www.springframework.org/schema/mvc xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd context:component-scan base-packagecom.supplychain.controller/ mvc:annotation-driven/ mvc:resources mapping/static/** location/static// bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean /beansInternalResourceViewResolver决定了Controller返回supplier/list时实际去/WEB-INF/views/supplier/list.jsp找页面。把JSP放在WEB-INF下是安全做法用户不能直接通过URL访问必须经过Controller跳转。静态资源单独映射到/static/否则会被DispatcherServlet拦掉。2.3 数据库表设计供应链管理系统里最关键的几张表百货中心供应链系统不追求特别复杂的设计核心就五张表供应商、商品、采购单主表、采购单明细、库存流水。采购单主表和明细表分开是为了支持一次采购多种商品库存流水记录每次数量变化方便日后对账。CREATE TABLE supplier ( id INT PRIMARY KEY AUTO_INCREMENT, supplier_no VARCHAR(32) NOT NULL, name VARCHAR(100) NOT NULL, contact_person VARCHAR(50), phone VARCHAR(20), address VARCHAR(200), status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE goods ( id INT PRIMARY KEY AUTO_INCREMENT, category_id INT, goods_no VARCHAR(32) NOT NULL, name VARCHAR(100) NOT NULL, spec VARCHAR(100), unit VARCHAR(10), purchase_price DECIMAL(10,2), sale_price DECIMAL(10,2), stock_quantity INT DEFAULT 0, safety_stock INT DEFAULT 0, supplier_id INT, status TINYINT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE purchase_order ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL, supplier_id INT, total_amount DECIMAL(12,2), status TINYINT DEFAULT 0 COMMENT 0草稿 1待审 2通过 3驳回, apply_user VARCHAR(50), apply_time DATETIME, audit_user VARCHAR(50), audit_time DATETIME ); CREATE TABLE purchase_order_item ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT, goods_id INT, quantity INT, price DECIMAL(10,2), amount DECIMAL(12,2) ); CREATE TABLE stock_record ( id INT PRIMARY KEY AUTO_INCREMENT, goods_id INT, change_type TINYINT COMMENT 1采购入库 2销售出库 3盘点调整, quantity INT, after_stock INT, remark VARCHAR(200), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );表之间的关系不难理解supplier_id把商品和供应商关联起来purchase_order_item通过order_id挂在主表下库存流水用goods_id关联商品。一个完整的采购入库动作是在采购单主表插入一条状态为“草稿”的单子明细表插入商品和数量审核通过后把状态改成“通过”同时循环更新商品库存并给每件商品写一条进货记录。事务边界就划在“审核通过更新库存写流水”这三步上任何一步失败都要整体回滚否则会出现单据说通过了、库存却没变的尴尬局面。3. 从zip源码到本地可运行环境、导入、启动三步走拿到zip包以后最忌讳直接往IDEA里拖。先解压看目录判断它是Maven项目还是普通Web项目再检查JDK和Tomcat版本最后才谈得上启动。这一章按“环境搭好→识别目录→改配置→处理启动报错”的顺序写照着走能少走很多弯路。3.1 JDK、Tomcat、Maven怎么选版本才能少翻车这类SSM项目大多是老框架用了javax.servlet命名空间。我的经验是JDK用1.8Maven用3.6.3Tomcat用8.5或9.0MySQL用5.7或8.0。注意不要直接用Tomcat 10。Tomcat 10把javax.servlet换成了jakarta.servlet老项目的SpringMVC、MyBatis源码在Tomcat 10里初始化会直接失败。在pom.xml里建议锁住编译版本和Spring版本properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target spring.version4.3.30.RELEASE/spring.version /propertiessource和target都设成1.8避免本机JDK版本高导致编译出来的class文件不兼容。Spring用4.3.x配Tomcat 9很稳如果项目本身用的是Spring 5.x也不要强行降级关键是不要只升级某一个框架。Maven建议改成国内的镜像不然下载依赖会等到怀疑人生。3.2 导入IDEA后的目录结构与必改文件打开一个标准的SSM供应链项目你会看到这样的结构src/main/java/com/supplychain/ controller/ service/ dao/ domain/ src/main/resources/ mapper/ jdbc.properties applicationContext.xml spring-mvc.xml src/main/webapp/ WEB-INF/ web.xml views/ static/ css/js/images第一次导入后不要急着点启动。先在IDEA右侧Maven面板里执行clean让依赖下载完。然后打开src/main/resources/jdbc.properties这是整个项目最关键的入口文件jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/supplychain_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456driver和url要根据MySQL版本调整。MySQL 5.7可以用com.mysql.jdbc.DriverMySQL 8必须用com.mysql.cj.jdbc.Driver并且url里要带serverTimezone否则连库时直接报时区错误。3.3 数据库初始化与连接串修改先在MySQL里建空库再把项目带的SQL脚本导进去。用命令行操作最直观mysql -u root -p -e create database supplychain_db default character set utf8mb4; mysql -u root -p supplychain_db supplychain.sql如果SQL文件里已经包含CREATE DATABASE语句第一步可以跳过。导入时要注意脚本编码Windows下用记事本另存为UTF-8格式再执行不然表里中文注释很容易乱码。导入成功后打开jdbc.properties确认库名、用户名、密码三项。特别提醒如果本机装了多个MySQL服务端口可能不是3306要在url里改。很多“页面能开但数据全是空”的诡异问题最后发现是连到了另一个测试库。3.4 启动后遇404、500时的第一排查顺序404先确认访问路径的项目名。IDEA里Tomcat部署后应用上下文路径可能是/ssm685_war_exploded或/ssm685访问地址要包含这个上下文。如果页面是编辑功能还要检查Controller里RequestMapping是否和方法里写的URL一致。500错误要看IDEA控制台的完整异常尤其是MyBatis的SQL异常往往在第一行。常见的现象是映射文件里的resultType写错或者表名带了数据库前缀本地表实际不存在。还有一种情况是JSP本身报错Tomcat日志里会直接打印行号按行号去对JSP里的EL表达式和JSTL标签。如果应用彻底启动不了先用命令行看端口是否被占用netstat -ano | findstr :8080 taskkill /PID PID /F把异常进程清掉再重启。Tomcat配置目录里改过端口的话访问路径也要跟着变。4. 供应链核心业务怎么落地供应商、采购单与库存的SSM实现框架搭好、数据库导完接下来就是把业务做成能点的功能。供应链系统有一个天然的主线先有供应商和商品再发起采购单审核通过后自动入库。按这条主线写增删改查代码量不大但每一层都要交代清楚。这一章直接给可拷贝的代码骨架再解释为什么这样写。4.1 商品分类与供应商管理的增删改查领域对象和Mapper先看供应商实体。日常开发中领域对象就是一张表的映射字段和数据库列一一对应public class Supplier { private Integer id; private String supplierNo; private String name; private String contactPerson; private String phone; private String address; private Integer status; private Date createTime; // getter/setter省略 }Mapper接口负责定义数据库方法public interface SupplierMapper { Supplier findById(Integer id); ListSupplier findList(Supplier condition); int insert(Supplier supplier); int update(Supplier supplier); }对应的mapper/SupplierMapper.xml?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.supplychain.dao.SupplierMapper sql idcolumns id, supplier_no, name, contact_person, phone, address, status, create_time /sql select idfindList resultTypeSupplier select include refidcolumns/ from supplier where if testname ! null and name ! and name like concat(%, #{name}, %) /if if teststatus ! null and status #{status} /if /where order by id desc /select insert idinsert parameterTypeSupplier useGeneratedKeystrue keyPropertyid insert into supplier(supplier_no, name, contact_person, phone, address, status) values(#{supplierNo}, #{name}, #{contactPerson}, #{phone}, #{address}, #{status}) /insert /mapper两个参数值得记住useGeneratedKeystrue告诉MyBatis把数据库自增主键回填到对象的id字段这样插入后马上能拿到idxml里用 复用字段列表表结构变更时只改一处。Service层就是包一层事务Controller只负责调用Controller RequestMapping(/supplier) public class SupplierController { Autowired private SupplierService supplierService; RequestMapping(/list) public String list(Supplier condition, Model model) { model.addAttribute(list, supplierService.findList(condition)); return supplier/list; } RequestMapping(/save) public String save(Supplier supplier) { if (supplier.getId() null) { supplierService.insert(supplier); } else { supplierService.update(supplier); } return redirect:/supplier/list; } }save方法里用id是不是空来判断新增或修改是SSM项目最常见的写法。因为前端编辑页会把已有id放在隐藏域里带过来新增时没有id这个判断就能省掉两个Controller方法。4.2 采购单审批流程用SpringMVC控制层串联Service采购单是供应链系统里业务规则最集中的地方它的核心逻辑是创建采购单时只存主表和明细表不碰库存审核通过时一边改状态一边批量更新库存并写流水。用Spring的Transactional把两步包在同一个事务里。Controller负责接收页面提交的数组参数RequestMapping(/apply) public String apply(Integer supplierId, Integer[] goodsIds, Integer[] quantities, BigDecimal[] prices, Model model) { String orderNo purchaseOrderService.createOrder( supplierId, goodsIds, quantities, prices); model.addAttribute(orderNo, orderNo); return purchase/apply_success; } RequestMapping(/audit) public String audit(Integer orderId, Integer auditStatus, HttpSession session) { String auditor (String) session.getAttribute(loginName); purchaseOrderService.audit(orderId, auditStatus, auditor); return redirect:/purchase/list; }页面提交的goodsIds、quantities、prices是三个平行数组Controller直接接收为Integer[]和BigDecimal[]SpringMVC会按参数名自动绑定。这个做法简单直接适合后台管理系统。Service层是重点Service public class PurchaseOrderService { Autowired private PurchaseOrderMapper orderMapper; Autowired private PurchaseOrderItemMapper itemMapper; Autowired private GoodsMapper goodsMapper; Autowired private StockRecordMapper stockRecordMapper; Transactional public String createOrder(Integer supplierId, Integer[] goodsIds, Integer[] quantities, BigDecimal[] prices) { PurchaseOrder order new PurchaseOrder(); order.setOrderNo(PO System.currentTimeMillis()); order.setSupplierId(supplierId); order.setStatus(1); order.setApplyUser(loginUser()); order.setApplyTime(new Date()); BigDecimal total BigDecimal.ZERO; orderMapper.insert(order); for (int i 0; i goodsIds.length; i) { PurchaseOrderItem item new PurchaseOrderItem(); item.setOrderId(order.getId()); item.setGoodsId(goodsIds[i]); item.setQuantity(quantities[i]); item.setPrice(prices[i]); item.setAmount(prices[i].multiply(new BigDecimal(quantities[i]))); itemMapper.insert(item); total total.add(item.getAmount()); } order.setTotalAmount(total); orderMapper.update(order); return order.getOrderNo(); } Transactional public void audit(Integer orderId, Integer auditStatus, String auditor) { PurchaseOrder order orderMapper.findById(orderId); if (order null) { throw new RuntimeException(采购单不存在); } order.setStatus(auditStatus); order.setAuditUser(auditor); order.setAuditTime(new Date()); orderMapper.update(order); if (auditStatus 2) { ListPurchaseOrderItem items itemMapper.findByOrderId(orderId); for (PurchaseOrderItem item : items) { Goods goods goodsMapper.findById(item.getGoodsId()); int afterStock goods.getStockQuantity() item.getQuantity(); goods.setStockQuantity(afterStock); goodsMapper.updateStock(goods); StockRecord record new StockRecord(); record.setGoodsId(goods.getId()); record.setChangeType(1); record.setQuantity(item.getQuantity()); record.setAfterStock(afterStock); stockRecordMapper.insert(record); } } } }这套写法有几个设计取舍。状态用状态机非常方便1代表待审2代表通过3代表驳回Controller只传数字避免把字符串传进数据库。审核时先查订单再更新防止乱传orderId把不存在的单子改掉。只有审核通过才走库存更新逻辑驳回时只改状态。订单号的生成用了PO加时间戳够应付毕业设计生产环境要再拼日期和随机数。如果忘了加Transactional插入明细后一旦库存更新报错单据和库存就出现不一致这是供应链系统最怕的事情。4.3 JSP页面数据展示与表单提交从后端List到前端表格Controller返回视图名之后JSP负责把数据展示出来。供应链系统的列表页核心就是JSTL循环加EL表达式% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle供应商列表/title/head body table border1 tr th供应商编号/thth名称/thth联系人/thth电话/thth操作/th /tr c:forEach items${list} vars tr td${s.supplierNo}/td td${s.name}/td td${s.contactPerson}/td td${s.phone}/td td a href${pageContext.request.contextPath}/supplier/edit?id${s.id}编辑/a a href${pageContext.request.contextPath}/supplier/delete?id${s.id} onclickreturn confirm(确定删除吗?)删除/a /td /tr /c:forEach /table /body /html页面里最容易被忽略的是${pageContext.request.contextPath}。不管应用部署名是ssm685还是别的它都会自动拼上项目根路径避免上下文路径变了导致所有链接404。form表单提交时也要用这个表达式form action${pageContext.request.contextPath}/supplier/save methodpost input typehidden nameid value${supplier.id}/ p名称input typetext namename value${supplier.name}//p p联系人input typetext namecontactPerson value${supplier.contactPerson}//p button typesubmit保存/button /form浏览器提交的name值必须和Controller方法参数名完全一致。SpringMVC默认不做驼峰转换所以JSP里写namecontactPersonController里就是String contactPerson不能写成contact_person。很多改页面的人在这里翻车改了数据库字段却没改JSP的name提交后对应字段始终是null。5. 避坑指南SSMJSP供应链系统最常见的5个坑这类老项目的问题很有共性很多都是环境或配置造成的而不是业务代码写错。我按“现象→原因→解决”的顺序整理了五条每一条都遇到过不止一次。5.1 现象JSP页面的EL表达式不显示页面上出现${s.name}这样的原样文本而不是真实数据让人第一反应是Controller没有把数据传过来。其实数据通常已经到Model里了问题出在JSP容器没有开启EL解析。原因是web.xml头部的Servlet版本声明太低。旧项目的web.xml如果写的是2.3版本JSP会默认忽略EL表达式浏览器直接把${}当字符串输出。解决方法是把web.xml升级到3.1版本?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 /web-app也可以偷懒在JSP头部加一条% page isELIgnoredfalse %但治标不治本。我建议直接把web.xml升级掉因为老版本还会影响文件上传和Session配置。5.2 现象插入中文后数据库乱码页面输入“洗发水”数据库中存成“????”。长字符串字段里还可能看到中文被截断这是字符集没对齐。原因有三个层面数据库表是latin1编码、jdbc连接串没指定utf8、Tomcat接收请求时没按UTF-8解码。只改其中一处乱码不会完全消失。解决方法是三层一起改。数据库层执行ALTER DATABASE supplychain_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; ALTER TABLE supplier CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;连接串改成jdbc:mysql://localhost:3306/supplychain_db?useUnicodetruecharacterEncodingutf8。Tomcat的server.xml里给Connector加URIEncodingUTF-8。还有一个容易被忽略的点IDEA的Tomcat启动配置里VM options加-Dfile.encodingUTF-8否则控制台日志中文会乱影响判断。5.3 现象MyBatis查询结果全是null数据库表里有数据列表页能查到记录条数但每个字段都是null。这种情况最容易让新手怀疑实体类写错了。原因是数据库列名supplier_no和Java属性supplierNo之间没有自动映射。MyBatis默认把下划线列原封不动交给resultType做属性赋值属性里没有supplier_no就返回null。解决方式的首选是开启驼峰映射。在applicationContext.xml里配置MyBatis核心参数bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nameconfiguration bean classorg.apache.ibatis.session.Configuration property namemapUnderscoreToCamelCase valuetrue/ /bean /property /bean如果项目里的resultMap是手写的那即使开了驼峰映射也得在resultMap里补全column和property的对应关系。两条路选一条不要混用否则结果会很难排查。5.4 现象修改数据后页面数据不变化明明执行了更新sqlMySQL里也能查到新值但页面还是显示旧数据。有时候重启Tomcat后才正常说明问题出在缓存或会话上。浏览器的页面缓存是常见原因。后台管理系统的列表页被浏览器缓存后按F5刷新可能还从本地加载。解决方式是强刷CtrlF5或在目标请求的URL后面拼时间戳参数。另一个更隐蔽的原因是MyBatis二级缓存。如果mapper.xml里写了 同一个Mapper查询结果会被缓存更新后不会立刻失效。在学习和演示阶段建议先去掉缓存标签观察业务正常后再决定要不要开。另外之前遇到过一个奇葩案例两个项目共用了同一个Tomcat和一个数据库连接池更新语句执行到最后回滚了但应用日志没有报错因为事务因为AOP配置失效没有被Spring管理。检查事务是否生效看Service方法是否是public以及当前类有没有被CGLIB代理。5.5 现象部署后访问JSP变成404或直接下载点开链接时要么提示404要么浏览器弹窗口让我下载jsp文件。这个现象在Tomcat环境里特别典型原因各不相同。404如果发生在/WEB-INF/views路径下是因为JSP被放在了WEB-INF里用户无法直接通过浏览器地址栏访问。这是预期的安全行为正确做法是访问Controller的映射路径由Spring转发到JSP。下载jsp文件则说明Tomcat的JspServlet被覆盖了。web.xml里如果把DispatcherServlet的映射配成/*所有请求都进SpringMVCTomcat自带的JspServlet就不处理.jsp文件了。解决方式是改成/servlet-mapping servlet-namedispatcherServlet/servlet-name url-pattern//url-pattern /servlet-mapping改成/之后只有非静态资源请求进DispatcherServletTomcat的JspServlet还是会处理.jsp。这个坑很经典几乎每个SSM新手都会踩一遍。6. 让这个供应链系统更像生产环境登录拦截、操作日志与全链路验证基础功能跑通只算第一步真正要放到答辩或工作环境里还需要补两样东西权限控制和操作日志。最后一章给出最小实现再用Postman走一遍完整链路验证你会不会用这系统。6.1 用一个拦截器把未登录请求挡在外面写一个实现HandlerInterceptor的拦截器在preHandle里检查Sessionpublic class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); if (session.getAttribute(loginName) null) { response.sendRedirect(request.getContextPath() /login); return false; } return true; } }在spring-mvc.xml注册放行登录页和静态资源mvc:interceptors mvc:interceptor mvc:mapping path/**/ mvc:exclude-mapping path/login/ mvc:exclude-mapping path/static/**/ bean classcom.supplychain.interceptor.LoginInterceptor/ /mvc:interceptor /mvc:interceptors注意exclude-mapping必须包含/static/**否则浏览器加载css/js也会被弹回登录页。登录页本身的Controller也不能被拦截否则会形成死循环。6.2 在Service层给采购审核和入库操作埋日志日志不用做得太重用Slf4j在关键业务方法里记入参和结果private static final Logger logger LoggerFactory.getLogger(PurchaseOrderService.class); public void audit(Integer orderId, Integer auditStatus, String auditor) { logger.info(开始审核采购单, orderId{}, auditor{}, orderId, auditor); // 业务逻辑... if (auditStatus 2) { logger.info(入库完成, orderId{}, goodsId{}, afterStock{}, orderId, goods.getId(), afterStock); } }日志的定位是“事后能还原现场”。采购单审核这种操作一旦出错能靠日志看出操作人和时间。对学生项目来说比引入完整的操作日志表更实用。6.3 用Postman走通“新建供应商→提交采购单→审核入库→查库存”的验证先启动Tomcat确保系统能登录。在Postman里先发一次登录请求获取JSESSIONID Cookie然后按下面顺序验证POST http://localhost:8080/loginBody传userName和password保存Cookie。POST http://localhost:8080/supplier/saveBody传name、contactPerson、phone。POST http://localhost:8080/purchase/apply传supplierId、goodsIds1、quantities2。POST http://localhost:8080/purchase/audit传orderId、auditStatus2。GET http://localhost:8080/goods/list确认商品库存从旧值变成了旧值2。如果第4步返回500先去Tomcat日志看异常。最多的情况是MyBatis字段映射问题其次是事务没生效。验证完整链路的意义在于它证明了你对这套供应链系统的理解是通的而不是只会点页面。我第一次搭这种SSM供应链项目时直接在数据库里改了库存再用系统去查数据半天没变。最后发现是jdbc配置连到了另一个MySQL实例。这种错让我学会了一件事遇到数据异常先确认连的是不是同一个库。希望这些步骤和坑能帮你在ssm685这类项目上少走几趟弯路。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询