简介:这是一套基于JavaWeb技术栈实现的外卖点餐系统完整项目源码,面向具备Java基础、希望借助真实项目理解MVC分层与前后端协作的开发者与在校学生。系统围绕用户管理、菜品管理、订单处理、支付接口与地图配送等核心模块展开,采用Servlet与JSP承担请求响应和视图渲染,JDBC完成数据库增删改查,并借助JSTL/EL简化页面逻辑,是练习企业级Web开发的典型范例。资源包共252个文件,以77个gif、43个jpg等图片素材和39个jar依赖库为主,辅以31个js脚本、16个java源文件、10个jsp页面及9个css样式表,整体约22.35MB,结构完整、便于直接导入IDE运行调试。目前已有583人学习下载。通过研读源码,读者可掌握数据库设计、前后端交互、订单状态流转与系统部署的完整思路,对提升全栈开发能力有实际帮助。
1. 从一份 JavaWeb 外卖点餐系统源码说起:它到底能跑出什么
如果你手头正好有一份基于javaweb的外卖点餐系统.zip,大概率是这几种情况之一:课程设计要交、毕业设计缺个能演示的完整案例、或者想找一个带 MySQL 的 JavaWeb 全流程项目练手。这个包的核心价值不在于界面多花哨,而在于它把「浏览器请求 → Servlet 处理 → JDBC 操作 MySQL → JSP 渲染」这条链路完整跑通了,而且业务是外卖点餐这种有真实数据流转的场景:菜品分类、购物车、下单、订单状态、后台管理,一个都不少。
它适合谁?适合刚学完 Servlet 和 JSP、还没独立做过完整项目的人,也适合需要快速搭一个可演示原型的人。不适合谁?不适合想直接拿去做生产级外卖平台的人——它的架构是经典 MVC 三层,没有分布式、没有缓存、没有消息队列,但正因为简单,你才能把每一层看清楚。下面我按「怎么把它跑起来 → 每一层怎么改 → 坑在哪」的顺序拆一遍,你照着走就能复现。
2. 把项目跑起来:IDEA 配置、Tomcat 部署与数据库初始化
2.1 环境选型与版本对齐
这个项目是典型的 JavaWeb 项目,不是 Spring Boot,所以没有内嵌 Tomcat,必须外挂 Servlet 容器。常见做法是用 IDEA + Tomcat 8.5/9.0 + JDK 8 + MySQL 5.7/8.0。版本对齐是第一个关键点:Tomcat 10 以上把javax.servlet改成了jakarta.servlet,如果你用 Tomcat 10 跑这个包,会直接报ClassNotFoundException: javax.servlet.http.HttpServlet,这不是代码问题,是包名不兼容。
我一般会先确认三件事:JDK 版本、Tomcat 版本、MySQL 驱动版本。JDK 8 配 Tomcat 9 是最稳的组合,MySQL 8.0 的话驱动要用mysql-connector-java-8.0.x,连接 URL 要加时区和 SSL 参数。下面是我常用的环境对照:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 项目源码多为 Java 8 语法 |
| Tomcat | 9.0.x | 保持 javax.servlet 包名 |
| MySQL | 5.7 或 8.0 | 8.0 需改驱动和 URL |
| 构建 | 无 Maven 时用 lib 目录 | 有 pom.xml 则直接导入 |
2.2 导入 IDEA 与配置 Artifact
把 zip 解压后,先看目录结构:如果有pom.xml,直接File → Open选根目录,等 Maven 拉依赖;如果没有,就是传统 Web 项目,需要手动配WEB-INF/lib下的 jar。导入后进Project Structure,重点配两处:Modules里确认Webfacet 的Web Resource Directory指向web或webapp目录,Artifacts里新建一个Web Application: Exploded,把编译输出和 lib 都加进去。
# 如果没有 pom.xml,检查 lib 目录下是否有这些核心 jar ls WEB-INF/lib/ # 常见需要:servlet-api.jar(Tomcat 提供,不要重复放) # mysql-connector-java-8.0.28.jar # jstl.jar + standard.jar(JSP 标签库) # druid 或 c3p0(连接池,看项目用哪个)逻辑说明:servlet-api.jar由 Tomcat 提供,如果项目 lib 里也放了一份,启动时可能冲突,建议 scope 设为 provided 或直接删掉。jstl和standard是 JSP 里<c:forEach>这类标签的依赖,缺了会报The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved。
2.3 数据库初始化与连接参数修改
找到项目里的.sql文件,用 Navicat 或命令行导入。导入后必须改数据库连接配置,通常在src下的db.properties、c3p0-config.xml或某个DBUtil.java里。参数就四个:URL、用户名、密码、驱动类名。
# db.properties 常见写法 jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/food_order?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=你的密码参数说明:serverTimezone=Asia/Shanghai是 MySQL 8.0 必须加的,不加会报时区错误;useSSL=false避免本地连接时的 SSL 警告;characterEncoding=utf8防止中文菜品名乱码。改完这些,在 IDEA 里配 Tomcat Server,Deployment 选刚才的 Exploded Artifact,Application context 设成/或/food,启动后浏览器访问http://localhost:8080/就能看到首页。
提示:如果启动报
Port 8080 already in use,改 Tomcat 的 HTTP port,或者先关掉占用端口的进程。Windows 下netstat -ano | findstr 8080找到 PID 再 taskkill。
3. 三层架构拆解:Servlet、Service、DAO 各管什么
3.1 请求流转路径与包结构
这个项目的包结构基本是controller(或servlet)、service、dao、entity、util五层。一次「加入购物车」的请求路径是:浏览器发/cart?action=add&foodId=3→CartServlet接收 → 调CartService.add()→ 调CartDao.insert()→ JDBC 写库 → 返回 JSP 或 JSON。理解这条链路,你改任何功能都知道该动哪个文件。
// CartServlet 典型写法 @WebServlet("/cart") public class CartServlet extends HttpServlet { private CartService cartService = new CartServiceImpl(); protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String action = req.getParameter("action"); if ("add".equals(action)) { int foodId = Integer.parseInt(req.getParameter("foodId")); int userId = (int) req.getSession().getAttribute("userId"); cartService.addToCart(userId, foodId, 1); // 数量默认 1 resp.sendRedirect("cart?action=list"); } } }逻辑说明:@WebServlet("/cart")是 Servlet 3.0 注解方式,不需要在web.xml里再配。action参数是同一个 Servlet 处理多种操作的常见手法,叫「前端控制器」的简化版。userId从 session 取,说明登录后会把用户信息存 session,这是最基础的会话管理。
3.2 Service 层的事务边界
Service 层最容易出问题的是事务。比如下单操作要同时写orders表和order_detail表,还要扣库存或清购物车,这几步必须在一个事务里。常见做法是在 Service 方法里手动控制 JDBC 事务,而不是每个 DAO 各自提交。
public void createOrder(int userId, List<CartItem> items) { Connection conn = null; try { conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 OrderDao orderDao = new OrderDaoImpl(conn); OrderDetailDao detailDao = new OrderDetailDaoImpl(conn); int orderId = orderDao.insertOrder(userId); for (CartItem item : items) { detailDao.insertDetail(orderId, item); } conn.commit(); // 全部成功才提交 } catch (Exception e) { DBUtil.rollback(conn); // 任一步失败回滚 throw new RuntimeException("下单失败", e); } finally { DBUtil.close(conn); } }参数说明:setAutoCommit(false)关闭自动提交,这是事务的起点;DAO 构造函数传入同一个Connection,保证它们在同一事务里;rollback放在 catch 里,close放在 finally 里。如果你发现下单后订单表有记录但详情表没有,八成是事务没配对,或者 DAO 内部自己拿了新连接。
3.3 JSP 页面与 EL 表达式取值
前端页面在web目录下,常见有index.jsp、cart.jsp、order.jsp、后台的admin/目录。JSP 里用 EL 表达式${}取 request 或 session 里的属性,用 JSTL 做循环和判断。如果你看到页面显示${food.name}原样输出而不是值,说明 EL 被禁用了,需要在 page 指令加isELIgnored="false",或者 web.xml 的版本太低。
<c:forEach items="${foodList}" var="food"> <div class="food-item"> <span>${food.name}</span> <span>¥${food.price}</span> <a href="cart?action=add&foodId=${food.id}">加入购物车</a> </div> </c:forEach>逻辑说明:items是后台request.setAttribute("foodList", list)传过来的;var是循环变量名;${food.id}会拼进 URL。注意&在 JSP 里最好写成&,虽然多数容器能容错,但严格解析时会出问题。
4. 避坑与排查:五个真实翻车记录
4.1 中文乱码:POST 和 GET 要分开治
现象:页面提交中文菜品名,数据库里变成???或者乱码。原因:POST 请求体默认 ISO-8859-1 解码,GET 请求参数在 Tomcat 8 以后默认 UTF-8 但 URL 编码可能不一致。解决:POST 在 Servlet 第一行加req.setCharacterEncoding("UTF-8");GET 要么改 Tomcat 的server.xml加URIEncoding="UTF-8",要么手动new String(name.getBytes("ISO-8859-1"), "UTF-8")。数据库连接 URL 也要带characterEncoding=utf8。
4.2 404:路径对不上,别急着改代码
现象:访问/cart报 404。原因:常见有三种——@WebServlet的值和访问路径不一致、Artifact 没更新、Application context 配错。解决:先看 Tomcat 启动日志里有没有Servlet cart mapped to [/cart],没有就是注解没生效或没编译;再看浏览器地址栏 context path 是不是/food而你访问了/。IDEA 里改完代码要Build → Rebuild,光保存不一定重新部署。
4.3 数据库连接池报错:驱动类名和 jar 不匹配
现象:启动时报ClassNotFoundException: com.mysql.jdbc.Driver。原因:MySQL 8.0 的驱动类名是com.mysql.cj.jdbc.Driver,旧写法com.mysql.jdbc.Driver在 8.0 里已废弃。解决:改 properties 里的 driver 值,同时确认 lib 下是mysql-connector-java-8.0.x.jar而不是 5.x 的包。如果两个版本同时存在,删掉旧的。
4.4 JSTL 标签不解析:uri 和 jar 版本要配对
现象:JSP 里<c:forEach>直接报错或原样输出。原因:JSTL 1.2 的 uri 是http://java.sun.com/jsp/jstl/core,但 jar 包放错或缺失。解决:确认WEB-INF/lib下有jstl-1.2.jar(1.2 版本一个 jar 就够,不需要 standard.jar)。如果用的是 1.0 版本,uri 要改成http://java.sun.com/jstl/core,两者不能混。
4.5 下单后库存没扣:事务和连接不是同一个
现象:订单生成了,但菜品库存没变。原因:OrderDao 和 FoodDao 各自从连接池拿了不同的 Connection,事务只覆盖了其中一个。解决:把两个 DAO 的调用放进同一个 Service 方法,用同一个 Connection 构造 DAO,或者用 ThreadLocal 绑定连接。这是 JavaWeb 项目里最隐蔽的坑之一,因为不报错,只是数据不一致。
5. 二次开发与验证:从能跑到能改
5.1 加一个「菜品搜索」功能的完整路径
假设你要加搜索,按三层走:DAO 加List<Food> searchByName(String keyword),Service 加对应方法,Servlet 里读keyword参数调 Service,JSP 加搜索框和结果循环。SQL 用LIKE CONCAT('%', ?, '%'),注意用 PreparedStatement 防注入。
public List<Food> searchByName(String keyword) { String sql = "SELECT * FROM food WHERE name LIKE ? AND status = 1"; List<Food> list = new ArrayList<>(); try (Connection conn = DBUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, "%" + keyword + "%"); ResultSet rs = ps.executeQuery(); while (rs.next()) { Food f = new Food(); f.setId(rs.getInt("id")); f.setName(rs.getString("name")); f.setPrice(rs.getBigDecimal("price")); list.add(f); } } catch (SQLException e) { e.printStackTrace(); } return list; }参数说明:status = 1是只查上架菜品,这是外卖系统的常见业务约束;try-with-resources自动关连接和语句,比手动 finally 更干净。改完记得在web.xml或注解里确认 Servlet 映射没冲突。
5.2 验证清单:每次改完必走一遍
我习惯改完功能后按这个顺序验:第一步,看 Tomcat 日志有没有异常堆栈;第二步,用浏览器开发者工具看 Network,确认请求 URL、参数、响应码;第三步,直接查数据库确认数据落库;第四步,换一个用户账号走一遍,确认 session 隔离没问题。这四步走完,基本能覆盖 90% 的低级错误。
| 验证项 | 工具 | 预期结果 |
|---|---|---|
| 请求是否到达 | 浏览器 F12 Network | 200 或 302 |
| 参数是否正确 | Network 的 Payload | 与页面输入一致 |
| 数据是否落库 | Navicat / 命令行 | 表里有新记录 |
| 会话是否隔离 | 两个浏览器窗口 | 各自购物车独立 |
5.3 一个我踩过的坑:别在 JSP 里写业务逻辑
刚接触这类项目时,我图省事在 JSP 里用<% %>直接查数据库,结果页面加载慢、报错难查、还容易 SQL 注入。后来强制自己:JSP 只负责展示,所有数据由 Servlet 通过 request 传过来。这个习惯让我后面改任何页面都不慌,因为逻辑都在 Java 里,能打断点、能写单元测试。从那以后我每次拿到新项目,第一件事就是搜 JSP 里有没有import java.sql,有就先重构掉。希望这份拆解帮到你,跑起来之后大胆改,改坏了再回来看排查那章。
本文还有配套的精品资源,点击获取