简介:这是一套基于SSM(Spring+SpringMVC+Mybatis)+MySQL+JSP实现的水果蔬菜商城系统,面向计算机专业毕业生及JavaWeb初学者,适合用于毕业设计、课程设计或期末大作业。项目已通过导师指导并验收,下载即可使用,功能完善且界面美观。用户端提供主页浏览、注册登录、个人中心、购物车与订单管理;管理端支持订单管理、客户管理、商品管理、类目管理、公告管理、留言管理,前后台逻辑清晰、操作便捷。整套资源共2000个文件,压缩包约227.54MB,主要涵盖Java源码及class文件、JSP页面、CSS/JS前端资源、图片素材、XML配置、Jar依赖库和SQL数据库脚本等,结构完整,便于导入Intelij Idea并配合Tomcat8.0、MySQL5.7直接运行。目前已有129人学习下载,适合希望快速获得可运行项目并参考其业务设计与编码实现的开发者。
1. 一个毕业设计级别的 856 行系统:SSM 水果商城为什么值得你跑一遍
每年到毕业设计节点,十个选题里能有三四个人在做 SSM 商城。这个项目我拆过不止一遍,工作量不大,但结构非常典型。Spring + SpringMVC + MyBatis 三件套,前端用 JSP 配合 JSTL,数据库 MySQL,容器 Tomcat 8.0。会员端有注册、登录、商品列表、购物车、订单提交,后台有商品管理、类目管理、订单管理、公告和留言管理,就是标准的电商最小闭环。
它适合两类人。一类是需要快速交付毕设、课程设计,又不想把时间浪费在环境折腾上的同学;另一类是刚接触 SSM,想知道 Controller、Service、Mapper 是怎么串起来的人。这套系统的好处是功能边界清楚,代码量不大,每一层都能找到对应的落地代码。你说它有多炫谈不上,但它能让你在答辩现场把链路讲明白。下面我把复现步骤和几个高频坑一次说透。
2. 理解这套系统的运行骨架:从用户注册到管理员发货的完整链路
2.1 三种角色下的核心业务流:先看懂数据往哪走
这套系统表面上是页面在跳,实际上是一条横向的数据流。访客可以浏览首页和商品详情,但要加入购物车、提交订单就必须登录。注册时写入 user 表,登录后用户信息放进 session,购物车数据一般挂在当前用户下,提交订单后生成订单记录和订单明细,管理员在后台看到新订单,修改订单状态,用户在前台“我的订单”里看到状态变化。
这段是购物车加购最常见的实现,用 CartService 把商品和数量收进 session 或数据库表:
@Controller @RequestMapping("/cart") public class CartController { @Resource private CartService cartService; @RequestMapping("/add") public String addCart(HttpSession session, Integer goodsId, Integer quantity) { User user = (User) session.getAttribute("user"); if (user == null) { return "redirect:/user/toLogin"; } cartService.addCart(user.getId(), goodsId, quantity); return "redirect:/cart/list"; } }这里 @Resource 按名称注入 CartService 实现,@RequestMapping 把 /cart/add 映射到 addCart 方法。注意返回值是重定向而不是转发,这样能避免浏览器刷新时重复加购。真实项目中购物车是否落库,要看产品需求;毕设里常见做法是登录后就写入 cart 表,这样管理员后台也能看到购物车数据,演示时更有说服力。
2.2 SSM 三层是怎么串起来的:注解、事务、SQL 映射的关系
SSM 设计的核心是分层。Controller 层只负责接收参数、调用 Service、决定跳转,不写 SQL;Service 层处理业务规则,比如下单要同时扣库存、生成订单,这里必须开启事务;Mapper 层就是接口加 XML,一个方法对应一条 SQL。
@Service public class OrderServiceImpl implements OrderService { @Resource private OrderMapper orderMapper; @Override @Transactional(rollbackFor = Exception.class) public void createOrder(Order order, List<OrderItem> items) { orderMapper.insertOrder(order); for (OrderItem item : items) { orderMapper.insertOrderItem(item); } } }注意 @Transactional 是 Spring 管理事务的关键。一旦 insertOrder 成功而 insertOrderItem 失败,回滚会把订单主表和明细表恢复到之前状态。rollbackFor 指定当抛运行时异常时回滚,这个写法比默认策略更稳。对应的 OrderMapper.xml 里,insertOrderItem 的 SQL 大概是:
<insert id="insertOrderItem" parameterType="com.demo.entity.OrderItem" useGeneratedKeys="true" keyProperty="id"> insert into order_item(order_id, goods_id, price, quantity) values(#{orderId}, #{goodsId}, #{price}, #{quantity}) </insert>useGeneratedKeys 的作用是让数据库自增主键回填到 Java 对象的 id 字段上,下单逻辑里后面要用到这个 id 拼接明细记录。很多同学在改毕设时忽略这条,导致订单明细里 order_id 永远是 null。
2.3 表结构与状态设计:没有外键也能撑起整个系统
商城系统的表通常是用户表、商品表、商品类目表、购物车表、订单表、订单明细表,加上公告和留言。用户表有 id、username、password、phone、address;商品表有 goods_name、price、stock、image、type_id;订单表有 order_no、user_id、total_price、status。
订单状态是这套系统里最值得答辩时讲的字段。常见设计是 0 待付款、1 待发货、2 已发货、3 已完成、4 已取消,前台“我的订单”页面根据状态显示不同按钮,后台订单管理根据状态筛选列表。这里埋一个提醒:表之间不要真加数据库外键,用代码逻辑去维护关联关系就行。毕设答辩现场,演示插入数据时外键约束经常跳出来弹错误,反而手忙脚乱。
3. 本地复现五步走:从解压源码到浏览器跑通
3.1 先认清工程结构:哪些文件决定项目能跑
拿到手先别急着点运行,先看目录。SQL 脚本一般单独放在 sql 目录,文件名类似 fruit_shop.sql;src/main/java 下面按 controller、service、mapper、entity 分包;src/main/resources 里是 jdbc.properties、spring-mybatis.xml、spring-mvc.xml 等配置文件;webapp/WEB-INF 下是 JSP 页面和 web.xml。
需要跑通的最低配置是 JDK 1.8、Tomcat 8.0、MySQL 5.7 以上、IDEA 旗舰版或社区版加插件。Maven 项目会带 pom.xml,依赖下载完就能用;非 Maven 项目会在 webapp/WEB-INF/lib 下带好 jar 包,省去联网下载的麻烦。这一步的最大意义是让你知道什么东西在哪,后面改配置时不会满世界找文件。
3.2 导入数据库脚本:命令行与可视化工具两条路
数据库脚本是整个项目的地基。用命令行导入时注意顺序,先建库,再指定库,再执行脚本:
mysql -uroot -p create database if not exists fruit_shop default character set utf8mb4; use fruit_shop; source /path/to/fruit_shop.sql;utf8mb4 是必须的,它比 utf8 多支持表情符号和扩展字符,电商系统里商品名、留言内容都可能有特殊字符。如果你用的 MySQL 8.0,utf8mb4 已经是默认字符集,但脚本里显式指定更保险。导入完成后用 show tables; 看看有没有 8 张以上的表。常见的翻车点是 SQL 脚本编码是 GBK,导入后中文变乱码,解决方法是导入前执行 set names utf8mb4。
修改数据库连接串是在 resources/jdbc.properties 里:
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fruit_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=你的密码这里密码必须改成你自己本机 MySQL 的密码。useSSL=false 是关掉 SSL 握手,本地调试时能减少连接报错;serverTimezone 指定时区,否则 MySQL 8.0 会报时区相关异常。driver 这个值在 MySQL 8.0 下要换成 com.mysql.cj.jdbc.Driver,后面第 4 章会细说。
3.3 IDEA 配置 Tomcat 8 并启动:见到首页才算跑通
导入项目后,打开 Project Structure 确认 SDK 是 1.8,接着配置 Tomcat。Run 菜单选 Edit Configurations,点左上角加号选 Tomcat Server Local。在 Deployment 标签页加 Artifact,选项目名 war exploded,Application context 填 /fruit_shop,这样访问地址就是 http://localhost:8080/fruit_shop。
启动前检查两处:一是 Ensure that no JBoss/JRebel conflicting libraries 这类提示,二是 Artifact 是否把 resources 目录打进去了。启动成功后看控制台有没有出现 Spring MVC 的映射日志,然后打开浏览器访问首页。如果首页出来了,再注册一个账号,去后台试登录。后台账号一般在 SQL 脚本里写死,去 admin 表里查一下就能拿到。
4. 配置改动地图:MySQL 版本、Tomcat 版本、IDEA 解码三处关键设置
4.1 MySQL 5.7 到 8.0:连接串必须跟着版本走
不少同学本机装的是 MySQL 8.0,而项目最初按 5.7 写的连接配置。两个版本在 JDBC 连接上有两个明显差异。驱动类不同,5.7 用 com.mysql.jdbc.Driver,8.0 必须改用 com.mysql.cj.jdbc.Driver,否则驱动加载直接失败。连接串参数上,8.0 对时区更敏感,不加 serverTimezone 会报错。
# MySQL 5.7 写法 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fruit_shop?useUnicode=true&characterEncoding=utf8&useSSL=false # MySQL 8.0 写法 jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fruit_shop?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai如果连接时出现 SSL 连接警告,那是 MySQL 8.0 默认要求 SSL 握手导致的,本地开发环境加 useSSL=false 就能关掉。新版 MySQL 8.0.33 之后对 caching_sha2_password 插件的处理也有变化,如果账号密码对但连接失败,可以在 MySQL 里执行 ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '密码',把认证插件换回旧版。
4.2 Tomcat 版本:8 和 10 是两回事,不要为了追新给自己挖坑
这个项目最稳的容器是 Tomcat 8.0。Tomcat 9 用 Servlet 4.0 规范,基本兼容;但到了 Tomcat 10,包名从 javax.* 改成 jakarta.*,SSM 项目里所有的 javax.servlet.http.HttpServlet、javax.servlet.Filter 引用会全部失效,页面能启动但访问即报 NoClassDefFoundError。
如果你在 Mac 或 Linux 上装的是 Tomcat 10,又不想换版本,正规做法是给项目加 jakarta.servlet 依赖并改造代码,但对毕设来说成本太高。我的习惯是去 Tomcat 官网下载 8.5.x 的压缩包,解压后直接让 IDEA 指向它。Windows 下注意 Tomcat 8.5 需要 JDK 8,如果是 JDK 11 以上还是要回到 9 或 10,所以环境尽量保持 JDK 8 + Tomcat 8 一对。
4.3 IDEA 编码与编译配置:乱码、编译错误往往在这里
项目导入后第一件事是统一编码。IDEA 里打开 Settings,Editor > File Encodings,把 Global Encoding、Project Encoding、Default encoding for properties files 全部设成 UTF-8。然后打开 Help > Edit Custom VM Options,如果启动时中文乱码,在末尾加上 -Dfile.encoding=UTF-8。
如果你用的是 Maven 版本,IDEA 默认会连中央仓库,国内网络经常拉依赖很慢甚至中断。常见做法是在本地仓库的 settings.xml 里配置阿里云镜像:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>配置完后在 IDEA 的 Maven Settings 里指向这个 settings.xml,重新导入一次。这一步能省下大量等依赖的时间。很多“项目导入报红”的问题,不是代码坏了,而是依赖没下全。
5. 毕设血泪排查:六条高频报错与现场修复思路
5.1 页面 404:控制层映射到了,但 JSP 显示不出来
现象:访问 http://localhost:8080/fruit_shop/goods/list 返回 404,控制台没有打印异常。
原因有三个:一是 SpringMVC 的组件扫描没扫到 Controller,通常是把包名写错了,比如在 context:component-scan base-package 里写成 com.fruit.controller,但 Controller 实际在 com.fruit.controller.user 下,扫描不到子包;二是 web.xml 的 servlet-mapping 把 *.do 拦截,而页面请求后缀是 .html 或直接没有后缀。解决方法是先看控制台有没有 RequestMappingHandlerMapping 的映射日志,没有就检查扫描路径。
常见配置是:
<context:component-scan base-package="com.fruit" />base-package 写包名根路径,Spring 会递归扫描子包,所以包结构最好不要嵌套过深,返工也容易定位。
5.2 数据库中文乱码:页面显示“???”或控制台乱码
现象:商品名称和公告内容在页面上显示乱码,数据库里也是乱码。
原因不是某一处,而是字符集链路逐层检查。数据库连接串没加 characterEncoding=utf8,建库时字符集是 latin1,页面本身没有 meta charset,四者只要一个断链,就会乱一片。解决方法是按顺序处理:数据库执行 ALTER DATABASE 改成 utf8mb4,jdbc.url 加 characterEncoding=utf8,JSP 顶部确保 <%@ page contentType="text/html;charset=UTF-8" %>。Tomcat 的 server.xml 里 Connector 加 URIEncoding="UTF-8",防止 GET 提交参数乱码。
5.3 Maven 依赖下载失败:pom 文件一片红或包一直加载中
现象:IDEA 右下角一直转圈,pom.xml 里报红,本地仓库对应目录只有 .lastUpdated 文件。
原因是中央仓库访问不稳定,或者本地仓库权限有问题。解决方法是换镜像源,配置阿里云 mirror 后,再在 IDEA 里右键项目 Maven > Reimport。如果还是失败,就看本地仓库路径下是不是残留了损坏的下载文件,删掉对应版本号目录后重来一次。血泪教训:不要用 IDEA 自带的 bundled Maven 配置连中央仓库跑大项目,镜像源对毕业设计是刚需。
5.4 上传的图片不显示:路径存了但浏览器加载不出
现象:后台添加上传图片成功,数据库有路径,但页面图片裂开。
原因是 SpringMVC 配置了拦截所有请求,却没有放行图片资源目录。总结一下,上传路径本身没问题,关键是页面读取时有没有走到静态资源映射。在 spring-mvc.xml 里加一段资源映射是常用做法:
<mvc:resources mapping="/upload/**" location="file:D:/fruit_shop_upload/" /> <mvc:annotation-driven />前提是数据库 image 字段里存的是 /upload/xxx.jpg 这种相对路径,而不是 D:/fruit_shop_upload/xxx.jpg 绝对路径。如果项目里图片上传后存的本来就是绝对路径,那多半要检查 JSP 里是不是少了获取上下文路径的前缀,这一点第 6 章有完整写法。
5.5 表单重复提交:订单和留言莫名出现两条
现象:刷新一下订单列表,多了一张一模一样的订单;留言一次性发了两条。
原因是用转发跳转页面。用户在表单提交后刷新,浏览器把上一次 POST 请求重新执行了一遍。解决方法是提交成功后在 Controller 里用 redirect 重定向,而不是 return 一个视图名。另外可以在生成订单前做一次性 token 校验,但毕设里能做到“提交后重定向”就已经避开九成问题。
5.6 Tomcat 端口被占用:Failed to initialize end point
现象:启动时提示 Port 8080 was already in use。
原因是另一个程序占了 8080,或者前一个启动的 Tomcat 没有完全关闭。解决方法是先排查占用进程,Windows 下执行 netstat -ano | findstr 8080,Linux 下用 lsof -i:8080。找到 PID 后结束进程紧张的建议是改 Tomcat 端口也行,但很多前端页面上写死了 8080,改端口容易引发资源路径问题,还是结束进程优先处理。
6. 答辩演示更稳的方案:打 war 包部署到独立 Tomcat
IDEA 里跑项目没问题后,我建议你把项目打包成 war 文件,部署到一个干净的 Tomcat 里再做最终演示。原因是 IDEA 的运行方式依赖 IDE 的 Artifact 和 Debug 配置,现场万一内存不足卡死或弹窗打扰,非常尴尬。独立 Tomcat 部署的好处是环境稳定,项目运行行为和用户真正部署一致,答辩演示时不容易翻车。
步骤很简单:IDEA 右侧 Maven 面板执行 package 命令,或菜单 Build > Build Artifacts,选择 war 类型。打包成功后把生成的 .war 文件复制到 Tomcat 的 webapps 目录,启动 Tomcat,它会自动解压并部署。访问地址是 http://localhost:8080/fruit_shop/。
还有一个细节值得做:把 JSP 里所有静态资源前缀统一改成动态获取上下文路径,避免部署上下文名改动后 CSS、JS、图片全部失效。在 JSP 头里用 JSTL 标签定义路径变量是常见做法:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %> <c:set var="ctx" value="${pageContext.request.contextPath}" /> <link rel="stylesheet" href="${ctx}/css/bootstrap.min.css"> <img src="${ctx}/upload/${goods.image}" />${pageContext.request.contextPath} 返回的是 /fruit_shop,加上它之后,不管项目部署在根路径还是二级路径,资源都能正确加载。如果不加,项目部署在 ROOT 下没事,一旦项目名变化,图片和样式就会全部失去。别问我为什么强调这个,有一年我就是在答辩演示时切换了部署方式,页面样式丢失,最后靠浏览器里手动补路径才救回来。
从那以后我每次拿到陌生项目,都会先做一套“部署 sanity check”:解压源码、导 SQL、改连接串、打 war、干净 Tomcat 跑通一遍,全程不碰 IDEA,确认“拿它去任何机器上都能跑”才算完。这套流程你复现的时候建议也走一遍,希望帮到你。
本文还有配套的精品资源,点击获取