3小时从零搭建SpringBoot+Vue3博客系统:毕业设计实战指南
你有没有过这样的经历毕业设计选题定了导师说“做个博客系统吧要前后端分离技术栈新一点”你满口答应回到电脑前却一片茫然SpringBoot 听过Vue3 也了解但怎么把它们串起来做成一个能跑起来、能演示、能写进简历的完整项目网上教程要么太老还在用 Vue2要么太散前端讲一点后端讲一点部署的时候各种报错要么直接给个压缩包代码能跑但完全看不懂为什么这么写。更现实的是时间不等人。你可能只有几天甚至一个周末的时间需要从零搭出一个像样的、能进行增删改查的博客管理系统。你需要的不只是一个源码压缩包而是一条清晰的、被验证过的路径环境怎么配代码怎么写前后端怎么联调最后怎么打包部署成一个能完美运行的完整应用。这篇文章就是为你解决这个问题。我不会只扔给你一个源码链接然后说“自己研究”。我会带你用大约3小时的时间从零开始手把手搭建一个基于 SpringBoot 3.x 和 Vue 3 Element Plus 的博客管理系统。重点不是复刻某个复杂框架而是让你理解一个“最小可行产品”级别的博客系统是如何从无到有构建起来的并掌握其中每个关键环节的“为什么”——为什么选这些技术为什么这样设计接口为什么这样配置以及遇到坑了该怎么排查。最终你会得到一个结构清晰、代码可读、前后端分离、可完美运行的毕设项目更重要的是你会获得一套可复用的“从零搭建Web应用”的方法论。1. 为什么是 SpringBoot Vue3理解你的技术选型在开始敲代码之前我们必须先搞清楚为什么选择这个技术栈组合。这不仅仅是“因为火”或者“导师要求”而是基于效率、学习成本和项目需求的综合判断。1.1 SpringBoot后端的“快速启动器”对于毕业设计或者个人小项目来说后端框架的核心诉求是“快”和“省心”。你不想花大量时间在配置 XML、管理复杂的依赖冲突或者搭建基础结构上。开箱即用SpringBoot 的核心优势。它通过“约定大于配置”的理念和大量的 Starter 依赖让你几乎不需要做任何繁琐的配置就能启动一个 Web 服务。比如引入spring-boot-starter-web你就自动拥有了内嵌的 Tomcat 服务器和 MVC 基础配置。简化配置所有的配置都可以在一个application.yml或application.properties文件中完成从数据库连接、服务器端口到日志级别清晰集中。生态强大Spring Data JPA 或 MyBatis-Plus 用于数据库操作Spring Security 用于安全控制Spring Validation 用于参数校验……这些成熟的子项目能极大提升开发效率并且它们与 SpringBoot 的集成度非常高。易于部署最终可以打包成一个包含所有依赖的 Jar 文件用一句java -jar命令就能在任何有 Java 环境的机器上运行这对于演示和部署极其友好。对于毕设项目SpringBoot 能让你把精力集中在业务逻辑博客的增删改查上而不是底层技术细节。它提供了毕业设计所需的绝大部分后端功能且代码结构规范易于答辩时讲解。1.2 Vue3 Element Plus前端的“现代工作台”前端的选择同样基于效率和体验。Vue3 的组合式 API相比于 Vue2 的选项式 API组合式 API (script setup) 让逻辑组织更灵活特别是对于需要复用的逻辑比如获取文章列表可以抽离成独立的“组合函数”代码可读性和可维护性更好。这对于一个需要展示多个数据视图文章列表、文章详情、分类管理的博客后台来说是很大的优势。响应式系统升级Vue3 基于 Proxy 的响应式系统更高效能更好地处理复杂数据结构减少不必要的渲染。Element Plus这是基于 Vue3 的桌面端组件库。它提供了博客管理系统所需的一切基础组件表格、表单、对话框、分页、导航菜单、消息提示等。使用成熟的 UI 库能让你避免从零开始编写样式和交互快速搭建出美观且功能完善的管理界面把时间留给核心业务逻辑的实现。Vite 构建工具Vue3 项目通常使用 Vite 作为构建工具它的启动速度和热更新速度远超传统的 Webpack能带来更流畅的开发体验。前后端分离的核心价值在这个架构下前端Vue3项目和后端SpringBoot项目是完全独立的进程通过 HTTP API通常是 RESTful 风格进行通信。这样做的好处是职责清晰前端负责页面渲染和用户交互后端负责数据处理和业务逻辑。并行开发前后端开发者可以约定好 API 接口后同时开工。技术栈灵活前后端可以独立选择或升级技术栈。易于扩展未来可以独立扩展前端如开发移动端或后端服务。理解了“为什么”我们才能更有目的地进行“怎么做”。接下来我们进入实战准备阶段。2. 环境准备与项目初始化搭建你的工作台在开始编码前确保你的“工作台”是就绪的。这个环节的细致程度直接决定了后续开发过程是顺畅还是磕绊。2.1 开发环境清单请确保你的电脑上已经安装了以下软件并确认版本工具推荐版本作用验证命令JDK17 或 21SpringBoot 3.x 需要 JDK 17java -versionNode.js18.x 或 20.x LTS运行 Vue 和 npm 命令node -v,npm -vMaven3.6管理 SpringBoot 项目依赖和构建mvn -vIDEIntelliJ IDEA (社区版即可)后端开发-代码编辑器VS Code前端开发-MySQL8.0数据库mysql --versionGit最新版版本管理可选但推荐git --version注意版本兼容性很重要。SpringBoot 3.x 默认支持 JDK 17如果你用 JDK 8请使用 SpringBoot 2.x但本文以 3.x 为例。Node.js 建议使用 LTS长期支持版本以获得更好的稳定性。2.2 后端项目初始化 (SpringBoot)我们使用 Spring Initializr 来快速生成项目骨架这是最标准、最不容易出错的方式。在线生成访问 start.spring.io 。填写项目信息Project: MavenLanguage: JavaSpring Boot: 选择最新的稳定版如 3.2.xProject Metadata:Group:com.yourname(例如com.example)Artifact:blog-systemName:blog-systemPackaging:Jar(这是默认且推荐的)Java Version:17或21添加依赖在 Dependencies 区域点击“ADD DEPENDENCIES”添加以下关键依赖Spring Web: 构建 Web 应用包含 RESTful API 支持。Spring Data JPA: 简化数据库操作你也可以选 MyBatis-Plus但 JPA 对于快速原型更友好。MySQL Driver: 连接 MySQL 数据库。Lombok: 通过注解自动生成 Getter/Setter、构造方法等减少样板代码。强烈推荐生成项目点击“GENERATE”按钮下载压缩包并解压到你的工作目录。导入 IDE用 IntelliJ IDEA 打开解压后的文件夹。IDEA 会自动识别为 Maven 项目并开始下载依赖。2.3 前端项目初始化 (Vue3)我们将使用 Vue 官方的脚手架工具create-vue来创建项目它基于 Vite非常快速。打开终端CMD, PowerShell 或 Terminal。执行创建命令# 切换到你的工作目录例如 D:\Projects cd D:\Projects # 创建 Vue 项目项目名设为 blog-admin npm create vuelatest blog-admin交互式配置命令行会提示你进行一些选择对于我们的博客后台建议如下Add TypeScript?No(为了简化本文用 JavaScript你可以选 Yes)Add JSX Support?NoAdd Vue Router for Single Page Application?Yes(必须用于页面路由)Add Pinia for state management?Yes(推荐用于状态管理比 Vuex 更简单)Add Vitest for Unit Testing?No(毕设可暂不选)Add an End-to-End Testing Solution?NoAdd ESLint for code quality?Yes(推荐保持代码规范)进入项目并安装依赖cd blog-admin npm install安装 Element Plus这是我们的 UI 组件库。npm install element-plus --save安装 Axios用于向后端发送 HTTP 请求。npm install axios --save运行项目验证前端环境是否成功。npm run dev打开浏览器访问http://localhost:5173Vite 默认端口看到 Vue 欢迎页即表示成功。至此前后端两个空项目已经准备就绪。接下来我们要开始设计最核心的部分数据与接口。3. 核心设计与实现从数据库到 API 再到页面这是项目的“骨架”和“肌肉”部分。我们将按照数据库设计 - 后端实体与API - 前端页面与交互的顺序进行这是一个清晰的、分层实现的思路。3.1 数据库设计定义你的数据模型一个最简单的博客管理系统至少需要文章(Article)和分类(Category)两张表。我们设计如下1. 分类表 (blog_category)字段名类型说明idBIGINT, 主键自增分类IDnameVARCHAR(50), NOT NULL分类名称descriptionVARCHAR(200)分类描述create_timeDATETIME创建时间update_timeDATETIME更新时间2. 文章表 (blog_article)字段名类型说明idBIGINT, 主键自增文章IDtitleVARCHAR(100), NOT NULL文章标题contentLONGTEXT, NOT NULL文章内容富文本summaryVARCHAR(300)文章摘要category_idBIGINT, 外键关联分类IDthumbnailVARCHAR(500)封面图链接statusTINYINT, DEFAULT 1状态1发布0草稿view_countINT, DEFAULT 0浏览量create_timeDATETIME创建时间update_timeDATETIME更新时间在 MySQL 中创建数据库blog_db然后执行对应的建表 SQL 语句。3.2 后端实现SpringBoot 构建 RESTful API第一步配置数据库连接在src/main/resources/application.yml文件中配置spring: datasource: url: jdbc:mysql://localhost:3306/blog_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root # 你的数据库用户名 password: yourpassword # 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update # 启动时根据实体类自动更新表结构生产环境请改为 validate 或 none show-sql: true # 在控制台显示 SQL方便调试 properties: hibernate: format_sql: true # 格式化显示的 SQL第二步创建实体类 (Entity)对应数据库表使用 JPA 注解。// Article.java package com.example.blogsystem.entity; import jakarta.persistence.*; import lombok.Data; import java.time.LocalDateTime; Entity Data // Lombok 注解自动生成 getter/setter 等方法 Table(name blog_article) public class Article { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String title; Lob // 表示大文本字段 private String content; private String summary; ManyToOne // 多对一关系一篇文章属于一个分类 JoinColumn(name category_id) private Category category; private String thumbnail; private Integer status 1; // 默认发布状态 private Integer viewCount 0; private LocalDateTime createTime; private LocalDateTime updateTime; PrePersist // 在持久化插入前自动设置创建时间 protected void onCreate() { createTime LocalDateTime.now(); updateTime LocalDateTime.now(); } PreUpdate // 在更新前自动设置更新时间 protected void onUpdate() { updateTime LocalDateTime.now(); } }Category实体类类似并且需要定义与Article的OneToMany关系。第三步创建数据访问层 (Repository)Spring Data JPA 会为我们自动实现基础的增删改查方法。// ArticleRepository.java package com.example.blogsystem.repository; import com.example.blogsystem.entity.Article; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; Repository public interface ArticleRepository extends JpaRepositoryArticle, Long { // 可以在这里定义自定义查询方法例如根据标题模糊查找 // ListArticle findByTitleContaining(String keyword); }第四步创建业务逻辑层 (Service)在这里编写具体的业务逻辑。// ArticleService.java package com.example.blogsystem.service; import com.example.blogsystem.entity.Article; import com.example.blogsystem.repository.ArticleRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import java.util.Optional; Service public class ArticleService { Autowired private ArticleRepository articleRepository; public PageArticle findAll(Pageable pageable) { return articleRepository.findAll(pageable); } public OptionalArticle findById(Long id) { return articleRepository.findById(id); } public Article save(Article article) { return articleRepository.save(article); } public void deleteById(Long id) { articleRepository.deleteById(id); } }第五步创建控制器层 (Controller)提供 RESTful API 给前端调用。这是前后端对接的关键。// ArticleController.java package com.example.blogsystem.controller; import com.example.blogsystem.entity.Article; import com.example.blogsystem.service.ArticleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; import java.util.Optional; RestController RequestMapping(/api/articles) // API 路径前缀 CrossOrigin // 允许跨域请求方便前端开发时联调 public class ArticleController { Autowired private ArticleService articleService; // 获取文章列表分页 GetMapping public MapString, Object getArticles( RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { Pageable pageable PageRequest.of(page - 1, size, Sort.by(Sort.Direction.DESC, createTime)); PageArticle articlePage articleService.findAll(pageable); MapString, Object response new HashMap(); response.put(data, articlePage.getContent()); // 当前页数据 response.put(total, articlePage.getTotalElements()); // 总条数 response.put(pages, articlePage.getTotalPages()); // 总页数 response.put(current, page); // 当前页 return response; } // 根据ID获取单篇文章 GetMapping(/{id}) public Article getArticleById(PathVariable Long id) { OptionalArticle article articleService.findById(id); return article.orElse(null); // 简单处理实际应返回统一响应体或抛出异常 } // 创建新文章 PostMapping public Article createArticle(RequestBody Article article) { return articleService.save(article); } // 更新文章 PutMapping(/{id}) public Article updateArticle(PathVariable Long id, RequestBody Article article) { article.setId(id); // 确保ID一致 return articleService.save(article); } // 删除文章 DeleteMapping(/{id}) public void deleteArticle(PathVariable Long id) { articleService.deleteById(id); } }启动 SpringBoot 应用 (BlogSystemApplication的 main 方法)访问http://localhost:8080/api/articles你应该能看到一个空的 JSON 数组[]。这说明后端 API 已经就绪。3.3 前端实现Vue3 Element Plus 构建管理界面第一步配置全局 Axios 和 Element Plus在src/main.js中import { createApp } from vue import App from ./App.vue import router from ./router // 引入 Element Plus 及其样式 import ElementPlus from element-plus import element-plus/dist/index.css // 引入 Axios 并配置全局实例 import axios from axios const axiosInstance axios.create({ baseURL: http://localhost:8080/api, // 后端 API 基础地址 timeout: 5000 // 超时时间 }) const app createApp(App) app.use(router) app.use(ElementPlus) app.config.globalProperties.$axios axiosInstance // 挂载到全局方便使用 app.mount(#app)第二步创建文章列表页面在src/views目录下创建ArticleListView.vue。template div classarticle-list el-card template #header div classcard-header span文章管理/span el-button typeprimary clickhandleCreate新增文章/el-button /div /template !-- 搜索和筛选区域 -- el-form :inlinetrue :modelsearchForm el-form-item label标题 el-input v-modelsearchForm.title placeholder请输入标题关键词 clearable / /el-form-item el-form-item el-button typeprimary clickloadArticles搜索/el-button el-button clickresetSearch重置/el-button /el-form-item /el-form !-- 数据表格 -- el-table :datatableData border stylewidth: 100% el-table-column propid labelID width80 / el-table-column proptitle label标题 / el-table-column propcategory.name label分类 / el-table-column propstatus label状态 width100 template #defaultscope el-tag :typescope.row.status 1 ? success : info {{ scope.row.status 1 ? 已发布 : 草稿 }} /el-tag /template /el-table-column el-table-column propviewCount label浏览 width80 / el-table-column propcreateTime label创建时间 width180 / el-table-column label操作 width200 template #defaultscope el-button sizesmall clickhandleEdit(scope.row)编辑/el-button el-button sizesmall typedanger clickhandleDelete(scope.row.id)删除/el-button /template /el-table-column /el-table !-- 分页组件 -- div classpagination el-pagination v-model:current-pagecurrentPage v-model:page-sizepageSize :page-sizes[5, 10, 20, 50] :totaltotal layouttotal, sizes, prev, pager, next, jumper size-changehandleSizeChange current-changehandleCurrentChange / /div /el-card !-- 新增/编辑文章的对话框 -- ArticleEditDialog v-modeldialogVisible :form-datacurrentArticle successhandleDialogSuccess / /div /template script setup import { ref, onMounted } from vue import { ElMessage, ElMessageBox } from element-plus import ArticleEditDialog from /components/ArticleEditDialog.vue // 假设有这个组件 const tableData ref([]) const currentPage ref(1) const pageSize ref(10) const total ref(0) const searchForm ref({ title: }) const dialogVisible ref(false) const currentArticle ref({}) // 加载文章列表 const loadArticles async () { try { const response await $axios.get(/articles, { params: { page: currentPage.value, size: pageSize.value, title: searchForm.value.title // 可以传递给后端进行筛选 } }) tableData.value response.data.data total.value response.data.total } catch (error) { ElMessage.error(获取文章列表失败) console.error(error) } } // 处理分页大小变化 const handleSizeChange (val) { pageSize.value val currentPage.value 1 // 重置到第一页 loadArticles() } // 处理页码变化 const handleCurrentChange (val) { currentPage.value val loadArticles() } // 新增文章 const handleCreate () { currentArticle.value {} // 清空表单 dialogVisible.value true } // 编辑文章 const handleEdit (row) { currentArticle.value { ...row } // 浅拷贝当前行数据 dialogVisible.value true } // 删除文章 const handleDelete (id) { ElMessageBox.confirm(确定要删除这篇文章吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(async () { try { await $axios.delete(/articles/${id}) ElMessage.success(删除成功) loadArticles() // 刷新列表 } catch (error) { ElMessage.error(删除失败) } }).catch(() {}) } // 对话框操作成功后的回调 const handleDialogSuccess () { dialogVisible.value false loadArticles() // 刷新列表 } // 重置搜索 const resetSearch () { searchForm.value { title: } loadArticles() } // 组件挂载时加载数据 onMounted(() { loadArticles() }) /script style scoped .card-header { display: flex; justify-content: space-between; align-items: center; } .pagination { margin-top: 20px; display: flex; justify-content: flex-end; } /style第三步创建文章编辑对话框组件在src/components目录下创建ArticleEditDialog.vue。这个组件负责新增和编辑文章的表单。由于代码较长核心是包含标题、分类下拉选择、内容富文本编辑器、摘要、封面图等字段的表单并使用$axios.post或$axios.put调用后端的创建/更新接口。第四步配置路由在src/router/index.js中将文章列表页面添加到路由中。import { createRouter, createWebHistory } from vue-router import ArticleListView from ../views/ArticleListView.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, component: ArticleListView // 将文章列表设为首页 }, // ... 其他路由 ] })现在启动前端 (npm run dev) 和后端你就能在浏览器中看到一个具备增删改查功能的博客文章管理界面了。通过这个过程你不仅实现了功能更重要的是理解了前后端如何通过 RESTful API 协同工作。4. 联调、部署与避坑指南让项目真正跑起来完成了核心功能但要让项目成为一个“可完美运行”的毕设作品还需要打通最后几公里前后端联调、打包部署以及解决那些几乎一定会遇到的坑。4.1 前后端联调解决跨域问题在开发阶段前端运行在localhost:5173后端运行在localhost:8080浏览器会因为同源策略而阻止前端请求后端 API这就是跨域问题。解决方案按推荐度排序后端配置 CORS推荐我们在ArticleController上加了CrossOrigin注解这是一种简单的全局配置。更规范的配置是在 SpringBoot 中创建一个配置类Configuration public class WebConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/api/**) // 针对所有 /api 开头的路径 .allowedOrigins(http://localhost:5173) // 允许的前端地址 .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true); } }使用开发代理Vite 提供了代理功能可以在前端配置将 API 请求转发到后端。在vite.config.js中export default defineConfig({ // ... 其他配置 server: { proxy: { /api: { target: http://localhost:8080, // 后端地址 changeOrigin: true, // rewrite: (path) path.replace(/^\/api/, ) // 如果需要重写路径 } } } })配置后前端代码中请求/api/articles会被 Vite 转发到http://localhost:8080/api/articles从而避免跨域。注意此配置仅用于开发环境。4.2 项目打包与部署后端打包 (SpringBoot):在项目根目录下执行 Maven 打包命令mvn clean package -DskipTests成功后会在target目录下生成一个blog-system-0.0.1-SNAPSHOT.jar文件。这个 Jar 包是可执行的包含了所有依赖和内置的 Tomcat 服务器。后端运行java -jar target/blog-system-0.0.1-SNAPSHOT.jar应用会启动默认监听 8080 端口。你可以通过application.yml修改server.port来改变端口。前端打包 (Vue3):npm run build这个命令会在项目根目录下生成一个dist文件夹里面是优化和压缩后的静态文件HTML, CSS, JS。前端部署前端是静态资源有多种部署方式放到后端静态资源目录将dist文件夹里的所有内容复制到 SpringBoot 项目的src/main/resources/static目录下然后重新打包后端 Jar。这样访问http://localhost:8080就能直接看到前端页面。最简单适合演示使用独立的 Web 服务器如 Nginx。将dist文件夹放到 Nginx 的 HTML 目录下并配置 Nginx 将 API 请求反向代理到后端 SpringBoot 服务。这是生产环境的常见做法。使用云服务商的对象存储和 CDN如阿里云 OSS、腾讯云 COS。4.3 常见问题与排查指南即使跟着教程你也可能会遇到问题。以下是几个高频问题及排查思路前端页面空白控制台报错Failed to fetch或Network Error检查后端服务是否成功启动控制台有无报错检查前端请求的 URL 是否正确axios的baseURL是否配置为后端地址如http://localhost:8080/api检查跨域问题是否解决查看浏览器 Network 面板请求是否被阻塞CORS错误确保后端已正确配置 CORS 或前端开发代理生效。后端启动失败报错java.net.ConnectException: Connection refused或数据库相关错误检查MySQL 服务是否启动application.yml中的数据库连接信息地址、端口、用户名、密码、数据库名是否正确检查MySQL 用户是否有远程连接权限如果地址不是 localhost数据库blog_db是否已创建前端能收到数据但表格不显示或显示异常检查前端axios请求返回的数据结构是否与代码中response.data.data的路径匹配在浏览器开发者工具的 Network 面板查看实际返回的 JSON 结构。检查el-table的:data绑定的变量名是否正确tableData.value是否成功赋值检查el-table-column的prop属性是否与返回数据对象的字段名完全一致大小写敏感新增或编辑文章时后端收到null值或报错400 Bad Request检查前端发送请求时Content-Type是否为application/jsonaxios默认会设置。检查后端 Controller 方法的参数前是否加了RequestBody注解来接收 JSON 数据检查前端传递的 JSON 对象结构是否与后端Article实体类属性匹配特别是日期格式、关联对象如category只传id即可。通用排查顺序遇到问题不要慌。按以下顺序排查看日志后端控制台和浏览器控制台Console 和 Network 标签是最重要的信息来源。定边界是前端问题JS错误、网络请求失败还是后端问题启动失败、接口500错误查数据对于接口问题用 Postman 或浏览器直接访问后端 API 地址看能否返回正确数据。这能快速定位是前端请求问题还是后端逻辑问题。对配置仔细核对所有配置项数据库连接、服务器端口、API路径、依赖版本。遵循这个从环境准备、核心实现到联调部署的完整路径你不仅能得到一个可运行的博客系统更能深刻理解一个现代 Web 应用是如何被构建和整合的。这个过程中积累的排查问题和整合技术栈的经验远比单纯复制代码更有价值也是你简历上和答辩中最能体现个人能力的部分。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →