news 2026/10/3 9:10:56

SpringBoot+Vue+Mysql美食推荐系统:前后端分离实战与部署全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue+Mysql美食推荐系统:前后端分离实战与部署全流程

很多做前后端分离项目的朋友,第一反应就是找个管理系统模板改改。这类系统看着热闹,但业务逻辑几乎是空的,做完除了熟悉一下Vue和SpringBoot的增删改查,很难沉淀出能写进简历的东西。我这次做的是一个美食信息推荐系统,技术栈是SpringBoot+Vue+MyBatis+MySQL,选择这个方向的原因很简单:美食推荐业务中有明确的用户行为、标签画像、推荐排序这些可以做深的内容,不是纯粹的CRUD展示,另外菜品、分类、口味、评分这些实体之间关系清晰,非常适合拿来完整走一遍前后端分离项目的全流程。

这个系统做完之后,除了基本的菜品浏览和管理,还实现了基于用户行为的推荐逻辑(包括热度排行、基于菜品标签的偏好匹配、相似菜品推荐),以及完整的管理后台和前端用户界面。整套代码已经整理好,也写了部署教程。这篇博文就把我的设计思路、核心实现和部署过程中踩过的坑完整记录下来,给正在做类似项目或者准备做毕设、准备面试项目的朋友一个参考。

1. 美食推荐系统的核心问题拆解:先想清楚做什么,再动手写代码

很多人拿到这类项目就直接建表写接口,这么做很容易做成一个"点菜管理系统"。我开始的思路是先回答三个问题:这个系统的使用者是谁、他们分别需要什么、什么功能能让这个系统区别于普通的增删改查项目。把这三个问题想清楚,写代码的时候方向才不会跑偏。

1.1 用户角色与需求边界

美食信息推荐系统实际上面临两类完全不同的使用者,他们的操作习惯和对系统的要求差别很大。第一类是普通用户,也就是游客和注册会员,他们关心的是"今天吃什么""附近有什么好吃的""这个菜别人评价如何",需要的是低门槛的浏览体验,菜品的分类筛选、关键词搜索、详情查看、收藏、评分、点赞,这些操作要流畅直接,最好一次点击就能完成。第二类是后台管理员,他们负责内容的维护和运营,需要有菜品管理(增删改查、上架下架、图片上传)、分类管理(菜系、口味标签、场景标签)、用户管理(查看注册用户列表、禁用异常账号)、数据统计(热门菜品排行榜、用户活跃度)。

这两类角色的需求决定了项目的界面和接口必须分离设计。我选择了经典的前后端分离模式,前端Vue提供用户端和后台管理端两套界面,后端SpringBoot只提供纯JSON接口,不掺任何页面渲染逻辑。这样做的好处一是前后端可以独立开发调试,二是将来如果想换成小程序端或App端,后端接口可以直接复用,不用重写业务逻辑。

界面规划上,用户端的核心页面包括:首页(推荐菜品流+分类入口+搜索框)、菜品详情页(图片、描述、评分、用户评论)、个人中心(我的收藏、我的评分记录)。管理端则分为:控制台首页(关键数据统计)、菜品管理页、分类管理页、用户管理页。页面的复杂度控制在一个毕业设计或练手项目合理的范围内,但功能是完整闭环的。

1.2 推荐功能的具体落地方式

推荐系统是这个项目的核心亮点,但很多人一说推荐就想到协同过滤、深度学习这些重型算法,对一个单体项目来说那是过度设计。我采用的是一套轻量级推荐策略,既能让推荐结果看起来"聪明",又不需要跑复杂的算法模型,逻辑清晰可解释,代码量也不大。

这套推荐策略从三个维度取特征值:

  1. 热度分:菜品的基础排序因素。计算方式是浏览数、收藏数、评分加权求和,比如浏览一次记1分,收藏一次记5分,评分按分值乘以3,这样能保证质量高、人气旺的菜品排在前面。
  2. 标签偏好匹配:用户在浏览和收藏过程中会对菜品打上隐式偏好标签,比如用户收藏了5个川菜,那"川菜"这个标签在他身上的权重就越高,推荐时优先推带川菜标签的菜品。
  3. 相似菜品扩展:基于菜品所属分类和标签的重合度寻找相似菜品,在用户浏览详情页时,通过"猜你喜欢"推荐相似的菜品。

