基于SpringBoot的餐厅菜品评价系统:从CRUD到推荐算法
发布时间:2026/9/29 16:41:12来源:尧图网络
1. 毕设选题背后的真实需求餐厅评价系统到底在解决什么问题每年到了毕设季基于SpringBoot的XX系统几乎是Java方向学生的标配选题餐厅菜品评价系统更是高频中的高频。但很多同学做完之后回头看发现自己做的不过是个CRUD拼盘用户能登录、能加评价、后台能看列表仅此而已。答辩老师问你的推荐逻辑怎么做的评价数据怎么影响菜品排序直接就卡住了。我接手这个题目时先把标题拆开看了三遍餐饮服务质量反馈与菜品推荐平台智慧餐厅顾客体验评价与订单管理系统。这其实隐藏了两个核心命题——一是评价不是孤立的要跟订单形成闭环二是评价数据不能只是存进去展示得让它真正影响菜品的推荐逻辑。如果只做一个评价增删改查相当于把宝马发动机装进了老头乐里题目白瞎了。先明确系统的角色边界普通顾客、餐厅运营管理员、系统管理员。顾客侧的核心诉求有三个——浏览菜品与下单、用餐后写评价、按口味偏好获得推荐运营侧的核心诉求是——看评价统计、看菜品口碑排名、处理评价反馈系统管理员则负责用户管理、菜品分类管理、基础参数配置。这三类角色的权限和数据边界必须一开始就划清楚后面开发才不会越改越乱。另外一个容易被忽略的点是评价的维度设计。餐厅评价不能只有打个分餐饮场景下顾客真正关心的是口味、服务、环境、性价比四个维度。如果你只设计一个总分字段后面想做推荐、想做口碑分析、想按维度输出报表全都无从下手。所以我在数据库设计阶段就把评价表拆成总分加四个子维度分这个决定在后面实现推荐模块时起到了决定性作用。这个系统适合谁来参考一类是正在做类似毕设题目、需要完整设计和代码思路的学生另一类是刚接触SpringBoot、想知道一个多模块业务系统如何组织的小白开发者。我会把从需求分析、库表设计、核心接口实现到部署踩坑的完整链路讲清楚尤其是推荐算法和评价闭环这两块是能让你在答辩时拉开差距的地方。2. 技术选型为什么要这么配从SpringBoot到前端方案的取舍逻辑SpringBoot在这个项目里几乎是必然选择但选它不光是毕业设计要求而是它切实地契合了这个系统的开发节奏。餐厅评价系统属于典型的中小型Web业务系统CRUD多、权限简单、并发量不高SpringBoot的自动装配和起步依赖能把大量配置工作直接消掉让你把精力集中在业务逻辑上。2.1 核心后端依赖配置我用的是SpringBoot 2.7.x版本配合JDK 8。先说版本问题很多同学一上来就装最新的SpringBoot 3.x然后被Jakarta命名空间转换、javax包名报错折磨一晚上。对于毕设这种追求稳定交付的项目2.7.x是成熟度最高、网上资料最全的版本遇到问题基本搜得到答案。核心pom依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies !-- Web层 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- 持久层 -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.1/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- 安全与登录态 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- 工具类 -- dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.25/version /dependency dependency groupIdcom.alibaba/groupId artifactIdfastjson2/artifactId version2.0.43/version /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies持久层我用了MyBatis不是Spring Data JPA。原因是高校的Java课程里MyBatis的教学覆盖率极高导师和答辩老师对这个框架的接受度也高另一个原因是这个系统的评价统计、推荐排序涉及大量自定义SQL尤其是多表联查和分组聚合MyBatis的XML里写起来比JPA的派生查询直观得多。你让一个新手用JPA写Query复杂关联查询调试成本远高于直接看SQL语句。2.2 前端与接口设计前端采用Vue 3 Element Plus管理后台和用户端都在这套方案上开发。之前我见过不少毕设用Thymeleaf模板直接套页面但餐厅评价系统里评价弹出的表单、图表展示、异步刷新这些交互用前后端分离做起来顺畅得多。Vue生态成熟Element Plus的表格、表单、弹窗、评分组件el-rate几乎是为这个场景量身定做的评价打分组件连写都不用写。接口风格上我选择了RESTful风格统一返回结构是一个容易忽略但很重要的细节。我定义了一个通用响应体ResultTData public class ResultT implements Serializable { 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(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }这个统一响应体看似简单但在前后端联调时价值极大前端不用每个接口都写一遍错误处理拦截器里根据code统一判断跳转登录或弹出错误信息即可。我在开发时坚持所有Controller都返回ResultT不直接返回实体类这为后面加全局异常处理器铺平了道路。选型上还有一个决策是使用Hutool工具库。它统一了日期处理、文件上传、验证码生成、加密等操作减少了很多工具类的重复编写。特别是图片上传时生成随机文件名Hutool的IdUtil直接搞定不用自己写UUID工具。3. 核心库表设计评价系统能不能撑住业务全看这几张表很多同学做毕设死在数据库设计这一步——表结构不合理、字段缺失、关联关系混乱导致写业务代码时不断回改。我在设计餐厅评价系统的数据库时遵循一个原则面向业务场景建表先想清楚每张表要支撑哪些页面和接口再定字段。3.1 用户、菜品、订单三张基础表用户表和常规系统类似但我在设计时额外加了一个user_type字段一次到位区分普通顾客0、餐厅运营1、系统管理员2这样Shiro或Spring Security做权限控制时可以统一走这个字段。菜品表要承载推荐系统的输入数据所以字段不能只停留在菜名、价格、分类。我设计的菜品表dish包含以下关键字段CREATE TABLE dish ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT 主键, dish_name VARCHAR(50) NOT NULL COMMENT 菜品名称, category_id BIGINT COMMENT 分类ID, price DECIMAL(10,2) NOT NULL COMMENT 售价, image_url VARCHAR(255) COMMENT 菜品图片, description VARCHAR(500) COMMENT 菜品描述, status TINYINT DEFAULT 1 COMMENT 上架状态0下架1上架, avg_score DECIMAL(3,2) DEFAULT 5.00 COMMENT 平均评分, total_sales INT DEFAULT 0 COMMENT 累计销量, tag_ids VARCHAR(100) COMMENT 口味标签ID集合逗号分隔, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP );avg_score和total_sales是冗余字段用来支撑列表页排序和推荐。可能有同学问为什么不在查询时实时聚合——实测下来评价量到了几百条以后实时聚合再加上多表关联接口响应会明显变慢。冗余字段虽然会在评价提交时多做一次更新操作但读性能好很多毕设演示时尤其流畅。订单表是评价请求的合法性依据。一个核心业务规则是必须先下单消费才能评价对应菜品。否则顾客随便写评价系统里的评分就失真了。所以我设计了订单主表orders和订单明细表order_item明细表记录了每个订单里的菜品快照菜名、价格、数量。3.2 评价表与推荐标签的关联设计评价表是整个系统的灵魂我把它设计成业务含义最丰富的一张表CREATE TABLE evaluation ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL COMMENT 关联订单, order_item_id BIGINT COMMENT 关联订单明细, user_id BIGINT NOT NULL COMMENT 评价人, dish_id BIGINT NOT NULL COMMENT 被评菜品, overall_score TINYINT NOT NULL COMMENT 总体评分1-5, taste_score TINYINT COMMENT 口味评分, service_score TINYINT COMMENT 服务评分, environment_score TINYINT COMMENT 环境评分, cost_score TINYINT COMMENT 性价比评分, content VARCHAR(500) COMMENT 文字评价, image_urls VARCHAR(1000) COMMENT 评价图片多张用逗号分隔, is_anonymous TINYINT DEFAULT 0 COMMENT 是否匿名0否1是, reply_content VARCHAR(500) COMMENT 商家回复, reply_time DATETIME COMMENT 回复时间, status TINYINT DEFAULT 1 COMMENT 1显示0隐藏, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );很多人不理解为什么要有order_item_id——这是为了确保同一笔订单里评价的是具体的某份菜品不是笼统的这顿饭打几分。同时设置唯一约束(order_item_id, user_id)防止一个用户对同一份菜品重复评价从数据库层面保证了评价的真实性。口味标签表tag和菜品标签关联表dish_tag_relation是推荐功能的基石。我把餐厅口味标签预设为微辣、重辣、清淡、偏甜、酸口、香辣、蒜香、麻香等常见维度。推荐逻辑后续就是基于用户历史评价中这些标签的出现频率找到用户的口味偏好画像。有个设计上的小坑要提醒标签关联表我一开始用的是dish_id加tag_id联合主键后来发现一张菜品的标签可能被运营人员修改如果直接删除再插入会连带删除历史评价里的标签关联。所以后续改成了逻辑删除加了一个is_deleted字段这个细节在答辩时讲出来老师会觉得你想到了他们没想到的问题。4. 菜品评价模块的实现链路从下单闭环到评价合法性校验评价模块不是简单的前端弹个窗提交就行真正的难点在于评价资格的校验链路。这个链路走通之后整个系统的业务逻辑才是自洽的。4.1 下单闭环与待评价列表用户操作路径是浏览菜品加入购物车、下单结算、订单完成、进入待评价列表、提交评价。我把每个订单项的状态用order_item_status字段管理0待消费、1已完成待评价、2已评价、3已申请退款。这个状态机虽然简单却可以让前端页面有的放矢待评价列表直接查状态为1的订单项即可。对应的Controller采用如下设计RestController RequestMapping(/api/evaluation) public class EvaluationController { Resource private EvaluationService evaluationService; /** * 查询当前用户的待评价列表 */ GetMapping(/pending/{userId}) public ResultListPendingEvaluationVO pendingList(PathVariable Long userId) { ListPendingEvaluationVO list evaluationService.listPendingByUserId(userId); return Result.success(list); } /** * 提交菜品评价 */ PostMapping(/submit) public Result? submitEvaluation(RequestBody Valid EvaluationSubmitDTO dto) { evaluationService.submitEvaluation(dto); return Result.success(null); } }提交评价的DTO里必须带上orderItemId、dishId、四个子维度分值和内容。有些同学只传orderId不传orderItemId后面实现一单多菜分别评价时就直接推进不去只能返工。4.2 评价合法性校验的三道关卡这里是我重点设计的部分。第一道关卡是状态校验检查order_item是否存在且其状态为已完成待评价。第二道关卡是归属校验这份订单必须是当前登录用户的防止A用户拿B用户的订单号去刷评价。第三道关卡是重复性校验按order_item_id user_id查重已评价的直接抛出业务异常。Override Transactional(rollbackFor Exception.class) public void submitEvaluation(EvaluationSubmitDTO dto) { // 关卡12订单项存在且属于当前用户 OrderItem item orderItemMapper.selectById(dto.getOrderItemId()); if (item null || !item.getOrderId().equals(dto.getOrderId()) || !item.getUserId().equals(dto.getUserId())) { throw new BusinessException(订单项不存在或无权评价); } // 关卡3状态必须是待评价 if (!1.equals(item.getItemStatus())) { throw new BusinessException(当前订单状态不可评价); } // 关卡4查重防止重复评价 Integer count evaluationMapper.countByOrderItemAndUser( dto.getOrderItemId(), dto.getUserId()); if (count 0) { throw new BusinessException(该菜品已经评价过了); } // 插入评价记录 Evaluation evaluation BeanUtil.copyProperties(dto, Evaluation.class); evaluationMapper.insert(evaluation); // 更新订单项状态为已评价 orderItemMapper.updateStatus(dto.getOrderItemId(), 2); // 更新菜品冗余字段平均分和评价数 updateDishScore(dto.getDishId()); }这里有个关键操作用Transactional把评价插入、订单状态更新、菜品平均分刷新绑定在同一事务中。我在测试阶段遇到过一个典型问题——评价插入成功但订单状态没更新用户刷新又看到一个待评价项点了又触发查重报错。加上事务之后这个问题从根本上消失了。4.3 图片上传与富文本输入的处理细节评价图片我采用的是本地磁盘存储加Nginx映射访问的方案不引入OSS或其他云存储依赖因为毕设环境最忌外网依赖。图片存放路径配置在application.yml里upload: dir: /data/restaurant/images/ url-prefix: /upload/images/上传接口用MultipartFile接收核心逻辑是先校验文件类型jpg、png、webp再用Hutool的IdUtil.fastSimpleUUID()生成不重复的文件名避免中文文件名在Linux下出现编码问题。图片大小限制我设置在单张5MB以内前端做了压缩后端也做了大小校验双保险。另一个值得注意的点是XSS过滤。评价内容是用户输入的自由文本如果不过滤一个恶意脚本放在评价内容里前端Vue渲染时可能造成XSS攻击。我在后端加了全局的XSS过滤器对所有json请求体的字符串字段做HTML转义在安全的预设标签白名单之外全部实体化编码。这个点在之前热词里看到有人专门问SpringBoot项目全局过滤器处理上传pdf文件时xss攻击说明大家都遇到类似问题。虽然毕设不追求企业级安全但答辩老师问到安全性怎么考虑时你能答出XSS和SQL注入两个词就是加分项。5. 菜品推荐的核心逻辑如何让评价数据变成推荐依据推荐系统是这个项目真正的亮点所在。很多毕设的推荐只是按销量降序排个序这不叫推荐叫排序。我设计的是一个基于用户口味偏好画像 菜品实时热度的混合推荐策略复杂度控制在能讲清楚原理的程度又比单纯排序高级得多。5.1 用户口味偏好画像的构建用户的偏好画像来自其历史评价记录。每条评价关联的菜品上有tag_ids字段记录了这道菜的口味标签集合。一个用户如果给麻辣香锅打了高分且回收了标签香辣、麻香那么系统就认为他对香辣和麻香的接受度为正。用矩阵来理解行是用户列是口味标签值是平均评分和打分次数的加权结果。画像构建的SQL逻辑是先查出该用户所有已评价记录关联菜品和标签表再按标签分组聚合SELECT tr.tag_id, AVG(e.overall_score) AS avg_score, COUNT(e.id) AS eval_cnt FROM evaluation e JOIN dish_tag_relation tr ON e.dish_id tr.dish_id WHERE e.user_id #{userId} AND tr.is_deleted 0 GROUP BY tr.tag_id得到的数据形如香辣平均分4.8、麻香平均分4.5、清淡平均分3.0。那么该用户的口味偏好向量就是{香辣: 4.8, 麻香: 4.5, 清淡: 3.0}。注意评分低于分数的标签如3.0的清淡不会进入偏好池我会在代码里设定阈值只有avg_score 4.0且评价次数大于1的标签才认为是正向偏好。5.2 候选菜品的加权推荐算法候选菜品集是当前上架状态为1的所有菜品。推荐打分的计算采用线性加权公式每个候选菜品得分由两部分相加推荐分 口味匹配分(60%) 菜品热度分(40%)口味匹配分将菜品的标签集合与用户偏好标签集合求交集交集中每个标签的偏好评分累加而成。比如用户偏好香辣4.8候选菜A包含香辣、微辣两个标签则A的口味匹配分为4.8候选菜B不含任何偏好标签则得0分。菜品热度分融合了total_sales和avg_score两个因素做标准化处理。因为销量和评分的量纲不同直接相加会让销量高的菜品永远霸榜。我做了min-max归一化把销量和评分都映射到0-5区间再乘以各自的系数。private double calcHeatScore(Dish dish) { double salesScore normalize(dish.getTotalSales(), minSales, maxSales) * 5; // 销量归一化 double score dish.getAvgScore() null ? 5.0 : dish.getAvgScore(); return 0.6 * salesScore 0.4 * score; }最后推荐分排序取TopN返回前端以推荐卡片列表展示。整个计算过程在菜品量级200以下时性能完全没问题量级再大就得引入Redis缓存候选集了但毕设场景不需要。5.3 冷启动问题怎么处理冷启动是推荐系统绕不开的坑。新用户没有任何评价记录偏好画像为空推荐算法直接失效。我的解法是新用户默认按综合人气排序返回——综合人气同样用上述热度分逻辑先让用户看到销量高的口碑菜一旦用户产生了评价行为画像逐步生成推荐结果自然开始个性化。还有一个我不建议做的操作是协同过滤或用户相似度算法。这类算法的实现在毕设里非常容易翻车数据量极小几十个用户几百条评价相似度矩阵稀疏到没有意义而且答辩时很难在有限时间内讲清楚原理。基于内容的推荐标签画像足够应对题目要求的菜品推荐平台定位并且可解释性强——每个推荐结果都能反推出因为你喜欢香辣口味所以推荐了这道麻婆豆腐这在演示环节是极强的辅助说明。6. 订单与评价的数据闭环状态流转与运营统计如果说推荐是系统的亮点那数据闭环就是系统的骨架。评价不能悬空它必须基于真实订单评价完成后又反向影响菜品的展示权重。这套闭环梳理清楚了系统的业务逻辑才算完整。6.1 订单状态的驱动机制订单状态流转设计为待支付 - 待消费餐厅确认接单 - 已完成待评价 - 已评价。我把状态字段同时放在订单主表和订单项上主表控制整体子表控制菜品维度。这样设计有个好处一笔订单如果包含三个菜品其中两个完成、一个未完成订单主表可以是待消费但已经完成的两个订单项可以提前进入评价流程。这在快餐场景很实用——先上的菜你先吃不一定要等你消费完整单才能评价。状态机之外的边界情况也要处理比如用户取消订单或申请退款后订单项状态变成已取消/退款中此时不能评价。这个逻辑放在evaluationService的上层判断里而不是依赖数据库约束因为业务规则可能有变化代码里判断比数据库约束更容易修改。6.2 运营报表与评分聚合接口评价数据沉淀之后运营后台需要看到有决策价值的统计结果。我只选了三个最核心的统计维度来开发既不臃肿又能支撑答辩展示。首先是菜品口碑排行榜。按菜品聚合所有评价数据输出总分平均、各维度平均、评价总数并按总分排序SELECT d.id, d.dish_name, COUNT(e.id) AS eval_count, ROUND(AVG(e.overall_score), 2) AS overall_avg, ROUND(AVG(e.taste_score), 2) AS taste_avg, ROUND(AVG(e.service_score), 2) AS service_avg FROM dish d LEFT JOIN evaluation e ON d.id e.dish_id AND e.status 1 WHERE d.status 1 GROUP BY d.id, d.dish_name HAVING COUNT(e.id) 0 ORDER BY overall_avg DESC, eval_count DESC其次是评价趋势统计。按月统计评价数量用ECharts在前端画折线图考察菜品口碑的变化趋势。这类SQL就是典型的DATE_FORMAT(create_time, %Y-%m)分组聚合。第三是标签热度统计。从dish_tag_relation和评价表中聚合出口味标签的分布饼图展示顾客偏好结构。这个数据还可以反过来指导运营调整菜品如果清淡标签评分低、评价少说明用户群更重口味新菜研发就向重口味倾斜。这三个统计接口的开发思路是统一的SQL聚合为主Java服务层做二次加工为辅。不要把所有排序逻辑都放进SQL里有些跨表格的自定义排序在Service层用流式操作处理更灵活后期调参也方便。7. 打包部署与毕设答辩的实战经验到了项目收尾阶段很多同学会发现代码写完了跟系统能跑起来完全是两码事。我这套系统在部署和演示环节踩过的坑比开发阶段还多。这里集中分享几个最关键的。7.1 JAR包打出来之后的隐藏问题第一个坎是SpringBoot项目的打包配置。我用的是Maven打包时必须确保pom.xml里配置了正确的打包插件并且排除掉测试代码否则spring-boot-maven-plugin会在打包时执行测试导致失败plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration excludes exclude groupIdorg.projectlombok/groupId artifactIdlombok/artifactId /exclude /excludes /configuration /plugin打包命令我推荐用mvn clean package -DskipTests跳过测试能避免很多测试类里的上下文初始化问题。第二个坑是外部配置文件的加载。我的图片上传路径、数据库密码等环境相关配置都放在application.yml里但打包成JAR后不想因为环境不同频繁改配置就开启了外部配置覆盖机制把application-prod.yml放到JAR包同级的config目录下SpringBoot启动时会自动优先加载外部配置。这样部署时只需要改外部配置JAR包本身不需要重新打包。第三个坑是数据库初始化。毕设评审环境里可能遇到MySQL 8和MySQL 5.7的差异尤其是时区问题。连接字符串我建议写成url: jdbc:mysql://localhost:3306/restaurant_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai少了serverTimezone参数运行时同样的代码在你本机正常换一台机器就报时区异常很浪费答辩前的调试时间。7.2 部署环境的另一种选择如果评审环境没有MySQL和JDK或者不想绑死在一台机器上我建议提前准备Docker部署方案。写一个docker-compose.yml把SpringBoot应用和MySQL一起编排起来评审老师想看系统时一条命令全部拉起version: 3 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: restaurant_db ports: - 3306:3306 volumes: - ./init.sql:/docker-entrypoint-initdb.d/init.sql app: build: . depends_on: - mysql ports: - 8080:8080 environment: MYSQL_HOST: mysql我用这个方案做了双保险本机开发时可以本地启动MySQL评审现场打不开的话切Docker环境两分钟就能把整套系统拉起来。这里强调一下init.sql的写法里面放建库建表语句和测试数据容器首次启动时自动执行。我预置了二十道菜品、五个标签、三个演示用户和十来条评价数据演示效果直接拉满不用现场手工造数据。7.3 答辩时怎么讲这套系统的亮点毕设答辩的逻辑核心是你的系统比别人的难点在哪儿。我总结了一套讲述顺序屡试不爽先讲清楚业务闭环——订单完成后才可评价评价更新菜品评分和销量数据数据又驱动推荐逻辑推荐又反过来影响顾客的下单选择。这个闭环本身就是题目要求的核心。然后讲推荐算法的可解释性——给评委看推荐结果的同时展示因为你对标签X的评分是Y所以推荐了这道菜Z的界面提示非技术背景的评委老师也听得懂。最后讲数据安全细节——XSS过滤、重复评价拦截、敏感操作的事务控制这三点是系统考虑周全的最好证据。有一个话术是必须避免的不要说我们的系统功能完整、覆盖全面这种大而空的话而要说我在评价合法性校验上设计了四个关卡包括归属校验和状态校验这种具体到实现细节的陈述。信息密度越高的回答越显得系统是你亲手做出来的而不是拼接了别人的开源代码。
网站建设高端定制企业官网