简介:基于JavaWeb的水果销售商城系统是一项完整的J2EE实战源码,采用SSH框架整合MVC分层架构,主要面向正在学习JavaWeb的开发者,以及需要完成课程设计或毕业设计的本科学生。系统内建管理员与普通用户双角色:普通用户可完成注册登录、浏览首页水果、按分类选购、加入购物车、生成订单并查询历史订单;管理员可维护水果信息、管理商品分类、处理订单以及管理用户账号,前后台闭环完整。资源包共139个文件,以JSP动态页面、Java业务类、XML映射与配置文件为主体,同时包含数据库SQL脚本、CSS/JS样式脚本和示例图片,便于在MyEclipse+MySQL+Tomcat环境中快速部署运行;压缩包仅2.7MB,轻量便于携带与分享。目前已有775人学习下载。通过研读源码,读者可以看清SSH各层之间的调用关系,理解电商系统从数据表设计、业务处理到前端页面交互的实现过程,并可直接以此为基础进行二次开发或毕业设计答辩演示,实用价值较高。
1. 一份 jsp+ssh+mvc 的水果商城源码包,到底能给你什么
拿到一个「水果销售商城系统」的毕设源码压缩包,先别急着解压就开跑。这套东西的技术栈写得很明白:jsp 做页面、ssh(Struts2 + Spring + Hibernate)做后端骨架、mvc 三层架构把请求和业务拆开,最后落在 java web 和 j2ee 这套经典组合上。它的价值不在于代码有多新,而在于链路完整——一个用户从注册、浏览水果、加购物车、下单到后台管理员发货,整条业务线都能跑通,这对毕设答辩和课程设计来说已经够用了。适合三类人:要交毕设系统的学生、找 java web 课程设计案例源码练手的人、以及公司里带新人入门 j2ee 的师兄。但大多数下载者卡在同一个地方:环境配不起来,数据库连不上,Tomcat 一启动就报错。这篇就按「把老工程跑起来」的顺序,把版本选型、数据库初始化、部署打包和高频翻车点一次讲透。
2. 把 j2ee 跑起来之前先配平:jdk 8、tomcat 8.5 和这份老 ssh 的兼容关系
老 ssh 项目和今天的 spring boot 项目有一个本质区别:它不是「拿来就能跑」的。代码编译目标、servlet 规范、甚至某些框架底层依赖的动态代理库,都和你本机的环境版本强绑定。我见过太多人把毕设源码下下来,装了个最新的 jdk 21 和 tomcat 10,启动直接失败,然后开始怀疑源码有问题——其实问题基本都在环境选型上。
2.1 版本选型是第一道坎:为什么 jdk 8 和 tomcat 8.5 是默认答案
先看 jdk。这份源码是 jsp + ssh 时代的东西,那会儿主流开发环境是 jdk 1.6 / 1.7,很多代码编译目标就是 1.6。现在你装 jdk 8 是最稳的,因为 jdk 8 还保留了大量老框架能用的反射行为;如果直接上 jdk 11 或 17,Hibernate 和 Spring 里大量基于 cglib 和 javassist 的动态代理会直接抛IllegalAccessError,这属于 java 模块化之后对反射的收紧,你改业务代码是改不好的,只能降级 jdk。
再看 Tomcat。servlet 版本决定兼容性:tomcat 8.5 对应 servlet 3.1,老项目的javax.servlet包名完全兼容;tomcat 9 对应 servlet 4.0,也能跑大多数老工程;但 tomcat 10 把包名改成了jakarta.servlet,这套毕设源码里所有 import 全是javax.servlet,启动必炸。你还得注意 tomcat 7 也别选,它太老,对 jdk 8 的支持不完整,有些注解扫描会出问题。所以我的建议非常固定:jdk 8 + tomcat 8.5,这是老毕设跑起来成功率最高的组合,没有之一。
提示:如果你本机已经装了新 jdk,不用卸载,把 jdk 8 解压在任意目录,通过环境变量切换即可,不影响其他项目。
2.2 用环境变量把 jdk 和 tomcat 绑到位
环境变量配不对,最典型的症状是:你启动了 Tomcat,日志里显示的 java version 和你以为的不一样。因为PATH里可能存在多个 java 可执行文件,系统按顺序取第一个。下面这个配置在 Windows 或 Linux 上都通用,只是写法差异。
# Linux / macOS 下写进 ~/.bashrc 或 ~/.zshrc export JAVA_HOME=/opt/jdk1.8.0_202 export CATALINA_HOME=/opt/apache-tomcat-8.5.100 export PATH=$JAVA_HOME/bin:$CATALINA_HOME/bin:$PATH # 验证是否真的切到了 jdk 8 java -version echo $JAVA_HOMEJAVA_HOME指向 jdk 安装根目录,Tomcat 的启动脚本catalina.sh会用它找java命令;CATALINA_HOME指向 tomcat 解压目录,startup.sh才能定位到bin下的脚本;PATH里把$JAVA_HOME/bin放最前面,是确保你执行java -version时用的是 jdk 8,而不是系统自带的 openjdk 17。Windows 用户在「系统属性-环境变量」里加同样的变量即可,注意PATH里不要有引号。配置完之后重启终端,再看java -version是否显示 1.8,这一步错了后面全是白搭。
2.3 解压源码包后先看这五个文件,判断这份源码能不能用
很多人拿到 rar 第一件事就是找代码往里翻,其实先做「体检」更高效。解压后按下面这张表快速过一遍,基本能判断这份源码的完整度和架构形态。
| 文件/目录 | 作用 | 怎么看 |
|---|---|---|
.project/.classpath | Eclipse 工程标识文件,IDEA 导入时依赖它识别项目结构 | 存在则导入顺利;缺失则要手动选 Web 目录 |
sql或.sql数据库脚本 | 建库建表语句,决定系统能不能启动 | 没有脚本=只能靠 Hibernate 自动建表或手写表结构 |
src/struts.xml | Struts2 的 action 路由配置 | 看里面有没有<action>节点,判断 mvc 控制层是否完整 |
WebRoot/WEB-INF/lib或webapp/WEB-INF/lib | 放 jar 包的地方,决定 ssh 三件套的版本 | 看有没有struts2-core、spring-web、hibernate-core对应 jar |
根目录.rar里的说明文档 | 有些作者会写 jdk 版本、mysql 版本、部署步骤 | 有就优先按作者的来,没有才按本文的默认方案 |
这里有个很现实的坑:很多二手javaweb毕设源码包里没有数据库脚本。遇到这种情况别慌,ssh 项目里 Hibernate 的映射文件(.hbm.xml)里通常写了每个表的字段和类型,可以反向推出建表语句。下一章我会把最基础的水果商城表结构给出来,即使没有 sql 文件,你也能照着手工建。
3. 数据库初始化与 ssh 数据源打通:让水果、订单、会员表真正落库
老 ssh 项目里,数据库连通性是整个系统能不能转起来的地基。Spring 管数据源,Hibernate 管表映射,Struts2 管请求分发——这三者只要有一个人在数据库这层掉链子,你启动 Tomcat 时就会看到一串红色的Exception。这章我把建表、配数据源、对映射这三步拆开讲,每一步都有可以直接抄的配置。
3.1 先建库:这套水果商城最少需要哪几张表
水果销售商城系统的核心业务是「用户选水果-下单-管理员发货」,所以数据模型围绕这条线展开。下面是兼容 mysql 5.7 和 8.0 的建表脚本,字符集统一用 utf8mb4,避免水果名称里的生僻字和 emoji 变成乱码。
CREATE DATABASE IF NOT EXISTS fruit_mall DEFAULT CHARACTER SET utf8mb4; USE fruit_mall; CREATE TABLE t_user ( id INT AUTO_INCREMENT PRIMARY KEY COMMENT '用户id', username VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', password VARCHAR(100) NOT NULL COMMENT 'md5或明文,实操建议md5', real_name VARCHAR(50) COMMENT '收货人姓名', phone VARCHAR(20) COMMENT '联系电话', address VARCHAR(200) COMMENT '收货地址', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT='会员表'; CREATE TABLE t_category ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL COMMENT '分类名,如:热带水果/时令水果' ) ENGINE=InnoDB COMMENT='水果分类表'; CREATE TABLE t_product ( id INT AUTO_INCREMENT PRIMARY KEY, category_id INT NOT NULL COMMENT '外键关联分类', name VARCHAR(100) NOT NULL COMMENT '水果名称', price DECIMAL(10,2) NOT NULL COMMENT '单价', stock INT NOT NULL DEFAULT 0 COMMENT '库存', image VARCHAR(255) COMMENT '图片路径', description TEXT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_category (category_id) ) ENGINE=InnoDB COMMENT='水果商品表'; CREATE TABLE t_cart ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, add_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id) ) ENGINE=InnoDB COMMENT='购物车表'; CREATE TABLE t_orders ( id INT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(32) NOT NULL COMMENT '订单号', user_id INT NOT NULL, total_amount DECIMAL(10,2) NOT NULL COMMENT '订单总金额', status TINYINT NOT NULL DEFAULT 0 COMMENT '0待付款 1已付款 2已发货 3已完成', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME, UNIQUE KEY uk_order_no (order_no) ) ENGINE=InnoDB COMMENT='订单主表'; CREATE TABLE t_order_item ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL COMMENT '关联订单主表', product_id INT NOT NULL, product_name VARCHAR(100) COMMENT '商品名称快照,防止改价后历史订单跟着变', price DECIMAL(10,2) COMMENT '下单时单价快照', quantity INT NOT NULL ) ENGINE=InnoDB COMMENT='订单明细表';建表时有几个字段是毕设源码里最常见的「雷区」。订单明细表里的product_name和price字段,我刻意用了「快照」思路——查询订单时直接 join 商品表虽然省事,但水果价格一变,历史订单对不上账,答辩时老师一问数据一致性就容易卡壳。t_orders的order_no必须加唯一索引,否则并发下单想查出重复单号都没地方查。所有外键我都没加物理约束,只留普通索引,原因很简单:Hibernate 的hbm2ddl在某些版本下和外键约束有兼容问题,老工程尽量少给自己找事。
3.2 改 jdbc 配置:mysql 5.7 与 mysql 8 完全是两回事
如果你用的是 mysql 8.0,而源码里还是com.mysql.jdbc.Driver,启动时一定会报ClassNotFoundException或者Communications link failure。mysql 8 之后的驱动类名改成了com.mysql.cj.jdbc.Driver,同时必须带上时区参数。下面是 Spring 管理数据源的完整配置,这是 ssh 项目里applicationContext.xml的核心片段。
<context:property-placeholder location="classpath:jdbc.properties"/> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="initialSize" value="5"/> <property name="maxActive" value="20"/> <property name="maxWait" value="60000"/> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="packagesToScan" value="com.fruit.mall.entity"/> <property name="hibernateProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop> <prop key="hibernate.show_sql">true</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean>jdbc.properties文件放在 src 根目录,内容决定了你连的是哪台数据库:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/fruit_mall?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=你的密码两个参数必须说明。useSSL=false是为了避免 mysql 8 默认开启 SSL 握手造成的控制台刷屏和偶发连接超时;serverTimezone=Asia/Shanghai是因为 mysql 8 的驱动强制要求时区,不配会直接报The server time zone value错误。数据库连接池用的是 DBCP,initialSize=5表示启动时预建 5 个连接,maxWait=60000是拿连接的超时时间,单位毫秒——如果你看到Cannot get a connection报错而数据库其实是通的,多半是maxActive太小或连接泄漏,把maxWait调大只能缓解症状,根子还是要在代码里检查有没有关闭Connection。
3.3 hibernate 映射文件要和表结构对齐,否则启动就报错
ssh 项目里 Hibernate 的映射有两种写法:注解和.hbm.xml。老毕设源码九成用的是.hbm.xml,比如下面这段就是TProduct的映射片段。
<class name="com.fruit.mall.entity.TProduct" table="t_product"> <id name="id" column="id"> <generator class="identity"/> </id> <property name="name" column="name" type="string" length="100" not-null="true"/> <property name="price" column="price" type="big_decimal"/> <property name="stock" column="stock" type="integer"/> <property name="image" column="image" type="string"/> <many-to-one name="category" class="com.fruit.mall.entity.TCategory" column="category_id" lazy="proxy"/> </class>这里有一个容易踩的隐形坑:generator class="identity"对应的是 mysql 的自增主键,如果你的建表语句里id没写AUTO_INCREMENT,Hibernate 插入数据时会报主键冲突或空值错误;反过来如果表是自增但映射用的是assigned,那你每次都要手动 set id,不然后台添加水果商品直接失败。我的建议是:建表语句和映射文件的column字段名、type类型必须逐一核对,特别是DECIMAL(10,2)在 Hibernate 里对应big_decimal,不是double,用double会把金额精度搞出0.1 + 0.2 = 0.30000000000000004这种经典笑话。核对方法很简单:启动 Tomcat 后看控制台有没有Table not found或Unknown column的报错,有的话按报错提示回去查.hbm.xml和建表脚本。
提示:
hibernate.hbm2ddl.auto=update可以自动补列,但对删列和改类型无能为力。毕设阶段直接改成update省事,但别指望它做结构迁移。
4. 导入工程到打包 war:一条 MyEclipse 老工程的可运行路线
数据库通了,接下来就是把这份源码变成 Tomcat 里能访问的 Web 应用。这一步难倒过很多 j2ee 新手,因为网上教程里讲的都是「在 idea 里新建 jsp 项目从头写」,而你现在手里是一个老 MyEclipse 工程,目录结构、依赖方式、部署方式都和新的 IDE 流程有出入。别急着把代码复制到新项目里,那会让 Spring 的扫描路径和 Struts2 的配置文件全部失效,属于最得不偿失的做法。
4.1 用 IDEA 直接把 MyEclipse 老工程导入并跑起来
IDEA 对老工程的兼容性是几个 IDE 里最好的。具体操作路径是:File -> New -> Project from Existing Sources,然后选中源码包里的.project文件或项目根目录,IDEA 会识别出这是一个 Web 项目,并自动读取.classpath里的依赖。导入后重点检查两处:Project Structure 里的 SDK 是否指向 jdk 8,以及 Artifacts 是否生成了war exploded格式。
war exploded和war的区别,一句话讲清楚:前者是解压目录,Tomcat 直接跑这个目录里的文件,改 jsp 不用重启就能看到效果;后者是压缩包,适合部署到服务器,但每次改完都要重新打包。本地调试阶段,我的习惯永远是配war exploded。IDEA 里配置 Tomcat:Run -> Edit Configurations -> + -> Tomcat Server -> Local,在Deployment页签把war exploded加进去,Application context 填/fruit_mall,这样启动后访问地址就是http://localhost:8080/fruit_mall/index.jsp。
# 如果你更喜欢命令行方式,也可以直接用 catalina 跑 cp target/fruit_mall.war $CATALINA_HOME/webapps/ $CATALINA_HOME/bin/startup.sh # 日志实时查看 tail -f $CATALINA_HOME/logs/catalina.out命令行方式适合你只想验证部署、不想开 IDEA 的场景。startup.sh启动后,Tomcat 会把 war 自动解压到webapps/fruit_mall目录。这里有个坑:如果有两个同名的 war 副本,Tomcat 会按时间戳覆盖,容易造成你改的是 A 目录、跑的是 B 目录的诡异现象。毕设调试期我建议始终只用一种部署方式,不要一会儿 IDEA 一会儿手动 copy,避免「改了代码没生效」的玄学找上门。
4.2 web.xml 到 struts.xml:把 ssh 三件套串起来的配置顺序
一套完整的 ssh 集成,入口是web.xml。Tomcat 启动时先读它,所以它的配置顺序就是框架的装配顺序,漏一个 filter 或监听器,后面全乱。
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>解析一下这套配置的含义:ContextLoaderListener是 Spring 的入口,它会加载applicationContext.xml,把 service 层对象全部实例化放进容器;StrutsPrepareAndExecuteFilter是 Struts2 的核心过滤器,拦截所有/开头的请求,再根据struts.xml里的路由规则决定交给哪个 action。这里要强调一个和 spring mvc 的概念区分:spring mvc 用的是DispatcherServlet,配置在web.xml的 servlet 节点里;而 ssh 项目的 mvc 控制层是 Struts2 的 filter 机制,两者请求流转模型完全不同,别把 spring mvc 的配置写法硬套到老工程上。
struts.xml 里最容易写错的是 action 的 class 属性。很多人会写成类的全限定名,比如com.fruit.mall.action.ProductAction,这在没有集成 Spring 的项目里没问题。但 ssh 集成后,action 应该由 Spring 容器创建,所以 class 属性要写 Spring 容器里的 bean id:
<package name="fruit" extends="struts-default"> <action name="product_list" class="productAction" method="list"> <result name="success">/jsp/product_list.jsp</result> </action> </package>对应applicationContext.xml里要有这个 bean:
<bean id="productAction" class="com.fruit.mall.action.ProductAction" scope="prototype"/>这里解释一下为什么scope必须是prototype:Struts2 的 action 是有状态的,每个请求要独立的实例来装参数。如果写成默认的单例,并发请求时用户 A 选的芒果会串到用户 B 的购物车里,这是毕设答辩现场最容易暴露的并发问题。而 service 层的 bean 则相反,用单例就够,因为它们是无状态的。
4.3 传统 jsp 项目打包 war 的三种方式与 nginx 的认知误区
毕设交付或者部署到服务器,最终形态基本都是一个 war 包。传统 jsp 项目不是 maven 工程的话,打包方式有三种:IDEA 的 Build Artifacts、手动 jar 命令、临时补一个 maven 插件。如果你手里这份源码没有 pom.xml,最不折腾的方式是用 IDEA 的Build -> Build Artifacts -> fruit_mall:war,输出在out/artifacts目录下。如果源码本身就带 maven 结构,直接在 pom 里加上 war 插件,用一条命令搞定:
mvn clean package -DskipTests # 产物在 target/ 目录下,部署方式同 4.1关于部署,有一个频繁被问的问题值得专门说:nginx 支持 jsp 吗?答案是不支持。nginx 本身只能处理静态资源转发,遇到.jsp请求它既不会编译也拿不到数据,正确的架构是 nginx 做静态资源代理和负载均衡,动态请求proxy_pass转发到后端的 Tomcat 处理。如果你在调一套前后端不分离的老项目,没必要硬上 nginx,让 Tomcat 直接同时处理 jsp 和图片即可;等以后你做 spring boot 前后端分离项目时,再让 nginx 接管静态资源才是合理分工。
5. 这套毕设源码的八个高频翻车点:现象、原因、处理顺序
跑老 ssh 毕设项目,很多报错你搜出来的答案互相矛盾,是因为同一个报错在不同版本组合下原因不同。我把处理这类源码时最常遇到的八个问题按「现象 → 原因 → 解决」列出来,每条都是踩过之后才真正理解的经验。
5.1 数据库连不上:一堆红色异常,看关键行是Access denied或Communications link failure
现象是 Tomcat 启动时 Spring 初始化 dataSource 失败。原因分两种:账号密码不对,或者驱动版本与 mysql 版本不匹配。解决方法是先确认 mysql 里是否创建了fruit_mall库并执行了建表脚本,再用命令行mysql -u root -p手动登录一次排除密码错误;如果密码没问题,基本就是驱动类名和 url 没按 3.2 节的写法配。mysql 5.7 用com.mysql.jdbc.Driver,mysql 8 必须换com.mysql.cj.jdbc.Driver,同时 url 里加时区参数,这条最值钱。
5.2 页面 500 白屏:JSTL 标签不被识别,Cannot find the tag library descriptor
现象是启动没问题,首页也能开,但一进带c:forEach的 jsp 页面就 500。原因很实际:lib 目录里缺jstl.jar和standard.jar,很多二手源码在压缩时把这两个 jar 漏掉了。解决方法是把这两个 jar 下载后丢进WEB-INF/lib,然后重新打包部署;顺手检查一下 lib 目录下其他 jar 是否齐全,最稳的做法是把整个 lib 目录替换成一套完整 ssh 依赖。
5.3 请求 404:struts2 的 action 路由找不到,控制台提示No action mapped
现象是点击某个功能链接后 URL 路径存在但显示 404。原因大概率是struts.xml里 action 的 name 和页面里的请求地址不匹配,或者是 class 里写成了类全限定名导致 Spring 容器里没有这个 bean。解决方法是先看地址栏 URL 的 action 名,再到struts.xml里搜索对应的<action name="">;如果 action 名对得上还 404,重点检查 class 属性是 bean id 还是全限定类名。
5.4 上传的水果图片刷新后丢失:图片明明传成功了,再访问 404
现象是管理员后台上传图片,当时页面能显示,重启 Tomcat 后图片就不见了。原因是 IDE 里部署用的是war exploded目录,上传文件写进了 tomcat 的临时部署路径,而源码目录里的upload文件夹根本没收到文件。解决方法是把上传路径配置改成绝对路径,比如D:/fruit_mall_upload/,在applicationContext.xml或工具类里统一读这个配置;图片访问通过 Tomcat 的虚拟路径映射到该目录。这个坑属于老 web 项目的通病,提前改能省很多事。
5.5 Tomcat 10 启动直接失败:Servlet类抛ClassNotFoundException: jakarta.servlet
现象是一启动就NoClassDefFoundError或者java.lang.ClassNotFoundException。原因是 tomcat 10 把javax.servlet全面升级成jakarta.servlet,老源码里所有 jsp/servlet 的 import 全废了。这不是改几个 import 能解决的,整个项目几十个类连带框架 jar 都基于旧包名。解决方法是别用 tomcat 10,按第 2 章换成 8.5,这类框架级升级成本完全不值得在毕设阶段承担。
5.6 IDEA 导入工程后一堆红叉:Error: java: 程序包com.fruit.mall.xx不存在
现象是代码里大量 import 飘红,但 lib 目录明明有 jar。原因是.classpath用的是 MyEclipse 的绝对路径引用,换机器后路径失效。解决方法是打开Project Structure -> Libraries,把所有依赖删掉重新添加WEB-INF/lib作为 jar 目录;如果项目还没有 maven 支持,这一步做完红叉应该能消掉九成。这也提醒一个事:老工程换 IDE 后,第一件事永远是检查依赖引用方式,而不是先改代码。
5.7 中文乱码:页面显示成丰富,或者存入数据库后全是问号
现象是登录用户名或水果名称在前端显示为乱码。原因分三层:jsp 页面编码、Tomcat 接收请求编码、数据库连接编码。解决方法是按顺序排查:jsp 文件头部加pageEncoding="UTF-8";Tomcat 的conf/server.xml里给 Connector 加URIEncoding="UTF-8";jdbc.url里确认有characterEncoding=utf8。三层都设一致,乱码问题基本绝迹。如果从 mysql 命令行看数据是正常的,只是页面乱,那问题就出在前两层。
5.8 Tomcat 启动报端口被占用:Port 8080 required by Tomcat v8.5 Server at localhost is already in use
现象很明显,8080 被占用。原因通常是上次启动的 Tomcat 没有完全关闭,或者别的开发服务占了端口。解决方法是先netstat -ano | findstr 8080找到 PID,然后taskkill /PID 进程号 /F强制结束;更优雅的方式是回到 IDEA 的 Tomcat 配置页,把端口从 8080 改成 8081 临时绕开,但别忘了把项目里的绝对路径写死逻辑端口也要一起改。这里有个小习惯:毕设答辩前,确认机器上只有一个 Tomcat 实例在跑,避免现场演示时端口冲突。
6. 答辩增量这样加:库存校验、个人信息展示和销量导出,一个都不能少
很多人拿到这套源码后最焦虑的不是跑不通,而是「代码全看得懂,但觉得自己没做什么」。毕设答辩老师的套路其实很固定:系统能演示、代码能讲清、有几个自己加的亮点。第二点和第三点是分不开的,因为只有你亲手改过,老师问细节你才能答得上来。下面三个增量功能都属于改动范围小、演示效果明显、且符合水果商城业务逻辑的,选一个或两个做进去就够。
第一个是购物车库存校验。很多老二手源码的购物车只做了「加数量」,但没校验库存,等于说顾客能买 9999 斤芒果。在CartAction或 service 层的addCart方法里加一段检查:
public String addCart() { // productService.getById 从数据库查当前库存 TProduct product = productService.getById(productId); if (product.getStock() < quantity) { this.addActionError("库存不足,当前仅剩" + product.getStock() + "件"); return "input"; } cartService.add(userId, productId, quantity); return "success"; }这段代码逻辑很简单,但演示价值很高——你可以现场演示:把后台商品库存改成 2,前台购物车加 3 件,页面立刻报「库存不足」。这就是一个完整的「业务规则」闭环,比单纯增删改查的选题有说服力得多。
第二个是个人信息展示页面。老源码里的用户模块通常只做了注册登录,个人中心就是个空壳。你可以做一个user/profile.jsp,把用户订单数、累计消费、常用收货地址展示出来。数据来源走 Hibernate 查t_orders表按user_id统计即可,不需要新表。这个功能贴合近期很多基于 jsp 的毕设选题偏好,而且实现起来就是一个 SQL 聚合的事。
第三个是销量导出。给管理员后台的订单列表加一个「导出今日销量 CSV」的按钮,核心逻辑是把t_orders按日期分组统计:
SELECT DATE(create_time) AS day, COUNT(*) AS order_count, SUM(total_amount) AS amount FROM t_orders WHERE status >= 1 GROUP BY DATE(create_time) ORDER BY day DESC;在 action 里执行这条语句后,把结果循环拼成 CSV 字符串,通过 response 输出成附件下载。这段代码展示的是「把数据库数据变成实际产物」的能力,比在页面上画个表格高级半档。CSV 格式很简单,列头用逗号分隔,每行一条记录,注意处理一下金额的千分位。答辩时把文件放到 Excel 里打开,效果非常直观——这就是一份最小可用的运营报表。
这三个功能我建议只挑一个做深,不要三个都上。理由很实在:毕设源码调试的时间成本和出 bug 的概率是叠加的,增量功能越多,启动时无法定位问题的可能性越大。我自己的习惯是选库存校验——它改动最小、逻辑最容易被追问、现场演示最容易制造「反差感」,一次秒答比背十页 PPT 都顶用。把数据库脚本、jdbc 配置、Tomcat 版本这三样东西固定成一份自己的「部署清单」,以后不管换机器还是换项目,都能在十分钟内跑起来。希望帮到你。
本文还有配套的精品资源,点击获取