这套策略被拆成三个推荐接口:首页推荐流接口(热度分+标签偏好综合排序)、相似菜品推荐接口(标签重合度匹配)、排行榜接口(纯热度分排序)。每个接口的实现逻辑都很清晰,代码量也不大,效果上却比单纯的按时间或按销量排序好很多。我在第3章和第4章会详细拆解推荐逻辑的实现方式。

2. 技术选型:为什么是SpringBoot+Vue+MyBatis+MySQL这套组合

这个项目的技术选型我几乎没有犹豫,就是SpringBoot + Vue + MyBatis + MySQL。这不是什么跟风的选择,而是这套组合在"开发效率、学习价值、团队协作、部署成本"四个维度上综合下来非常均衡,非常适合这个规模的项目。

后端框架,SpringBoot是当前Java生态里绝对的主流。它对配置做了大量简化,以前Spring项目里繁琐的XML配置、Bean装配在SpringBoot里大多变成了约定大于配置,内嵌Tomcat的机制让项目打包后一个java -jar就能跑起来,不管是开发调试还是服务器部署都极其方便。在版本选择上我建议用SpringBoot 2.7.x系列,原因有两个:一是和MyBatis、PageHelper等组件的兼容性经过了充分验证;二是相比SpringBoot 3.x,2.7.x对JDK 8的支持更友好,而JDK 8还是目前绝大多数生产环境的主流。

MyBatis作为持久层框架,它的自由度是我最看重的一点。相比JPA和MyBatis-Plus,原生MyBatis需要手写SQL,表面上看好像麻烦了一点,但恰恰是这种"麻烦"让我能完全掌控SQL语句的写法。推荐逻辑里的那些多表关联查询、子查询统计、动态排序,用MyBatis的XML文件写起来特别顺手,SQL优化也有直接的操作空间。另外,学会手写SQL对面试和工作都是硬通货,这也是我想练的核心技能。

前端Vue选择的是Vue 2.6,配合Vue Router 3.x和Vuex 3.x。很多人觉得应该直接上Vue 3,但从实际项目角度讲,Vue 2的生态非常成熟,特别是Element UI组件库和vue-cli构建工具链,资料多、坑少,照着官方文档就能把项目搭起来。如果是一开始学前后端分离开发,Vue 2 + Element UI是风险最低的方案。如果之前有经验,那换Vue 3 + Element Plus也完全没问题,架构设计上差别不大。

数据库方面,MySQL 8.0已经是标配。表结构设计和SQL编写中需要特别注意字符集(utf8mb4)、时区配置、索引设计三个关键点,这些细节我在第3章会展开讲。

关于项目结构,后端采用经典的分层结构,用包名区分职责边界:

  • controller:接收HTTP请求,做参数校验,返回统一响应体
  • service:业务逻辑层,推荐算法、收藏逻辑、统计逻辑都在这层实现
  • mapper:MyBatis的Mapper接口,只定义方法,SQL写在XML文件里
  • entity:实体类,对应数据库中的表结构
  • common:通用类,包括统一返回体R、异常处理、工具类
  • config:配置类,包括跨域配置、分页插件配置

前端结构用Vue CLI创建的标准模板,按功能拆分为:views(页面组件)、components(复用组件)、router(路由配置)、store(Vuex状态管理)、api(接口请求封装)、utils(工具函数)。前后端的接口访问统一走axios封装,开发环境下通过vue.config.js配置代理转发,生产环境下用Nginx反向代理,这两个方案都能避免跨域问题。

实际花的时间上,从搭建骨架到核心功能跑通大概用了一周的工作时间,后面细化界面和调优又用了三四天。整体效率在同类项目里算比较快的,核心原因就是选型稳、结构清晰、没有在冷门技术上浪费时间。

3. 数据库设计与推荐逻辑的关联:表结构决定推荐效果的上限

数据库设计是最不能急的一步,因为推荐逻辑的质量很大程度上取决于表结构是否合理。我设计表的时候不是单纯满足功能需求,而是把"推荐算法需要什么数据"作为一个重要的设计约束,确保后续写推荐SQL的时候不至于缺字段或者查起来非常别扭。

