ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

餐厅点餐系统毕设全解析:从业务拆解到SpringBoot部署上线

餐厅点餐系统毕设全解析:从业务拆解到SpringBoot部署上线 餐厅点餐系统这个毕设题目几乎是每个做Java方向的同学都会放进备选清单的选项。它不像商城系统那么庞大也不像学生管理系统那样简陋业务链路完整、角色清晰、扩展点也多算是一个性价比很高的选题。但正因为做的人多答辩老师见过的版本也多如果你只是把增删改查包装一下交上去很难拿到高分。我自己带过几个类似的指导项目也见过太多功能全但说不出为什么的稿子同时也见过不少题目普通但设计细节很到位的作品两者分数差距非常明显。这套系统我建议理解成一个餐饮门店数字化服务管理平台顾客在桌边扫码或者通过服务员手持设备下单订单实时推到后厨大屏后厨完成一盘菜就点一个完成传菜员把菜送到对应桌台前台在后台系统里管菜品、管桌台、看营业数据。整个流程不只是在做CRUD而是把堂食点餐、后厨制作、前厅服务、后台管理串成了一个闭环。技术选型上用SpringBoot做后端、Vue做前端页面这个组合响应速度快、资料多、也贴合当前行业内主流的技术栈对毕设来说是比较稳妥的选择。这篇文章就把我从业务拆解到数据库设计、从核心代码到部署上线的完整思路过一遍重点讲清楚设计时每个关键选择的理由以及实际操作时踩过的坑。不管你是打算照这个方向做一份毕设还是想借这个项目把SpringBoot前后端分离开发流程捋顺这篇文章应该都能帮你省下不少时间。1. 先从业务讲起点餐系统到底在管理什么1.1 一次完整的堂食点餐要经过哪些环节毕设项目最容易犯的错就是一上来就建表写代码结果写到一半发现流程对不上。餐厅点餐系统也不例外。第一步应该先把一次堂食点餐涉及的角色和流程梳理清楚。一个典型的中型餐厅从顾客进门到离开至少要经过这几个环节顾客落座、服务员引导扫码点餐或由服务员代下、顾客加菜退菜、下单成功、后厨按菜品分类显示订单、厨师出餐、传菜员上菜、顾客吃完结账、收银核对、桌台状态复位。此外后台还要管理菜品上下架、菜品分类、菜品价格与图片、会员信息、营销优惠等。对应到系统设计上用户至少有四类角色顾客、服务员前台收银、后厨、管理员。从需求上讲顾客要能看菜品、下订单、查看订单状态服务员要能开台、协助下单、结账后厨要能看到待制作订单并按状态推进管理员要有菜品管理、分类管理、桌台管理、订单查询统计等完整后台能力。把这些角色和流程走一遍之后系统的模块边界自然就清楚了。一般拆成用户端前台点餐、餐厅端H5或平板的服务界面、后厨端大屏和管理后台四个入口前端可以是四套页面也可以合并成两个比如用户端加管理端。毕设如果时间紧张可以先做管理后台加前台点餐两个大模块后厨联动作为亮点逐步加上。1.2 技术选型为什么SpringBoot是毕设最优解选技术栈时大多数同学会在SSM、SpringBoot、单体SpringCloud之间犹豫。我的建议很直接除非老师明确指定老技术路线否则优先SpringBoot。原因很现实。SSM的问题在于配置太散Spring配置文件、SpringMVC配置文件、MyBatis配置、web.xml、pom.xml到处都要写稍不注意就启动失败而且这类问题对复盘和讲答辩没有加分。SpringBoot把约定优于配置做到了极致内嵌Tomcat一个main方法就能跑起来开发效率高一大截。实际项目里SpringBoot也确实取代了SSM的很多应用场景学它毕业之后找工作还有直接用处去看看招聘要求和面试题就明白了。选Java加SpringBoot还有一层好处是生态成熟MyBatis-Plus做持久层查询Redis做缓存和验证码存储JWT做登录令牌MinIO或本地上传做图片存储这些组件在毕设阶段单机部署完全够用而且每个都有大量资料遇到问题搜得到、讨论得了。1.3 用Vue还是JSP前端方案怎么定前端方案是另一个容易纠结的地方。我的经验是除非老师要求使用JSP或模板引擎否则优先做前后端分离前端用Vue加Element Plus如果做移动端H5就配合Vant。答辩演示的时候界面观感确实是加分项这一点很现实。用Vue的话后台管理端用Element Plus组件库可以很快搭出菜品列表、订单表格、弹窗表单用户点餐端如果用移动端H5配合Vant组件库菜品卡片滑动、购物车角标、金额计算都很顺手。Vue生态里的axios、vue-router、pinia都是现成的跟SpringBoot后端配合起来很标准。当然前后端分离也意味着要处理跨域、要约定接口返回格式、要考虑token怎么传。这些不是难题但在选题初期想清楚后面能省去不少返工时间。2. 设计这套系统前我先把表和状态机想清楚了2.1 核心表结构用户、菜品、订单三张表撑起主流程数据库设计时我没有一上来就设计十几张表而是先保证主流程的核心表用户、菜品、订单再加上订单明细、菜品分类、桌台一步一步扩展。用户表存用户ID、用户名、密码、手机号、角色标识顾客/员工/管理员、创建时间。注意密码必须加密存储通常用BCrypt或MD5加盐直接用明文保存是答辩时老师最容易挑的问题之一也是实际项目里绝对不能犯的低级错误。菜品表要包括菜品ID、菜品名称、分类ID、单价、图片URL、描述、状态上架/下架、排序值、创建时间和更新时间。这里单价的数据类型要注意建议用decimal(10,2)而不是double或float因为二进制浮点数在涉及金额的场景会出现精度误差。金额精度在点餐系统里是底线必须一开始就定好。订单表字段相对多订单ID、订单号、桌台ID、用户ID、订单状态、订单金额、优惠金额、实付金额、支付方式、备注、下单时间、支付时间、完成时间。订单号一般不用自增ID对外展示建议用时间戳加随机数的方式生成唯一业务单号方便后续对账和查询。订单明细表用来记录每次订单中每道菜的信息包括订单ID、菜品ID、菜品名称、菜品单价、数量、小计金额。菜品名称在这里做一个冗余字段防止菜品信息后续修改导致历史订单对不上这是实际业务中常见的做法。2.2 订单状态机从待支付到已完成的流转规则订单状态是整个系统最核心的业务数据需要在设计时就定好流转规则。我接触过做餐饮SaaS的朋友他跟我讲过一句话订单状态没设计清楚后厨和前厅一定会吵起来。这话一点不夸张。我用的状态规则是这样的待支付、待接单、制作中、待出餐、已完成外加已取消。同样可以加退款中状态完善闭环。为什么要把待接单和制作中分开因为顾客下单后后厨需要先确认接单确认之后才进入制作。制作中状态可以让顾客在前台页面看到后厨正在制作您的菜品的提示体验上很直观。待出餐则对应菜品已制作完成、传菜员准备上菜的环节。如果是多菜品订单可以做整体状态也可以做菜品粒度状态毕设阶段用整体状态会更简洁。状态流转在代码里要集中控制。我建议用状态枚举加Service层的方法做流转而不是在Controller里到处改状态。这样每个状态变化都走同一个校验逻辑比如已完成订单不能直接改成待支付已取消订单不能进入制作中避免业务错乱。2.3 金额处理BigDecimal的坑与统一精度方案金额处理是用Java做业务系统绕不开的细节。点餐系统的价格、折扣、实付金额全都要用BigDecimal处理。用double计算金额短期看不出问题但一旦出现类似0.1乘3不等于0.3的情形前台的金额显示就坏了。我的做法是所有涉及金额的VO、DTO属性都声明为BigDecimal数据库用decimal(10,2)查询返回后前端展示时保留两位小数。在计算优惠后金额时用BigDecimal的setScale(2, RoundingMode.HALF_UP)统一舍入。注意BigDecimal构造函数要用字符串形式而不是double形式比如new BigDecimal(19.9)可以new BigDecimal(19.9)会出现精度问题这个细节在Java面试里也是常考的。3. 核心流程落地点餐、下单、出餐的关键代码逻辑3.1 统一返回格式与全局异常处理后端接口设计上我习惯先定一个统一的返回结构。所有Controller方法返回Result对象里面包含code、message、data三个字段成功时code为200失败时按业务错误码定义。这样前端拦截器只需要判断code就能统一处理成功失败不需要为每个接口单独写错误分支。配合统一返回全局异常处理用RestControllerAdvice实现。自定义业务异常直接抛ServiceException参数校验异常、数据库异常、兜底异常分别对应不同的提示信息。这样做的好处是Controller层很干净业务代码里的判断逻辑只需要抛异常剩下的统一处理对后面写答辩PPT、讲代码规范也很有利。Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }3.2 购物车与下单流程的实现细节点餐端购物车是用户操作的起点。购物车既可以存Redis也可以存数据库毕设阶段我倾向于存数据库的购物车表简单直观、不依赖额外中间件。购物车表字段包含用户ID、菜品ID、数量、加入时间每次加菜时先查是否存在记录存在则累加数量否则新增。下单时要做几件事校验菜品是否上架、判断菜品状态根据购物车明细计算订单金额生成订单主表记录生成订单明细记录清空购物车。整个流程必须用事务控制任何一个环节失败都要回滚。如果需要给后厨大屏推送可以选择在订单创建后调用WebSocket接口推送也可以由后厨端轮询查询毕设演示一般用轮询或者简单WebSocket就够。事务可以加在Service方法上用Transactional注解。需要注意的是事务失效的常见原因有几种方法被内部调用同类之间调用不走代理、异常被try-catch吞掉、被调用的方法不是public。这些坑很典型排查时直接往这几个方向看。3.3 后厨端接单、制作、出餐的状态变更后厨端的设计原则是少操作、高效率。后厨大屏打开后默认展示所有待接单、制作中的订单每个订单大字号显示桌号和菜品清单方便厨师在几米之外也能看清。后厨操作主要为两个按钮接单、出餐。接单操作将订单从待接单改为制作中出餐操作将订单从制作中改为待出餐。这里要注意的操作规范是必须限制状态修改的操作权限只有后厨角色能调用这些接口。前端按订单状态显示不同的操作项后端接口同样要校验当前状态是否允许该操作不能只依赖前端隐藏按钮。还有一个值得做的细节出餐时给前端用户端推送一条消息比如您点的红烧肉已经出餐即将为您上菜。这种体验细节可能只多几十行代码但对演示效果的提升很明显答辩时也可以顺势引出WebSocket或消息推送相关的讨论点。3.4 多条件分页查询订单MyBatis-Plus怎么用订单查询是管理后台最常用的功能。管理员需要按订单号、手机号、订单状态、时间范围等多条件筛选还要分页展示。用MyBatis-Plus的LambdaQueryWrapper配合分页插件可以很简洁地实现。在SpringBoot里启用MyBatis-Plus分页需要配置分页拦截器。不同版本配置位置略有差异但核心思路一样设置数据库方言查询时传入Page对象返回的结果里就自动带上了总记录数和当前页数据。配置分页插件时要注意版本匹配热词里mybatis的分页插件的用法 springboot搜索量一直很高可见这个点确实是不少人卡住的地方。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor paginationInnerInterceptor new PaginationInnerInterceptor(DbType.MYSQL); paginationInnerInterceptor.setMaxLimit(500L); interceptor.addInnerInterceptor(paginationInnerInterceptor); return interceptor; } }用LambdaQueryWrapper做多条件查询时只需要动态拼接状态非空就加eq时间范围非空就加between订单号非空就加like。这样接口参数很灵活代码里又不会出现大量if嵌套。Override public PageOrderVO pageOrders(OrderQuery query) { PageOrder page new Page(query.getPageNum(), query.getPageSize()); LambdaQueryWrapperOrder wrapper new LambdaQueryWrapper(); wrapper.eq(StringUtils.hasText(query.getOrderNo()), Order::getOrderNo, query.getOrderNo()) .eq(query.getStatus() ! null, Order::getStatus, query.getStatus()) .between(query.getStartTime() ! null query.getEndTime() ! null, Order::getCreateTime, query.getStartTime(), query.getEndTime()) .orderByDesc(Order::getCreateTime); return orderMapper.selectPage(page, wrapper); }4. 细节决定成败前后端交互与配置要点4.1 跨域问题与拦截器令牌校验前后端分离首先要解决跨域。SpringBoot处理CORS的方式通常是实现WebMvcConfigurer并重写addCorsMappings允许前端地址的跨域请求。注意如果前端请求带了自定义请求头比如Authorization需要配置allowedHeaders为星号并将allowedMethods设置为GET、POST、PUT、DELETE等常用方法还要允许携带凭证。登录鉴权我用的方案是JWT令牌。用户登录成功后后端返回一个token前端存到localStorage或pinia中之后每次请求在axios拦截器里把token放进请求头。后端写一个拦截器拦截需要登录的接口从请求头取出token并解析解析失败或过期则返回401。这里要注意把放行白名单列清楚比如注册、登录、菜品浏览这些接口不需要登录否则会出现前端报错、接口被拦截的奇怪问题。一个常见的坑是预检请求。浏览器对跨域请求会先发一个OPTIONS请求如果拦截器没有放行OPTIONS接口就会一直报跨域错误。处理方式是在拦截器里判断请求方法为OPTIONS时直接放行或者在CORS配置里把OPTIONS也加入允许方法。4.2 时间与金额的格式化陷阱SpringBoot默认返回的时间格式跟前端预期常常不一致最典型的是LocalDateTime序列化后变成一串数组格式前端读取时直接懵。解决方式有两个一个是在application.yml里配置全局的Jackson日期格式另一个是加JsonFormat注解。我建议直接用全局配置格式定为yyyy-MM-dd HH:mm:ss前端展示时就不用再手动转换。金额格式化也有类似的问题。BigDecimal如果直接序列化成JSON前端拿到的是数字比如19.90会显示成19.9。如果订单金额需要保持两位小数展示建议在VO层用字符串格式输出或者前端统一用toFixed(2)处理。这个看着是小事但在验收演示时很容易被发现。spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT84.3 菜品排序、图片存储与加载问题菜品排序在前台菜单展示中影响很大。我习惯在菜品表加sort字段查询时按sort升序、id升序排列这样管理员在后台调整排序后前台菜单顺序立刻变化。排序字段虽然不起眼却是让项目比纯增删改查更高一档的细节。菜品图片存储是毕设里比较头疼的一块。如果不想引入复杂的文件服务可以把图片放在服务器的静态目录下用SpringBoot的静态资源映射访问。如果想让项目显得更专业可以引入MinIO做对象存储MinIO部署简单本地跑一个Docker容器就能用接入SpringBoot后图片上传下载更正规答辩时也是个不错的亮点。图片上传还有一个容易忽略的点数据库里不要存完整的图片URL建议只存相对路径或者文件名展示时由后端或前端拼装完整路径。这样切换存储方式时不需要改数据库数据也避免硬编码本机IP地址导致部署后图片全挂的尴尬。5. 从开发机到上线SpringBoot项目的环境配置与部署5.1 本地开发环境一次配到位开发环境的配置比较标准但有几个小细节值得注意。JDK版本建议跟项目保持一致SpringBoot 2.x用JDK 8或11都可以SpringBoot 3.x要求JDK 17以上。选版本时先确认本地JDK再新建项目不要用最新版本顺手创建项目结果编译报错找不到原因。数据库方面我用MySQL 5.7或8.0都可以注意连接URL里加上serverTimezoneAsia/Shanghai并设置useSSLfalse否则时间差8小时和SSL警告会让你排查半天。数据库账号密码不要直接暴露在代码里至少在application.yml中用环境变量或配置占位符的方式管理这在展示源码时相当加分。IDEA里创建SpringBoot项目比较简单用Spring Initializr生成基础工程后再把MyBatis-Plus、Lombok、Validation等依赖加进去。注意Lombok和JDK的版本兼容问题特别是新版JDK下Lombok需要较高版本才能正常处理注解如果遇到编译失败优先检查Lombok版本。5.2 Docker部署SpringBoot项目部署环节我想多说一点因为很多项目到了最后一步会卡住。Docker部署的核心就两步先写Dockerfile把项目打包成镜像再用docker run或docker-compose启动容器。Dockerfile的设计思路是用Maven或Gradle构建出jar包再用一个精简的JDK运行时镜像去运行它。用多阶段构建可以让最终镜像更小。启动容器时需要注意几个点容器内端口映射要正确比如项目配置端口8080运行容器时用-p 8080:8080映射数据库连接如果用localhost在容器里会连不上宿主机因为localhost指向容器自身需要改成宿主机IP或在docker-compose里通过服务名访问。这个localhost陷阱是我见过最多的部署报错之一。# 构建阶段 FROM maven:3.8-openjdk-17 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline -B COPY src ./src RUN mvn package -DskipTests # 运行阶段 FROM openjdk:17-jre-slim WORKDIR /app COPY --frombuilder /app/target/order-system.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, app.jar]如果前端也要部署可以单独打一个Nginx镜像通过docker-compose把前端和后端编排到一起用Nginx做反向代理将/api开头的请求转发到后端容器。这样整套系统在一台云服务器上就能跑起来演示时只需要访问一个IP或域名效果比本地开两个服务要好得多。6. 毕设答辩与排障实录6.1 答辩时高频问题怎么回答答辩环节老师最常问的问题基本都集中在设计决策上而不是代码本身。比如为什么订单状态要用枚举而不是数字要回答枚举让状态流转的代码可读性更强且类型安全金额为什么不用double回答时先举精度问题再说BigDecimal配合decimal类型可以保证金额精确为什么用JWT而不是session回答JWT适合前后端分离架构服务端无需存储会话状态扩展性好但也要诚实提到token过期和续期问题。准备的时候把技术选型里的每个为什么都想清楚答辩就成功了一大半。比如为什么用MyBatis-Plus而不是原生MyBatis可以说它内置了分页插件和通用CRUD方法减少重复代码但底层仍然是MyBatis核心为什么用Vue而不是纯HTML可以说组件化开发减少了DOM操作页面交互更流畅数据驱动视图更新。6.2 我开发过程中踩过的几个坑第一个坑是MySQL连接配置里时区问题导致的日期差8小时。这个问题的现象是数据库存的时间一致但Java查询出来后发现跟实际时间不一致。原因就是连接URL没设置serverTimezone或者设置的值不对。统一在连接参数里加serverTimezoneAsia/Shanghai就能解决。第二个坑是前后端联调时接口一直报跨域排查配置发现allowedMethods没配置全导致预检请求被拦截。处理方式是把OPTIONS请求也加入跨域允许或增加一个拦截器优先放行OPTIONS请求。第三个坑是部署到服务器后前端能打开但所有数据请求都失败。排查发现后端容器里用的数据库地址是localhost实际应该指向宿主机IP或者docker-compose中数据库服务名。这个坑太容易忽略了我特意列出来提醒。还有一个小坑是菜品图片上传到服务器后刷新页面图片丢失。原因是图片上传到了本地的临时目录项目重启后路径变化或文件丢失。解决思路是给图片设置一个固定的静态资源目录比如项目外的/opt/uploads目录通过配置映射到URL路径上这样数据和项目代码分离重启服务不会丢。6.3 常见问题速查表最后把我遇到过的典型问题整理成速查表方便你开发过程中直接对照。问题现象可能原因排查/解决方向项目启动报端口被占用本地8080端口被其他进程占用改端口或找到并停止占用进程页面能打开但数据请求失败跨域配置不完整或接口路径不一致检查CORS配置与axios baseURL确认请求路径和后端Controller路径完全一致数据库中文乱码连接URL缺少characterEncoding参数连接URL加characterEncodingUTF-8并确保数据库表和库使用utf8mb4返回时间格式显示一串数字Jackson未配置时间格式在application.yml配置全局时间格式化或加JsonFormat部署后无法连接数据库容器内使用localhost访问宿主机数据库改用宿主机IP或docker-compose中用服务名连接菜品图片上传成功但访问404静态资源映射未配置配置SpringBoot静态资源路径或使用独立对象存储服务登录后访问接口提示未登录token未放行或已过期检查axios请求拦截器、token过期时间核实拦截器白名单页面提交订单总是不成功事务未生效或参数校验失败检查Transactional注解是否被内部调用绕过看日志中具体异常信息这些坑绝大多数是环境细节造成的代码逻辑本身反而很少出问题。开发时遇到报错不要慌先看日志再看配置最后再怀疑代码按这个顺序排查效率最高。最后再分享一个实际的经验我每次做完一个项目都会给自己写一份类似如果重做一次我会怎么设计的复盘文档。点餐系统这类业务第一版可能只做到功能跑通但如果重做我会在一开始就考虑好菜品多规格比如大份小份、桌台状态与订单状态的联动、菜品售罄自动隐藏、高峰期并发下单这些在真实门店里一定会遇到的需求。毕设做得深入一点收获的不只是分数还有一套解决问题的思路这对后面工作面试的帮助比多刷几十道题更实在。
返回列表