
简介这是一份面向计算机专业本科生的Java Web毕业设计实战项目聚焦餐厅点餐系统开发适用于Spring框架入门与全栈工程实践学习。项目基于IntelliJ IDEA开发采用Maven标准化构建后端以Spring为核心实现依赖注入与分层架构集成MySQL数据库含菜品、订单、用户等实体表设计前端融合Bootstrap、jQuery、Font Awesome及Material Design组件库支持响应式界面与基础交互功能。压缩包共297个文件涵盖57个Java业务逻辑与实体类、97个JS交互脚本、36个CSS样式文件、29个PNG图标资源及16个XML配置文件整体2.61MB结构清晰体现典型MVC分层与前后端资源组织规范。目前已有128人学习下载读者可直接导入IDE运行调试完整掌握从环境搭建、模块开发菜单展示、购物车、订单管理、用户登录、数据库映射到静态资源集成的全流程实践能力。1. 这不是又一个“学生管理系统”一个真实跑在 IntelliJ IDEA 里的 Maven 餐厅点餐系统后端用 Spring Boot MyBatis MySQL前端是 Thymeleaf 模板——它能直接部署、支持多角色顾客/服务员/管理员、完成从菜单浏览→下单→厨房接单→状态更新→结账全链路你可能已经点开过十几个标着“Java 毕业设计”的压缩包解压后发现没有pom.xml、src/main/java下空空如也、数据库脚本里连建表语句都缺字段、README 里写着“运行前请自行配置 Tomcat”却没说配哪几个参数……这不是资源这是玄学考题。而这个项目不同它是一个完整可启动、可交互、有业务闭环的轻量级 Web 应用不是 Demo不是截图集合更不是 PPT 生成器。它用最主流的 Java Web 技术栈Spring Boot 2.7.x MyBatis 3.4.x MySQL 5.7所有模块按分层架构组织清晰Maven 依赖无冲突、无废弃坐标IntelliJ IDEA 导入即识别连 Lombok 的Data和AllArgsConstructor都已正确启用。适合两类人一是大三下刚学完 Spring Boot 的同学想拿一个不改核心逻辑就能交毕设、还能现场演示下单流程的基线项目二是刚转 Java 后端的新人需要一个没有黑匣子、每个 Controller 方法都能顺藤摸瓜到 SQL 和页面跳转的练手样本。它不追求高并发或微服务但把“餐厅点餐”这个场景里最易被忽略的细节——比如订单状态机流转待接单→制作中→已完成、菜品库存扣减时机、服务员与管理员权限隔离——都落到了代码行里。2. 从解压到启动五步走通 IntelliJ IDEA Maven 环境下的完整运行链路2.1 解压与项目结构初判确认这不是“伪 Maven 项目”拿到S.zip后先别急着导入 IDEA。用任意解压工具打开重点检查以下 5 个文件/目录是否存在且结构合理pom.xml必须位于根目录且parent节点应指向spring-boot-starter-parent版本建议 2.7.18兼容 JDK 8/11src/main/java/com/example/restaurant/包路径需含controller、service、mapper、entity、config五个标准子包src/main/resources/application.yml必须存在且含spring: datasource:和mybatis:配置段src/main/resources/static/与templates/Thymeleaf 项目必备前者放 CSS/JS后者放.html模板sql/restaurant_init.sql或类似命名建库建表初始化数据脚本非空文件提示若pom.xml中packaging是jar而非war说明它内嵌 Tomcat无需额外配服务器——这是现代 Spring Boot 项目的标准做法也是本项目可一键启动的前提。2.2 IntelliJ IDEA 导入拒绝“Add as Maven Project”陷阱很多新手卡在这一步右键 → “Add as Maven Project”结果 IDEA 报错Cannot resolve symbol SpringBootApplication。正确操作是# 在终端中进入解压后的项目根目录含 pom.xml 的那一层 cd /path/to/restaurant-system # 执行 Maven 初始化确保本地有 Maven 3.6 mvn clean compile -Dmaven.test.skiptrue然后在 IDEA 中File → Open → 选择项目根目录不是 zip 文件也不是 src 文件夹弹窗中勾选Auto-import和Create module groups for multi-module projects点击 OK等待右下角 Importing project... 完成约 30~90 秒取决于网络和本地仓库此时pom.xml左上角应出现蓝色 Maven 图标src/main/java下包名应可展开且SpringBootApplication注解无红色波浪线。2.3 数据库准备MySQL 5.7 创建库、执行初始化脚本本项目默认使用 MySQL不兼容 SQLite 或 H2 内存库因涉及多表事务与外键约束。按以下步骤操作启动本地 MySQL 服务如使用 XAMPP/MAMP请确保 MySQL 已 start新建数据库字符集强制设为utf8mb4避免中文乱码CREATE DATABASE restaurant_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;找到项目中的 SQL 初始化文件通常在sql/目录下用 MySQL 客户端执行mysql -u root -p restaurant_db sql/restaurant_init.sql参数说明-u root是登录用户名若改过密码执行时会提示输入restaurant_db必须与application.yml中spring.datasource.url的库名一致表示重定向执行 SQL 文件。执行成功后应看到t_user、t_menu、t_order、t_order_item四张表且t_user中已有 3 条测试数据id1 管理员、id2 服务员、id3 顾客。2.4 application.yml 配置三处必改项与两处易错点打开src/main/resources/application.yml修改以下位置其他保持默认spring: datasource: url: jdbc:mysql://localhost:3306/restaurant_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_mysql_password # ← 此处填你 MySQL 的实际密码 thymeleaf: cache: false # ← 开发期务必关掉模板缓存否则改 HTML 不生效 encoding: UTF-8 mybatis: mapper-locations: classpath:mapper/*.xml # ← 确保路径匹配 src/main/resources/mapper/ 下的 XML 文件 configuration: map-underscore-to-camel-case: true关键参数说明serverTimezoneAsia/Shanghai解决 MySQL 8.0 因时区导致的java.sql.SQLException: The server time zone value ... is unrecognized错误allowPublicKeyRetrievaltrue适配 MySQL 8.0 默认开启的caching_sha2_password认证插件cache: falseThymeleaf 模板热加载开关不关则改完 HTML 必须重启应用。2.5 启动与首屏验证访问http://localhost:8080/login看到登录页即成功在 IDEA 中找到RestaurantApplication.java含SpringBootApplication的主类右键 →Run RestaurantApplication.main()。观察控制台输出出现Tomcat started on port(s): 8080 (http)表示 Web 容器启动成功出现Started RestaurantApplication in X.XXX seconds表示 Spring 上下文加载完毕若有Caused by: java.lang.IllegalArgumentException: Invalid bound statement (not found)说明mapper-locations路径错误或 XML 文件名与MapperScan不匹配。启动成功后在浏览器访问http://localhost:8080/login应看到一个简洁的登录表单含“用户名”、“密码”、“角色选择管理员/服务员/顾客”三个字段。用测试账号尝试登录管理员admin/123456服务员waiter/123456顾客customer/123456登录后跳转至对应首页管理员看后台管理、服务员看待处理订单、顾客看菜单页即证明整个 MVC 链路跑通。3. 核心业务模块拆解从菜单展示到订单状态机代码级还原餐厅点餐逻辑3.1 菜单展示与搜索Thymeleaf MyBatis 多条件查询实战顾客端首页/menu展示所有菜品并支持按名称模糊搜索、按分类筛选。关键代码在MenuController.javaGetMapping(/menu) public String showMenu(Model model, RequestParam(value keyword, required false) String keyword, RequestParam(value category, required false) String category) { ListMenu menuList menuService.findMenus(keyword, category); model.addAttribute(menuList, menuList); model.addAttribute(keyword, keyword ! null ? keyword : ); model.addAttribute(category, category ! null ? category : ); return menu; // 对应 templates/menu.html }对应MenuMapper.java接口Mapper public interface MenuMapper { ListMenu findMenus(Param(keyword) String keyword, Param(category) String category); }其 XML 实现resources/mapper/MenuMapper.xmlselect idfindMenus resultTypecom.example.restaurant.entity.Menu SELECT * FROM t_menu where if testkeyword ! null and keyword ! AND name LIKE CONCAT(%, #{keyword}, %) /if if testcategory ! null and category ! AND category #{category} /if /where ORDER BY sort_order ASC /select逻辑说明where标签自动处理 SQL 中的AND前缀避免手动拼接导致语法错误if实现动态条件当keyword为空时不加入LIKE子句sort_order字段用于控制菜品在页面上的显示顺序数值越小越靠前这是真实餐厅系统中常被忽略但极其实用的设计。3.2 下单流程事务边界在哪库存扣减何时发生顾客点击“加入购物车” → 提交订单是整个系统最关键的事务点。代码位于OrderController.javaPostMapping(/order/submit) Transactional(rollbackFor Exception.class) // ← 事务注解在此 public String submitOrder(RequestParam(items) String itemsJson, RequestParam(note) String note, Model model, HttpSession session) { User currentUser (User) session.getAttribute(user); try { Order order orderService.createOrder(currentUser.getId(), itemsJson, note); model.addAttribute(order, order); return redirect:/order/success?id order.getId(); } catch (Exception e) { model.addAttribute(error, 下单失败 e.getMessage()); return menu; } }OrderService.createOrder()方法内部执行解析itemsJson格式如[{menuId:1,quantity:2},{menuId:3,quantity:1}]校验库存对每个菜品查t_menu.stock是否 ≥ quantity扣减库存执行UPDATE t_menu SET stock stock - ? WHERE id ?插入订单主表INSERT INTO t_order (...) VALUES (...)插入订单明细INSERT INTO t_order_item (...) VALUES (...)关键设计点事务注解Transactional加在 Service 方法上而非 Controller确保“校验-扣减-写库”原子性库存扣减发生在订单创建之前避免超卖t_menu.stock字段为INT类型初始值由restaurant_init.sql设定如宫保鸡丁 stock50这是防止并发下单超卖的最简有效手段。3.3 订单状态机从“待接单”到“已完成”状态流转由谁驱动本项目采用显式状态字段 服务端控制而非消息队列。状态定义在OrderStatusEnum.javapublic enum OrderStatusEnum { WAITING(0, 待接单), // 顾客提交后初始状态 COOKING(1, 制作中), // 服务员点击“开始制作” COMPLETED(2, 已完成), // 服务员点击“完成” CANCELLED(-1, 已取消); // 管理员或超时自动取消 }状态变更入口在OrderController.java的两个接口/order/accept/{id}服务员接单将status从WAITING改为COOKING/order/complete/{id}服务员完成将status从COOKING改为COMPLETEDPostMapping(/order/accept/{id}) public String acceptOrder(PathVariable Long id) { orderService.updateStatus(id, OrderStatusEnum.COOKING.getValue()); return redirect:/waiter/orders; }注意状态变更不触发页面自动刷新需服务员手动点击“刷新”按钮或 F5这是简化版设计。若需实时推送可后续集成 WebSocket但本项目未引入避免复杂度溢出。3.4 权限隔离如何让管理员看到所有订单而服务员只能看自己接的单权限控制未使用 Spring Security而是基于User.role字段 查询条件过滤轻量且直观。以订单列表为例管理员访问/admin/orders调用AdminOrderController.listAllOrders()ListOrder orders orderMapper.findAll(); // 查全部服务员访问/waiter/orders调用WaiterOrderController.listMyOrders()ListOrder orders orderMapper.findByWaiterId(waiterId); // 只查 assigned_waiter_id ?对应 XML 中的findByWaiterIdselect idfindByWaiterId resultTypecom.example.restaurant.entity.Order SELECT o.*, u.username AS waiter_name FROM t_order o LEFT JOIN t_user u ON o.assigned_waiter_id u.id WHERE o.assigned_waiter_id #{waiterId} ORDER BY o.create_time DESC /select优势无学习成本SQL 直观可调劣势无法细粒度到按钮级如“删除订单”按钮对服务员隐藏。若需升级可在 Thymeleaf 模板中用sec:authorizehasRole(ADMIN)控制按钮显示但本项目未预置 Spring Security 依赖故采用更直接的 Controller 层过滤。4. 避坑指南五个真实踩过的雷省下你三天调试时间4.1 现象启动时报java.lang.ClassNotFoundException: javax.servlet.Filter原因项目使用了 Spring Boot 2.7.x但本地 JDK 是 17而javax.servlet.*包在 Jakarta EE 9 中已迁移至jakarta.servlet.*Spring Boot 2.7.x 默认仍依赖旧包。解决在pom.xml中强制指定 Servlet API 版本并排除冲突传递依赖properties servlet-api.version4.0.1/servlet-api.version /properties dependencies dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version${servlet-api.version}/version scopeprovided/scope /dependency !-- 排除 spring-boot-starter-web 传递进来的 jakarta.servlet -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency /dependencies4.2 现象登录后跳转 404URL 变成http://localhost:8080/undefined原因LoginController.java中登录成功后的return redirect: redirectUrl但redirectUrl为空字符串或 null导致拼接出redirect:。解决在LoginController.login()方法末尾添加空值保护String redirectUrl (String) session.getAttribute(redirectUrl); if (redirectUrl null || redirectUrl.trim().isEmpty()) { redirectUrl /home; // 默认首页 } session.removeAttribute(redirectUrl); return redirect: redirectUrl;4.3 现象菜单页图片不显示控制台报GET http://localhost:8080/images/dish1.jpg 404原因项目中图片存放在src/main/resources/static/images/但application.yml未配置静态资源路径或前端 HTML 中写死了images/而非 Thymeleaf 的{/images/}。解决确认图片文件在src/main/resources/static/images/下在menu.html中将img srcimages/dish1.jpg改为img th:src{/images/dish1.jpg}可选在application.yml中显式声明spring: web: resources: static-locations: classpath:/static/4.4 现象下单时提示“库存不足”但数据库里t_menu.stock明明是 100原因MenuService.findMenus()查询时未 selectstock字段导致前端显示的“剩余库存”是 0对象未赋值但实际扣减逻辑用的是数据库真实值。解决检查MenuMapper.xml的resultMap确保stock字段被映射resultMap idMenuResultMap typecom.example.restaurant.entity.Menu id propertyid columnid/ result propertyname columnname/ result propertyprice columnprice/ result propertystock columnstock/ !-- ← 此行必须存在 -- result propertycategory columncategory/ /resultMap4.5 现象管理员修改菜品价格后历史订单明细中的单价未变但新订单变了原因这是正确行为而非 Bug。订单明细表t_order_item在创建时已将item_price字段固化为下单瞬间的价格INSERT ... SELECT price FROM t_menu WHERE id ?保证财务数据不随菜单价变动而漂移。验证查t_order_item表item_price列值应与当时t_menu.price一致且独立于t_menu后续更新。若需求是“价格联动”则需去掉item_price字段查询时JOIN t_menu但本项目按电商通用实践做了快照。5. 进阶技巧三招让这个毕设项目从“能跑”变成“值得讲”5.1 把 Thymeleaf 模板抽成可复用组件减少重复代码的硬核写法当前header.html和footer.html在每个页面都用th:replace引入但导航栏的“当前高亮”逻辑如/menu页面时“菜单”文字加粗分散在各 Controller 中传参。更优雅的做法是用 Thymeleaf 的fragmentsth:fragment在templates/layout.html中定义!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head th:fragmentcommon-head title th:text${title} ?: 餐厅点餐系统系统标题/title /head body nav th:fragmenttop-nav a th:classappend${active menu} ? active th:href{/menu}菜单/a a th:classappend${active order} ? active th:href{/order/cart}购物车/a a th:classappend${active history} ? active th:href{/order/history}订单历史/a /nav div th:fragmentcontent th:removetag页面主体内容/div /body /html在menu.html中引用html xmlns:thhttp://www.thymeleaf.org th:replace~{layout :: layout(~{::body}, ~{::title}, ~{::content})} headtitle菜单页/title/head body div th:fragmentcontent div th:replace~{layout :: top-nav(activemenu)}/div !-- 具体菜单列表 -- /div /body /html效果所有页面共享同一套导航逻辑“当前页高亮”由active参数驱动Controller 中只需model.addAttribute(active, menu)彻底消灭重复判断。5.2 用 Actuator Prometheus 快速接入基础监控零侵入虽然项目轻量但加个/actuator/health和/actuator/metrics就能体现工程素养。只需两步在pom.xml中添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency在application.yml中暴露端点management: endpoints: web: exposure: include: health,info,metrics,prometheus endpoint: health: show-details: always启动后访问http://localhost:8080/actuator/health返回{status:UP}访问http://localhost:8080/actuator/metrics可看到 JVM 内存、HTTP 请求计数等指标。若后续想可视化可配合 Grafana Prometheus但本项目仅需暴露端点即达标。5.3 生成可执行 JAR 包让答辩老师双击就能运行毕业答辩时老师大概率不会装 IDEA 和 Maven。你需要一个restaurant.jar双击或java -jar restaurant.jar即可启动。确保pom.xml中packaging为jar默认就是在项目根目录执行mvn clean package -Dmaven.test.skiptrue找到target/restaurant-system-0.0.1-SNAPSHOT.jar文件名以pom.xml中artifactId和version为准测试运行java -jar target/restaurant-system-0.0.1-SNAPSHOT.jar关键参数说明-Dmaven.test.skiptrue跳过测试避免因测试数据缺失失败生成的 JAR 包内含所有依赖fat jar无需额外 classpath若需指定配置文件可java -jar restaurant.jar --spring.config.locationfile:./application-prod.yml从那以后我每次交付毕设项目都会在README.md里用加粗字体写清楚“双击运行java -jar target/*.jar”并附上默认账号密码。不是怕老师不会用而是把‘能用’这件事做到比‘会用’更确定。希望帮到你。本文还有配套的精品资源点击获取