资讯详情

资讯详情

Java Web外卖系统实战:Servlet+JSP+MySQL完整开发指南

简介本资源是一套完整的基于SpringBoot的在线外卖系统毕业设计项目源码面向计算机专业本科生及Java初学者解决课程设计、毕设选题与Web全栈开发实践需求。项目采用B/S架构后端以Java 1.8 SpringBoot MyBatisPlus构建前端使用Vue ElementUI Ajax实现响应式交互配套MySQL 5.7数据库脚本及SQLyog/Navicat导入说明涵盖用户管理、订单处理、菜品展示等核心业务模块。压缩包含835个文件131个Java类、49个Vue组件、164个JS逻辑文件、79个GIF动效素材及53个CSS样式文件总大小15.53MB结构清晰含完整前后端工程、启动脚本.bat、配置文件yml/properties及论文目录框架含摘要、技术介绍、系统分析等章节。目前已有226人学习下载提供开箱即用的可运行环境与典型电商类系统开发范式适合快速理解SpringBootVue协同开发流程与外卖业务建模逻辑。1. 为什么一个“在线外卖系统”Java Web项目至今仍是校招面试和课程设计的硬通货你打开任意一家高校计算机学院的《Web程序设计》或《Java企业级开发》课程大纲几乎总能看到“基于Web的在线外卖系统设计与实现”作为期末大作业——不是因为它多新颖而是它像一把解剖刀能精准切开Java Web开发的完整技术栈从用户注册登录、商家菜品管理、购物车状态同步、订单生命周期流转到支付回调模拟、后台数据统计全部落在一个可理解、可调试、可部署的闭环里。它不依赖第三方云服务或复杂中间件用JDK Tomcat MySQL JSP/Servlet或轻量Spring MVC就能跑通它又足够真实会逼你处理并发下单的库存扣减、重复提交防护、页面跳转时的Session失效、中文乱码、表单验证与后端校验双保险。对初学者它是把书本Servlet生命周期、MVC分层、JDBC连接池概念焊进肌肉记忆的锤子对有经验者它是快速验证新工具链比如用MyBatis-Plus替代原生JDBC、接入Redis缓存热门菜品的沙盒。这不是一个“玩具项目”而是一套被反复验证过的、最小可行的业务建模训练场。2. 用Java Servlet JSP搭建核心骨架为什么不用Spring Boot先跑通再升级很多新手一上来就想用Spring Boot结果卡在自动配置、Starter依赖冲突、内嵌Tomcat端口占用上反而忽略了HTTP请求如何真正抵达Java类、响应如何组装回浏览器这些底层事实。本节用最朴素的Java Web原生技术栈Servlet 3.1、JSP 2.3、JSTL 1.2搭出可运行的最小闭环所有代码均可直接放入webapp/目录下无需Maven或IDEA向导。2.1 创建标准Web工程结构与部署描述符Java Web项目必须遵循WEB-INF/web.xml定义的部署契约。即使Servlet 3.0支持注解显式配置仍是最易排查问题的方式。以下为web.xml关键片段?xml version1.0 encodingUTF-8? 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_4_0.xsd version4.0 !-- 全局编码过滤器解决POST中文乱码 -- filter filter-nameCharacterEncodingFilter/filter-name filter-classorg.example.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param /filter filter-mapping filter-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping !-- 前端控制器所有*.do请求交由DispatcherServlet处理 -- servlet servlet-nameDispatcherServlet/servlet-name servlet-classorg.example.servlet.DispatcherServlet/servlet-class /servlet servlet-mapping servlet-nameDispatcherServlet/servlet-name url-pattern*.do/url-pattern /servlet-mapping !-- 首页重定向 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list /web-app提示web.xml版本必须声明为4.0对应Servlet 4.0规范否则filter-mapping中url-pattern/*/url-pattern可能无法拦截静态资源导致CSS/JS加载失败。CharacterEncodingFilter需自行实现其doFilter()方法中调用request.setCharacterEncoding(UTF-8)和response.setCharacterEncoding(UTF-8)。2.2 实现DispatcherServlet手动路由分发的核心逻辑DispatcherServlet是整个流程的中枢。它不依赖Spring仅靠HttpServletRequest的getRequestURI()提取路径再通过String.endsWith()匹配后缀将请求转发给具体业务Servlet// org.example.servlet.DispatcherServlet.java public class DispatcherServlet extends HttpServlet { Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String uri req.getRequestURI(); // 例如: /online-food/order/list.do String path uri.substring(uri.lastIndexOf(/) 1); // 提取 list.do // 根据后缀分发list.do → OrderListServlet, submit.do → OrderSubmitServlet if (path.equals(login.do)) { req.getRequestDispatcher(/servlet/LoginServlet).forward(req, resp); } else if (path.equals(order/list.do)) { req.getRequestDispatcher(/servlet/OrderListServlet).forward(req, resp); } else if (path.equals(cart/add.do)) { req.getRequestDispatcher(/servlet/CartAddServlet).forward(req, resp); } else { resp.sendError(HttpServletResponse.SC_NOT_FOUND, Request not handled: path); } } }2.2.1 LoginServlet演示Session管理与重定向陷阱用户登录成功后必须将用户信息存入HttpSession并重定向到首页而非转发否则浏览器地址栏仍显示login.do刷新会导致重复提交// org.example.servlet.LoginServlet.java public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 简单校验实际应查数据库 if (admin.equals(username) 123456.equals(password)) { // ✅ 正确存入Session并重定向 HttpSession session req.getSession(true); session.setAttribute(user, new User(username)); resp.sendRedirect(req.getContextPath() /index.jsp); // 生成 /online-food/index.jsp } else { // ❌ 错误转发会导致URL不变下次刷新仍触发登录 req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } }注意req.getContextPath()返回应用上下文路径如/online-food硬编码/index.jsp会导致部署到ROOT路径时404。sendRedirect()必须传入完整路径以/开头否则视为相对路径。2.3 JSP页面与JSTL分离逻辑与展示的关键实践避免在JSP中写Java脚本% %全部使用JSTL标签库。index.jsp首页展示用户信息与菜单入口% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % html headtitle在线外卖系统/title/head body h1欢迎来到在线外卖系统/h1 !-- 判断用户是否已登录 -- c:choose c:when test${not empty sessionScope.user} p你好strong${sessionScope.user.username}/strong a href${pageContext.request.contextPath}/servlet/LogoutServlet退出/a /p /c:when c:otherwise pa href${pageContext.request.contextPath}/login.jsp请登录/a/p /c:otherwise /c:choose !-- 菜品列表简化版 -- h2今日推荐/h2 c:forEach items${requestScope.dishes} vardish div h3${dish.name} - ¥${dish.price}/h3 p${dish.description}/p form action${pageContext.request.contextPath}/cart/add.do methodpost input typehidden namedishId value${dish.id} button typesubmit加入购物车/button /form /div /c:forEach /body /html关键点${pageContext.request.contextPath}确保所有链接带应用路径前缀sessionScope.user和requestScope.dishes是Servlet中session.setAttribute()和request.setAttribute()设置的属性JSTL自动从对应作用域读取。3. 数据库设计与JDBC操作用ER图约束业务用DAO隔离变化一个外卖系统的核心实体只有四个用户User、商家Merchant、菜品Dish、订单Order。它们的关系必须用ER图明确约束否则后续扩展必然混乱。以下是MySQL建表语句及关键设计说明3.1 四张核心表的DDL与设计意图表名主键外键关键字段设计说明t_userid(BIGINT)—username,password,phone,address,role(customer/merchant)role字段区分用户类型避免建customer和merchant两张表简化权限判断t_merchantid(BIGINT)user_id→t_user.idname,address,status(0停业,1营业)商家信息与用户账号绑定一个账号可同时是顾客和商家如管理员t_dishid(BIGINT)merchant_id→t_merchant.idname,price,description,status(0下架,1上架)status控制菜品可见性比物理删除更安全t_orderid(BIGINT)user_id→t_user.id,merchant_id→t_merchant.idorder_no,status(created/paid/delivered/completed),total_amount,create_time订单号用UUID.randomUUID().toString().replace(-, )生成保证全局唯一-- 创建t_user表 CREATE TABLE t_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, phone VARCHAR(20), address TEXT, role ENUM(customer, merchant) DEFAULT customer, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 创建t_merchant表关联t_user CREATE TABLE t_merchant ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, name VARCHAR(100) NOT NULL, address TEXT, status TINYINT DEFAULT 1, FOREIGN KEY (user_id) REFERENCES t_user(id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意ON DELETE CASCADE确保删除用户时自动清理其商家信息避免外键约束异常。ENUM类型比VARCHAR更节省空间且防错但需确保应用层不传非法值。3.2 封装JDBC操作DAO模式与连接池实战直接在Servlet中写JDBC会导致代码臃肿且难以复用。创建BaseDao抽象类统一管理连接并用DruidDataSource实现连接池// org.example.dao.BaseDao.java public abstract class BaseDao { private static DruidDataSource dataSource; static { Properties props new Properties(); props.load(BaseDao.class.getClassLoader().getResourceAsStream(db.properties)); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } protected Connection getConnection() throws SQLException { return dataSource.getConnection(); } protected void close(Connection conn, PreparedStatement ps, ResultSet rs) { if (rs ! null) try { rs.close(); } catch (SQLException e) {} if (ps ! null) try { ps.close(); } catch (SQLException e) {} if (conn ! null) try { conn.close(); } catch (SQLException e) {} } } // org.example.dao.UserDao.java public class UserDao extends BaseDao { public User findByUsername(String username) { String sql SELECT id, username, password, phone, address, role FROM t_user WHERE username ?; User user null; try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, username); try (ResultSet rs ps.executeQuery()) { if (rs.next()) { user new User(); user.setId(rs.getLong(id)); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); user.setPhone(rs.getString(phone)); user.setAddress(rs.getString(address)); user.setRole(rs.getString(role)); } } } catch (Exception e) { e.printStackTrace(); } return user; } }3.2.1db.properties配置文件内容明文密码仅用于教学driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/online_food?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8mb4 usernameroot password123456 initialSize5 minIdle5 maxActive20 maxWait60000 timeBetweenEvictionRunsMillis60000 minEvictableIdleTimeMillis300000 validationQuerySELECT 1 testWhileIdletrue testOnBorrowfalse testOnReturnfalse poolPreparedStatementstrue maxPoolPreparedStatementPerConnectionSize20 filtersstat,wall关键参数说明maxActive20限制最大连接数防止数据库被打爆testWhileIdletrue让连接池定期检测空闲连接有效性filtersstat,wall开启Druid监控页面访问/druid和SQL防火墙这是Web项目上线前必开的安全开关。4. 安全加固与常见Web错误修复从“能跑”到“能用”的临门一脚一个能通过课程验收的系统常因几个典型Web错误被拒登录后F5刷新导致重复下单、未登录访问订单页、中文乱码、Service Worker注册失败。这些问题不源于框架缺陷而源于对HTTP协议和浏览器行为的理解偏差。本节直击高频故障点。4.1 防止重复提交Post-Redirect-GetPRG模式强制落地用户点击“提交订单”按钮后若直接返回结果页POST → HTML刷新该页面会重新提交表单。正确做法是Servlet处理完订单后重定向到一个只读的结果页POST → Redirect → GET// OrderSubmitServlet.java public class OrderSubmitServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 从Session获取当前用户 User user (User) req.getSession().getAttribute(user); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 2. 执行业务逻辑扣库存、生成订单、更新状态... Order order new Order(); order.setUserId(user.getId()); order.setTotalAmount(calculateTotal(req)); order.setStatus(created); new OrderDao().insert(order); // 3. ✅ PRG模式重定向到订单详情页GET请求 resp.sendRedirect(req.getContextPath() /order/detail.jsp?id order.getId()); } }验证方法在浏览器开发者工具Network面板中观察order/submit.do的响应头是否有Location: /online-food/order/detail.jsp?id123且该请求Method为302 Found。此时刷新detail.jsp页面只会重新GET不会再次创建订单。4.2 解决“加载 web 视图时出错: error: could not register service worker: invalidstateerror”此错误常见于Chrome浏览器本质是Service Worker注册时机错误。纯Java Web项目无前端构建工具默认不应启用Service Worker。若你的index.jsp或base.jsp中包含类似以下代码必须删除!-- ❌ 删除这段代码 -- script if (serviceWorker in navigator) { window.addEventListener(load, () { navigator.serviceWorker.register(/sw.js); // 此处/sw.js不存在或未正确配置 }); } /script原因navigator.serviceWorker.register()要求注册脚本必须与页面同源且服务器需返回Content-Type: application/javascript。在Tomcat中.js文件默认由DefaultServlet处理但若web.xml中未配置mime-mapping可能返回text/plain触发InvalidStateError。最稳妥方案是Java Web项目初期完全禁用PWA特性待引入Vue/React等现代前端框架后再按需配置。4.3 统一解决中文乱码的三层防御体系乱码问题必须从前端输入、后端接收、数据库存储三环节同时设防环节问题现象解决方案验证方式前端表单提交输入中文后端getParameter()得到??在web.xml中配置CharacterEncodingFilter见2.1节且url-pattern/*/url-pattern覆盖所有路径在Servlet中打印new String(req.getParameter(name).getBytes(ISO-8859-1), UTF-8)若输出正常则证明Filter生效数据库存储MySQL中存入中文显示为?创建数据库时指定字符集CREATE DATABASE online_food CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;建表时加DEFAULT CHARSETutf8mb4执行SHOW CREATE TABLE t_user;确认表字符集为utf8mb4JSP页面输出浏览器显示中文为方块JSP顶部声明% page contentTypetext/html;charsetUTF-8 %HTML中添加meta charsetUTF-8查看浏览器页面源码确认meta标签存在且charset值为UTF-8终极验证命令在MySQL命令行执行SET NAMES utf8mb4; INSERT INTO t_user(username) VALUES(张三); SELECT * FROM t_user;若username列显示张三而非??则数据库层无乱码。5. 本地部署与调试技巧用Tomcat 9 JDK 11快速验证绕过所有环境陷阱很多同学卡在“项目导入IDEA后无法启动”根源常是JDK版本、Tomcat配置或路径权限问题。以下为经过千次实测的极简部署流程适用于Windows 10/11、macOS、Ubuntu 22.04。5.1 环境准备清单严格按顺序执行JDK 11安装与验证下载 Adoptium Temurin JDK 11 非Oracle JDK免许可证风险。安装后执行java -version # 输出应为openjdk version 11.0.22 2024-01-16 echo $JAVA_HOME # Linux/macOS 或 echo %JAVA_HOME% Windows # 确保指向JDK安装目录非JRETomcat 9.0.x下载与解压从 Apache官网 下载tar.gzLinux/macOS或zipWindows包解压到无中文、无空格路径如C:\apache-tomcat-9.0.85。注意Tomcat 10使用jakarta.*包名与Servlet 4.0不兼容必须用Tomcat 9。项目部署到Tomcat将你的Java Web项目根目录含WEB-INF文件夹整体复制到apache-tomcat-9.0.85/webapps/下重命名为online-food即应用上下文路径。启动Tomcat# Linux/macOS cd apache-tomcat-9.0.85/bin ./startup.sh # Windows cd apache-tomcat-9.0.85\bin startup.bat5.2 快速定位启动失败的三大日志位置Tomcat启动失败时不要只看控制台红字必须检查以下三个日志文件日志文件路径记录内容典型错误示例应对措施logs/catalina.outLinux/macOS或logs/catalina.yyyy-MM-dd.logWindowsTomcat自身启动过程、JVM参数、各组件初始化日志SEVERE: Failed to initialize connector [Connector[HTTP/1.1-8080]]检查8080端口是否被占用netstat -ano | findstr :8080修改conf/server.xml中Connector port8080logs/localhost.yyyy-MM-dd.log当前应用online-food的Servlet初始化日志SEVERE [main] org.apache.catalina.core.StandardContext.startInternal Error filterStart检查web.xml中filter-class类路径是否拼写错误或WEB-INF/lib下缺少druid-1.2.16.jar等依赖logs/manager.yyyy-MM-dd.log管理界面操作日志如热部署INFO [http-nio-8080-exec-1] org.apache.catalina.manager.ManagerServlet.deploy Deploying web application archive [/path/to/online-food.war]若看到deploy但访问/online-food仍404检查webapps/online-food/WEB-INF/web.xml是否存在且格式正确调试技巧在online-food/WEB-INF/web.xml中添加display-nameOnline Food System/display-name启动后访问http://localhost:8080/manager/html在应用列表中可直观看到应用状态Running/Stopped和路径。5.3 一个命令验证数据库连通性绕过Java代码当怀疑数据库连接失败时不要反复重启Tomcat直接用MySQL客户端验证# 连接本地MySQL假设root密码为123456 mysql -u root -p123456 -h 127.0.0.1 -P 3306 # 在MySQL中执行 USE online_food; SELECT COUNT(*) FROM t_user; -- 应返回0或大于0的数字 SHOW VARIABLES LIKE character_set%; -- 确认所有值均为utf8mb4若此步骤失败则问题100%在数据库侧与Java代码无关。此时应检查MySQL服务是否运行、用户权限GRANT ALL ON online_food.* TO rootlocalhost; FLUSH PRIVILEGES;、防火墙是否放行3306端口。最终访问http://localhost:8080/online-food/index.jsp看到“欢迎来到在线外卖系统”标题且能成功登录、浏览菜品、加入购物车——你的Java Web外卖系统就真正活了过来。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →