简介:基于JSP+Servlet开发的外卖订餐系统是一套完整可运行的JavaWeb项目,采用模型-视图-控制器分层设计,业务逻辑与页面展示清晰分离,并使用MySQL数据库存储会员、商家、骑手、订单等业务数据,覆盖用户注册登录、菜品浏览、下单结算、商家接单、骑手配送及管理员后台管理等完整业务闭环,适合正在学习Java Web开发、准备课程设计或毕业设计的在校生与初级开发者参考。压缩包整体约93.63MB,内含项目源代码、数据库初始化脚本、界面截图与操作演示视频,整体结构清晰,便于按模块拆解并复现部署流程。目前已有659人学习下载,属于同类实战资源中热度较高的案例。通过运行该项目并结合视频演示,读者可直观理解Servlet控制层与JSP视图层的协同方式,掌握登录鉴权、订单状态流转、角色权限分配、菜品维护、配送任务分配等核心功能的实现思路,并能借助预设数据库快速搭建Eclipse开发环境,进一步巩固JavaWeb、Servlet、JSP与MySQL的综合编程能力,也可基于现有代码进行二次扩展与优化,形成自己的外卖点餐系统。
1. 外卖订餐系统的毕业设计怎么做:JSP+Servlet 全套源码的落地复盘
拿到这套基于 JSP+Servlet 开发的外卖订餐系统源码时,我的第一反应是“这不就是教科书里说的 Java Web 标准搭配吗”——视图层用 JSP 动态渲染页面,Servlet 接收请求做路由分发,MySQL 存订单和用户数据,跑在 Tomcat 上。真把它导入 Eclipse 跑通一遍后,发现这套项目比想象中完整:会员、商家、骑手、管理员四个端口都做了界面和权限控制,购物车、下单、接单、配送、结算这条主链路能从头走到尾,不是只有几个增删改查页面的半成品。如果你正在找 Java Web 课程的课程设计或毕业论文项目,这套源码的价值在于它把 MVC 分层、会话管理、多角色权限、订单状态流转这些高频考点全部落到了具体代码里。下文我从架构、数据库、核心链路、部署到排查,把它拆开讲透。
2. 系统架构与数据库设计:MVC 分层、四类角色与 online.sql 表结构
2.1 技术选型与 MVC 分工:为什么 JSP+Servlet 仍是稳妥选择
先泼一盆冷水:这套项目不是 Spring Boot,没有注解式开发,没有 MyBatis 自动映射,连依赖管理都是传统方式把 jar 包放进 WEB-INF/lib。但正因为传统,它的学习价值反而更高。JSP 本质上是 servlet 运行时的视图模板,容器会把 .jsp 文件编译成 Java 类再执行;Servlet 则直接处理 HTTP 请求和响应。两者配合时,Servlet 充当控制器——取参数、调模型、存 session、转发或重定向到 JSP;JSP 只负责用<%= %>、EL 表达式和 JSTL 标签把数据渲染成 HTML。
用 Java Web 开发外卖订餐类项目,选 JSP+Servlet 而非 Spring 全家桶的考量,主要有三点。第一,Servlet 的生命周期(init、service、destroy)和请求转发(forward/redirect)是面试和笔试常考内容,亲手写一遍才能理解容器是怎么管理这些对象的。第二,不用框架时,HTTP 请求怎么进、路由怎么分、session 怎么存取都暴露在眼前,排查问题时没有“黑匣子”,调起参来反而直接。第三,这个组合对机器配置要求低,Eclipse + Tomcat 8.5 + JDK 8 就能跑,适合学生电脑和学生机房的部署环境。
这套项目的设计上还有一点值得注意:虽然用的是 JSP 渲染,但页面呈现和业务数据分离做得比较干净,servlet 包里不掺 HTML 代码,JSP 页面里不写 Java 业务逻辑(少量循环输出除外)。这种“前端页面相对独立”的写法,能帮你后期把 JSP 替换成 Bootstrap 静态页 + Ajax 接口时省不少力气。
2.2 数据库表设计:订单、用户、商品、商家、骑手怎么关联
打开 online.sql,会看到一套按外卖业务场景设计的表结构。核心表大致分为五组:用户表(含会员、商家账号、骑手账号、管理员账号,通过角色字段区分)、商家表(商家名称、营业执照、营业状态等)、商品表(归属某个商家、价格、库存、分类)、订单表(下单用户、商家、配送骑手、订单状态、总金额、收货地址)、订单明细表(一个订单对应多道菜品,记录数量和小计)。
这里有个典型的“用户角色冗余”设计:普通用户、骑手、商家、管理员合并在同一张用户表中,用 role 字段区分,而不是拆成四张独立的表。这种做法的好处是登录逻辑统一——一次查询就能知道这个人属于哪个角色,跳转到对应首页;坏处是如果后期要给不同角色加完全不同的属性字段,表会越改越乱。但对于外卖订餐这种角色功能有大量交集的场景(都要登录、都要有联系方式、都要有状态),合并设计反而更务实。
订单表与订单明细表是典型的主从表结构。主表订单表记录这笔订单的全局信息:下单用户 id、商家 id、总金额、配送地址、下单时间、订单状态;从表订单明细表逐条记录每道菜:商品 id、数量、单价、小计。为什么要拆两张表而不是在订单表里加一个“商品列表”字段?因为查询“某用户最近 10 笔订单里的所有菜品”时,拆表后一条 SQL 就能关联出来;而且结算、退款、商家对账都需要按明细统计。理解了这个场景,你就知道数据库设计里“先拆开再按需关联”才是正解。
2.3 online.sql 导入方法:从建库到初始化数据的完整命令
拿到项目后,第一步永远是把数据库建起来。不要用可视化工具点来点去,直接命令行操作最快,也方便排查编码问题。
mysql -u root -p < online.sql如果提示找不到文件,先用绝对路径:
mysql -u root -p < D:/wbds/online.sql导入时有两个参数建议加上——字符集和数据库名覆盖。常用的稳妥写法:
mysql -u root -p --default-character-set=utf8 < online.sql导入完成后,登录 MySQL 验证表和初始数据:
SHOW DATABASES; USE online_order; SHOW TABLES; SELECT id, username, role FROM user LIMIT 10;代码逻辑说明:mysql 命令从文件读取 SQL 并逐条执行,online.sql 里如果包含 CREATE DATABASE 语句,导入前不用手动建库;如果文件里没有建库语句,则需要先 CREATE DATABASE 再 USE。验证的目的是确认数据表数量和账号初始数据都在,避免后面项目连不上或登录不了才反过来查数据库的问题。
提示:导入前先确认 MySQL 版本。如果本机装的是 MySQL 8.0,账密认证方式默认是 caching_sha2_password,老项目里的 JDBC 驱动连不上时会报 unable to load authentication plugin,这个坑在第 5 章单独讲。
3. 源码走读与核心实现:登录鉴权、购物车与订单状态流转
3.1 包结构与 Servlet 映射:一个 Controller 类如何分发四种角色
用 Eclipse 导入项目后,先看 src 目录的包结构。常见的组织方式是:com.xxx.servlet 放控制层,com.xxx.dao 放数据库访问层,com.xxx.entity(或 model/bean)放实体类,com.xxx.util 放工具类如 DBHelper。这种分层在包名上就能看出 MVC 的影子——entity 是 Model 的数据载体,dao 是 Model 的数据访问实现,servlet 是 Controller,WebContent 下的 JSP 是 View。
观察 web.xml 里的 Servlet 映射,会发现一个关键设计:很多模块共用一个 Servlet 类,通过请求参数区分操作。例如一个 OrderServlet,既处理“会员提交订单”,又处理“商家发货”和“骑手确认送达”,靠的是前端表单里隐藏的 method 参数或者请求路径后缀。常见的写法有两种:
// 方式一:同一个 Servlet,通过参数区分动作 protected void doPost(HttpServletRequest request, HttpServletResponse response) { String action = request.getParameter("action"); if ("add".equals(action)) { // 加入购物车逻辑 } else if ("submit".equals(action)) { // 提交订单逻辑 } else if ("deliver".equals(action)) { // 商家发货 / 骑手配送逻辑 } }// 方式二:用路径后缀区分动作,例如 /order/add、/order/submit protected void service(HttpServletRequest request, HttpServletResponse response) { String path = request.getServletPath(); if (path.endsWith("/add")) { // 加入购物车 } else if (path.endsWith("/submit")) { // 提交订单 } }参数说明:方式一适合 JSP 表单里写<input type="hidden" name="action" value="submit">,优点是 Servlet 类少、web.xml 配置简单;缺点是一个 Servlet 里 method 分支多了之后,代码会膨胀到几百行,后期维护要看很多分支。方式二把动作体现在 URL 上,更接近 REST 风格,但 web.xml 要配多个 url-pattern 或采用 *.do 后缀通配。这套源码里两种风格可能混用,读懂一个,其他就好猜了。
登录鉴权这块,一般用 session 存当前登录用户对象。拦截未登录请求时,常见做法是在每个 JSP 页面顶部检查 session:
<% User user = (User) session.getAttribute("loginUser"); if (user == null) { response.sendRedirect("login.jsp"); return; } %>更规范的做法是写一个 Filter 统一做登录拦截,在 web.xml 里配置 url-pattern 排除登录页和静态资源。如果这套源码用的是每个页面手写检查,不要觉得它 low,很多老项目都是这么干的。理解后用 Filter 重构是一个很好的练习方向。
3.2 会员下单链路:从浏览菜单到生成订单的完整数据流
会员端最核心的链路是“浏览商家菜谱 → 加入购物车 → 提交订单 → 生成订单+订单明细 → 等待商家接单”。这段链路会把前面说的表结构全部串起来。我建议按这个顺序读代码:
第一步,商品浏览。会员登录后进入商家列表页面,点击某个商家跳转到该商家的菜谱页面。这里涉及两张表的查询:商家表(取商家名称和状态)和商品表(WHERE merchant_id = ?)。注意商品表里会有“是否在售”之类的状态字段,在售状态为 1 的才会显示在前台。
第二步,加入购物车。如果购物车数据存在 session 里,逻辑比较简单——用商品 id 作为 key 存成 Map,再存数量。核心代码示意:
// 购物车操作:从 session 取出购物车,追加或修改商品数量 HttpSession session = request.getSession(); Map<Integer, CartItem> cart = (Map<Integer, CartItem>) session.getAttribute("cart"); if (cart == null) { cart = new HashMap<Integer, CartItem>(); } int goodsId = Integer.parseInt(request.getParameter("goodsId")); int count = Integer.parseInt(request.getParameter("count")); CartItem item = cart.get(goodsId); if (item == null) { item = new CartItem(); item.setGoodsId(goodsId); item.setCount(count); // 这里通常还会把商品名称、单价查出来存进 CartItem,便于页面直接展示 } else { item.setCount(item.getCount() + count); } cart.put(goodsId, item); session.setAttribute("cart", cart);代码逻辑说明:这里把购物车放在 session 中,利用了“同一用户的多个请求共享同一个 session 对象”这一机制。这个做法对单体应用来说够用,重启 Tomcat 后 session 消失,购物车也就清空了。如果你想要重启不丢购物车,需要把购物车持久化到数据库,但那种做法复杂度会高不少,适合后期优化。
第三步,提交订单。购物车数据在 session 里,提交订单时要做的动作是:读取 session 中的购物车数据,计算总金额,插入订单主表,再循环遍历购物车每一项插入订单明细表。这中间务必注意事务——订单主表和明细表必须同时插入成功或者同时回滚,否则会出现“订单有总金额但没有菜品明细”的脏数据。代码大概长这样:
Connection conn = DBHelper.getConnection(); try { conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 1. 插入订单主表,拿到自增主键 orderId // 2. 循环插入订单明细表,每条带上 orderId conn.commit(); // 全部成功,提交 } catch (Exception e) { conn.rollback(); // 任何一步失败,回滚 e.printStackTrace(); } finally { conn.setAutoCommit(true); DBHelper.close(conn, null, null); }参数说明:setAutoCommit(false) 与 commit/rollback 的配合是事务控制的标准写法,在 JDBC 操作多张表时一定要养成这个习惯。很多初学者翻车是因为只在单表上写增删改,没处理过跨表事务,结果订单表和明细表数据对不上。这套源码里订单创建这段如果没写事务,建议你手动补上,这是毕业论文里能写进“创新点”的稳妥优化。
3.3 商家接单与骑手配送:订单状态字段驱动的流程推进
订单从生成到完成,不是简单地改一次状态,而是有明确的生命周期。常见状态设计:待接单(0)→ 已接单/备餐中(1)→ 配送中(2)→ 已完成(3),另外要有已取消(-1)兜底。
“状态机”的实现方式一般是:所有操作只做一件事——把订单状态从某个值改成下一个值。商家端点击“接单”时,后台执行UPDATE orders SET status = 1 WHERE id = ? AND status = 0,这里的 AND status = 0 是并发保护,防止商家重复点击导致状态错乱。骑手端点击“开始配送”时,执行SET status = 2 WHERE id = ? AND status = 1。
为什么强调在 UPDATE 语句里带上当前状态?因为如果有两个请求同时到达(比如商家双击接单按钮),不带条件的更新会导致状态被覆盖。带上了旧状态条件,第二次更新影响行数为 0,业务端可以通过判断int rows = preparedStatement.executeUpdate()返回 0 来提示“订单状态已更新,请刷新页面”。这套源码里如果有的地方没加这个条件,你在二次开发时把它补上,就能在论文里写一笔“基于乐观锁思路的订单并发控制”。
骑手端还涉及一个关键点:一个骑手能看到的订单列表,是那些“商家已接单但尚未被其他骑手抢单”的订单。这里的 SQL 查询条件一般是WHERE status = 1——商家接单后,状态为 1 的订单变成可抢单状态;骑手点击“接单”后,订单状态改成 2,同时把骑手 id 写入订单表。这样其他骑手刷新列表时,就看不见这笔订单了。抢单并发场景下,依然要依靠“更新时带条件”来控制。
3.4 JSP 视图复用:用 include 与 EL 表达式减少重复代码
四个角色的首页、顶部导航、侧边栏有很大一部分是重复的。直接把整页复制四份,改动一个链接要改四个文件,是新手项目最痛苦的地方。这套源码在视图层通常用了两种复用手段。
第一种是 JSP 的 include 指令或 include 动作。例如把顶栏单独做成 header.jsp,在每个角色页面里引入:
<%@ include file="common/header.jsp" %><jsp:include page="common/header.jsp" flush="true" />两种 include 的区别值得注意。<%@ include %>是静态导入,在 JSP 编译阶段就把文件内容合并进来,相当于把代码粘贴进来,效率高但不支持传递参数;<jsp:include>是动态导入,运行时单独执行被包含文件,灵活性高但每次请求多一次调用开销。做顶栏这种固定内容用静态导入足够。
第二种是 EL 表达式。在 JSP 页面里直接通过${sessionScope.loginUser.username}输出当前登录用户的用户名,不再需要写<% out.println(user.getUsername()) %>。配合 JSTL 的 c:forEach 遍历订单列表,代码会简洁很多。建议你在读源码时统计一下 JSP 里还有多少<%= %>的原始输出方式,把它们替换成 EL 不仅代码更干净,也贴合新版教材的评分标准。
这里补充一个容易被忽略的细节:角色的首页跳转通常是在登录 Servlet 里根据 role 做的。登录成功查用户表得到 role 字段,然后 switch 判断 role 等于 1 跳 member.jsp,等于 2 跳 merchant.jsp,等于 3 跳 rider.jsp,等于 0 跳 admin.jsp。你要留意这种 if-else 或 switch 是否覆盖了未知角色值的情况,否则遇到脏数据会被打到空白页。
4. 部署与运行全流程:Eclipse + Tomcat + MySQL 从导入到浏览器访问
4.1 环境准备清单:JDK、Tomcat、Eclipse、MySQL 的版本搭配建议
这套项目没有用 Maven,所以没有 pom.xml 依赖自动下载。你需要手动准备以下环境:
| 组件 | 建议版本 | 说明 |
|---|---|---|
| JDK | 1.8 | Java Web 老项目最稳的是 JDK 8,JDK 11 以上可能出现兼容问题 |
| Tomcat | 8.5 或 9.0 | 对应 Servlet 3.1/4.0 规范,兼容 JDK 8 |
| Eclipse | Eclipse IDE for Enterprise Java and Web Developers | 必须是 Java EE 版本,Java SE 版没有 Web 项目视图 |
| MySQL | 5.7 或 8.0 | 5.7 兼容性最好,8.0 需要改驱动和认证方式 |
| JDBC 驱动 | mysql-connector-java 5.1.49 或 8.0.x | 必须与 MySQL 版本配套 |
版本搭配是最容易翻车的一环。如果你本机只有 MySQL 8.0,项目 lib 目录里若放的是 5.1.x 老驱动,连接时会报 “Unable to load authentication plugin 'caching_sha2_password'”。解决办法是下载 mysql-connector-java 8.0.x 的 jar 包,替换掉 lib 里旧驱动,同时检查数据库连接 URL 是否带了useSSL=false&serverTimezone=Asia/Shanghai参数。这些细节在第 5 章展开讲。
4.2 导入项目与修改数据库连接:三个必改参数别漏
打开 Eclipse,File → Import → General → Existing Projects into Workspace,选择源码根目录,把项目导入进来。项目导入后先别急着启动,首要任务是找到数据库连接配置文件。常见位置是 src 下的 db.properties、jdbc.properties,或是有个 DBHelper.java / DBUtil.java 工具类,里面硬编码了连接参数。
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/online_order?useUnicode=true&characterEncoding=utf8 username=root password=123456参数说明:url 里的 3306 是 MySQL 默认端口,online_order 是库名,必须和 online.sql 里创建的库名一致;useUnicode=true&characterEncoding=utf8 保证中文写入不乱码,建议保留;username 和 password 改成你自己 MySQL 的账号密码。如果项目用的是硬编码方式,直接在 DBHelper.java 对应的 String 常量里改,改完保存会让 Eclipse 自动重新编译。
注意:改完连接配置后,建议先单独写个测试类或在浏览器访问一个会查数据库的页面,确认连接成功。不要等启动了 Tomcat 才发现数据库连不上,那时候报错信息混在一起,排查效率很低。
4.3 启动 Tomcat 与验证登录:用账号.txt 里的角色账号逐个测试
Eclipse 里右键项目 → Run As → Run on Server,选择已配置的 Tomcat 8.5,项目就会部署到 Tomcat 的 webapps 目录并启动。首次启动后浏览器访问:
http://localhost:8080/项目名/login.jsp项目名取决于 Eclipse 里项目的名称,如果你导入时改过名,URL 要跟着改。登录页面出来后,打开压缩包里的“帐号.txt”,里面一般有预设的测试账号。如果你发现账号表里密码是明文,那说明这套项目没有做加密处理。这个点可以从安全性角度在论文里提一句“建议引入 MD5 或 BCrypt 对密码加盐哈希”,但本地测试阶段用明文账号直接登录即可。
登录后逐个角色验证功能:会员端测试“浏览商家 → 加购 → 下单”;商家端测试“查看订单 → 接单”;骑手端测试“查看可抢订单 → 开始配送 → 确认送达”;管理员端测试“查看用户列表 → 禁用或删除用户”。这个过程不仅是验收功能,更重要的是观察数据变化——每完成一步操作,去 MySQL 里查对应数据表的记录。我一般会在 Navicat 里开一个查询窗口,反复执行SELECT * FROM orders ORDER BY create_time DESC LIMIT 5;,看到状态位从 0 变 1 变 2 变 3,说明链路是通的。
4.4 war 包导出与独立部署:脱离 Eclipse 运行的两种方式
如果演示环境没有 Eclipse(比如答辩用的教室电脑),可以把项目打成 war 包,丢到 Tomcat 的 webapps 目录直接运行。Eclipse 中导出 war 包:右键项目 → Export → WAR file,目标文件选到 Tomcat 的 webapps 目录下,命名成ROOT.war可以免去 URL 里的项目名;再或者手动改 server.xml 配 Host appBase,不推荐新手折腾。
打好的 war 包放到 Tomcat 的 webapps 目录,启动 Tomcat 后容器会自动解压。访问效果和 Eclipse 里运行一致。但注意一点:如果数据库是本机的 MySQL,独立部署时确保 MySQL 服务已启动,且连接配置。jar 包依赖都在 WEB-INF/lib 里,war 包里已经带上了,不需要额外配置 classpath。这种“导出 war 包再部署”的方式,比现场开 Eclipse 跑演示要稳得多,我见过太多人答辩时 IDE 启动失败然后一脸尴尬的场景。
5. 部署与开发中的常见问题排查与避坑记录,5 条血泪经验
5.1 Tomcat 启动时报错“The origin server did not find a current representation”
现象:浏览器访问页面返回 404,控制台没有明显异常。
原因:通常是项目部署路径不对,或者 WebContent 目录下没有默认首页 index.jsp。直接访问http://localhost:8080/login.jsp时,Tomcat 找的是 webapps/项目名/login.jsp,如果项目部署名不一致或文件不在根目录,就会 404。
解决:先确认项目的 Web 根目录设置。Eclipse 里右键项目 → Properties → Web Project Settings,把 Context Root 记下来,访问时路径必须和它一致。再看 login.jsp 是否在 WebContent 根目录下(不是 WEB-INF 里面),JSP 放在 WEB-INF 下无法通过 URL 直接访问,这也是常见的低级错误。
5.2 MySQL 8.0 连接报错“Unable to load authentication plugin 'caching_sha2_password'”
现象:启动 Tomcat 后访问任何查询数据库的页面,报错信息里出现 caching_sha2_password。
原因:MySQL 8.0 默认认证插件是 caching_sha2_password,而项目 lib 目录里的 JDBC 驱动是 5.1.x 旧版,不支持这种认证方式。这个坑在装了 MySQL 8 的电脑上几乎必踩。
解决:两个办法任选。一是把 lib 下的 mysql-connector-java 替换成 8.0.x 版本 jar 包;二是把 MySQL 用户的认证插件改回 mysql_native_password。第二种办法在命令行执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码'; FLUSH PRIVILEGES;如果是改驱动,连接 URL 里还要加上?useSSL=false&serverTimezone=Asia/Shanghai,否则会报 SSL 或时区相关的错。改完后重启 Tomcat。以后每次新装 MySQL 8,我做的第一件事就是把驱动换成 8.0.x,再也不用跟认证插件死磕。
5.3 JSP 页面中文全是乱码,改页面编码也没用
现象:页面显示中文为“???”,或显示为乱码符号,数据库里中文正常或同样乱码。
原因:这是一个三重编码不一致叠加的问题。JSP 文件本身的编码、服务器响应给浏览器的编码、数据库连接的字符集,这三者只要有一个不一致就乱码。最常见的情况是 JSP 文件是 UTF-8,但 request/response 没有设置编码,Tomcat 默认用 ISO-8859-1 处理 POST 请求参数,导致中文参数在 Servlet 里取出来就是乱码。
解决:第一,在每个 JSP 页面顶部确保有<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>。第二,在 Servlet 的 doPost 方法开头加:
request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");第三,数据库连接 URL 里带characterEncoding=utf8。这三处全设置对了,中文基本不会再乱。如果还乱,再检查 MySQL 表本身的默认字符集,SHOW CREATE TABLE user;看是不是 latin1,是的话用 UPDATE 或 ALTER 改成 utf8mb4。
5.4 Tomcat 端口被占用,启动直接失败
现象:点击 Start 后控制台报错“Port 8080 required by Tomcat v8.5 Server at localhost is already in use”。
原因:之前启动的 Tomcat 实例没有正常关闭,或者本机装了其他软件占了 8080。Eclipse 里多次强制关闭服务器容易出现这个情况。
解决:打开命令行,找到占用进程并结束:
netstat -ano | findstr 8080 taskkill /PID 进程号 /F或者直接改 Tomcat 端口。找到 Tomcat 安装目录下 conf/server.xml,把 Connector 的 port 从 8080 改成 8088:
<Connector port="8088" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />改完后启动,访问地址要同步变成http://localhost:8088/项目名/login.jsp。这里要提醒,如果项目代码里有写死端口号的跳转逻辑(比如 response.sendRedirect 带端口),那改端口后这些写死的地址也要跟着改,否则会出现“页面跳转后无法访问”的坑。
5.5 页面能开但登录后一直跳回登录页
现象:输入正确的账号密码,点击登录后页面刷新回登录页,或提示未登录。
原因:session 存取失败。常见三类原因:其一是登录 Servlet 里存的 session key 和 JSP 里取出的 key 不一致——比如一个地方用loginUser,另一个地方用user,永远取不到;其二是登录后使用response.sendRedirect跳转时,带上的是相对路径而不是绝对路径,导致跳到了别的地方;其三是浏览器禁用了 Cookie,session 依赖 Cookie 传递 JSESSIONID,Cookie 被禁后每次都新开 session。
解决:先看代码里存 session 的地方:
session.setAttribute("loginUser", user);再看 JSP 页面里取值的地方:
<% User user = (User) session.getAttribute("loginUser"); %>两处 key 必须完全一致。跳转用response.sendRedirect(request.getContextPath() + "/index.jsp"),加上 getContextPath 前缀是最稳的。浏览器禁用 Cookie 的情况,检查浏览器的隐私设置,把网站的 Cookie 权限改回允许。
6. 验证方法与二次开发技巧:SQL 日志跟踪、权限测试与重构方向
把系统跑通只是第一步,距离“能答辩、能写进论文”还差一个可展示的验证过程。我自己的习惯是先做三层验证,再决定要不要二次开发。第一层验证是数据链路的完整性。下单前在 Navicat 里记录订单表最大 id,下完单再查一遍,对比订单明细表的行数是否等于购物车条目数,订单状态是否从 0 依次走到 3。注意查一下订单明细表里商品单价和数量计算出的金额,和订单主表总金额是否一致,这一步能查出很多“看起来能跑但逻辑有 bug”的问题。第二层验证是权限边界测试。用会员账号访问商家端的管理页面 URL,看看会不会越权;未登录状态直接访问下单 Servlet,看会不会被拦截。这套源码如果有权限漏洞,不要一味贬低,把它记录下来作为安全类课题的切入点,毕业论文里写“系统存在越权漏洞并提出了 Filter 拦截方案”反而是一个亮点。第三层验证是并发模拟。打开两个浏览器窗口,同时操作同一个订单的“接单”按钮,观察是否会出现状态重复更新。如果出现,在第 3 章讲的UPDATE ... AND status = ?条件更新就能派上用场了。
二次开发方向,我建议按投入产出比排序。排第一的是引入 Bootstrap 美化页面。原项目的 JSP 页面样式比较朴素,把 Bootstrap 的 CSS/JS 放进 WebContent 下,改几个主要页面的 class,视觉上会专业很多,这个工作量不大,对答辩印象分提升却很明显。排第二的是把购物车从 session 迁移到数据库。新增 cart 表和 cart_item 表,把当前 session 中购物车逻辑替换为数据库读写,这样刷新页面和重启服务都不会丢购物车,属于实实在在的功能优化。排第三的是注册功能的增强。原项目可能只支持预置账号登录,你可以补全用户注册、验证码、密码加密存储这三个功能。验证码可以用 Java 原生的 Graphics 类画一个简单的四位数字验证码,密码加密用常见的 SHA-256 加盐即可,这些都有大量现成代码可以参考,但写进论文里却是“你独立完成”的技术点。
最后说说我自己的习惯。每次拿到一套新源码,我从来不会先急着启动,而是强制自己按“读库表结构 → 画请求链路图 → 读 web.xml → 读工具类 → 跑通主流程”的顺序走一遍。这个顺序看起来很笨,但它能在 30 分钟内告诉你这个系统哪里设计得巧、哪里是豆腐渣,后面调试时出问题的概率能小一半。希望帮到你。
本文还有配套的精品资源,点击获取