尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SpringBoot+Vue点餐平台开发实战:从表设计到订单状态机

SpringBoot+Vue点餐平台开发实战:从表设计到订单状态机 简介以点餐平台网站作为主题的Java毕业设计项目基于Spring Boot与Vue技术栈实现前后端分离的B/S架构是一份包含源码、说明与数据库的完整工程资料。项目面向计算机专业学生适用于毕业设计、课程设计及全栈开发练习完整覆盖管理员端、用户端和前台首页三大模块包含菜品分类、菜品信息、订单管理、购物车、在线客服、菜品评价等核心业务功能。压缩包内共1652个文件整体大小约31.25MB主要文件类型有java与class后端源码、vue与js前端脚本、html与css静态页面、sql数据库脚本及docx说明文档可方便查看设计文档与数据库结构。目前已有115人学习下载资源附带数据库与说明文件能帮助快速搭建运行环境并通过完整源码深入理解权限控制、业务流转和前后端交互逻辑便于二次开发与论文写作。1. 点餐平台为什么是 Java 毕业设计的“闭环首选”点餐平台在线订餐系统在 Java 毕业设计里属于业务闭环最完整的选题前台负责菜品展示、购物车、下单、评价后台负责用户、菜品分类、菜品信息、订单和系统管理把 SpringBoot 的 ORM、事务控制和 Vue 的路由、组件通信都串了起来。拿到源码包先确认三件事数据库脚本能否单独导入、配置文件里的 MySQL 账号密码是否匹配、前后端是否分端口启动。项目里的update-password.vue.bak、IndexAsideStatic.vue.bak等备份是交付前改过登录页和后台布局留下的痕迹页面改坏了可以直接用.bak回滚。适合课程设计赶时间、答辩要现场演示的学生也适合想快速搭一套带权限和订单流的管理系统骨架的初级工程师。后面按表结构、后端接口、前端交互、权限状态机、部署验证的顺序展开每步都能在源码里找到对应位置。2. SpringBoot 后端数据库表设计与菜品模块的实体映射先看后端整体结构标准的三层Controller 收参数、Service 写业务、Mapper 操作数据库。点餐平台这类项目的核心不在一张表多少字段而在表与表之间的关联关系怎么建。用户、菜品、订单、评价、收藏这几张表一旦打通前台展示和后台管理就都能挂在同一套数据模型上。2.1 点餐系统最少需要几张表表名关键字段在系统里的职责userusername、password、role普通用户与管理员共用靠 role 区分0 用户 / 1 管理员dish_categoryname、sort菜品分类前台按分类筛选的依据dishname、category_id、image、price、sales菜品主表sales 用于按销量排序ordersorder_no、user_id、amount、status、address订单主表status 驱动订单流程order_detailorder_id、dish_id、dish_name、number下单时的菜品快照防止菜品改名后订单无据可查dish_commentdish_id、user_id、star、content菜品评价star 用 1~5 的整数collectuser_id、dish_id用户“我的收藏”newstitle、content、cover前台菜品资讯一个直接踩得上的坑订单主表别叫order它是 MySQL 的保留字建表时会报语法错误源码里用的是orders课程设计里也建议统一用复数或加tb_前缀。价格字段我一般建议用「分」存整数而不是用decimal存元。理由是浮点精度问题0.1 加 0.2 在计算机里不是 0.3点餐这种高频加购计算的场景一旦用浮点累加订单金额最后一位会对不上。用整数分存储前端展示时除以 100 就行这个细节也常被用来回答 java 面试题里的金额精度问题。2.2 实体类与 MyBatis-Plus 的映射关系源码用的持久层框架是 MyBatis-Plus实体类通过注解映射到表不需要写一堆 XMLTableName(dish) public class Dish { TableId(type IdType.AUTO) private Integer id; private String name; TableField(category_id) private Integer categoryId; private String image; private Integer price; // 单位分 private Integer sales; // getter / setter 省略 }TableName指定实体对应的表名TableId(type IdType.AUTO)声明主键自增TableField只在驼峰字段和下划线字段对不上时用。MyBatis-Plus 默认开启驼峰转换categoryId会自动映射到category_id所以大部分字段不用额外加注解。注意如果改动过数据库字段名启动后报Unknown column先去application.yml确认map-underscore-to-camel-case有没有被改成 false这是改配置时最容易被误伤的一项。2.3 菜品分页查询接口的参数设计前台菜品列表要支持分页、按名称模糊搜索、按分类过滤Controller 里对应的接口参数如下RestController RequestMapping(/dish) public class DishController { GetMapping(/page) public Result page(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 6) Integer pageSize, RequestParam(required false) String name, RequestParam(required false) Integer categoryId) { PageDish page new Page(pageNum, pageSize); LambdaQueryWrapperDish qw new LambdaQueryWrapper(); qw.like(StrUtil.isNotBlank(name), Dish::getName, name) .eq(categoryId ! null, Dish::getCategoryId, categoryId) .orderByDesc(Dish::getSales); return Result.ok(dishMapper.selectPage(page, qw)); } }参数类型默认值说明pageNumInteger1页码从 1 开始pageSizeInteger6每页条数建议设上限 20nameString空菜品名称模糊匹配categoryIdInteger空分类精确过滤like方法第一个条件为 false 时整段查询条件会被跳过所以name为空时不会拼WHERE name LIKE %%这种低效条件。orderByDesc(Dish::getSales)让销量高的菜品排在前面前台首页不做额外 SQL 就能拿到热销排序。我一般会在 Service 层再加一道pageSize Math.min(pageSize, 20)的兜底防止前端传 9999 一次性拉全表。分页返回的total、records都来自同一个Page对象前端 Vue 的分页组件直接绑定total就能正常显示总页数。3. Vue 前端菜品展示、购物车与评价的交互实现前端分成两个区域面向用户的商城页和面向管理员的控制台。后台布局用的 Element UI源码里的几个.bak文件正好暴露了布局组件的构成从这些备份能反推出原项目的页面结构改起来也有据可依。3.1 从 .bak 文件推断后台布局结构组件文件职责对应备份IndexAsideStatic.vue后台侧边菜单静态版IndexAsideStatic.vue.bakIndexHeader.vue顶部栏含头像和退出IndexHeader.vue.bakBreadCrumbs.vue面包屑导航BreadCrumbs.vue.bakupdate-password.vue个人中心修改密码update-password.vue.bak说明原作者在交付前把侧边栏从动态渲染改成了静态菜单同时重做了密码修改页。备份文件的作用很直接改组件前cp IndexHeader.vue IndexHeader.vue.bak布局改崩了随时还原。答辩时能讲出这层用意比单纯说“我用了 Vue”更有信息量。3.2 路由拆分与页面懒加载// router/index.js const routes [{ path: /, component: () import(/views/front/Layout.vue), children: [ { path: , component: () import(/views/front/DishList.vue) }, { path: cart, component: () import(/views/front/Cart.vue) }, { path: order, component: () import(/views/front/MyOrder.vue) } ] }, { path: /admin, component: () import(/views/admin/Layout.vue), children: [ { path: dish, component: () import(/views/admin/DishManage.vue) }, { path: order, component: () import(/views/admin/OrderManage.vue) } ] }]路由按用户端和管理员端拆成两个 Layout各自有独立的侧边栏和头部。() import()是懒加载首屏只下载当前路由对应的组件而不是把整个后台代码一次性打进app.js。vue 打包后布局异常的一个常见原因就是首屏包太大渲染慢懒加载能顺带缓解这个问题。注意/admin下的页面必须配合登录权限守卫否则直接输 URL 就能绕过前端菜单进后台。守卫通常写在router.beforeEach里检查 localStorage 里的登录态和 role不满足条件就next(/login)。3.3 购物车的本地状态与下单前校验购物车这个模块很多课程设计会建一张cart表但常见做法是放前端用 localStorage 持久化// store/cart.js const KEY cart_items export default { namespaced: true, state: { items: JSON.parse(localStorage.getItem(KEY) || []) }, mutations: { add(state, dish) { const item state.items.find(i i.id dish.id) if (item) { item.count } else { state.items.push({ id: dish.id, name: dish.name, price: dish.price, count: 1 }) } localStorage.setItem(KEY, JSON.stringify(state.items)) }, clear(state) { state.items [] localStorage.removeItem(KEY) } } }加购、改数量、清空购物车全部在浏览器端完成刷新页面数据不丢后端不需要维护中间态数据。要点是下单提交时商品单价必须以服务端查询结果为准不能用 localStorage 里的价格直接算总额——用户可以改浏览器里的值服务端必须重新校验一遍价格和库存。3.4 菜品评价的提交与重复校验用户端有“菜品评价管理”管理员端也能看到评价评价不是随便发的必须和订单状态绑定。常见规则是订单完成后才允许评价防止没消费就刷好评后端校验Order order orderService.getById(orderId); if (order null || !order.getUserId().equals(loginUserId) || !3.equals(order.getStatus())) { return Result.error(订单未完成不能评价); } DishComment comment new DishComment(); comment.setDishId(dishId); comment.setUserId(loginUserId); comment.setStar(star); // 1~5 comment.setContent(content); commentService.save(comment);3对应订单状态机里的“已完成”这个值和第 4 章的状态定义必须一致前后端任何一处写死成别的数字都会导致评价功能失效。如果再严一点给dish_comment加上order_id字段查询时先select count判断是否评过就能避免同一个订单对同一道菜重复评价。4. 双端权限与订单状态机从下单到完成的闭环用户端和管理员端最核心的差异在权限和数据范围用户只能看自己的订单和自己的收藏管理员能看到全部订单且能推进订单状态。这两件事靠一张user表的 role 字段和一套订单状态机就能实现。4.1 登录态与角色权限的两种做法课程设计级别的项目session 加拦截器是最稳的方案登录成功后把用户对象放进 session需要管理员权限的接口统一走拦截器public class AdminInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object admin request.getSession().getAttribute(admin); if (admin null) { response.setStatus(401); response.getWriter().write({\code\:401,\msg\:\未登录\}); return false; } return true; } }注册到WebMvcConfigurer里只拦截/admin/**路径。这套做法的好处是不用引入 JWT 依赖session 失效就自动退出适合演示环境。如果改为 JWT请求头里带 token后端每次解析优点是前后端完全分离、扩展性好但要在拦截器里自己处理 token 过期。两种实现二选一即可答辩被问到就讲清楚各自取舍。4.2 订单状态机的五态流转与防跳变订单状态是整个项目里最容易出 bug 的地方。直接update orders set status 新值 where id ?会允许从“已完成”跳回“已接单”逻辑上就乱了。规范做法是先定义合法流转状态值状态含义允许的下一步0待处理新订单1 接单、4 取消1已接单2 配送中、4 取消2配送中3 已完成3已完成无仅可评价4已取消无对应的判定逻辑private static final MapInteger, ListInteger FLOW new HashMap(); static { FLOW.put(0, Arrays.asList(1, 4)); FLOW.put(1, Arrays.asList(2, 4)); FLOW.put(2, Arrays.asList(3)); } public boolean canTransit(int from, int to) { return FLOW.getOrDefault(from, Collections.emptyList()).contains(to); }管理员接单、发货、完成三个操作分别调用canTransit校验不合法直接返回“当前状态不可执行该操作”。更新 SQL 也建议带上旧状态条件UPDATE orders SET status 2 WHERE id #{id} AND status 1即使两个请求并发进来只有一条能真正更新成功这是数据库层面的最后一道防线。状态机防跳变的思路在 springboot 面试题里也常被拿来问“如何防止业务状态被非法修改”答上这一层会比只说if判断留下更好的印象。4.3 订单列表的关联查询与订单明细快照订单列表要显示用户名、菜品名、金额和状态一次关联查询搞定SELECT o.id, o.order_no, u.username, GROUP_CONCAT(d.dish_name) AS dish_names, o.amount, o.status, o.create_time FROM orders o LEFT JOIN user u ON o.user_id u.id LEFT JOIN order_detail d ON d.order_id o.id GROUP BY o.id ORDER BY o.create_time DESCGROUP_CONCAT把同一个订单的多个菜品名拼成一行适合列表展示。注意user和order这类名字建议加反引号避免在不同 MySQL 版本下触发保留字问题。订单提交时order_detail里存的是菜品名称和单价快照之后菜品改价或下架都不影响历史订单的展示这是订单系统的基本要求。管理员端的“系统管理”通常是轮播图和菜品资讯这类简单 CRUD前台“在线客服”在课程设计里一般是页面右下角的对话面板复杂一点的接 WebSocket 做实时聊天但为了答辩稳定性模拟对话比长连接更不容易在演示时翻车。5. 部署与答辩前的验证清单5.1 解压后跑通全流程的三个高频坑# 1. 导入数据库先建库再导数据 mysql -uroot -p -e CREATE DATABASE IF NOT EXISTS ordering DEFAULT CHARSET utf8mb4; mysql -uroot -p ordering sql/ordering.sql # 2. 启动后端 mvn spring-boot:run # 3. 启动前端 cd frontend npm install npm run serve第一个坑是 MySQL 连接报Public Key Retrieval is not allowed在 jdbc url 上加allowPublicKeyRetrievaltrueuseSSLfalse即可本质是 mysql-connector-j 8.x 对加密连接的行为变化如果 springboot 版本太高导致驱动类加载异常优先把驱动版本降回和数据库匹配的版本而不是盲目升级框架。第二个坑是 vue 打包后布局异常npm run build出来的页面空白或 CSS 路径 404多半是静态资源用了绝对路径。在vue.config.js里设publicPath: ./资源改成相对路径就正常了。开发模式下的异常则优先检查 Element UI 版本和 Vue 2/3 是否匹配。第三个坑是端口占用。后端改application.yml里的server.port前端通过devServer.proxy把/api代理到后端地址代理配错的表现是前端能打开但所有请求 404。改页面代码前按原作者的习惯先cp 目标文件 目标文件.bak改坏了能立刻还原。5.2 答辩演示的完整验证路径演示顺序建议直接沿着业务主线走管理员登录后新增菜品分类和菜品前台用户注册登录把菜品加入购物车并下单管理员在订单管理中接单、标记配送中、完成订单用户端确认订单完成后对菜品评价最后切到管理员端的评价管理查看这条评价。每个关键步骤后打开数据库客户端查一眼orders和order_detail的记录让评委看到数据是真实落库的而不是前端写死的假数据。准备一个能自圆其说的失败场景也很有用比如演示时故意用未完成的订单去评价让接口返回“订单未完成不能评价”再解释这是状态机校验在起作用。现场能把一个报错讲清楚比一路顺畅地跑完更有说服力。答辩前把这三件事验证到位数据库重新导入一次能成功、前后端从零启动一次能通、核心链路完整走一遍这份源码就能稳稳撑起整场演示。本文还有配套的精品资源点击获取
返回列表