SpringBoot+Vue美食网站项目实战:从后端配置到前端部署全解析 简介基于SpringBootVueBootstrap的美食网站毕业设计源码面向计算机专业本科/专科毕业生及前后端全栈初学者解决选题难、动手实践不足、项目代码不规范等问题。后端采用SpringBoot结合MySQL前端使用Vue与Bootstrap包含用户登录、商品展示、购物车、订单管理等模块整体界面精美、功能完整。压缩包共204个文件以83个Java后端类、32个HTML页面、18个JavaScript脚本、15个XML配置和14个CSS样式为主另附SQL数据库脚本、图片字体等静态资源整包仅3.1MB目录结构清晰便于按模块检索学习。项目提供可直接运行的源码与数据库脚本内置管理员账号部署即可使用。已有1583人学习下载适合用于课程设计、毕业答辩或快速搭建Web全栈项目参考。1. 一套能直接跑起来的SpringBootVue美食网站先看它解决了什么很多准备SpringBoot面试题的人或被课程设计催到起火的同学拿到一个美食网站项目的第一反应是双击启动类结果数据库没建、端口被占用、前端页面白屏。这套毕业设计把SpringBoot后端和VueBootstrap前端放在同一个工程里前端静态页面由SpringBoot直接托管访问http://localhost:8888/front/index.html就能看到首页后台账号是admin/123456。对想快速交付毕设的人来说它最大的价值不是代码量而是把前后端分离后的联调成本压到了最低对准备SpringBoot和Vue面试的人来说它展示了Controller、Mapper、页面静态资源这三层到底是怎么粘在一起的。拿到代码先别急着启动从端口、数据库表、登录态三个关键点依次拆才能真正看懂这个项目。2. SpringBoot后端从启动入口到数据库映射先理清项目骨架2.1 工程结构与启动入口后端部分不是那种几百个类堆在一起的老毕设比较常见的分包方式是controller/service/mapper/entity四层。打开src/main/java目录后先找到带SpringBootApplication的启动类一般是FoodApplication.java这才是整个SpringBoot应用的入口。SpringBootApplication MapperScan(com.example.food.mapper) public class FoodApplication { public static void main(String[] args) { SpringApplication.run(FoodApplication.class, args); } }这里有两个容易漏掉的点。第一MapperScan必须指向Mapper接口所在包如果漏写启动时会报Field mapper in ... required a bean of type ...。第二如果使用IDEA直接启动要把启动类放在com.example.food根包下否则SpringBoot扫描不到Controller和Service。第一次跑项目时我习惯先看控制台有没有打印端口号SpringBoot默认8080但美食网站这里用了8888说明在application.yml里改过server.port后面需要记住这个端口前端静态页面会跟它保持一致。2.2 application.yml端口、数据源、MyBatis的关键配置SpringBoot的一个核心优势就是约定优于配置但毕业设计项目里的application.yml往往是坑最多的地方。拿这套美食网站来说至少要确认下面的配置项都和你本地环境对得上。server: port: 8888 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/food_website?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 20MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这段配置里最值得注意的不是port而是数据库连接参数。serverTimezoneAsia/Shanghai是MySQL 8.x连接时必须加的不加会报时区错误characterEncodingutf8负责中文编码美食网站里的菜品名称、描述都靠它。Mapper映射文件路径classpath*:mapper/**/*.xml告诉MyBatis到哪里找XML如果路径配错运行时会报Invalid bound statement (not found)。map-underscore-to-camel-case设为true后数据库的category_id才能自动映射到Java实体类的categoryId比较容易忽略。如果你的项目用的是原生MyBatis而不是MyBatis-Plus把mybatis-plus换成mybatis前缀就行比如mybatis.mapper-locations。参数对照关系可以看下面这个表。配置项作用常见问题server.port后端服务端口被占用时启动失败spring.datasource.url数据库连接地址数据库名不一致、缺少时区参数mapper-locationsXML映射文件路径路径不对导致Mapper方法找不到SQLmap-underscore-to-camel-case下划线转驼峰关闭时categoryId取不到值log-impl输出SQL日志不配置则控制台看不到SQL2.3 实体类与数据库表映射美食网站的核心表是菜品表通常叫dish或者food。建表语句在项目里的sql目录下执行完再看实体类会更清楚。CREATE TABLE dish ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 菜品名称, category_id int(11) DEFAULT NULL COMMENT 分类ID, price decimal(10,2) DEFAULT NULL COMMENT 价格, image varchar(255) DEFAULT NULL COMMENT 图片路径, status tinyint(1) DEFAULT 1 COMMENT 上架状态, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;对应的实体类写法是Data public class Dish { private Integer id; private String name; private Integer categoryId; private BigDecimal price; private String image; private Integer status; private LocalDateTime createTime; }我一般会把Data注解用起来省去手写getter和setter。这里有个细节实体类叫Dish但数据库表叫dishMyBatis-Plus默认也能对上如果你用原生MyBatis需要在XML里写resultMap。美食网站的图片字段存的是相对路径比如/images/dish/xxx.jpg后面后端托管静态资源时会映射到对应目录。2.4 Controller与Service调用链从前端页面发请求最终落到数据库中间的调用链是Controller - Service - Mapper。以菜品列表为例Controller层负责接收HTTP参数。RestController RequestMapping(/api/dish) public class DishController { Autowired private DishService dishService; GetMapping(/list) public Result list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { return Result.success(dishService.pageDish(page, size)); } }RestController表示这个类里的方法返回值直接序列化成JSONRequestMapping(/api/dish)定义了统一前缀。RequestParam(defaultValue 1)的意思是前端如果没传page参数就默认取第1页。这里的Result是项目自己封装的统一返回体一般包含code、message、data三个字段前端通过res.data.data拿列表数据。再往下是Service接口和实现类这里不贴完整代码只说关键点分页查询时不要直接在Controller里写new QueryWrapper()把查询条件封装在Service层代码结构更干净。Controller返回给前端的数据中image字段可以拼接成完整URL也可以让前端自己拼取决于资源路径怎么配。3. VueBootstrap前端页面结构与接口调用的实际写法3.1 前端为什么是静态资源而不是独立工程打开项目的前端目录看到的是一堆CSS和JS文件bootstrap.min.css、font-awesome.css、quill.snow.css、swiper.min.css、basic.css、my.css、custom.css。这套美食网站的前端不是用npm create vue初始化的工程而是把Vue和Bootstrap通过CDN或本地文件引入页面写在src/main/resources/static/front/下由SpringBoot直接当作静态资源托管。这样做的最大好处是不需要额外启动一个Node服务只要后端在运行访问localhost:8888/front/index.html就能看到页面。但这个模式在前后端分离的面试题里经常被追问Vue是CDN方式引入没有组件编译过程所以不能用.vue单文件组件所有模板都要写在HTML里。如果你的毕设要求必须用Vue CLI脚手架那这个项目的改法是把静态页面里的HTML拆成App.vue和子组件再通过axios请求后端接口整体思路不变。CSS文件里同时出现了quill.snow.css和swiper.min.css说明后台菜品编辑用到了富文本编辑器首页轮播用到了Swiper。custom.css一般是覆盖Bootstrap默认样式的地方比如调整导航栏高度和首页卡片阴影。3.2 页面加载顺序和Vue实例初始化前端页面主要靠一个全局Vue实例控制数据渲染。以首页为例简化后的HTML结构是!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title美食网站首页/title link relstylesheet hrefcss/bootstrap.min.css link relstylesheet hrefcss/font-awesome.css link relstylesheet hrefcss/swiper.min.css link relstylesheet hrefcss/custom.css /head body div idapp div classcontainer div classrow div classcol-md-3 v-foritem in dishList :keyitem.id div classcard mb-3 img :srcitem.image classcard-img-top alt菜品 div classcard-body h5 classcard-title{{ item.name }}/h5 p classcard-text text-danger¥{{ item.price }}/p button classbtn btn-primary btn-sm clickaddCart(item)加入购物车/button /div /div /div /div /div /div script srcjs/vue.min.js/script script srcjs/axios.min.js/script script var app new Vue({ el: #app, data: { dishList: [] }, mounted: function () { this.loadDish(); }, methods: { loadDish: function () { var self this; axios.get(/api/dish/list, { params: { page: 1, size: 12 } }).then(function (response) { self.dishList response.data.data.records || []; }).catch(function (error) { console.error(error); }); }, addCart: function (item) { // 实际项目里这里会把item加入购物车 } } }); /script /body /html这段代码里有两个新手容易踩的坑。第一CSS和JS的路径要写成相对路径css/...和js/...因为浏览器当前URL是/front/index.html它会自动去/front/css/目录下找如果写成/static/front/css/...反而会404。第二在黑洞里等着吞注释的地方axios.get(/api/dish/list)的绝对路径是相对于当前域名和端口的也就是http://localhost:8888正好匹配SpringBoot的后端端口。如果这里写死http://localhost:8080换到别人的电脑上就会跨域或404。3.3 用Bootstrap组件搭出美食后台表格美食网站一般分前台展示和后台管理两部分。后台页面最常用的是表格组件Bootstrap的table类组合起来非常快。table classtable table-bordered table-hover thead tr thID/th th菜品名称/th th分类/th th价格/th th状态/th th操作/th /tr /thead tbody tr v-foritem in dishList :keyitem.id td{{ item.id }}/td td{{ item.name }}/td td{{ item.categoryId }}/td td{{ item.price }}/td td span classbadge :classitem.status 1 ? badge-success : badge-secondary {{ item.status 1 ? 上架 : 下架 }} /span /td td button classbtn btn-sm btn-primary clickeditDish(item)编辑/button button classbtn btn-sm btn-danger clickdeleteDish(item)删除/button /td /tr /tbody /tabletable-bordered加边框table-hover在鼠标悬停时变色badge用来做状态标签。很多介绍Bootstrap入门头歌实训的教程会让你死记这些类名但在实际项目里我更建议先把v-for渲染调通再决定用哪种表格样式。btn-sm配合btn-danger让删除按钮不显得笨重这套组合是我在后台管理页里用得最频繁的。表格和卡片是Bootstrap的两大基础组件前者负责数据管理后者负责前台展示。3.4 Swiper轮播与Bootstrap轮播的选择swiper.min.css出现在项目里通常是为了让首页主图轮播更顺滑。Swiper的初始化代码很短new Swiper(.swiper-container, { loop: true, autoplay: { delay: 3000, disableOnInteraction: false }, pagination: { el: .swiper-pagination, clickable: true } });初始化Swiper要确保DOM已经渲染完成最好放在mounted钩子里。loop: true让轮播可以无限循环delay: 3000是3秒切换一次。如果只是做一个简单BannerBootstrap自带的carousel slide也够用而且不需要额外引入Swiper。这个项目两种CSS都放了说明可能有的页面用Swiper有的页面用Bootstrap轮播。实际使用中我只会留一种否则样式冲突的时候难以排查是bootstrap.min.css的优先级问题还是Swiper覆盖了类名。4. 本地跑通环境版本、建库和踩坑记录4.1 JDK、Maven、Node版本选择在本地运行这套美食网站环境变量建议这样配。工具推荐版本备注JDK1.8 或 11SpringBoot 2.x推荐1.8SpringBoot 3.x需要JDK17Maven3.6.3以上IDEA内置Maven也可以MySQL5.7 或 8.08.0需要加driver和时区参数Node.js不需要前端是静态资源无独立构建过程网上很多SpringBoot项目一上来就提示SpringBoot版本太高本质是JDK版本和SpringBoot版本不匹配。如果你下载的源码是SpringBoot 2.7.x用JDK 8最省事如果是SpringBoot 3.xJDK 8跑不起来。项目里没有package.json说明不需要执行npm install对不熟悉vue安装及环境配置的人来说是个优势但也意味着不能享受组件热更新。4.2 数据库初始化与账号密码配置建库和导入表结构的命令很简单但要注意在sql目录下找到对应的.sql文件。mysql -u root -p -e CREATE DATABASE IF NOT EXISTS food_website DEFAULT CHARACTER SET utf8mb4; mysql -u root -p food_website sql/food_website.sql第一条命令创建数据库第二条命令把表结构和初始数据导入。执行完以后打开application.yml把username和password改成自己本机的MySQL账号。我遇到过很多人卡在Access denied for user不是密码错了而是使用了MySQL 8的caching_sha2_password插件但项目里的JDBC驱动版本太低。解决办法是把驱动升到mysql-connector-java8.0.x以上并在URL里加serverTimezoneAsia/Shanghai。4.3 静态资源访问与跨域处理如果按照原项目的方式前端页面由SpringBoot托管就不存在跨域问题。但如果非要额外部署一个前端服务比如把front目录放到Nginx里然后用http://localhost:3000访问就必须在后端配置跨域。Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) .allowedOrigins(http://localhost:3000) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS); } }addMapping(/api/**)限定只有/api开头的接口允许跨域allowedMethods把前端用到的HTTP方法都列出来。毕业设计里为了省事有人会写allowedOrigins(*)但在浏览器安全和面试评分上都不太好。如果项目里已经有一个WebMvcConfigurer的实现类就把这段合并进去不要重复定义配置类。4.4 常见报错与排查跑SpringBoot项目时错误信息其实已经把线索给得很明显关键是别急着改代码。这里是我在实际排错中经常遇到的问题。报错信息原因排查方法Access denied for user rootlocalhost数据库账号密码错误检查application.yml先用命令行连一下Unknown database food_website数据库没建执行建库SQLInvalid bound statement (not found)Mapper XML没扫描到检查mapper-locations路径Port 8888 was already in use端口被占用Windowsnetstat -anoLinuxlsof -i:8888Whitelabel Error Page接口404确认Controller前缀和前端请求路径一致端口占用可以用下面的命令快速找到进程。lsof -i:8888 kill -9 PID在Windows上使用netstat -ano | findstr 8888然后把最后一列PID对应的进程结束掉。这些细节看起来很基础但SpringBoot面试题里经常从“如何排查端口冲突”展开。另一个高频问题是在idea 创建springboot项目超时时生成了不完整的pom文件导致依赖下载不全这时候要把Maven仓库里的.lastUpdated后缀文件清掉再重新reimport。5. 给毕设加分的小技巧自动建表、SQL日志与登录拦截5.1 用Spring Boot的SQL初始化自动建表为了避免别人拿到项目后还要手动执行SQL可以在application.yml里配置自动建表。spring: sql: init: schema-locations: classpath:sql/schema.sql >logging: level: com.example.food.mapper: debug这条配置只把Mapper包下的日志级别设为debug不要直接全局debug不然日志量会大到刷屏。MyBatis-Plus还可以把log-impl设成StdOutImpl会在控制台输出带参数值的SQL但对生产环境不友好。这个技巧在本地调试时能省不少时间。5.3 用拦截器保护后台接口前台页面可以直接访问但后台的菜品管理、订单管理这些接口要先登录。用SpringBoot的HandlerInterceptor做最简单。public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getSession().getAttribute(admin) null) { response.setStatus(401); return false; } return true; } }再把它注册到WebMvcConfigurer里。registry.addInterceptor(new LoginInterceptor()) .addPathPatterns(/api/admin/**) .excludePathPatterns(/api/login);addPathPatterns(/api/admin/**)表示只有管理端接口需要校验/api/login是登录接口必须放行。前端在axios响应拦截器里判断status 401然后跳转到登录页。这种方案的优点是代码量少适合毕设缺点是不能细粒度控制每个角色的权限。如果要在SpringBoot面试题里把权限方案讲得更深可以接Spring Security但对一个美食网站来说拦截器已经完全够用。把拦截器注册好之后访问/api/admin/dish/list时就会先检查Session未登录的请求直接返回401前端用window.location.replace(/front/login.html)完成跳转整个登录链路就闭合了。本文还有配套的精品资源点击获取