系统的核心数据表一共六张:

用户表(user)存用户基础信息,包括用户名、密码(加密存储)、昵称、头像、创建时间;分类表(category)用一张表维护两级分类,即菜系分类(川菜、粤菜、湘菜等)和场景分类(家常菜、下饭菜、夜宵等),用parent_id字段做自关联。

菜品表(dish)是核心业务表,字段包括菜品名称、所属分类id、价格、图片URL、描述、口味标签(用逗号分隔存储,如"麻辣,鲜香")、浏览数、收藏数、评分总分、评分人数、状态(上架/下架)、创建时间。浏览数、收藏数这些冗余字段会在每次用户操作时同步更新,作为推荐算法的热度因子。

用户行为表(user_behavior)记录用户浏览、收藏、评分三类行为。字段包括用户id、菜品id、行为类型(browse/favorite/rate)、行为值(评分行为记录分值,比如1到5),以及行为时间。这张表是推荐系统的数据基础,每个用户的偏好画像都从这里分析出来。收藏表(favorite)单独拆出来,记录用户收藏菜品的关系,前端"我的收藏"和个人中心的红心状态都查这张表。

评分表(rating)和收藏表类似,记录用户对菜品的评分,一张表同时解决评分展示、个人评分记录、菜品评分更新三个问题。除此之外还有一个菜品标签表(dish_tag可选),如果菜品标签需要独立管理可以拆表,我这里为了控制表数量,把标签直接放到了菜品表的tags字段里。

这几张表的关系本质上就是:用户通过行为关联到菜品,菜品通过分类和标签建立相似性,推荐逻辑就是在这个关系网络上做统计和匹配。

3.1 索引设计:推荐查询快不快,看索引有没有用对

索引设计上我想多说几句,因为很多项目上线后性能出问题,就是栽在没建索引或者索引建得乱七八糟。这张表的数据量虽然不大,但查询模式很典型,建索引时我遵循了几个基本原则:

  • 外键字段必须建索引。用户行为表、收藏表、评分表里的user_id和dish_id都会频繁出现在where条件和join条件里,建立普通索引能显著加速关联查询。
  • 排序字段要建索引。菜品表和排行榜相关的查询按browse_count、favorite_count排序,给这两个字段建索引可以减少排序耗时。
  • 复合索引要符合最左前缀原则。比如用户行为表经常按(user_id, behavior_type)过滤,那就建一个(user_id, behavior_type)的复合索引,不要分别建两个单列索引。

当初建表时如果把索引设计好,后面写推荐接口就能明显感觉到查询速度快很多,不需要回头再补优化。这个习惯不管做毕业设计还是实际工作都建议尽早养成。

数据库字符集必须用utf8mb4而不是utf8,否则遇到生僻字、特殊符号时会出现乱码或存储失败。连接配置里还要加上时区参数,避免MySQL 8.0版本因为serverTimezone不一致报错。

3.2 从表结构推导推荐特征:热度分和标签权重

我设计的推荐算法不需要单独跑离线任务,而是直接用SQL和简单的Java逻辑在请求时实时计算。核心的特征值都是从表结构里推导出来的:

热度分的计算公式定义为:浏览数×1 + 收藏数×5 + 评分均分×3。我已经把浏览数、收藏数、评分总分这几个字段冗余到了菜品表,所以SQL里做一次简单的数学运算就能算出热度分,不需要每次去关联行为表统计。这个冗余设计的收益在写SQL的时候体现得特别明显,排行榜接口只需要一条带ORDER BY的查询就能搞定,不用做复杂的聚合计算。

用户标签偏好的推导则是实时的。用户浏览和收藏了某些菜品,这些菜品带的标签就会被记录下来。我给每个标签的权重定义了一个加权公式:收藏行为权重高于浏览行为权重,比如收藏一次给标签加2分,浏览一次加1分。系统会统计用户最近一段时间内所有行为的标签权重,排序后得到该用户的前三个偏好标签。这在Java代码里实现,最终输出一个标签列表。

相似菜品匹配的思路是:把当前菜品的分类id和标签列表拿出来,查找相同分类下标签重合度最高的其他菜品,重合度计算可以简单设为两个菜品相同标签的数量。SQL查询在分类相同的前提下,对每个候选菜品统计标签交集数量,按交集数量倒序排列取前几个。

