ARTICLE DETAIL

建站实战干货

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

SSM+JSP母婴用品网站开发实战:从环境配置到部署上线

2026/10/1 17:37:57 拓冰建站 浏览量
SSM+JSP母婴用品网站开发实战:从环境配置到部署上线 简介基于SSMJSP的母婴用品网站项目资源包是一套适合Java方向毕业设计、课程设计与期末大作业的完整实战项目面向有一定Java基础、希望系统了解SSM整合开发流程的读者覆盖从需求理解、模块拆分到编码部署的常见开发环节。项目除完整前后端源码外还附带数据库脚本、项目配置文件与相关软件工具且关键代码包含注释部署难度较低便于快速还原运行环境。资源共1370个文件压缩包大小17.89MB其中127个Java类与169个JSP页面构成核心业务逻辑和页面展示配合JS、CSS、Bootstrap等前端资源以及SQL数据库脚本目录结构清晰可按模块查阅也适合按个人节奏逐模块研读。目前已有53人学习下载。通过该项目可以重点练习SSM三层框架整合、JSP与后端的数据交互、MySQL表结构设计以及Tomcat部署等关键技能完整的母婴商城业务代码既适合用于毕业设计演示与课程答辩也便于作为后续二次开发的基础。1. SSMJSP的母婴用品网站为什么这套老技术栈仍是毕设的稳妥选择每到毕业设计季总有人对着「基于SSMJSP的母婴用品网站」这种题目犹豫都什么年代了还在用JSP但打开压缩包看到源码、数据库和教程三件套齐整地躺在里面又觉得香。这个题目本质上是一个完整的Java Web全栈练习——SSM负责业务逻辑和持久层JSP负责服务端渲染页面MySQL存数据串起来就是一条从前端页面到数据库的标准链路。它能解决的是你「不知道毕设做什么、又怕做不出来」的焦虑适合Java方向想快速落地一个可用系统的学生也适合刚学完SSM想找个完整项目练手的开发者。这个组合最大的价值不在于技术新而在于每个环节都能独立验证、出了问题网上资料一抓一大把。2. 从压缩包到能访问的首页环境版本与最小启动路径2.1 环境匹配JDK、Tomcat、MySQL先对齐版本拿到「SSMJSP母婴用品网站」项目包之后第一步不是急着开IDEA而是先把运行时环境理清楚。这类毕设项目大多是老版本配出来的常见组合是JDK 1.8、Maven 3.6以内、Tomcat 8.5或9.0、MySQL 5.7。用太新的JDK比如17、21跑老项目经常会报出Java Base模块相关的奇怪错误这不是你代码写得有问题而是Tomcat版本和JDK版本不兼容。先用命令确认本机环境java -version mvn -v mysql --version逻辑说明三条命令分别确认JDK、Maven、MySQL的版本号。java -version能看到是1.8还是更高版本如果显示的是openjdk 17或者更高后面启动Tomcat报错概率会直线上升。mvn -v检查Maven是否装了如果没装后面编译打包会很痛苦。mysql --version确认数据库版本5.7和8.0在连接驱动和密码加密方式上有差别项目里的jdbc.properties里写的驱动如果是com.mysql.jdbc.Driver配MySQL 8.0会有兼容问题。参数说明JDK建议直接锁1.8不要犹豫。Tomcat如果项目里没有特定的版本要求8.5是最稳的——它对JSP 2.3和EL 3.0的支持和老代码兼容性最好。MySQL用5.7能省掉很多连接层面的麻烦如果本地只有8.0后面连接时记得把驱动换成com.mysql.cj.jdbc.DriverURL里加上serverTimezoneAsia/Shanghai否则报时区错误会让你在深夜怀疑人生。环境对齐之后把项目包解压。注意看目录结构通常是一个标准的Maven工程src/main/java、src/main/resources、src/main/webapp该有的都有。如果解压后只有一个war包没有源码文件夹说明这是编译好的产物开发调试会很别扭。正常毕设包里应该是源码SQL脚本教程文档三部分SQL脚本一般在db或sql目录下文件名类似mom_shop.sql或baby_shop.sql。2.2 初始化数据库SQL脚本导入与连接配置数据库是这套系统的地基商品、订单、会员全要靠表撑着。打开SQL脚本先浏览一遍重点看建库语句和插入语句的格式。很多项目的SQL脚本是用5.7导出的到了8.0里执行会出现Unknown collation这类报错。先登录MySQL创建一个专用账号并授权然后用命令行导入脚本mysql -u root -p CREATE DATABASE IF NOT EXISTS baby_shop DEFAULT CHARSET utf8mb4; USE baby_shop; SOURCE /path/to/baby_shop.sql;逻辑说明CREATE DATABASE先建库utf8mb4保证表情符号和生僻字不丢母婴用品商品描述里经常有特殊符号用utf8会翻车。SOURCE是MySQL从外部文件执行SQL的命令路径要写绝对路径不要写相对路径否则MySQL会跑到自己的数据目录下找文件你一定找不到。导入完成后用SHOW TABLES;确认表都建出来了。常见的表有会员表、商品表、商品分类表、购物车表、订单表、订单明细表、评论表表格数量在8到15张之间都正常。如果表数特别少比如只有两三张说明是简化版功能可能不够撑答辩。接下来改数据库连接配置。在src/main/resources目录下找jdbc.properties或db.properties把连接信息改成你自己数据库的用户名和密码jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/baby_shop?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password你自己的密码逻辑说明这份properties文件是MyBatis数据源配置的入口Spring容器启动时会读取它来创建数据源。useUnicodetruecharacterEncodingutf8这两参数必须带上否则后面页面展示商品名会出现中文乱码这算是SSM项目的经典坑。如果你的MySQL是8.0驱动和URL改成上面说的那种写法。参数说明localhost是数据库地址3306是默认端口/baby_shop对应你创建的库名。user和password不一定用root建议新建一个普通账号专门给项目用权限只给SELECT、INSERT、UPDATE、DELETE就够不要图省事直接拿root跑项目这个习惯能让你以后在公司里少挨骂。2.3 部署到TomcatIDEA配置与war包两种方式环境配好、数据库通了就差把项目挂到Tomcat上跑。常见做法是用IDEA里的Tomcat插件直接跑配置上有个关键点打开Run/Debug Configurations新建Tomcat Server选Local在Deployment标签页把Artifact加进去。注意Artifact的类型一定是war exploded不是war——前者直接拿编译目录跑改JSP不用重新打包后者每次改完页面都要重新构建一次调试效率低到让你怀疑人生。如果你习惯用传统方式部署也可以先打包再扔进Tomcat的webapps目录mvn clean package -DskipTests cp target/baby_shop.war /path/to/tomcat/webapps/ cd /path/to/tomcat/bin ./startup.sh逻辑说明mvn clean package先把旧的编译产物清掉再打包成war-DskipTests跳过测试类避免测试代码报错导致打包失败。war包复制到webapps目录后Tomcat启动时会自动解压部署。启动后浏览器访问http://localhost:8080/baby_shopURL里的baby_shop是war包的文件名跟项目上下文路径有关系改名就要改访问路径。启动之后会看到Tomcat的日志刷刷往上滚最怕的是最后一行不是Server startup in XXX ms而是SEVERE开头的报错——这时候不要慌去logs/catalina.out看完整堆栈。第一行带Caused by的异常往往才是根因前面几行都只是表象。到这里一个环境OK的SSMJSP项目就能跑出首页了。首页通常会推荐一批母婴商品配着轮播图和分类导航。但首页出来了不代表系统就真的能交付业务功能验证才是后面几章的重点。3. 母婴用品业务的数据建模商品、订单、会员三张核心表怎么设计3.1 商品与分类表为什么需要冗余字段但不冗余数据打开数据库里最核心的几张表通常能看出当初创作者的设计水平。母婴用品网站的商品和普通电商没本质区别分类表与商品表是父子关系。典型的结构是分类表存一级分类和二级分类商品表通过category_id挂在分类上。设计要点在于分类表不要只存一个ID还要存parent_id用自关联的方式支持两级分类母婴用品里「奶粉 » 婴儿奶粉」这种层级才放得下。商品表的核心字段一般是这样CREATE TABLE product ( id INT(11) NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL COMMENT 商品名称, category_id INT(11) NOT NULL COMMENT 所属分类, subtitle VARCHAR(200) DEFAULT NULL COMMENT 副标题, main_image VARCHAR(500) DEFAULT COMMENT 主图URL, detail TEXT COMMENT 商品详情, price DECIMAL(10,2) NOT NULL COMMENT 价格, stock INT(11) NOT NULL COMMENT 库存, status TINYINT(4) NOT NULL DEFAULT 1 COMMENT 1上架 2下架, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明price用DECIMAL(10,2)而不是FLOAT是电商系统的常识——浮点数存价格会在精度上出幺蛾子比如19.99被存成19.989999。main_image直接存URL字符串不走外键关联图片表这是为了查商品列表时少一次关联查询属于用空间换时间的取舍。status用TINYINT而不是VARCHAR(上架)是为了后续SQL条件查询方便WHERE status1就能筛出上架商品。参数说明DECIMAL(10,2)总长度10位小数占2位上限是千万级别母婴商品价格够用。stock存的是可售库存量一个商品真正能卖多少得在订单里判断库存扣减这里存的是当前余量。很多新手在detail字段上直接存富文本HTML把页面样式写死在里面——这没问题但要注意JSP页面取出来时要配合${product.detail}的EL表达式输出才能正常渲染这在后面JSP章节会展开。3.2 订单与购物车事务边界划在哪张表上订单是这套系统里最见功力的部分。母婴用品的购买决策链路长会员可能先把商品丢进购物车过几天再下单所以购物车表和订单表要拆开。但拆开之后事务怎么控制就成了关键——点击结算时是先清购物车还是先建订单正确答案是先建订单和订单明细再清购物车两者放在同一个事务里。如果先清购物车再建订单万一订单创建失败用户的购物清单就没了这属于数据一致性上的翻车。订单表通常会拆成两张订单主表和订单明细表。主表存收货人、总价、订单状态明细表存每个商品ID、购买数量、当时的快照价格。快照价格是重点用户在结算一刻看到的价格必须被冻结在订单里否则以后改商品价格历史订单金额也跟着变财务上说不通。对应的建表语句大致如下CREATE TABLE order_main ( id INT(11) NOT NULL AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT 订单号, user_id INT(11) NOT NULL COMMENT 下单用户, total_price DECIMAL(10,2) NOT NULL, status TINYINT(4) NOT NULL DEFAULT 10 COMMENT 10待支付 20已支付 30已发货 40已完成 50已取消, receiver_name VARCHAR(50) NOT NULL, receiver_phone VARCHAR(20) NOT NULL, receiver_address VARCHAR(200) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE order_item ( id INT(11) NOT NULL AUTO_INCREMENT, order_id INT(11) NOT NULL, product_id INT(11) NOT NULL, product_name VARCHAR(100) NOT NULL, product_image VARCHAR(500) DEFAULT NULL, current_price DECIMAL(10,2) NOT NULL COMMENT 下单时价格快照, quantity INT(11) NOT NULL COMMENT 购买数量, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明order_no用UNIQUE索引约束是为了防止并发下单时生成相同订单号。毕设项目用时间戳加随机数就能扛住但真正上线要配分布式ID方案——这套单机系统不需要别过度设计。order_item里冗余了product_name和product_image看似违背了「不冗余」的数据库规范但实际查询订单详情时不用再去商品表join而且商品标题改了也不影响历史订单展示这在电商行业是标准做法。参数说明order_main的status设计成数字状态机而不是简单的0/1是为了后续订单流转能加状态。毕设答辩时如果能说出「这里我预留了状态机扩展位后续接入支付后可以加60退款中、70退款完成」这种话比堆功能更能体现设计感。订单表还必须有receiver_*三个字段因为收货信息是下单那一刻的抄录跟用户表里的最新收货地址是两回事。3.3 MyBatis逆向工程Dao层代码从哪来表结构定了Dao层代码有三种来源手写SQL、MyBatis逆向工程生成、通用Mapper。这个项目里的Dao层大概率是逆向工程或者手写SQL混搭。手写SQL的好处是每一条select语句你都看得懂答辩时老师问「你这个查询是怎么实现的」你能答得上来。坏处是表一多重复的增删改查能写到吐。逆向工程生成的是基础的CRUD复杂的多表关联还是要自己补。比如查询商品列表需要连分类表拿分类名称这种SQL逆向工程搞不定得自己在Mapper XML里写select idselectProductWithCategory resultTypemap SELECT p.id, p.name, p.price, p.main_image, c.name AS category_name FROM product p LEFT JOIN category c ON p.category_id c.id WHERE p.status 1 ORDER BY p.id DESC /select逻辑说明LEFT JOIN保证即使商品分类被删除理论上不应该删但系统里可能会发生商品记录依然能查出来。resultTypemap适合临时查询不需要建VO的场景但如果这个查询要被多个地方复用建议建一个专门的ProductWithCategoryVO类持久层返回map这个偷懒行为在毕设项目里能过在工作里会被同事骂死。4. SSM三件套的整合配置Spring、SpringMVC、MyBatis逐条拆解4.1 web.xml是总开关DispatcherServlet与编码过滤器SSM项目的入口不在Java类里而在web.xml。别看这个文件平时不动它里面每一行都有自己的使命。毕设项目的web.xml里有两样东西必须存在ContextLoaderListener监听器用来加载Spring根容器DispatcherServlet用来接管所有请求。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 context-param param-namecontextConfigLocation/param-name param-valueclasspath:applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener filter filter-nameencodingFilter/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping servlet servlet-namespringmvc/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namespringmvc/servlet-name url-pattern//url-pattern /servlet-mapping /web-app逻辑说明ContextLoaderListener负责加载Spring的根容器里面管的是Service、Dao、数据源这些业务层组件。DispatcherServlet单独加载spring-mvc.xml只管Controller和视图解析。这两个容器有父子关系子容器能看见父容器的Bean反过来不行。如果把Controller的扫描配置塞到applicationContext里启动时会报Bean找不到的错。参数说明CharacterEncodingFilter的url-pattern要写成/*而不是//*才能拦到所有请求包括JSP/只会拦到非JSP的请求。这个过滤器必须在最前面如果顺序放错POST请求的中文参数照样乱码——曾经有人把过滤器写到别的Filter后面排查了一下午最后发现是过滤器顺序问题。spring-mvc.xml是SpringMVC的大脑管Controller扫描、视图解析器、静态资源放行三件事mvc:annotation-driven/ context:component-scan base-packagecom.babyshop.controller/ bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp// property namesuffix value.jsp/ /bean mvc:resources mapping/static/** location/static//逻辑说明annotation-driven开启注解驱动让RequestMapping、RequestBody这些注解生效。component-scan只扫controller包千万别把service包也扫进来——Spring容器已经管了ServiceSpringMVC再扫一遍会出事务失效的坑。InternalResourceViewResolver的意思是Controller返回的index字符串会拼成/WEB-INF/jsp/index.jsp去找页面。mvc:resources把/static/**路径下的静态资源放行给Tomcat默认Servlet处理不然CSS、JS全会被DispatcherServlet拦下来返回404。参数说明prefix和suffix的路径必须对应你的实际目录。有些项目JSP页面放在src/main/webapp/WEB-INF/jsp下有些放在src/main/webapp根下放在WEB-INF下更安全——外部浏览器没法直接访问WEB-INF目录里的文件所有页面必须经过Controller跳转这也是一种安全防护。4.2 数据源与事务数据库连接池两个参数最容易配错MyBatis与Spring整合的核心是SqlSessionFactoryBean它的数据源决定了所有SQL从哪取连接。常见配置如下context:property-placeholder locationclasspath:jdbc.properties/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ property nameinitialSize value5/ property namemaxActive value20/ /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.babyshop.dao/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/逻辑说明property-placeholder用来读取jdbc.properties让数据库配置和Spring配置分离换环境只改properties文件不用动XML。DruidDataSource是阿里连接池比裸的JDBC强在支持监控SQL、防SQL注入、池化管理连接。SqlSessionFactoryBean的mapperLocations指向XML文件的位置basePackage指定Mapper接口所在包MyBatis自动生成实现类并把它们注册成Spring Bean。参数说明initialSize5是连接池启动时就预创建的连接数maxActive20是最大连接数。这里有个典型反模式事务方法里SELECT操作拿着连接不释放maxActive设小了之后并发一多就会报Cannot get a connection这个问题在第五章会细说。DruidDataSource忘了配timeBetweenEvictionRunsMillis会让空闲连接被MySQL服务端断开重启应用时第一次请求必报Connection is not available。4.3 事务是毕设答辩的高频提问点Transactional在Service层怎么落事务配置到位了但真正决定数据一致性的还是在Service实现类里有没有正确地加Transactional。这个注解加的位置很有讲究——必须加在public方法上、必须通过Spring代理调用才能生效。也就是说同一个类里methodA调用methodB就算methodB标了Transactional也不生效因为调用没有经过代理对象。Service public class OrderServiceImpl implements OrderService { Resource private OrderMapper orderMapper; Resource private OrderItemMapper orderItemMapper; Resource private CartMapper cartMapper; Override Transactional(rollbackFor Exception.class) public void createOrder(OrderMain order, ListOrderItem items, ListInteger cartIds) { orderMapper.insert(order); for (OrderItem item : items) { orderItemMapper.insert(item); } cartMapper.deleteBatchByIds(cartIds); } }逻辑说明Transactional(rollbackFor Exception.class)里的rollbackFor很重要。Spring默认只对RuntimeException回滚Exception受检异常不会自动回滚。毕设项目里Service层经常自己抛一些受检异常不写rollbackFor的话第一个商品插进去了第二个失败整个订单数据就残了。createOrder内部三个操作——插订单表、插明细表、清购物车——必须保证全成功或全失败这才能跟答辩老师讲清楚事务边界。参数说明Transactional的propagation默认REQUIRED就够了不要乱调。isolation保持默认的数据库级别除非你能讲清楚脏读、不可重复读的差异否则在答辩时不要主动提隔离级别给自己挖坑。5. JSP项目常见问题排查404、乱码、热更新失效的五个真实场景5.1 页面404但Tomcat没报错请求被DispatcherServlet吃掉了现象启动不报错浏览器访问http://localhost:8080/baby_shop/index返回404但http://localhost:8080/baby_shop/首页能出来。原因DispatcherServlet的url-pattern配成/后所有请求都会进SpringMVCController里如果没写RequestMapping(/index)或者写了但返回的视图名不对就会404。还有一种隐蔽情况是Controller返回了页面路径但路径拼出来不对——视图解析器前缀是/WEB-INF/jsp/你返回了index可实际JSP文件在/WEB-INF/jsp/index.jsp吗文件不存在也会404但Tomcat日志里不报错。解决打开浏览器开发者工具的Network面板看请求的响应码。如果是404先确认访问路径和RequestMapping里的值是否完全一致大小写、斜杠都不能差。再检查JSP文件是否存在、路径是否匹配视图解析器前缀后缀。5.2 商品名显示问号CharacterEncodingFilter没拦到POST现象后台录入商品时填的中文名称在商品列表页显示成???。原因录入商品的表单是POST提交Tomcat 8.5以上对POST请求默认按ISO-8859-1解码中文会变成乱码。虽然web.xml里配了CharacterEncodingFilter但如果顺序在其他Filter之后或者后端JSP里用的还是老式的request.setCharacterEncoding(UTF-8)被过滤器覆盖都会出问题。解决把CharacterEncodingFilter的url-pattern改成/*并放在Filter链第一位。同时确认页面JSP顶部加了% page contentTypetext/html;charsetUTF-8 languagejava %数据库连接URL里也带了characterEncodingutf8。这三个地方只要有一处漏掉中文就会在某一个环节变成乱码。这属于SSM项目最多的血泪经验。5.3 改完JSP页面不生效浏览器缓存还是没重新编译现象改了JSP里的文字刷新页面还是旧内容。原因两种可能。第一是浏览器缓存尤其是Chrome对本地静态内容缓存得很狠第二是IDEA里跑的是war exploded还是war如果用的是war方式部署每次改动JSP都要重新打一次war包否则改的是source目录Tomcat跑的是webapps里的旧包。解决先强刷CtrlF5排除浏览器缓存再改一个JSP文件后启动Tomcat观察启动日志里有没有重新编译JSP的痕迹。如果没反应说明部署方式不对回到IDEA的Run/Debug Configurations里把Deployment改成war exploded。5.4 应用跑着跑着突然连不上数据库连接池耗尽了现象系统刚开始用都正常点几个页面后开始报Cannot get a connection之类的错误。原因连接池的maxActive设得太小或者连接泄漏了。Druid连接池默认的testWhileIdle为true但如果一个连接从连接池拿出来之后在方法里没有被正常归还连接就会一直占着池子很快被耗尽。解决检查Service方法里有没有自己开Connection、用完不关的情况——用MyBatis一般不会但如果是自己写了JDBC的代码块就得小心。用Druid监控页面看一眼活跃连接数如果只增不减就是泄漏重点查finally里没有conn.close()的方法。处理方式是给连接池加上removeAbandonedtrue和removeAbandonedTimeout60让Druid自动回收超时未归还的连接——但这是治标治本是把裸JDBC全换成MyBatis。5.5 本地能跑部署到服务器上就404路径被写死了现象本地IDEA跑一切正常用mvn clean package打成war包部署到Linux服务器上CSS样式没了、页面跳转404。原因JSP页面里用了绝对路径或者相对路径不对。比如直接写href/static/css/main.css如果项目部署在Tomcat的根路径这个没问题但如果war包是baby_shop.war访问路径是http://ip:8080/baby_shop/静态资源就在/baby_shop/static/css/main.css直接/static开头就找不到了。解决JSP里用${pageContext.request.contextPath}拼路径% String path request.getContextPath(); % link relstylesheet href%path %/static/css/main.css逻辑说明getContextPath()返回的是Web应用的上下文路径比如/baby_shop。它跟部署的war包名联动不管部署到哪一级路径都不会出错。用JSTL的话也可以写c:url value/static/css/main.css/效果一样。后端Controller里return redirect:/product/list这种跳转也应该用redirect:${pageContext.request.contextPath}拼好再返回。排查建议出现404先看浏览器地址栏URL再对比静态资源请求的完整URL。凡是静态资源路径里少了/baby_shop前缀八成就是没有拼接contextPath的错。6. 毕业设计怎么验收从注册到下单的核心链路验证与答辩加分项项目能跑通只是第一步答辩前的系统验证才是决定成绩的分水岭。别等答辩前一天才临阵磨枪建议按一条完整的用户链路走一遍并截图存档新用户注册 → 登录 → 浏览首页商品 → 查看商品详情 → 加入购物车 → 从购物车结算下单 → 在后台订单列表中看到新订单 → 修改订单状态。这条链路走通系统的主干功能就证明没问题了。验证时重点关注三个容易出bug的环节。注册时用户名重复有没有提示下单后库存有没有同步扣减后台修改商品上下架状态后前端列表有没有即时变化。这三处直接对应用户表SELECT查重、订单事务里的UPDATE product SET stockstock-?、商品列表的WHERE status1条件是答辩老师最爱追问的点。如果想让项目在答辩时脱颖而出不需要大改架构加两个小而实用的功能就够订单列表加分页用PageHelper插件三行配置搞定后台登录加拦截器HandlerInterceptor验证session里有没有用户。这两个功能都是能画出示意图、能讲清楚原理的加分项比加一个华丽的首页更有说服力。我的习惯是留一份验证记录文档把每一步操作和截图按顺序贴好再到答辩前把war包重新打一次确认服务器上从零到跑通全流程没遗漏。这样答辩时老师问「项目部署遇到过什么坑」你可以把数据库乱码、路径写死这些真实排错经历说成自己踩过又解决的案例——评委想听到的不仅仅是能跑的代码更想知道你是不是真的动手做过。项目包里的教程写得再细也不如实操过一次印象深刻。希望帮到你祝你答辩顺利。本文还有配套的精品资源点击获取