ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SpringBoot+Vue全栈实战:高校学生饮食推荐系统设计与部署

SpringBoot+Vue全栈实战:高校学生饮食推荐系统设计与部署 又到毕业设计旺季每年这个时候都能看到大量相似的问题“老师SpringBootVue的项目怎么做”“前后端分离到底怎么联调”“毕设系统跑不起来怎么办”正好手里有一套刚整理完的高校学生饮食推荐系统源码和部署文档趁这个机会把整个项目的设计思路、核心实现、部署流程一次说明白。这套系统是个非常典型的全栈实战项目后端SpringBoot MyBatis MySQL前端Vue Element UI前后端通过RESTful API通信。功能上围绕“学生吃什么”这个场景展开包含用户登录注册、菜品分类浏览、饮食偏好推荐、收藏管理、健康档案等模块。不管你是在准备毕业设计还是想练手前后端分离项目的完整开发流程这套代码都值得精读一遍。文章后面会从架构设计、表结构、接口实现、前后端联调到服务器部署、常见报错排查一步步拆开讲尽量把每个“为什么这样做”都解释清楚方便你直接照着复现也能举一反三改造成自己的项目。1. 项目整体设计与思路拆解1.1 核心需求学生饮食推荐到底在推荐什么决定做这套系统之前我先把需求捋了一遍。所谓“高校学生饮食推荐”本质上不是一个算法问题而是一个基于条件筛选的推荐问题一个学生身处某个校园或者某个校区有口味偏好辣或不辣、有忌口不吃香菜、清真、有预算区间每顿10-15元还是15-20元系统要给出附近食堂或商户里符合条件的菜品。所以推荐引擎不需要搞协同过滤也不需要上深度学习模型核心就是一个多条件组合查询最多加一个简单的评分排序。想明白这一点项目难度就立刻降下来了技术上完全可控业务上又能讲出完整的故事。这也给想拿这个题目做毕设的同学提了个醒不要为了炫技把系统做复杂把基础CRUD、用户体系、推荐逻辑、后台管理做扎实答辨时比什么都管用。另一个容易被忽略的需求是“后台管理”。学生端能看到推荐、收藏、个人中心但管理员需要管理菜品信息、分类、用户状态、统计数据。所以系统天然要拆成两个端客户端学生用和管理端运营/管理员用。这两个端共用同一套后端API只是角色权限不同。1.2 前后端分离架构为什么是这个项目的最优解选型上后端SpringBoot、前端Vue这套组合在高校项目里已经是事实标准了。原因很简单SpringBoot让Java后端开发变得极其轻量内嵌Tomcat意味着不用单独装Tomcat服务器打一个jar包就能跑Vue则用组件化写法把页面拆成一个个独立单元多人协同开发时互不干扰而且生态里有Element UI这种现成的组件库后台管理界面几小时就能搭出来。前后端分离的核心特征是前端只负责渲染和交互后端只负责提供JSON数据接口。两者通过HTTP协议通信开发阶段前端用Node开发服务器通过代理转发请求到后端避免跨域问题生产阶段前端打包成静态文件交给Nginx托管再通过反向代理把/api路径下的请求转发到后端服务。这个架构最大的好处是“解耦”。后端的接口写好了前端可以并行开发前端页面写好了后端可以Mock数据联调。如果哪一天你想把Vue换成React只要接口协议不变后端完全不用动。相比传统的Thymeleaf服务端渲染前后端分离在部署一致性、横向扩展、多端适配比如以后要出小程序上都有明显优势。1.3 功能模块划分与角色权限整个系统的功能模块我按用户角色来划分一共三类角色模块学生端管理员端登录注册账号密码登录、注册管理员账号登录菜品浏览按分类浏览、关键词搜索菜品CRUD、上下架饮食推荐按口味/预算/忌口筛选推荐推荐规则配置健康档案身高体重、过敏原、偏好标签查看统计数据收藏管理添加/取消收藏、收藏列表无评价管理对菜品评分评论删除违规评论数据统计浏览记录菜品热度、用户量、分类占比权限控制上后端通过SpringMVC拦截器校验登录状态再通过角色字段区分管理员接口。前端则通过Vue Router的导航守卫控制页面访问未登录跳转登录页非管理员跳转403页。2. 核心技术与数据库设计深度解析2.1 SpringBoot MyBatis MySQL这套组合的关键点后端技术栈选SpringBoot 2.x MyBatis MySQL 5.7/8.0是最稳妥的搭配。SpringBoot负责web层、依赖注入、事务管理MyBatis负责数据持久化MySQL负责存储。选MyBatis而不是JPA/Hibernate核心考量是SQL可控性高、学习曲线平缓、网上资料多对毕设和初学者非常友好。MyBatis有几个细节值得注意。第一个是一级缓存它默认开启作用范围是SqlSession。在同一个SqlSession里执行相同的SQL第二次会直接命中缓存不查数据库。这个特性在大部分场景是好事但如果你在同一个Session里先查后改再查同一行数据就可能读到脏数据。解决办法是在更新操作后手动调用sqlSession.clearCache()或者干脆使用Spring管理的事务每个请求独立Session一级缓存的生命周期就很短问题不明显。第二个是二级缓存作用范围是Mapper Namespace默认关闭。在毕设项目里我建议开启尤其像菜品分类这种很少变更的数据开启后能大幅减少数据库查询压力。配置方式很简单在Mapper XML里加cache/标签然后实体类实现Serializable接口。但要注意开启二级缓存后如果关联表发生了更新缓存不会自动失效容易出现脏数据。我的做法是只给字典类、分类类这种低频更新表开启业务主表一律不开。第三个是分页插件PageHelper这是MyBatis生态里最常用的插件之一。它底层是通过MyBatis拦截器实现的拦截Executor的query方法自动拼接LIMIT分页语句。配置时有一个常见的坑PageHelper在使用时必须紧跟第一条查询语句中间不能插入其他SQL操作否则分页会失效。比如下面这段代码就有问题// 错误示例 PageHelper.startPage(pageNum, pageSize); User user userMapper.selectById(1); // 这条查询会被分页拦下来 ListFood foods foodMapper.selectList(query); // 这条反而没有被拦截2.2 Vue Element UI前端架构设计前端项目我用Vue CLI创建Vue 2.x版本配合Element UI组件库。选Vue 2而不是Vue 3主要是考虑到Element UI对Vue 2的支持最成熟网上踩坑记录多出问题好查。如果你熟悉Composition API也可以换成Vue 3 Element Plus接口逻辑代码差异不大。前端代码目录结构按照业务模块划分src/ ├─ api/ # 封装所有后端接口请求 │ ├─ user.js │ ├─ food.js │ ├─ recommend.js │ └─ collection.js ├─ assets/ # 静态资源 ├─ components/ # 公共组件分页、上传、富文本等 ├─ router/ # 路由配置 ├─ store/ # Vuex状态管理用户信息、全局状态 ├─ views/ # 页面组件 │ ├─ student/ │ │ ├─ Home.vue │ │ ├─ FoodList.vue │ │ ├─ Recommend.vue │ │ └─ Profile.vue │ └─ admin/ │ ├─ DashBoard.vue │ ├─ FoodManage.vue │ └─ UserManage.vue └─ utils/ # 工具函数如request.js封装axiosutils/request.js里统一封装Axios实例拦截器里做三件事请求头加token、响应拦截统一处理错误码、401跳转登录页。这样每个接口函数只需要关注业务逻辑不需要重复写错误处理代码。前端页面设计上学生端首页要考虑“推荐感”——大图Banner、今日推荐卡片流、按标签筛选的快捷入口。管理端则要突出表格和表单的效率大量使用el-table和el-dialog组件。2.3 数据库表结构设计实战数据库命名我用canteen_db核心表设计如下-- 用户表 CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(255) NOT NULL COMMENT BCrypt加密后的密码, nickname VARCHAR(50) DEFAULT NULL, role TINYINT DEFAULT 0 COMMENT 0-学生 1-管理员, avatar VARCHAR(255) DEFAULT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 菜品表 CREATE TABLE food ( id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, category_id INT NOT NULL, price DECIMAL(10,2) NOT NULL, image VARCHAR(255) DEFAULT NULL, description TEXT, spicy_level TINYINT DEFAULT 0 COMMENT 0-不辣 1-微辣 2-中辣 3-重辣, taste_tags VARCHAR(255) DEFAULT NULL COMMENT 口味标签逗号分隔, status TINYINT DEFAULT 1 COMMENT 1-上架 0-下架, sales_count INT DEFAULT 0, rating DECIMAL(2,1) DEFAULT 5.0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 健康档案表 CREATE TABLE health_profile ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, height DECIMAL(5,1) DEFAULT NULL, weight DECIMAL(5,1) DEFAULT NULL, allergens VARCHAR(255) DEFAULT NULL COMMENT 过敏原, preferred_tags VARCHAR(255) DEFAULT NULL COMMENT 偏好标签, budget_min DECIMAL(10,2) DEFAULT NULL, budget_max DECIMAL(10,2) DEFAULT NULL, PRIMARY KEY (id), UNIQUE KEY uk_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 收藏表 CREATE TABLE favorite ( id INT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL, food_id INT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_user_food (user_id, food_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;强调两个设计细节密码字段我用VARCHAR(255)因为存的是BCrypt哈希串不是明文密码所有表都加create_time字段方便后期做时间维度统计。spicy_level用TINYINT而不是VARCHAR这样推荐筛选时直接做数值区间判断索引效率更高。3. 实操过程与核心模块实现3.1 环境搭建本地开发必装的工具清单开始敲代码之前先把环境准备好。我列一个完整的清单每个都给出版本参考工具版本建议用途JDK1.8运行SpringBoot项目Maven3.6.x后端依赖管理MySQL5.7或8.0数据库Node.js14.x或16.x前端构建环境npm/yarn随Node附带前端依赖管理IDEA2020后端开发IDEVSCode最新版前端开发IDEMySQL安装时有几个坑必须提前说第一个是安装8.0后连接报Public Key Retrieval is not allowed需要在JDBC连接串上加allowPublicKeyRetrievaltrue第二个是时区问题报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized在连接串加serverTimezoneAsia/Shanghai。这两个问题基本是每个用MySQL 8.0的SpringBoot项目都会遇到的后面排查章节还会详细讲。后端项目用IDEA直连创建Spring Initializr工程选Spring Web、MyBatis、MySQL Driver依赖。如果网络不稳创建失败可以直接在pom.xml里手动添加dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.2.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency前端项目创建则简单得多npm install -g vue/cli vue create canteen-frontend选默认预设即可创建完cd canteen-frontend npm run serve能启动就说明环境OK。3.2 后端核心接口实现推荐模块的SQL与业务逻辑后端重点讲推荐接口。前端传参包括分类ID、价格区间、辣度、偏好标签、关键词。对应后端Service层做组合条件查询。先看Mapper接口定义public interface FoodMapper { ListFood selectRecommendList(Param(query) FoodQuery query); ListFood selectHotFoods(Param(limit) Integer limit); Food selectDetail(Param(id) Integer id); }XML里核心SQL长这样select idselectRecommendList resultTypecom.example.entity.Food SELECT f.*, c.name AS categoryName FROM food f LEFT JOIN category c ON f.category_id c.id where f.status 1 if testquery.categoryId ! null AND f.category_id #{query.categoryId} /if if testquery.minPrice ! null AND f.price gt; #{query.minPrice} /if if testquery.maxPrice ! null AND f.price lt; #{query.maxPrice} /if if testquery.spicyLevel ! null AND f.spicy_level lt; #{query.spicyLevel} /if if testquery.tags ! null and query.tags.size() 0 AND EXISTS ( SELECT 1 FROM food_tag ft WHERE ft.food_id f.id AND ft.tag IN foreach collectionquery.tags itemtag open( separator, close) #{tag} /foreach ) /if if testquery.keyword ! null and query.keyword ! AND f.name LIKE CONCAT(%, #{query.keyword}, %) /if /where ORDER BY f.rating DESC, f.sales_count DESC /select这个SQL的关键在于动态where标签它会在条件满足时自动拼SQL片段并且自动去除多余的AND前缀。价格、辣度、标签、关键词全部可空实现了一个方法适配多种推荐场景。推荐模块还接入了用户健康档案如果用户在档案中保存了过敏原比如花生、海鲜查询时会额外加一个条件AND f.id NOT IN (SELECT food_id FROM food_allergen WHERE allergen IN (...))。这个过敏原排除逻辑用子查询实现虽然性能不是最优但在数据量几千条的场景下完全够用。为了让推荐结果不那么死板我在Service层加了两种策略轮换默认按评分排序但30%概率会加入一个随机因子让推荐结果有一定惊喜感避免用户每次都看到同样的菜。这个做法技术上实现很简单但答辨的时候可以讲成“基于探索与利用思想”显得很有想法。3.3 前端页面与接口联调从Mock到真实数据前端页面开发阶段建议先按后端接口文档定义好api目录下的方法然后用Mock数据把页面写完。比如api/food.jsimport request from /utils/request export function getRecommendList(params) { return request({ url: /api/food/recommend, method: get, params }) } export function getFoodDetail(id) { return request({ url: /api/food/${id}, method: get }) } export function addFavorite(foodId) { return request({ url: /api/favorite/add, method: post, data: { foodId } }) }这时候后端还没开发完接口不存在页面一请求就报404。解决办法是在vue.config.js里配一个devServer的before中间件模拟几个核心接口返回Mock数据。等后端接口写完了删掉before函数换成真正的proxy代理代码不用动一行联调就完成了。实际联调时会遇到跨域问题开发阶段最好的解法就是vue.config.js里的代理配置module.exports { devServer: { port: 8088, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: /api } } } } }这里的原理是前端开发服务器运行在8088端口浏览器访问/api/food/recommend时请求先发给8088的Node服务器Node服务器再转发给8080的Java后端。因为浏览器只与8088通信不存在跨域自然就绕过了浏览器的同源策略限制。生产环境则把这一层代理交给Nginx实现逻辑完全一样。前端页面组件开发我推荐一个技巧按“页面-卡片-弹窗”三层拆分组件。页面负责数据请求和状态管理卡片负责展示弹窗负责表单交互。比如菜品列表页FoodList.vue负责调用getRecommendList()存数据然后循环渲染food-card组件编辑菜品时弹出food-form-dialog提交后刷新列表。这样每个组件都职责单一调试时能快速定位问题。3.4 登录鉴权与会话管理Token方案落地登录模块我用JWTJSON Web Token实现无状态认证。思路是用户登录成功后后端生成一个Token返回给前端前端把Token存在Vuex和localStorage里后续每次请求在Header里带上Authorization: Bearer token后端拦截器校验Token合法性从中解析出用户ID和角色。后端Token生成和校验的核心代码Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire}) private Long expire; public String generateToken(Integer userId, String role) { Date now new Date(); Date expireDate new Date(now.getTime() expire * 1000); return Jwts.builder() .setHeaderParam(typ, JWT) .setSubject(String.valueOf(userId)) .claim(role, role) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } }拦截器里通过HandlerInterceptor统一做Token校验配置在WebMvcConfigurer中指定addPathPatterns(/api/**)和excludePathPatterns(/api/login, /api/register, /api/food/list)。注意放行某个接口时要确认这个接口是否需要登录才能访问。比如菜品列表页通常允许未登录用户浏览但收藏接口必须登录。前端路由守卫配合router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next(/login) } else if (to.meta.requiresAdmin localStorage.getItem(role) ! admin) { next(/403) } else { next() } })4. 部署上线全流程从零到公网可访问部署是整个项目最后一道坎也是评审老师最喜欢追问的环节。我这里给出一套完整的Linux服务器部署方案适用于阿里云、腾讯云等主流云服务器系统镜像选CentOS 7或Ubuntu 20.04都可以。4.1 前端构建与Nginx静态资源托管前端部署第一步是构建。在项目根目录执行npm install npm run build构建完成后项目目录下会生成dist文件夹里面是html、css、js等静态资源。接下来在服务器上安装Nginxyum install nginx # CentOS # 或 apt install nginx # UbuntuNginx配置的核心是静态托管加反向代理server { listen 80; server_name your_domain.com; # 前端静态资源 location / { root /usr/share/nginx/html/canteen; index index.html; try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里有两个必坑点。第一个是try_files $uri $uri/ /index.html;这行必须加因为Vue是单页应用路由是前端history模式用户直接访问/food/1或刷新页面时Nginx要去后端找这个路径找不到就404加上这行后所有路径都会回到index.html由Vue Router自己解析路由。我补充一个部署Linux服务器不装的坑Vue是history模式下如果刷新404首先要确认是不是try_files配置正确如果确认了还没生效执行nginx -t检查配置语法然后nginx -s reload重启不要改完配置忘了重启就以为代码有问题。第二个是location /api/的proxy_pass。注意http://127.0.0.1:8080结尾不带斜杠这样请求/api/food/recommend会被转发成http://127.0.0.1:8080/api/food/recommend跟后端接口路径完全一致。如果proxy_pass写成http://127.0.0.1:8080/那么请求会变成http://127.0.0.1:8080/food/recommend丢了/api前缀后端就匹配不到控制器直接404。把dist内容复制到Nginx指定目录后还要检查一个权限问题。我遇到过很多次静态文件权限是root:root且权限是700Nginx的worker进程属主是nginx用户导致读不到文件浏览器访问时白屏或403。稳妥的做法是chown -R nginx:nginx /usr/share/nginx/html/canteen chmod -R 755 /usr/share/nginx/html/canteen4.2 后端Jar包构建与进程守护后端部署相对简单。本地在IDEA的Terminal或者直接用命令行mvn clean package -DskipTests构建成功后target目录下会出现一个xxx.jar文件。把这个文件上传到服务器的/opt/canteen目录。后端运行前先确认Java环境java -version如果没装JavaCentOS执行yum install -y java-1.8.0-openjdkUbuntu执行apt install -y openjdk-8-jdk。然后启动cd /opt/canteen nohup java -jar canteen-server.jar --server.port8080 canteen.log 21 解释一下这条命令nohup让进程不受终端退出影响让它在后台运行日志输出到canteen.log。之后查看启动日志用tail -f canteen.log看到Started Application in xx seconds就说明启动成功了。有一个常见错误jar包启动时报端口被占用。排查方法netstat -tlnp | grep 8080如果有进程占用了8080端口要么换端口启动要么先干掉占用进程kill -9 PID但这里有个坑直接kill可能导致数据库连接没释放。更优雅的方式是kill -15SIGTERM信号让SpringBoot做优雅停机等几秒再确认进程是否退出。生产环境部署一个小技巧用ps aux \| grep java查看进程PID用kill -15关闭不要上来就kill -9否则可能产生数据不一致的问题。为了管理方便可以写一个启动脚本start.sh#!/bin/bash APP_NAMEcanteen-server.jar APP_HOME/opt/canteen cd $APP_HOME # 如果旧进程还在先退出 ps aux | grep $APP_NAME | grep -v grep | awk {print $2} | xargs kill -15 sleep 3 # 启动新进程 nohup java -Xms256m -Xmx512m -jar $APP_NAME --spring.profiles.activeprod $APP_HOME/canteen.log 21 echo Application started.4.3 数据库初始化与服务器配置注意事项在服务器上创建数据库并导入SQLmysql -uroot -p CREATE DATABASE canteen_db DEFAULT CHARACTER SET utf8mb4; exit; mysql -uroot -p canteen_db canteen_db.sql导入完成后要检查后端application-prod.yml里的数据库连接配置必须正确spring: datasource: url: jdbc:mysql://127.0.0.1:3306/canteen_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver这里连接串里的allowPublicKeyRetrievaltrue是MySQL 8.0必须要加的参数不加会报Public Key Retrieval is not allowed错误。serverTimezoneAsia/Shanghai解决时区问题。还有服务器安全组/防火墙检查。如果你的服务器是云服务器控制台里的安全组规则必须放行80端口Nginx和8080端口后端直接访问时用。如果安全组没放行外网永远访问不到但本地curl是通的这个坑特别容易忽视。检查放行的命令curl http://127.0.0.1:8080/api/food/list curl http://127.0.0.1/ # 本机Nginx如果本机能通、外网不通第一顺序排查云服务器安全组。然后检查系统防火墙systemctl status firewalld # 或 systemctl status ufw开发阶段图省事可以直接关闭防火墙但生产环境更推荐只放行需要的端口firewall-cmd --zonepublic --add-port80/tcp --permanent firewall-cmd --zonepublic --add-port8080/tcp --permanent firewall-cmd --reload4.4 部署后的验证清单部署完成后我习惯按顺序执行一组自检命令检查项命令/方法预期结果后端进程存活ps aux | grep java能找到java进程后端日志无异常tail -n 50 canteen.log无Exception/Error数据库连接正常调用登录接口返回JSON而非500前端页面可访问浏览器访问http://公网IP页面正常展示静态资源加载F12看NetworkCSS/JS无404后端接口代理F12看接口请求/api路径响应200刷新页面刷新首页/任意子页面不出现4045. 常见问题与排查技巧实录5.1 高频报错速查表下面这些报错是我在开发、部署、帮助学生调试过程中遇到最多的问题整理成表格遇到直接对照排查报错信息原因解决方案Whitelabel Error Page后端接口500或404看canteen.log定位具体异常Public Key Retrieval is not allowedMySQL 8.0认证插件问题JDBC连接串加allowPublicKeyRetrievaltrueThe server time zone value ... unrecognizedMySQL时区问题JDBC连接串加serverTimezoneAsia/ShanghaiAccess denied for user rootlocalhost数据库密码错误或权限不足检查用户名密码授权GRANT ALL ON *.* TO root%Failed to configure a DataSource后端包启动时找不到数据源检查application.yml配置和依赖是否完整npm ERR! code ERESOLVE前端依赖版本冲突删node_modules和package-lock.json重新npm installCORS policy报错跨域配置缺失开发用devServer代理生产用Nginx反向代理Cannot find module vue前端依赖没装全或版本问题npm install后重试确认Vue版本对齐前端正常但接口404Nginx代理配置问题检查proxy_pass是否丢失/api前缀刷新页面404Nginx缺少try_files配置在location /中添加try_files $uri $uri/ /index.html;Java进程启动后立刻退出端口冲突或配置错误tail -f canteen.log看失败原因java.sql.SQLException: Unknown database数据库没创建或名字不对执行CREATE DATABASE canteen_db;5.2 MyBatis缓存引发的脏数据问题我在测试阶段遇到过一个问题菜品价格更新后前端列表页还是显示旧价格刷新也没用。排查后发现是MyBatis二级缓存在捣鬼。当时对food表开启了二级缓存但管理员更新菜品后没有执行缓存清除导致查询结果一直命中旧的缓存数据。解决方案有两个层面代码层面在更新操作后手动调用SqlSession.clearCache()或者在Mapper XML里把更新语句的flushCache属性设为true默认就是true但前提是缓存配置正确走了同一个Mapper的Namespace设计层面对于更新频繁的表不建议开二级缓存只保留一级缓存即可。我这里建议毕设项目默认不开MyBatis二级缓存。上面说了那么多二级缓存在单机单应用架构下收益很小却引入了一堆缓存一致性问题。把优化工作放到SQL和索引上走正常查询一百个项目里有九十九个不需要二级缓存。5.3 前后端分离项目的“最后一次部署”教训最后分享一个亲身经历。有一次帮一个学生把系统部署到服务器前端构建没问题后端启动也没问题数据库连接也通但浏览器访问后页面白屏控制台显示所有CSS/JS都加载失败。我排查了半天发现问题出在publicPath配置上。Vue CLI默认的publicPath是/构建后的index.html里引用的静态资源路径是/js/app.js。如果前端静态文件部署在域名根目录比如http://ip/这样没问题但如果部署在子路径比如http://ip/canteen/路径就错了应该改成// vue.config.js module.exports { publicPath: process.env.NODE_ENV production ? /canteen/ : / }或者更通用点直接用相对路径publicPath: ./这样构建后的资源路径就是相对路径不管部署到哪个子目录都能正确加载。但也带来一个副作用Vue Router的history模式下的刷新又容易变成404。所以我的建议是有域名、部署在根目录用publicPath: /加try_files没域名、IP加子路径部署用publicPath: ./路由尽量用hash模式。先想清楚部署方式再定配置能省掉大量来回折腾的时间。结尾这套系统从我最初画草图到最终可部署运行中间来回打磨了将近一个月。回头来看真正有价值的不是那几行推荐SQL也不是Vue组件怎么写而是把一个模糊的“做个饮食推荐系统”的想法一步步拆解成功能清单、数据库表、接口文档、页面原型再变成看得见摸得着的产品。这种从0到1的完整链路就是全栈项目最磨人也是最锻炼人的地方。如果你想拿这套项目做毕业设计我有几个建议第一不要急着敲代码先把表结构设计好表设计合理了整个项目就成功了一半第二推荐功能哪怕只用条件筛选也要把筛选条件做细答辩时从健康档案、预算约束、口味偏好这些角度切入比单纯说“我用了协同过滤算法”更有说服力第三提前把部署环境在本地虚拟机里模拟一遍真正部署到服务器时才能不慌。如果你在复现过程中遇到问题按照上面的排查表一项项对照大部分都能解决。这套项目的完整源码、数据库脚本和部署文档我都已经整理到一个压缩包里了你拿到后从环境准备开始走一遍流程跑通之后再根据自己的需求改功能、换样式就是我们常说的“先模仿再创造”。毕竟编程这东西代码量是练出来的经验是踩坑踩出来的。祝你的系统早日上线也祝你答辩顺利。
返回列表