这套设计把推荐系统的三大经典要素——热度、个性化、相关性——都覆盖到了,而且每一样都是依托数据库结构就能直接实现的,给项目的代码可读性和后期维护都留了足够的空间。

4. 后端实现详解:SpringBoot+MyBatis的完整链路

后端项目的骨架搭建非常快,Spring Initializr创建一个基础工程,然后在pom.xml中加入依赖。我列一下关键依赖版本,按我的配置方式可以直接跑起来:

  • SpringBoot 2.7.14
  • MyBatis Spring Boot Starter 2.3.1
  • MySQL Connector Java 8.0.33
  • PageHelper 1.4.7(分页插件)
  • Lombok(简化实体类代码)
  • Hutool 5.8.22(工具类库,生成验证码、处理字符串等)

结构化流程是Controller接收请求并校验参数,然后调用Service层处理业务逻辑,Service调用Mapper层接口访问数据库,最终结果封装到统一返回体R里返回给前端。异常处理用全局异常处理器@RestControllerAdvice统一拦截,业务异常返回500和错误信息,参数校验异常返回400,数据库异常则打印错误日志并返回提示信息,避免把异常堆栈直接暴露给前端。

4.1 统一响应体的作用:前后端对接不扯皮

统一响应体虽然是个不起眼的小设计,但前后端分离项目里它几乎是必须的。我用一个泛型类R来包装所有接口的返回结果,结构是code、message和data三个字段。成功时code为200,data存放业务数据;失败时code为500或其他业务码,message放提示信息。

public class R<T> { private Integer code; private String message; private T data; public static <T> R<T> success(T data) { R<T> r = new R<>(); r.setCode(200); r.setMessage("操作成功"); r.setData(data); return r; } public static <T> R<T> error(Integer code, String message) { R<T> r = new R<>(); r.setCode(code); r.setMessage(message); return r; } }

没有这个统一结构时,前端每个接口都要单独判断返回格式,十分痛苦。有了它之后,前端axios拦截器里统一判断code === 200,不是200就弹错误提示,代码思路一下就清爽了。

4.2 推荐接口的SQL实现细节:手写SQL的价值体现

后端最核心的就是推荐相关的那几个接口。第一个是首页推荐流接口,处理逻辑是先看用户是否登录。未登录用户直接按热度分倒序返回菜品列表;已登录用户,先查询他最近30天的行为记录,统计出偏好标签,再执行一条带条件的SQL查询,对匹配偏好标签的菜品做热度分加权,实现个性化排序。接口用PageHelper做分页,懒加载式地加载20条一页的数据。

关键SQL是这样一个查询逻辑:

  • 未登录:SELECT * FROM dish WHERE status = 1 ORDER BY (browse_count + favorite_count * 5 + score_total / score_count * 3) DESC
  • 已登录带标签偏好:在上面的基础上,对dish.tags中包含用户偏好标签的菜品排序权重额外加上一个固定值,实现方式用ORDER BY (热度分 + IF(FIND_IN_SET('川菜', tags) > 0, 50, 0)) DESC这种直观写法。

第二个是相似菜品推荐接口。SQL写法是SELECT目标菜品的category_id和tags字段,然后查同分类下其他菜品,用Java代码计算标签重合度后再排序。菜品总数不多时完全够用,不需要引入Elasticsearch这类重型技术。

第三个是排行榜接口。简单实现就是按热度分倒序,配合LIMIT取前10,还可以自己扩展按周榜月榜、按菜系分类榜单这些维度。

用户行为采集接口是给推荐"喂数据"的。前端在用户浏览菜品详情页时,会调用一个记录行为接口,把用户id、菜品id、行为类型、行为值传给后端,后端做三步处理:更新user_behavior表插入一条行为记录,更新dish表的热度冗余字段,如果行为是评分还需要同步更新评分总分和评分人数。我在这里用的是事务控制,确保这些更新要么全部成功要么全部回滚,避免数据不一致。

Python和大数据生态里处理这类行为数据可能用埋点、消息队列,但在这个单人项目里,一个异步的HTTP调用加数据库更新已经完全够用,过度设计没有意义。

