简介:这是一份基于Java的饭店点餐系统毕业设计论文,面向计算机软件、电子商务等相关专业学生,也适合需要完成课程设计或毕业设计的开发者参考。论文针对传统餐饮场景中人工点餐、查看菜谱速度慢、效率低且易出错的问题,设计并实现了一套基于B/S架构的点餐系统,开发语言为Java,数据库采用MySQL,Web服务器为Tomcat,开发工具为MyEclipse。系统分为用户与管理员两种角色,功能模块覆盖菜品明细、订单管理、菜品管理、厨师管理、菜品分类管理、餐桌座位管理、管理员管理等七大部分,并从安全性、可扩展性、易用性、实时性四个方面阐述了系统设计要点。资源包为doc格式,共1个文档,大小约2.31MB,包含完整论文正文、中英文摘要、目录、绪论及开发技术介绍等内容。目前已有153人学习下载,适合作为论文写作模板或系统设计参考,可直接帮助梳理模块结构、技术选型与实现思路。
1. 先看这份JAVA饭店点餐系统资源:能跑通点餐全流程的毕设源码与论文
这份基于JAVA的饭店点餐系统资源,本质是一套完整可落地的餐饮B/S项目。摘要里描述的场景很直白——大多数餐厅还在用纸质菜单人工点餐,服务员记菜名、跑后厨、人工算账,这套系统把顾客点餐、厨师接单、管理员管菜品的流程整体搬到了浏览器上。资源包含毕业设计论文文档和配套源码,技术栈为Java + MySQL + Tomcat,角色分为管理员、顾客和厨师三类,覆盖菜品管理、订单管理、厨师管理、菜品分类管理、餐桌座位管理等核心模块。适合正在做Java Web方向毕业设计的学生、需要课程设计源码的从业者,以及想快速搭一个点餐系统验证业务流的人。读完你会清楚这套系统的数据库表如何设计、下单核心代码怎么写、部署时会踩哪些坑。
2. 技术选型分析:为什么是JAVA + MySQL + Tomcat这套组合
2.1 B/S结构在点餐场景下的取舍
大部分餐饮管理系统选择B/S架构,主要原因是维护成本低。C/S结构需要在每一台收银电脑上安装客户端,餐厅服务员流动性又大,每换一次设备就得重新部署,工作量不小。B/S结构把所有逻辑集中在服务器端,客户端只需要一个浏览器,餐厅里任意一个能联网的设备都可以作为点餐终端。
这套资源正是典型的B/S架构:浏览器发出请求,Tomcat作为Web服务器接收并处理,业务逻辑用Java编写,数据存储在MySQL中,查询结果通过JSP页面返回给用户。对于餐厅这种并发量不算大、但终端数量多的场景,B/S的“瘦客户端、胖服务器”模式非常合适。
从开发角度看,B/S带来的直接好处是开发调试方便。后端改完代码重新部署一次,前端刷新页面就能看到效果,不用像C/S那样同步更新多个客户端。本资源是毕业设计项目,规模不大,采用B/S结构还能顺带体现Web开发的规范性,在论文答辩时也更容易讲清楚。
2.2 JAVA与MySQL的组合:面向对象和关系型数据的配合
Java在这个系统里承担的是《面向对象编程》要求中的核心逻辑层。点餐业务中天然存在对象概念:顾客是一个对象,菜品是一个对象,订单是一个对象,厨师也是一个对象。Java的类把属性和方法封装在一起,比如菜品对象包含名称、价格、分类、描述等字段,正好映射到数据库中的菜品表。
MySQL在这个组合里负责数据持久化。点餐系统的数据量不大,菜品可能几十上百条,订单一天几百条,MySQL在这个量级下性能完全够用,而且开源免费。资源正文里也强调了这一点——Java和MySQL都是免费技术,不会产生版权费用问题,这在经济可行性分析中是加分项。
数据库连接方面,这套资源采用的是JDBC直连方式。JDBC是Java访问数据库的标准接口,通过DriverManager获取连接、通过PreparedStatement执行SQL、通过ResultSet读取结果集。在没有引入MyBatis或Hibernate这类ORM框架的情况下,JDBC是最直接也最好理解的数据访问方案。对于毕设来说,JDBC代码虽然写起来啰嗦,但每一步都在明面上,指导老师检查代码时不会有任何黑匣子。
2.3 Tomcat与MyEclipse:开发运行时环境的选择
Tomcat是这套系统的Web服务器,负责解析Servlet和JSP。Tomcat本身是免费开源的,配置简单,下载解压就能用。点餐系统属于典型的中小并发Web应用,Tomcat默认配置就能支撑,不需要额外调优。
开发工具上,原资源提到用的是MyEclipse。现在来看,MyEclipse和Eclipse的定位有区别:Eclipse是轻量化IDE,通过插件扩展功能;MyEclipse把Web开发常用的插件打包集成,装上就能直接开发JSP/Servlet项目,省去大量插件配置时间。不过要注意,新版MyEclipse是付费订阅的,如果你不想付费,完全可以用免费版的Eclipse IDE for Enterprise Java and Web Developers替代,功能上覆盖这套毕设的开发绰绰有余。
部署路径上有一个细节值得注意:开发完Web项目后,最终要打成WAR包或复制整个Web应用目录到Tomcat的webapps文件夹下,才能对外提供服务。MyEclipse本身集成了部署功能,但很多人开发时能跑通,换一台机器部署就出问题,原因就是Tomcat目录没弄清楚。
2.4 角色权限:管理员、顾客、厨师各管什么
这套系统的角色划分很清晰,三个角色的职责边界如下表:
| 角色 | 核心操作 | 对应模块 |
|---|---|---|
| 管理员 | 菜品增删改、分类维护、厨师信息管理、餐桌座位管理、订单结账、后台账号管理 | 菜品管理、订单管理、厨师管理、分类管理、餐桌管理、管理员管理 |
| 顾客 | 浏览菜品、按分类查看、在线点餐、生成订单、查看餐桌信息 | 前台展示、在线点餐、餐桌浏览 |
| 厨师 | 登录后台、查看订单、处理订单、制作上菜 | 订单处理、菜品制作状态更新 |
管理员是系统核心用户,掌握全部后台功能;顾客只操作前台,核心动作是“看菜”和“下单”;厨师角色相对简单,登录后查看属于自己的待处理订单,点击接单后进入制作流程。这种角色划分在毕设里属于标准设计,既能展示权限控制的实现思路,又不会把复杂性拉得太高。
3. 数据库与需求设计:点餐系统的地基是八张表
3.1 从用例图到功能模块:三端功能边界
需求分析阶段,资源原文给出了三类用户的用例图。管理员用例集中在后台管理:登录后维护菜品信息、管理厨师、维护餐桌、处理顾客结账。顾客用例相对简单:浏览菜品、按类别查看、点餐生成订单、浏览餐桌信息。厨师用例是登录后查看订单、处理订单、制作上菜。
从这个用例图出发,功能模块可以拆成前台和后台两大部分。前台面向顾客,重点是菜品展示和点餐交互;后台面向管理员和厨师,重点是数据维护和订单流转。前台设计得好看不好看不重要,重要的是顾客能快速找到菜、看清价格、完成下单。后台则要保证数据操作不混乱,权限边界要清晰。
3.2 核心数据表设计:菜品、订单、明细、餐桌、厨师
资源的功能模块明确提到了菜品明细管理、订单管理、厨师管理、菜品分类管理、餐桌座位管理等模块,数据表设计围绕这些模块展开。整套系统我建议建这八张表:
-- 管理员表:后台登录账号 CREATE TABLE `admin` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '登录账号', `password` VARCHAR(64) NOT NULL COMMENT '登录密码', `realname` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表'; -- 顾客表:前台点餐用户 CREATE TABLE `customer` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL COMMENT '登录账号', `password` VARCHAR(64) NOT NULL COMMENT '登录密码', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='顾客表'; -- 菜品分类表 CREATE TABLE `category` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL COMMENT '分类名称,如热菜、凉菜、汤类', `description` VARCHAR(200) DEFAULT NULL COMMENT '分类描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜品分类表'; -- 菜品表 CREATE TABLE `dish` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(100) NOT NULL COMMENT '菜品名称', `category_id` INT NOT NULL COMMENT '所属分类ID', `price` DECIMAL(10,2) NOT NULL COMMENT '菜品单价', `description` VARCHAR(500) DEFAULT NULL COMMENT '菜品描述', `image` VARCHAR(200) DEFAULT NULL COMMENT '图片路径', `status` TINYINT DEFAULT 1 COMMENT '1上架 0下架', PRIMARY KEY (`id`), KEY `idx_category` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='菜品表'; -- 厨师表 CREATE TABLE `chef` ( `id` INT NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL COMMENT '厨师姓名', `phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话', `specialty` VARCHAR(100) DEFAULT NULL COMMENT '擅长菜系', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='厨师表'; -- 餐桌表 CREATE TABLE `dining_table` ( `id` INT NOT NULL AUTO_INCREMENT, `table_no` VARCHAR(20) NOT NULL COMMENT '桌号或包厢号', `seat_count` INT DEFAULT 4 COMMENT '座位数', `status` TINYINT DEFAULT 0 COMMENT '0空闲 1占用', `location` VARCHAR(100) DEFAULT NULL COMMENT '位置描述,如大厅/包厢', PRIMARY KEY (`id`), UNIQUE KEY `uk_table_no` (`table_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='餐桌表'; -- 订单表 CREATE TABLE `orders` ( `id` INT NOT NULL AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL COMMENT '订单编号,可用时间戳生成', `customer_id` INT DEFAULT NULL COMMENT '下单顾客ID', `table_id` INT NOT NULL COMMENT '餐桌ID', `chef_id` INT DEFAULT NULL COMMENT '接单厨师ID', `total_price` DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '订单总金额', `status` TINYINT DEFAULT 0 COMMENT '0待接单 1制作中 2已上菜 3已结账', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '下单时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表'; -- 订单明细表:一个订单包含多个菜品 CREATE TABLE `order_item` ( `id` INT NOT NULL AUTO_INCREMENT, `order_id` INT NOT NULL COMMENT '所属订单ID', `dish_id` INT NOT NULL COMMENT '菜品ID', `dish_name` VARCHAR(100) NOT NULL COMMENT '冗余菜品名称,防止菜品删除后订单丢失名称', `price` DECIMAL(10,2) NOT NULL COMMENT '下单时单价快照', `count` INT NOT NULL DEFAULT 1 COMMENT '购买数量', PRIMARY KEY (`id`), KEY `idx_order` (`order_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单明细表';这段建表语句里有几个设计点值得注意。菜品表中的dish_name冗余在order_item中,这是故意为之的:如果菜品被删除,历史订单还能显示当时的菜名和价格,不会出现“订单里的菜查不到”的尴尬局面。订单表中的status字段用数字表示状态流转,比存中文字符串省空间,也比直接存状态名称更规范。
价格字段用的是DECIMAL(10,2)而不是FLOAT或DOUBLE,这是很多初学者容易犯错的点。浮点数在计算金额时会有精度丢失,比如4.99这样的小数,用DOUBLE存储和计算可能出现4.989999999的情况。DECIMAL是精确类型,适合存储金额类数据。
3.3 订单流转:从顾客下单到结账的状态机
订单状态是整个系统的灵魂,我在表设计里定义了四个状态:待接单、制作中、已上菜、已结账。这套状态机对应餐厅的完整业务流程。
顾客选好菜品提交订单后,订单状态为待接单,此时厨师可以在后台看到新订单,点击接单后状态变为制作中。菜品做完服务员端上桌,由管理员或服务员在系统里确认已上菜,状态变为已上菜。顾客用餐结束,管理员在后台完成结账操作,状态变为已结账。每一步状态变更都对应一个更新SQL,代码层面的核心逻辑就是围绕这个状态机转的。
餐桌状态与订单状态联动。顾客下单时餐桌从空闲变为占用,结账后从占用恢复为空闲。这个联动在实现时要注意事务控制,不能让餐桌状态和订单状态出现不一致。
4. 核心功能实现:登录、点餐、接单的代码怎么写
4.1 数据库连接与登录模块:JDBC工具类和会话管理
这套系统没有使用框架,数据库连接采用JDBC直连。我一般建议把数据库连接封装成一个工具类,避免每个Servlet里重复写连接代码:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/restaurant?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USERNAME = "root"; private static final String PASSWORD = "123456"; static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); } public static void close(Connection conn, java.sql.PreparedStatement ps, java.sql.ResultSet rs) { try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (ps != null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }JDBC连接URL中的characterEncoding=UTF-8是防止中文乱码的关键参数,useSSL=false是避免MySQL 8.0版本因为SSL握手报错,serverTimezone=Asia/Shanghai解决MySQL 8.0时区问题。驱动类名也需要注意:MySQL 5.x用com.mysql.jdbc.Driver,MySQL 8.x要用com.mysql.cj.jdbc.Driver。
登录模块的核心逻辑在Servlet中实现:接收用户名和密码,查库验证,成功后把用户信息放入session。JSP页面通过session判断是否已登录,未登录则跳转登录页。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); String username = request.getParameter("username"); String password = request.getParameter("password"); String role = request.getParameter("role"); // "admin" 或 "customer" 或 "chef" Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); // 根据角色选择不同的表,防止一个SQL里拼表名导致注入风险 String tableName = "admin"; if ("customer".equals(role)) { tableName = "customer"; } else if ("chef".equals(role)) { tableName = "chef"; } String sql = "SELECT * FROM " + tableName + " WHERE username = ? AND password = ?"; ps = conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); rs = ps.executeQuery(); if (rs.next()) { request.getSession().setAttribute("loginUser", username); request.getSession().setAttribute("role", role); // 按角色跳转到不同首页 if ("admin".equals(role)) { response.sendRedirect("admin/index.jsp"); } else if ("chef".equals(role)) { response.sendRedirect("chef/orderList.jsp"); } else { response.sendRedirect("index.jsp"); } } else { request.setAttribute("errorMsg", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } } catch (SQLException e) { e.printStackTrace(); request.setAttribute("errorMsg", "系统异常,请稍后再试"); request.getRequestDispatcher("login.jsp").forward(request, response); } finally { DBUtil.close(conn, ps, rs); } }这里有一个容易被忽略的安全细节:表名是根据角色参数拼接的。虽然tableName的取值是代码里写死的三选一,不是用户直接传入的原始值,但我见过有同学直接把request.getParameter("role")拼进SQL,这是典型的SQL注入漏洞。密码存储这里用了明文比对,毕设可以这样简化,实际商用一定要用MD5或BCrypt加密。
4.2 点餐下单:购物车、事务提交和订单生成
顾客点餐的核心操作是把菜品加入购物车,然后提交生成订单。购物车在JSP页面中可以用session存储一个Map集合,键是菜品ID,值是购买数量。提交订单时,把购物车内容一次性写入订单表和订单明细表,这个过程中必须开启事务,否则会出现“订单主表写入了,明细表没写入”的数据不一致。
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); Map<Integer, Integer> cart = (Map<Integer, Integer>) request.getSession().getAttribute("cart"); int tableId = Integer.parseInt(request.getParameter("tableId")); int customerId = Integer.parseInt(request.getParameter("customerId")); Connection conn = null; conn = DBUtil.getConnection(); conn.setAutoCommit(false); // 开启事务 PreparedStatement ps1 = null; PreparedStatement ps2 = null; ResultSet rs = null; try { // 生成订单号,用时间戳加随机数防止并发重复 String orderNo = System.currentTimeMillis() + "" + (int)(Math.random() * 9000 + 1000); // 第一步:插入订单主表,状态=0表示待接单 String insertOrder = "INSERT INTO orders(order_no, customer_id, table_id, status, create_time) VALUES(?,?,?,0,NOW())"; ps1 = conn.prepareStatement(insertOrder, PreparedStatement.RETURN_GENERATED_KEYS); ps1.setString(1, orderNo); ps1.setInt(2, customerId); ps1.setInt(3, tableId); ps1.executeUpdate(); // 取出自增主键,作为明细表的订单ID rs = ps1.getGeneratedKeys(); int orderId = 0; if (rs.next()) { orderId = rs.getInt(1); } // 第二步:遍历购物车,逐条插入订单明细 String insertItem = "INSERT INTO order_item(order_id, dish_id, dish_name, price, count) " + "SELECT ?, id, name, price, ? FROM dish WHERE id = ?"; ps2 = conn.prepareStatement(insertItem); for (Map.Entry<Integer, Integer> entry : cart.entrySet()) { ps2.setInt(1, orderId); ps2.setInt(2, entry.getValue()); ps2.setInt(3, entry.getKey()); ps2.addBatch(); // 批量执行,减少数据库往返 } ps2.executeBatch(); // 第三步:更新餐桌状态为占用 String updateTable = "UPDATE dining_table SET status = 1 WHERE id = ?"; try (PreparedStatement ps3 = conn.prepareStatement(updateTable)) { ps3.setInt(1, tableId); ps3.executeUpdate(); } conn.commit(); // 全部成功才提交 request.getSession().removeAttribute("cart"); response.sendRedirect("customer/myOrders.jsp?orderNo=" + orderNo); } catch (Exception e) { conn.rollback(); // 任何一步失败都回滚,保证数据一致 e.printStackTrace(); request.setAttribute("errorMsg", "下单失败,请重试"); request.getRequestDispatcher("cart.jsp").forward(request, response); } finally { DBUtil.close(conn, ps1, ps2); try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); } } }事务是这个代码块的关键。conn.setAutoCommit(false)之后,所有SQL都在同一个事务中执行,只有conn.commit()才会真正落库;任何一步抛出异常,conn.rollback()会把之前所有操作撤销。这一步能防止最严重的资金类数据问题——订单主表写了但明细没写,顾客付了钱却不知道点了什么菜。
批量插入用addBatch()和executeBatch(),是为了减少SQL执行次数。如果顾客点了10个菜,逐条执行意味着10次数据库往返,批量执行只往返一次,性能上明显更优。
4.3 厨师接单与管理员结账:状态更新操作
厨师端和后端管理员端的操作本质都是更新订单状态,但要注意权限控制。厨师能看到的订单列表应该过滤状态为0(待接单)和1(制作中)的订单,结账操作只允许管理员执行。这部分用JSP查询列表加更新状态即可,代码量不大,但要注意每次状态更新时记录操作日志,方便回溯。
结账操作的SQL很简单,但餐费计算要准确。通过订单明细表的price和count计算总金额,注意是用明细表的价格乘以数量,而不是信任订单主表的total_price——因为total_price可能在后续修改菜品时不同步。我习惯在结账时重新聚合计算并回写主表总金额。
5. 部署与常见问题避坑:乱码、驱动版本、端口占用
5.1 项目部署步骤
拿到这份资源后,部署流程建议按以下顺序操作。先装好JDK 1.8并配置JAVA_HOME环境变量,再装MySQL并创建数据库,执行建表SQL并插入测试数据。然后准备Tomcat 8.5版本,这是兼容性最好的版本。最后在MyEclipse或Eclipse中导入项目,修改数据库连接配置,部署到Tomcat后启动。
5.2 这些坑几乎每次都会遇到
坑1:启动时报ClassNotFoundException: com.mysql.jdbc.Driver
现象:Tomcat启动后访问系统,页面报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver。
原因:数据库驱动JAR包没有正确放到WEB-INF/lib目录下,或者驱动版本与MySQL版本不匹配。如果MySQL是8.0以上,驱动类名变为com.mysql.cj.jdbc.Driver,但很多老项目代码里还写的旧类名。
解决:打开项目下的WEB-INF/lib目录,确认有mysql-connector-java-5.1.49.jar或8.0以上版本的JAR包;检查DBUtil里的Class.forName字符串,MySQL 8.0要改成com.mysql.cj.jdbc.Driver。如果改了类名还报错,大概率是JAR包冲突,把lib目录下的多个驱动JAR清理成只留一个。
坑2:页面中文全部变成问号
现象:菜品名称、分类名称显示为“???”,后台管理页面提交中文数据后乱码。
原因:三个层面的编码不统一。JSP页面默认编码不是UTF-8;Servlet接收请求参数时没有设置request.setCharacterEncoding("UTF-8");数据库连接URL缺少characterEncoding=UTF-8。任何一层断掉都会乱码。
解决:JSP文件头部加<%@ page contentType="text/html;charset=UTF-8" %>;所有Servlet的doGet和doPost方法第一行加request.setCharacterEncoding("UTF-8")写入编码;连接URL加上useUnicode=true&characterEncoding=UTF-8。这三处都改到位基本能根治。最血泪的经验是:即使前面全都设置正确,MySQL端如果表结构是latin1编码,数据入库时照样乱。
坑3:Tomcat端口8080被占用
现象:启动Tomcat时控制台报Port 8080 required by Tomcat v8.0 Server is already in use。
原因:另一个Tomcat实例或某个进程占用了8080端口。常见的是之前开发的另一个Web项目没关干净。
解决:在tomcat/conf/server.xml中找到<Connector port="8080",把端口改成8081或9090,改完重启。如果想保留8080端口,打开命令行执行netstat -ano | findstr 8080查看占用进程PID,再用taskkill /PID 进程号 /F强制结束。要注意的是修改端口后,浏览器访问路径也要同步改,这个细节最容易忽略。
坑4:上传的菜品图片页面显示不出来
现象:菜品管理中成功上传了图片,但前台页面<img>标签是破图。
原因:图片上传后保存到了本地磁盘路径,比如D:/upload/,但页面用相对路径访问,Tomcat找不到。JSP页面渲染的路径是相对于Web应用根目录的,磁盘路径和Web路径是两套体系。
解决:最简单的方案是把图片保存到Web应用的upload目录下,部署后图片路径就是项目名/upload/文件名,这是毕设里最快的做法。稍微正规一点的方案是给Tomcat配置虚拟目录:在server.xml的<Host>节点下加<Context docBase="D:/upload" path="/upload" />,图片路径映射到磁盘路径。如果项目迁移服务器,虚拟目录配置要同步迁移,否则图片全会丢。
坑5:MySQL 8.0连接报Public Key Retrieval is not allowed
现象:系统功能一切正常,但登录时偶发报错Public Key Retrieval is not allowed。
原因:MySQL 8.0默认使用caching_sha2_password认证插件,JDBC连接时默认不允许获取RSA公钥,导致认证失败。
解决:连接URL加两个参数:allowPublicKeyRetrieval=true&useSSL=false。这两个参数必须同时出现,只用其中一个还是会报错。如果项目中多个地方获取连接,确保所有连接URL都加上,最好直接改在DBUtil的静态常量里,一处修改全局生效。
6. 验收与二次开发:怎么证明系统能用,以及升级到SSM/微服务
6.1 测试用例怎么设计
拿到资源后不要直接上传当作业,先按下面的用例清单过一遍,确认系统真的能跑:
| 测试模块 | 测试步骤 | 预期结果 |
|---|---|---|
| 登录权限 | 用管理员/顾客/厨师账号分别登录 | 跳转到各自首页,不能互相越权访问 |
| 菜品管理 | 管理员新增、修改、下架菜品 | 前台立即显示变化,价格修改后前台同步 |
| 在线点餐 | 顾客选菜加入购物车,提交订单 | 订单生成,明细正确,餐桌状态变占用 |
| 厨师接单 | 厨师后台查看待接单订单,点击接单 | 状态变为制作中,前台可查看进度 |
| 结账 | 管理员对已上菜订单执行结账 | 状态变为已结账,餐桌状态恢复空闲 |
| 异常处理 | 不登录直接访问后台页面 | 被拦截跳转到登录页 |
| 数据一致性 | 下单中途模拟数据库断开 | 订单不产生半截数据,事务回滚 |
走完这套用例,系统的核心功能就算验证通过。特别要注意的是权限拦截测试——直接访问后台JSP页面能否被拦截,这是答辩时最容易暴露的问题。
6.2 从毕设到商用:二次开发的四个方向
这套系统是标准的JSP + Servlet + JDBC架构,能跑通但结构偏老。如果要把它当作课程设计的进阶项目来升级,我建议优先改数据访问层,用MyBatis替换JDBC,把SQL从代码中剥离到Mapper文件里,维护性会明显提升。
第二个方向是引入Spring和SpringMVC,实现控制反转和依赖注入,替换目前一个Servlet一个类的写法。但注意Spring版本选择问题,老项目用Spring 3.x或4.x比较稳妥。再往上可以把项目改造成Spring Boot,这个改动比较大,牵扯到配置方式、打包方式、部署方式的全链路变化,工程量大,适合有时间打磨的情况。
第三个实用方向是前端升级。当前的JSP页面是传统的服务端渲染,体验一般。可以保留原有Java后端不变,只是把页面换成JSP + AJAX异步加载菜品信息的方式优化体验,改动相对小。
第四个方向是业务扩展。当前系统是单店模式,可以在订单表中增加store_id字段来扩展多门店支持;也可以增加支付模块,集成微信支付或支付宝沙箱环境,走通支付流程。不管选哪个方向,数据库表结构都要先动,核心是order和order_item两张表,不要因为新增功能破坏了原有状态机逻辑。
最后说一个个人习惯。从那以后我每次导入这套项目,都会强制先走三件事:检查MySQL连接驱动和URL配置、检查JSP页面和数据库的表编码、检查Tomcat的端口和部署路径。因为这三个位置出的问题,占了这类老项目翻车原因的八成以上。每次先把这三关过了,再去做功能验证就会非常顺。这算是拆了太多套毕设项目攒下来的血泪经验,分享给你,希望帮到你。
本文还有配套的精品资源,点击获取