尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
JSP+Servlet外卖系统:四角色协同与订单状态机实战
简介这是一套基于Java Web技术栈开发的完整外卖订餐系统实战项目面向Java初学者与Web开发入门者帮助掌握JSP、Servlet、MySQL及MVC分层架构在真实业务场景中的落地应用。资源包为ZIP格式大小93.63MB包含源代码、数据库脚本online.sql、角色权限说明、系统截图及配套视频教程.wmv覆盖会员下单、商家接单、骑手配送、管理员监管等全链路功能模块。已有660人学习下载项目结构清晰Eclipse可直接导入运行数据库预置初始数据降低环境搭建门槛同时提供多角色登录界面与业务逻辑实现细节便于理解前后端协作机制与典型电商类系统设计范式是巩固Servlet生命周期、JSP页面交互、MySQL表关系建模等核心技能的优质实践案例。1. 这不是“又一个JSP练手项目”它真能跑通四角色协同的外卖闭环从会员下单、商家接单、骑手抢单到管理员调度全链路可调试、可断点、可改数据库字段你可能已经下载过十几个标着“JSP外卖系统”的压缩包——解压后发现只有3个JSP页面1个Servlet空荡荡的MySQL建表语句登录页能跳转但点“下单”就404查日志全是NullPointerException连骑手怎么抢单都找不到入口。而这个基于JSPServlet开发外卖订餐点餐系统会员 骑手 商家 管理员.zip是少有的、真实跑通四角色状态机流转的完整工程会员下单后订单自动进入“待接单”状态商家后台刷新即见新订单点击“确认接单”后状态变“制作中”骑手登录后首页显示“可抢单列表”点击即锁定并更新为“配送中”管理员能实时看到各角色在线数、订单完成率、超时订单告警。它不靠前端JS模拟状态所有流转由Servlet调用DAO层执行SQL事务控制MySQL里order_status字段真实参与业务逻辑判断。适合Java Web新手建立“请求→Servlet→Service→DAO→DB→响应”的肌肉记忆也适合老手快速复用其角色权限控制模块RBAC实现未用框架纯JDBCsessionURL拦截。注意它不是Spring Boot项目没有自动装配所有依赖、连接池、路径映射都得你亲手配——但正因如此每个web.xml里的servlet-mapping、每个context.xml里的Resource定义、每个DAO里手动写的PreparedStatement参数绑定你都能一眼看懂、一改就生效。2. 拆包即用从Eclipse导入到Tomcat启动绕过90%新手卡点的实操路径2.1 解压后目录结构与关键文件定位别急着Run As → Run on Server解压基于JSPServlet开发外卖订餐点餐系统会员 骑手 商家 管理员.zip后你会看到如下核心目录非全部仅列必须关注项/online/ ← 项目根目录Eclipse导入时选这个 ├── WebContent/ ← JSP、CSS、JS、图片存放处相当于标准WebApp的WebRoot │ ├── index.jsp ← 会员首页含搜索框和菜品瀑布流 │ ├── login.jsp ← 四角色统一登录入口通过role参数区分 │ ├── merchant/ ← 商家专属目录含addDish.jsp, orderList.jsp等 │ ├── rider/ ← 骑手专属目录riderHome.jsp, takeOrder.jsp │ └── admin/ ← 管理员目录userManage.jsp, orderMonitor.jsp ├── src/ ← Java源码包结构com.online.dao, com.online.service, com.online.servlet │ ├── com.online.dao/ │ │ ├── BaseDao.java ← 封装JDBC连接获取、关闭关键用的是DBCP连接池 │ │ ├── OrderDao.java ← 订单状态变更核心DAOupdateStatus()方法含事务 │ │ └── UserDao.java ← 角色查询根据username查role_id │ ├── com.online.servlet/ │ │ ├── LoginServlet.java ← 处理login.jsp提交校验后存role到session │ │ ├── OrderServlet.java ← 处理会员下单、商家确认、骑手抢单同一Servlet多action │ │ └── AdminServlet.java ← 管理员操作禁用用户、强制完成订单等 │ └── com.online.service/ ← Service层含OrderService.java调用多个DAO保证事务 ├── WEB-INF/ │ ├── web.xml ← Servlet注册、过滤器、欢迎文件列表重点看servlet-mapping │ └── context.xml ← Tomcat数据源配置JNDI名java:comp/env/jdbc/online ├── online.sql ← MySQL建库建表初始数据含4类用户账号 └── 帐号.txt ← 明文记录默认账号密码会员user/123商家shop/123...提示帐号.txt不是摆设它对应online.sql里user表的username和password明文存储无加密调试阶段可直接用。online.sql必须先导入否则LoginServlet查不到用户直接报500。2.2 Eclipse导入三步法拒绝“Project Facets不匹配”报错关闭自动构建Eclipse菜单 → Project → Uncheck “Build Automatically”避免导入时编译失败中断导入为Dynamic Web ProjectFile → Import → Web → Existing Dynamic Web ProjectRoot directory 选/online不是zip包是解压后的文件夹关键设置Target runtime选已配置好的Apache Tomcat v8.5或v9.0勿选v10JSP EL表达式兼容性问题Configuration选“Generate web.xml deployment descriptor”确保生成web.xml本项目依赖它修复Java Build Path右键项目 → Properties → Java Build Path → Libraries → Add Library → Server Runtime → 选同上的Tomcat再Add External JARs添加/online/WebContent/WEB-INF/lib/下的commons-dbcp-1.4.jar、mysql-connector-java-5.1.47.jar版本必须匹配高版本MySQL驱动在JSP EL中会报javax.el.PropertyNotFoundException2.3 Tomcat配置与数据库连接context.xml和web.xml的生死联动本项目用JNDI方式获取数据库连接不是在DAO里硬编码DriverManager.getConnection()。这意味着两处配置必须严丝合缝第一步确认Tomcatconf/context.xml已注入数据源打开Tomcat安装目录 →conf/context.xml在Context标签内添加Resource namejdbc/online authContainer typejavax.sql.DataSource maxTotal20 maxIdle10 minIdle5 initialSize5 maxWaitMillis10000 usernameroot passwordyour_mysql_password !-- 替换为你本地MySQL密码 -- driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/online?useUnicodetrueamp;characterEncodingUTF-8amp;serverTimezoneGMT%2B8/注意url中的amp;是XML转义不可写成serverTimezoneGMT%2B8解决时区报错useUnicodetrueamp;characterEncodingUTF-8防止中文乱码。第二步验证web.xml中资源引用正确检查/online/WEB-INF/web.xml确认存在resource-ref descriptionDB Connection/description res-ref-namejdbc/online/res-ref-name res-typejavax.sql.DataSource/res-type res-authContainer/res-auth /resource-ref此段声明告诉Tomcat“我在代码里要用名为jdbc/online的数据源请从context.xml里找”。第三步DAO层获取连接的代码必须匹配打开BaseDao.java核心方法public Connection getConnection() throws SQLException { Context ctx null; DataSource ds null; try { ctx new InitialContext(); // JNDI上下文 ds (DataSource) ctx.lookup(java:comp/env/jdbc/online); // 注意前缀java:comp/env/ return ds.getConnection(); } catch (NamingException e) { throw new SQLException(JNDI lookup failed, e); } }关键点ctx.lookup(java:comp/env/jdbc/online)中的java:comp/env/是JNDI标准前缀缺一字符如漏掉/env/就会抛NameNotFoundException且错误日志只显示“Cannot create JDBC driver”极易误判为驱动问题。2.4 启动前必做MySQL建库、导入SQL、验证账号创建数据库UTF8mb4编码兼容emojiCREATE DATABASE online CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;导入online.sql在MySQL命令行或Navicat中选中online库 → 执行online.sql全文验证数据SELECT username, role_id FROM user;应返回至少4条记录会员、商家、骑手、管理员各1检查帐号.txt与数据库一致性文件中写的admin/admin123必须在user表里存在且role_id1管理员若修改过密码需同步更新user表的password字段本项目无密码加密存明文启动Tomcat后在浏览器访问http://localhost:8080/online/login.jsp输入帐号.txt任一账号应跳转至对应角色首页。若卡在登录页无反应90%是JNDI配置或MySQL连接问题若跳转后页面空白检查JSP中EL表达式是否被禁用见3.2节。3. 四角色权限落地URL拦截、Session角色校验、状态机驱动的订单流转3.1LoginServlet如何识别角色并注入Session不止是request.getParameter(role)login.jsp表单提交到LoginServlet其doPost()方法核心逻辑String username request.getParameter(username); String password request.getParameter(password); User user userService.login(username, password); // 调用Service查库 if (user ! null) { // 关键不仅存user对象还存role_id和role_name到session request.getSession().setAttribute(user, user); request.getSession().setAttribute(role_id, user.getRoleId()); request.getSession().setAttribute(role_name, getRoleName(user.getRoleId())); // 1→管理员, 2→商家... // 根据role_id重定向到不同首页非前端JS跳转 String redirectPath ; switch (user.getRoleId()) { case 1: redirectPath /admin/adminHome.jsp; break; case 2: redirectPath /merchant/merchantHome.jsp; break; case 3: redirectPath /rider/riderHome.jsp; break; case 4: redirectPath /index.jsp; break; // 会员首页 default: redirectPath /login.jsp?errorrole; } response.sendRedirect(request.getContextPath() redirectPath); } else { request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); }逻辑说明角色判断在服务端完成role_id存入Session后后续所有Servlet都可通过session.getAttribute(role_id)获取不依赖URL参数或隐藏域杜绝前端篡改。getRoleName()是硬编码映射非数据库查确保性能。3.2 URL过滤器实现角色隔离AdminFilter和MerchantFilter的双重校验项目在web.xml中配置了两个过滤器filter filter-nameAdminFilter/filter-name filter-classcom.online.filter.AdminFilter/filter-class /filter filter-mapping filter-nameAdminFilter/filter-name url-pattern/admin/*/url-pattern !-- 拦截所有/admin/路径 -- /filter-mapping filter filter-nameMerchantFilter/filter-name filter-classcom.online.filter.MerchantFilter/filter-class /filter filter-mapping filter-nameMerchantFilter/filter-name url-pattern/merchant/*/url-pattern /filter-mapping以AdminFilter.java为例public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); Integer roleId (Integer) session.getAttribute(role_id); // 必须同时满足有session、role_id存在、且为管理员1 if (session null || roleId null || roleId ! 1) { request.setAttribute(error, 请先以管理员身份登录); request.getRequestDispatcher(/login.jsp).forward(request, response); return; } chain.doFilter(req, resp); // 放行 }参数说明session.getAttribute(role_id)返回Integer而非String因数据库role_id是INT类型roleId ! 1严格等于不接受字符串1session null判断防止NPE。此设计比单纯检查session.getAttribute(user) ! null更安全——用户可能登录后手动修改session中user对象但无法伪造role_id整型值。3.3 订单状态机OrderServlet如何用单一URL处理四角色操作所有订单操作会员下单、商家接单、骑手抢单、管理员强制完成均提交到同一URL/OrderServlet?actionxxx。OrderServlet.doPost()通过action参数分发String action request.getParameter(action); switch (action) { case create: createOrder(request, response); break; // 会员调用 case confirm: confirmOrder(request, response); break; // 商家调用 case take: takeOrder(request, response); break; // 骑手调用 case finish: finishOrder(request, response); break; // 管理员调用 default: response.sendError(HttpServletResponse.SC_BAD_REQUEST); }以takeOrder()为例体现状态校验private void takeOrder(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Integer riderId (Integer) request.getSession().getAttribute(user_id); Integer orderId Integer.parseInt(request.getParameter(orderId)); // 关键状态校验只允许抢单“待接单”状态的订单 Order order orderService.findById(orderId); if (!待接单.equals(order.getStatus())) { request.setAttribute(error, 该订单状态不可抢单当前 order.getStatus() ); request.getRequestDispatcher(/rider/riderHome.jsp).forward(request, response); return; } // 事务更新订单状态 绑定骑手ID boolean success orderService.takeOrder(orderId, riderId); if (success) { response.sendRedirect(request.getContextPath() /rider/riderHome.jsp?msg抢单成功); } else { request.setAttribute(error, 抢单失败请重试); request.getRequestDispatcher(/rider/riderHome.jsp).forward(request, response); } }状态机价值order.getStatus()从数据库读取orderService.takeOrder()内部执行SQLUPDATE order SET status配送中, rider_id? WHERE id? AND status待接单WHERE条件包含status确保并发抢单时不会覆盖他人操作乐观锁思想。这是比单纯UPDATE ... WHERE id?更健壮的设计。4. 避坑指南那些让新手调试3小时却只差一行配置的致命细节4.1 现象登录成功后跳转到空白页查看源码发现JSP中${user.username}不解析显示原样文字原因Tomcat 8.5默认禁用EL表达式Expression Language因安全考虑。本项目JSP大量使用${}取Session值如h2欢迎 ${user.username}!/h2若EL被禁页面将显示${user.username}而非真实用户名。解决在/online/WebContent/WEB-INF/web.xml的web-app根标签内顶部添加jsp-config jsp-property-group url-pattern*.jsp/url-pattern el-ignoredfalse/el-ignored !-- 关键设为false -- /jsp-property-group /jsp-config血泪经验此配置必须放在web-app最开头若插在servlet之后Tomcat会忽略。且el-ignored默认值为true不显式声明即失效。4.2 现象点击“下单”按钮报HTTP 404浏览器地址栏显示http://localhost:8080/online/OrderServlet原因web.xml中OrderServlet的url-pattern配置错误。常见错误是写成/OrderServlet绝对路径但实际请求URL是/online/OrderServlet项目上下文路径/online Servlet路径。解决检查web.xml中OrderServlet的映射servlet-mapping servlet-nameOrderServlet/servlet-name url-pattern/OrderServlet/url-pattern !-- 正确相对路径Tomcat自动补上下文 -- /servlet-mapping玄学提醒若仍404右键项目 → Properties → Web Project Settings → Context root 必须为online与压缩包名一致否则Tomcat部署路径错位。4.3 现象MySQL插入中文显示为??订单状态“制作中”存库后变“??原因三处编码未统一MySQL服务器编码、数据库编码、JDBC连接URL编码、JSP页面编码。解决四步强制统一MySQL服务器my.cnf中[mysqld]下加character-set-serverutf8mb4数据库CREATE DATABASE online CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;JDBC URLurljdbc:mysql://localhost:3306/online?useUnicodetruecharacterEncodingUTF-8serverTimezoneGMT%2B8注意在XML中要写amp;JSP页面每页顶部加% page contentTypetext/html;charsetUTF-8 %4.4 现象骑手抢单后订单状态变为“配送中”但商家后台订单列表仍显示“待接单”原因浏览器缓存了商家订单列表页面/merchant/orderList.jspAJAX未触发或页面未重载。本项目未用AJAX完全依赖页面刷新。解决在/merchant/orderList.jsp的head中强制禁止缓存meta http-equivCache-Control contentno-cache, no-store, must-revalidate / meta http-equivPragma contentno-cache / meta http-equivExpires content0 /后悔药开发时按CtrlF5强制刷新而非普通F5。4.5 现象online.sql导入后user表有数据但LoginServlet查不到日志报SQLException: Column role_id not found in ResultSet原因MySQL驱动版本与JDBC代码不匹配。online.sql中user表字段为role_idINT但旧版驱动如mysql-connector-java-5.1.13在ResultSet.getInt(role_id)时可能因元数据问题找不到列。解决确认/online/WebContent/WEB-INF/lib/下驱动jar为mysql-connector-java-5.1.47.jar项目自带删除其他版本驱动如mysql-connector-java-8.0.x.jar8.0驱动要求driverClassNamecom.mysql.cj.jdbc.Driver且URL加allowPublicKeyRetrievaltrue本项目未适配。5. 进阶技巧把“会员下单”流程拆解为可验证的原子操作建立你的调试肌肉记忆5.1 用Postman模拟会员下单绕过前端直击Servlet逻辑会员下单本质是向/OrderServlet?actioncreate发送POST请求携带菜品ID和数量。用Postman构造请求验证KeyValue说明actioncreate必填指定操作类型dishId1菜品ID从dish表查如SELECT id FROM dish LIMIT 1quantity2数量整数address北京市朝阳区XX大厦1栋会员收货地址发送后验证响应应为302重定向到/index.jsp?msg下单成功查MySQLSELECT * FROM order_info WHERE user_id4 ORDER BY create_time DESC LIMIT 1;会员user_id4关键字段status待接单、dish_id1、quantity2、address正确为什么这么做前端JSP可能因JavaScript错误阻断提交而Postman直连Servlet能100%确认后端逻辑是否畅通。这是排查“前端说点了没反应后端说没收到请求”的黄金手段。5.2 在OrderService.createOrder()中埋设日志追踪事务边界createOrder()方法是订单创建的核心含事务控制。在src/com/online/service/OrderService.java中在关键步骤添加System.out.println()生产环境应换为log4jpublic boolean createOrder(Order order) { System.out.println([DEBUG] 开始创建订单用户ID order.getUserId()); Connection conn null; PreparedStatement ps null; try { conn BaseDao.getConnection(); conn.setAutoCommit(false); // 开启事务 // 插入订单主表 String sql1 INSERT INTO order_info (user_id, dish_id, quantity, address, status) VALUES (?, ?, ?, ?, ?); ps conn.prepareStatement(sql1, Statement.RETURN_GENERATED_KEYS); ps.setInt(1, order.getUserId()); ps.setInt(2, order.getDishId()); ps.setInt(3, order.getQuantity()); ps.setString(4, order.getAddress()); ps.setString(5, 待接单); ps.executeUpdate(); // 获取生成的order_id ResultSet rs ps.getGeneratedKeys(); if (rs.next()) { order.setId(rs.getInt(1)); System.out.println([DEBUG] 订单主表插入成功生成order_id order.getId()); } // 插入订单详情本项目简化无详情表此步省略 conn.commit(); System.out.println([DEBUG] 事务提交成功); return true; } catch (SQLException e) { if (conn ! null) { try { conn.rollback(); } catch (SQLException ex) {} } System.out.println([ERROR] 订单创建失败回滚事务 e.getMessage()); return false; } finally { BaseDao.close(ps, conn); } }验证价值启动Tomcat后控制台输出清晰显示事务流程。若看到开始创建订单但没看到事务提交成功说明卡在某步SQL执行若看到回滚事务则根据e.getMessage()定位具体SQL错误如字段长度超限、外键约束失败。5.3 修改订单状态的SQL必须带AND status条件一份可复用的状态机SQL模板OrderDao.java中更新状态的方法如updateStatus()其SQL应始终包含状态校验// 安全写法只更新指定状态的记录防并发覆盖 String sql UPDATE order_info SET status?, rider_id? WHERE id? AND status?; ps.setString(1, newStatus); // 如配送中 ps.setInt(2, riderId); ps.setInt(3, orderId); ps.setString(4, oldStatus); // 如待接单对比危险写法项目中不存在但新手易犯// ❌ 危险无状态校验任何角色都能强行改状态 String sql UPDATE order_info SET status? WHERE id?;我的习惯从那以后我每次写状态更新SQL都强制走一遍“WHERE status?”条件并在DAO方法注释中写明允许的状态流转如param oldStatus 允许从待接单状态更新。这比在Service层if判断更底层、更可靠——数据库才是最终仲裁者。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Labelme启动报错No Qt bindings解决指南

Labelme启动报错No Qt bindings解决指南

1. 这个报错不是labelme的问题,而是Qt绑定层的“身份认证失败”你刚装完labelme,兴冲冲在命令行敲下labelme,结果终端里猛地跳出一行红字:qtpy.PythonQtError: No Qt bindings could be found别急着重装labelme——这行报错根本没…

📅 2026/10/4 4:17:42
用 MarkdownInstance 泛型为 Astro.glob() 读取的 Markdown 文件声明强类型

用 MarkdownInstance 泛型为 Astro.glob() 读取的 Markdown 文件声明强类型

文档教程知识库 【免费下载链接】til :memo: Today I Learned 项目地址: https://gitcode.com/gh_mirrors/ti/til 点击查看 免费下载 在 Astro 项目中,把 Markdown 文件渲染成页面之后,往往还需要在索引页拿到这批文件的清单;使用…

📅 2026/10/4 4:17:42
VS Code+Anaconda+PyTorch环境搭建全攻略:从零到跑通第一个神经网络

VS Code+Anaconda+PyTorch环境搭建全攻略:从零到跑通第一个神经网络

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/4 4:17:42
MORE NEWS

更多资讯

📰

开源模块化架构:重构机构级AI量化金融基础设施的可行性路径

在量化金融领域,构建具备机构级稳健性与可审计性的交易基础设施长期以来被视为大型机构的专属能力。然而,随着开源生态的成熟,小型团队正通过组装经过验证的模块化组件,实现可复现且透明的AI交易工作流,从而显著降低了…

📰

本地万亿参数模型还能再快多少:WARP 路由前瞻、专家缓存与多盘分片性能调优实战

本地万亿参数模型还能再快多少:WARP 路由前瞻、专家缓存与多盘分片性能调优实战 【免费下载链接】warp Run the full 2.78-trillion-parameter Kimi K3 model, DeepSeek V4.1 Flash or GLM-5.3-Flash beyond available RAM by streaming activated weights directly…

📰

深信服sCloud V6.2.60运维实战手册精要

简介:本资源是深信服官方发布的《信云sCloud_SCP用户手册(V6.2.60)》PDF文档,面向网络设计工程师与云计算平台运维人员,系统支撑sCloud_SCP私有云平台的部署实施、日常运维及故障处置。手册全面覆盖产品架构&#xff0…

📰

A2B总线原理与车载音频架构重构

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

为什么飞书里的流式卡片这么顺?lark-coding-agent-bridge 流式卡片与 COT 过程消息深度解析

为什么飞书里的流式卡片这么顺?lark-coding-agent-bridge 流式卡片与 COT 过程消息深度解析 【免费下载链接】lark-coding-agent-bridge Bot that bridges Feishu/Lark messenger with a local Claude Code or Codex CLI. Streaming cards, per-chat sessions, mult…

📰

MRAM+PIC24F实现可靠工业存储:SPI接口与掉电保护实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