4.3 图片上传与静态资源映射:一个容易翻车的细节

美食系统菜品图片的存储方式和访问路径是一个容易翻车的地方,很多项目开发时图片能传,部署上线后图片就挂了。我的方案是:图片上传接口接收MultipartFile,保存到本地磁盘的/upload/dish/目录,文件名用时间戳加随机数重新生成,避免中文名和重复问题。保存成功后返回URL路径,这个路径是/upload/dish/xxx.jpg。

前端访问这个路径的关键在于SpringBoot的静态资源映射配置,要在配置类里把磁盘上的upload目录映射为HTTP访问路径。否则上传成功了,但前端拿到URL打开就是一个404。

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { // 将 /upload/** 路径映射到本地磁盘目录 registry.addResourceHandler("/upload/**") .addResourceMapper("file:" + uploadPath + "/"); } }

开发阶段这么做没问题。真正部署到服务器,更推荐用Nginx直接托管upload目录,这样静态资源访问和动静分离的效果更好,我在第6章部署部分会细说。

4.4 跨域配置:前后端分离第一个拦路虎

前后端分离第一个必踩的坑就是跨域。开发环境下前端跑在8080端口,后端跑在9090端口,前端axios请求后端接口时,浏览器的同源策略会直接拦截响应。解决方案在开发环境有两种:

方案一是在后端加CORS配置类,用@CrossOrigin注解或者注册CorsFilter,允许所有来源的跨域请求。方案二是在前端vue.config.js里配置devServer的proxy代理,把/api开头的请求代理到后端地址。我开发时就是用方案二,因为它更接近生产环境的部署方式,而且代理后前端代码里请求路径可以和部署环境保持一致,不用随时切换。

// vue.config.js module.exports = { devServer: { port: 8080, proxy: { '/api': { target: 'http://localhost:9090', changeOrigin: true, pathRewrite: { '^/api': '' } } } } };

这样前端请求/api/dish/list,开发环境下就会被代理到http://localhost:9090/dish/list。生产环境部署时,由Nginx完成这个反向代理的活儿。

5. Vue前端页面组织与推荐功能的前端配合

前端是整个系统的脸面,一个美食系统如果界面看着没有食欲,用户很难有继续使用的兴趣。我用Vue 2 + Element UI + axios + Vuex搭建前端,界面风格围绕美食主题做了定制,整体色调偏暖色系,卡片式展示菜品,分类导航清晰,搜索框醒目。

路由设计是每个页面的入口,权限控制也依赖路由。用户端路由表包括:首页/home、菜品详情/dish/:id、搜索结果/search、个人中心/user、登录注册/login。后台管理端路由要求管理员权限才能访问,字段用meta.requiresAuth和meta.role控制。实现方式是前置路由守卫beforeEach判断是否有token、token对应角色是否为管理员,不满足条件的重定向到登录页。

5.1 菜品卡的组件化设计与推荐流渲染

首页是推荐流和分类入口的展示层,核心组件是一个菜品卡片DishCard组件,展示菜品缩略图、名称、分类、价格、评分星星、收藏数和人气标签。这个组件在首页、搜索页、详情页的推荐区都会复用,所以做成通用组件利润很高。

首页推荐流数据通过recommend接口获取,返回的菜品列表渲染成DishCard组成的网格。界面交互上我做了两个细节:卡片悬停时显示缩略信息,点击进入详情页;收藏的爱心图标会实时反映当前用户是否已经收藏,点击可以切换收藏状态,无需刷新页面。

收藏状态切换用的是乐观更新:先调用收藏接口再更新Vuex中的状态,如果接口失败再回滚状态。这样对用户体验友好,不会因为网络切换到按钮卡顿半秒,是很值得养成的一个小习惯。

5.2 Vuex管理用户状态与Vue Router的动态权限

Vuex在这个项目里主要管理三类全局状态:用户登录信息(token、用户名、角色)、全局收场状态(收藏列表)、界面状态(侧边栏展开与收起)。登录成功后把token存到localStorage,同时commit到Vuex的state,axios请求拦截器在请求头带上Authorization: Bearer ${token}。后端通过拦截器校验token并解析出用户信息,放入ThreadLocal供Service层获取当前用户。

管理端的路由权限也通过Vuex控制。登录者角色是管理员时,前端会动态添加管理端路由,这个功能的坑在于直接用router.addRoutes动态添加路由时,页面刷新后Vuex状态清空导致路由丢失,页面变成空白。解决办法是刷新时重新拉取用户信息并重新挂载路由,或者把用户信息持久化到sessionStorage。这个细节如果不处理好,部署上线后一刷新管理界面就白屏,体验很差。

5.3 图片与数据渲染的细节处理

一些容易被忽视的小细节也会影响使用体验。菜品图片加载失败时,前端要有一个备用图机制,我用的是Element UI的el-image组件,设置slot="error"为默认占位图。价格显示为两位小数,评分做半星展示支持,这些用计算属性处理即可。

对于发布时间,后端返回的时间戳或时间字符串在前端格式化时注意时区差8小时的问题。我用Hutool格式化时间字符串且带上时区,前端不做额外转换。接口数据里有null字段时,前端展示要加默认值,避免页面上出现"undefined"这种丑东西。

6. 部署全流程:从本地开发到服务器上线,手把手拆解

部署是很多项目从"能跑"到"真正能用"之间的一道坎,我在部署过程中也掉了不少头发。整个项目的完整部署分为四个部分:MySQL部署、后端jar包部署、前端打包部署、Nginx反向代理配置。这里把整个流程和容易出的问题完整写出来。

6.1 MySQL的安装与初始化配置

服务器上的MySQL安装我建议用Docker快速启动,更干净也好管理。一条命令就能拉起MySQL 8.0:

docker run -d \\ --name mysql8 \\ -p 3306:3306 \\ -e MYSQL_ROOT_PASSWORD=你的密码 \\ -e TZ=Asia/Shanghai \\ -v /opt/mysql/data:/var/lib/mysql \\ mysql:8.0

或者用yum install mysql-server在CentOS上直接装。手动安装方式的注意点在于初始化完成之后要设置密码、调整字符集和时区。不管哪种方式,都要确认default-character-set=utf8mb4和default-time-zone='+08:00'这两个配置,否则数据入库乱码和时区偏差问题会在后面冒出来。

数据库初始化时直接执行项目的db/init.sql脚本,这个脚本建了六张表和初始的测试数据(包含20多个菜品、3个分类、2个测试账号)。我建议测试数据一定提前准备好,不然前端页面打开一片空白,分不清是接口问题、数据库问题还是前端渲染问题。

6.2 后端打包部署:application.yml的坑

后端打包用Maven,执行mvn clean package -DskipTests,生成target目录下的jar包。上传到服务器前,application.yml里几个配置要重点检查:

  • MySQL连接地址改成服务器的IP或域名,不能用localhost(除非jar包就在数据库同一台机器上)
  • 数据库账号密码改成生产环境的强密码
  • 图片上传路径得改成服务器的绝对路径,比如/opt/upload
  • 日志配置建议加上,排查问题靠print是不行的

启动命令我用的是nohup java -jar food-recommend.jar --server.port=9090 > app.log 2>&1 &。这里强调一下端口冲突问题:如果服务器上8080被占用了,后端端口一定要选一个没被占用的,我习惯用9090这一类的端口。启动之后检查app.log日志里有没有"Started Application"关键字,没有的话说明启动失败,需要看日志里的具体报错。

常见启动失败原因几乎就三类:数据库连接失败(检查IP端口密码和防火墙)、端口被占用(netstat -lnp | grep 9090查一下)、JDK版本不匹配(SpringBoot 2.7要求JDK8以上,服务器装了更高版本JDK一般也没太大问题,但有些组件对版本很敏感)。

6.3 前端打包与Nginx配置:history路由的404问题

前端构建出生产包,执行npm run build,生成的dist目录就是部署目录。生产环境我用Nginx来做静态文件服务和API反向代理,这份配置我直接列出来,改一下IP和端口就能用:

server { listen 80; server_name your_domain.com; # 前端静态文件 location / { root /opt/food-recommend/dist; index index.html; try_files $uri $uri/ /index.html; } # API反向代理到后端 location /api/ { proxy_pass http://127.0.0.1:9090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 图片访问路径映射到本地磁盘目录 location /upload/ { alias /opt/upload/; } }

这份配置解决了三件事:前端路由用的history模式,如果直接刷新一个非首页的路径,try_files指令会把请求引导回index.html,避免404;/api/开头的请求代理到后端服务,解决了跨域问题;/upload/开头的图片请求直接映射到磁盘目录,由Nginx处理静态文件,比经过Java服务效率高得多。

前端打包前还有一个小坑要提醒:如果前端代码里请求地址写死了开发环境的代理路径,打包后是不会走代理的,必须检查请求地址用的是相对路径/api/...形式,才能被Nginx规则正确匹配。

6.4 部署后推荐功能异常排查

部署完成后我花了些时间验证推荐功能的完整性,这里说一下常见的异常场景和排查思路:

现象一:首页能打开,菜品列表为空。排查顺序是:检查后端日志有没有接口请求进来,如果没有说明Nginx代理没生效,检查/api/的location配置;如果有请求但返回500,检查后端日志的SQL异常,多半是数据库表结构和实体类字段不匹配;SQL能跑但前端列表为空,检查接口返回的数据结构是不是前端预期的结构。

现象二:图片全部显示裂图。类似场景往往是因为Nginx配置的/upload/路径映射不对,或者图片URL里的路径是后端拼的本地路径,部署后没有换成服务器路径。

现象三:登录后接口返回401。排查token的生成和校验是否依赖了Redis或本地缓存,如果是本地内存存储的token,那么后端重启服务之后所有用户的token都会失效,重新登录就好。大规模多实例场景下要换成Redis存储token,单机部署用本地缓存即可。

7. 一套完整源码的目录说明与二次开发建议

项目源码我已经整理成完整结构,后端的核心目录如下:

food-recommend-backend/ ├── pom.xml ├── src/main/java/com/food/recommend/ │ ├── FoodRecommendApplication.java │ ├── common/ # 统一返回体R、全局异常处理 │ ├── config/ # 跨域配置、WebMvcConfig │ ├── controller/ # 用户、菜品、分类、行为、推荐、文件上传等接口 │ ├── service/ # 业务逻辑层,推荐算法核心代码 │ ├── mapper/ # MyBatis Mapper接口 │ ├── entity/ # 实体类 │ └── utils/ # JWT工具类等 ├── src/main/resources/ │ ├── application.yml │ ├── mapper/ # MyBatis XML映射文件 │ └── db/init.sql # 建表和测试数据 └── ...

前端目录相对简单,就不赘述了。拿这份源码做二次开发的话,我建议优先从这几个方向切入:

一是扩展推荐策略的维度,加入协同过滤思想,基于相似用户的行为做推荐。用户行为表已经有数据基础,多写几条统计SQL就能求出用户相似度矩阵,在小规模数据上跑起来非常直观。

二是接入第三方登录(微信、QQ),需要增加一个oauth_user表存储openid和昵称头像信息,登录流程变成第三方授权后自动注册或绑定账号。

三是增加评论功能,设计评论表关联用户和菜品,前端在详情页追加评论列表和评论框,后端增加评论的增删查接口。这个功能对完善系统的内容生态帮助很大。

四是把图片存储从本地磁盘迁移到MinIO或云存储OSS,菜品图片是运营过程中增长最快的资源,本地存储到后期可能容量紧张,迁移方案相对标准,MinIO比较简单,我在这上面获取的经验很顺畅。

源码里已经把基础的权限系统、用户行为采集和推荐逻辑都实现了,二次开发时不必在底层结构上多花时间,直接朝加功能的方向走就行。

8. 复盘:这套架构的亮点与不足

项目做完之后做一个复盘是很有价值的,既是对自己项目的一个整体审视,也能帮助后面准备面试或者做毕设答辩的朋友理清思路。这个系统的亮点和不足我诚实地说一下。

亮点方面,一是推荐逻辑覆盖了热度、个性化、相关性三个维度,让系统有了"智能感",不是简单堆CRUD;二是数据表结构设计考虑了推荐特征的计算需要,冗余字段合理,查询效率可控;三是前后端分工清晰,接口文档规范,联调顺畅;四是部署方案完整,从开发到上线全流程走通。

不足方面,一是个性化推荐逻辑比较轻量,严格来说还达不到"精准推荐"的水平,用户行为数据积累多了之后,推荐效果的上限受限于标签匹配的规则;二是没有做缓存层,用户量大以后推荐接口每次都会查数据库,可能需要引入Redis做热点缓存;三是测试数据量偏少,推荐逻辑在大数据量下的表现没有充分验证。

这些不足对毕业设计或练手项目来说不是致命问题,而且每一块恰恰是后续可以延伸的优化点。如果要把这个项目写进简历,建议在"优化方向"里把这些说清楚,并挑一个方向实际动手做掉,佐证自己的工程能力,效果会比只描述现有功能好很多。

做这个项目的过程中,我最大的体会是:前后端分离项目最忌讳一上来就埋头敲代码,功能闭环想清楚、表结构设计好、接口规划明确,代码写起来就是顺水推舟的事。推荐系统的部分尤其让我意识到,复杂的技术不一定适合所有项目,在合适的数据基础和业务规模下,简单清晰的规则有时反而能产生更好的效果。如果没有头绪,完全可以参考这个项目从零起步,从搭建环境到功能跑通,再到部署上线,每一步踩的坑都可能会变成你简历上比别的候选人多一点的经验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 9:10:55

PyCharm与WSL:定位真实Python环境的终极指南

写这篇的起因&#xff0c;是我在技术社区里被连续问了好几次同一个问题&#xff1a;“我明明在 PyCharm 里选了 WSL 的 Python 环境&#xff0c;怎么跑起来之后装的东西全不见了&#xff1f;”“PyCharm 里配置的那个 python.exe 到底是 Windows 的还是 WSL 的&#xff1f;”“…

作者头像 李华
网站建设 2026/10/3 9:08:11

PyQt5五子棋AI实战:α-β剪枝、评估函数与桌面应用全解析

简介&#xff1a;基于Python与PyQt5打造的“多智能体博弈AI五子棋游戏”毕业设计项目&#xff0c;核心涵盖人机对战、深度优先搜索&#xff08;DFS&#xff09;与α-β剪枝算法&#xff0c;通过完整工程展示了博弈树搜索在棋类AI中的实际应用。资源面向计算机类毕业设计、课程设…

作者头像 李华
网站建设 2026/10/3 9:08:08

WPF动画实战:从属性机制到MVVM与3D看板全解析

做WPF开发这几年&#xff0c;我对界面的判断标准慢慢从“能不能用”变成了“好不好用、有没有质感”。早期用WinForm写上位机&#xff0c;按钮按下去没有任何反馈&#xff0c;页面切到哪一步全靠猜&#xff1b;后来转WPF&#xff0c;发现它天生自带一套动画引擎&#xff0c;不用…

作者头像 李华
网站建设 2026/10/3 9:06:11

Canvas实战:随机撒100颗五角星的几何原理与动画实现

先讲个真实经历。有阵子我想给一个活动页面做星空氛围&#xff0c;产品君丢过来一句话&#xff1a;“撒点五角星上去&#xff0c;要动的那种。”我心想&#xff0c;五角星有啥难的&#xff0c;循环五个点连起来不就成了&#xff1f;结果画出来的东西连我自己都看不下去——有的…

作者头像 李华
网站建设 2026/10/3 9:06:11

网络故障排查实战:从DNS解析到TCP连接的完整定位链路

先说一个几乎所有做过线上故障处理的人都会遇到的场面&#xff1a;业务监控大屏飘红&#xff0c;研发群里有人喊“调不通了”&#xff0c;紧接着就有人接一句“是不是DNS挂了”&#xff0c;另一个人说“不对&#xff0c;感觉是TCP连不上”。然后&#xff0c;争论的人各自打开工…

作者头像 李华
网站建设 2026/10/3 9:05:51

手脚冰凉别硬扛:激活人体产热机制的科学方案

1. 为什么你总是手脚冰凉——先把人体产热机制弄清楚每年一到冬天&#xff0c;办公室里总有那么几个同事裹着羽绒服、抱着暖水袋&#xff0c;手指头还是冰得能戳出凉气。我身边很多人把原因归结为“体质虚”&#xff0c;其实事情没那么玄乎&#xff0c;身体觉得冷&#xff0c;本…

作者头像 李华