Spring Boot与Vue 3全栈博客系统开发实战:从零搭建前后端分离项目

📅 2026/7/4 11:39:08
Spring Boot与Vue 3全栈博客系统开发实战:从零搭建前后端分离项目
30款热门AI模型一站整合DeepSeek/GLM/Claude 随心用限时 5 折。 点击领海量免费额度在实际企业级应用开发中Spring Boot 与 Vue 3 的组合已成为构建前后端分离项目的首选技术栈之一。这种架构模式清晰地将后端业务逻辑与前端用户界面解耦使得团队可以并行开发、独立部署并利用各自生态的优势。然而对于许多初学者或希望深入理解全链路开发的开发者而言从零开始搭建一个功能完整的博客管理系统会面临诸多挑战如何设计合理的数据库表结构如何配置 Spring Boot 与 Vue 3 的集成环境如何实现前后端的数据交互与状态管理以及如何将项目打包部署到生产环境这些问题往往需要跨越多个技术领域将零散的知识点串联成一个可运行、可维护的完整项目。本文将以一个博客管理项目为蓝本详细拆解从项目初始化到核心功能实现的每一步。我们将使用 Spring Boot 2.7.x 作为后端框架Vue 3 (Composition API) 配合 Vite 作为前端构建工具MySQL 作为数据存储。文章不仅会提供可运行的代码片段更会解释每一步背后的设计考量、配置原理以及开发中常见的“坑”和解决方案。无论你是希望巩固全栈技能还是为毕业设计或实际项目寻找参考这篇手把手教程都将为你提供一个清晰的实现路径。1. 项目整体架构与技术选型解析在动手编码之前理解项目的整体架构和每个技术组件扮演的角色至关重要。这有助于你在后续开发中做出正确的技术决策并在遇到问题时能快速定位。1.1 前后端分离架构的优势与挑战传统的单体应用将前端页面如 JSP、Thymeleaf与后端代码打包在一起部署简单但耦合度高不利于前后端独立演进和技术栈升级。前后端分离架构则不同后端 (Spring Boot)专注于提供 RESTful API。它负责业务逻辑处理、数据持久化通过 MyBatis-Plus、用户认证授权如 Spring Security、数据校验等。它不关心页面如何渲染只通过 JSON 格式与前端通信。前端 (Vue 3)专注于构建用户界面。它通过 Axios 等 HTTP 客户端调用后端 API 获取数据利用 Vue Router 管理页面路由使用 Pinia 或 Vuex 进行状态管理并将数据渲染到由 Element Plus 等 UI 库构建的组件中。这种架构的挑战在于需要协调两个独立项目的开发、联调、跨域请求以及最终的协同部署。我们将通过配置解决这些问题。1.2 核心依赖清单与版本说明版本兼容性是项目能否顺利启动的关键。以下是我们项目主要依赖的版本建议在开始前确认你的环境与之匹配。组件名称版本主要作用后端Spring Boot2.7.18 (LTS)提供核心框架、自动配置、内嵌Web服务器MyBatis-Plus3.5.x增强的 MyBatis 框架简化 CRUD 操作MySQL Driver8.0.x连接 MySQL 数据库Lombok1.18.x通过注解简化 Java Bean 的编写如 getter/setterSpring Boot Starter Validation2.7.x提供参数校验功能前端Node.js 16.0.0JavaScript 运行时环境Vue3.3.x前端核心框架Vite4.4.x下一代前端构建工具提供极速的开发服务器Vue Router4.2.x官方路由管理器Pinia2.1.xVue 官方推荐的状态管理库Element Plus2.3.x基于 Vue 3 的桌面端组件库Axios1.4.x基于 Promise 的 HTTP 客户端注意Spring Boot 2.7.x 是一个长期支持版本与 Java 8、11、17 兼容良好。避免使用过新的 Spring Boot 3.x因为它要求最低 Java 17且部分依赖的配置方式有较大变化可能增加学习成本。2. 后端工程Spring Boot 项目搭建与核心配置我们将从后端开始因为后端定义了数据的结构和业务的规则前端的工作很大程度上依赖于后端的 API 设计。2.1 使用 IDEA 创建 Spring Boot 项目打开 IntelliJ IDEA选择File-New-Project。在左侧选择Spring Initializr。确保Project SDK是你安装的 Java 版本如 Java 11。填写项目元数据Group:com.example(通常使用公司域名倒写)Artifact:blog-backendType:MavenLanguage:JavaPackaging:JarJava Version:11点击Next进入依赖选择页面。在这里勾选我们需要的起步依赖Spring Web(构建 Web 应用包含 RESTful 支持)MyBatis Framework(或稍后手动引入 MyBatis-Plus)MySQL DriverLombokValidation点击Next选择项目存储位置然后点击Finish。项目创建完成后IDEA 会自动下载依赖并构建项目结构。2.2 手动添加 MyBatis-Plus 依赖并配置Spring Initializr 可能没有 MyBatis-Plus 的选项我们需要手动修改pom.xml文件。打开blog-backend/pom.xml在dependencies部分添加 MyBatis-Plus 的 starter 依赖并移除或注释掉之前选择的MyBatis Framework依赖。dependencies !-- Spring Boot Web -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- MyBatis-Plus 起步依赖 (替换 MyBatis Framework) -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency !-- MySQL 驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency !-- Lombok -- dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency !-- Validation -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency !-- 测试 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies配置数据库连接。打开src/main/resources/application.yml(如果没有则创建它比.properties文件更易读)。server: port: 8080 # 后端服务端口 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/blog_db?useUnicodetruecharacterEncodingutf-8serverTimezoneAsia/Shanghai username: root password: your_password # 替换为你的数据库密码 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 在控制台打印 SQL 语句便于调试 global-config: db-config: id-type: auto # 主键策略数据库自增 logic-delete-field: deleted # 全局逻辑删除字段名如果表中有此字段 logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值关键解释server.port: 后端 API 服务运行的端口前端将通过这个端口访问后端。spring.datasource.url: 连接字符串中的blog_db是数据库名需要提前在 MySQL 中创建。serverTimezone参数对于避免时区问题至关重要。mybatis-plus.configuration.log-impl: 开发阶段开启 SQL 日志可以清晰看到 MyBatis-Plus 生成的 SQL是排查问题的利器。global-config.db-config: 配置了逻辑删除这是一种“软删除”数据并未从数据库物理移除只是标记为删除状态有利于数据恢复和审计。在 MySQL 中创建数据库和用户表示例。CREATE DATABASE IF NOT EXISTS blog_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE blog_db; CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 主键ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(255) NOT NULL COMMENT 密码加密后, nickname varchar(50) DEFAULT NULL COMMENT 昵称, email varchar(100) DEFAULT NULL COMMENT 邮箱, avatar varchar(500) DEFAULT NULL COMMENT 头像URL, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, deleted tinyint(1) DEFAULT 0 COMMENT 逻辑删除标志0-未删除1-已删除, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT用户表;2.3 创建实体类、Mapper 与 ServiceMyBatis-Plus 的强大之处在于其通用 CRUD 接口我们只需极少的代码即可实现基础数据操作。创建实体类User.java。这个类对应数据库中的user表。package com.example.blogbackend.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; Data TableName(user) // 指定对应的数据库表名如果类名和表名遵循驼峰转下划线规则可省略 public class User { TableId(type IdType.AUTO) // 主键自增 private Long id; private String username; private String password; private String nickname; private String email; private String avatar; TableField(fill FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; TableLogic // 标识逻辑删除字段 private Integer deleted; }创建 Mapper 接口UserMapper.java。继承 MyBatis-Plus 的BaseMapper即可获得全套 CRUD 方法。package com.example.blogbackend.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.blogbackend.entity.User; import org.apache.ibatis.annotations.Mapper; Mapper // 让 Spring 管理此接口并生成代理实现类 public interface UserMapper extends BaseMapperUser { // 无需编写任何方法BaseMapper已提供了 insert, selectById, updateById, deleteById 等 }创建 Service 接口及其实现。Service 层封装业务逻辑。// UserService.java (接口) package com.example.blogbackend.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.blogbackend.entity.User; public interface UserService extends IServiceUser { // 可以在此定义特殊的业务方法例如根据用户名查找用户 User getByUsername(String username); }// UserServiceImpl.java (实现类) package com.example.blogbackend.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.blogbackend.entity.User; import com.example.blogbackend.mapper.UserMapper; import com.example.blogbackend.service.UserService; import org.springframework.stereotype.Service; Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { Override public User getByUsername(String username) { // 使用 MyBatis-Plus 的 QueryWrapper 构建查询条件 return this.lambdaQuery() .eq(User::getUsername, username) .one(); // 查询一条记录 } }2.4 实现一个简单的 RESTful 控制器现在让我们创建一个控制器来暴露一个获取用户列表的 API。package com.example.blogbackend.controller; import com.example.blogbackend.entity.User; import com.example.blogbackend.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; GetMapping(/list) public ListUser listUsers() { // 调用 Service 的 list() 方法查询所有未逻辑删除的用户 return userService.list(); } }2.5 解决跨域问题由于前端项目运行在另一个端口如localhost:5173浏览器出于安全考虑会阻止跨域请求。我们需要在后端配置 CORS (跨源资源共享)。创建一个配置类WebConfig.javapackage com.example.blogbackend.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 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) // 允许的 HTTP 方法 .allowedHeaders(*) // 允许所有请求头 .allowCredentials(true); // 允许携带 Cookie 等凭证 } }至此一个最简单的 Spring Boot 后端项目已经完成。运行BlogBackendApplication主类访问http://localhost:8080/api/user/list你应该能看到一个空的 JSON 数组[]因为数据库还没有数据。接下来我们转向前端。3. 前端工程Vue 3 Vite Element Plus 项目搭建前端项目将使用 Vue 3 的 Composition API 和script setup语法这是当前最推荐的做法代码更简洁逻辑更清晰。3.1 使用 Vite 创建 Vue 3 项目在命令行中进入你希望创建前端项目的目录执行以下命令# 使用 npm确保已安装 Node.js npm create vuelatest blog-frontend # 执行后会进入一个交互式配置流程 # 项目名称可以直接回车默认 blog-frontend # 是否添加 TypeScript? 根据需求选择本教程选 No # 是否添加 JSX 支持? 选 No # 是否添加 Vue Router? 选 Yes (单页应用必需) # 是否添加 Pinia? 选 Yes (状态管理) # 是否添加 Vitest? 选 No (单元测试可选) # 是否添加 ESLint? 选 Yes (代码规范) # 是否添加 Prettier? 选 Yes (代码格式化)创建完成后进入项目目录并安装依赖cd blog-frontend npm install3.2 安装 Element Plus 和 AxiosElement Plus 是功能丰富的 UI 组件库Axios 用于发起 HTTP 请求。npm install element-plus axios3.3 配置 Element Plus 和 Axios全局引入 Element Plus。修改src/main.js(或src/main.ts) 文件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 // 创建应用实例 const app createApp(App) // 使用路由和 Element Plus app.use(router) app.use(ElementPlus) // 挂载应用 app.mount(#app)创建并配置 Axios 实例。在src目录下创建utils/request.js文件import axios from axios // 创建 axios 实例 const request axios.create({ baseURL: http://localhost:8080, // 后端 API 的基础地址 timeout: 5000 // 请求超时时间 }) // 请求拦截器 request.interceptors.request.use( config { // 在发送请求之前做些什么例如添加 token // const token localStorage.getItem(token) // if (token) { // config.headers.Authorization Bearer ${token} // } return config }, error { // 对请求错误做些什么 return Promise.reject(error) } ) // 响应拦截器 request.interceptors.response.use( response { // 对响应数据做点什么 return response.data }, error { // 对响应错误做点什么例如统一处理 401、403、500 等错误 console.error(请求错误:, error.response?.status, error.message) return Promise.reject(error) } ) export default request3.4 创建用户列表页面并调用后端 API创建页面组件。在src/views目录下创建UserListView.vue。template div classuser-list-container h2用户列表/h2 el-table :datauserList stylewidth: 100% stripe border el-table-column propid labelID width80/el-table-column el-table-column propusername label用户名/el-table-column el-table-column propnickname label昵称/el-table-column el-table-column propemail label邮箱/el-table-column el-table-column propcreateTime label创建时间 template #defaultscope {{ formatDate(scope.row.createTime) }} /template /el-table-column el-table-column label操作 width180 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 /template script setup import { ref, onMounted } from vue import request from /utils/request // 引入配置好的 axios 实例 import { ElMessage, ElMessageBox } from element-plus // 响应式数据用户列表 const userList ref([]) // 生命周期钩子组件挂载后加载数据 onMounted(() { fetchUserList() }) // 方法获取用户列表 const fetchUserList async () { try { const response await request.get(/api/user/list) userList.value response // 因为配置了响应拦截器response 直接是 data } catch (error) { ElMessage.error(获取用户列表失败 error.message) } } // 方法格式化日期 const formatDate (dateString) { if (!dateString) return const date new Date(dateString) return date.toLocaleString(zh-CN) } // 方法处理编辑 const handleEdit (row) { ElMessage.info(编辑用户 ${row.username}) // 实际项目中这里应该跳转到编辑页面或打开编辑对话框 } // 方法处理删除 const handleDelete (id) { ElMessageBox.confirm(确定要删除此用户吗, 提示, { confirmButtonText: 确定, cancelButtonText: 取消, type: warning }).then(async () { try { // 注意这里调用的是后端的删除接口我们尚未实现 // await request.delete(/api/user/${id}) ElMessage.success(删除成功演示) fetchUserList() // 重新加载列表 } catch (error) { ElMessage.error(删除失败 error.message) } }).catch(() { // 用户点击了取消 }) } /script style scoped .user-list-container { padding: 20px; } /style配置路由。修改src/router/index.js将新页面添加到路由中。import { createRouter, createWebHistory } from vue-router import UserListView from ../views/UserListView.vue const router createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: /, name: home, component: () import(../views/HomeView.vue) // 假设的首页 }, { path: /users, name: users, component: UserListView // 用户列表页 } ] }) export default router修改导航菜单。为了能访问到新页面可以简单修改src/App.vue添加一个导航链接。template div idapp nav router-link to/首页/router-link | router-link to/users用户管理/router-link /nav router-view / /div /template3.5 启动前端项目并验证在blog-frontend目录下运行npm run devVite 开发服务器会启动通常在http://localhost:5173。访问该地址点击“用户管理”链接如果后端服务也在运行 (localhost:8080)页面应该能成功加载并显示用户列表目前为空。你可以在数据库user表中手动插入几条测试数据刷新页面即可看到效果。4. 核心功能扩展与联调实战一个博客系统远不止用户管理。让我们继续实现博客文章Article的核心 CRUD 功能并完成前后端联调。4.1 后端文章实体、Mapper、Service 与 Controller创建数据库表CREATE TABLE article ( id bigint NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 文章标题, content longtext COMMENT 文章内容Markdown格式, summary varchar(500) DEFAULT NULL COMMENT 文章摘要, cover_image varchar(500) DEFAULT NULL COMMENT 封面图URL, author_id bigint NOT NULL COMMENT 作者ID, category_id bigint DEFAULT NULL COMMENT 分类ID, status tinyint DEFAULT 0 COMMENT 状态0-草稿1-发布, view_count int DEFAULT 0 COMMENT 浏览量, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted tinyint(1) DEFAULT 0, PRIMARY KEY (id), KEY idx_author_id (author_id), KEY idx_category_id (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci COMMENT文章表;创建实体类Article.javapackage com.example.blogbackend.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; Data TableName(article) public class Article { TableId(type IdType.AUTO) private Long id; private String title; private String content; private String summary; private String coverImage; private Long authorId; private Long categoryId; private Integer status; private Integer viewCount; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; TableLogic private Integer deleted; }创建 MapperArticleMapper.java和ServiceArticleService.java/ArticleServiceImpl.java模式与User类似。创建控制器ArticleController.java实现增删改查和条件分页查询。package com.example.blogbackend.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.example.blogbackend.entity.Article; import com.example.blogbackend.service.ArticleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.util.List; RestController RequestMapping(/api/article) Validated public class ArticleController { Autowired private ArticleService articleService; // 新增文章 PostMapping public boolean saveArticle(Valid RequestBody Article article) { // 实际项目中authorId 应从当前登录用户上下文中获取 article.setAuthorId(1L); // 模拟作者ID return articleService.save(article); } // 根据ID删除文章逻辑删除 DeleteMapping(/{id}) public boolean deleteArticle(PathVariable Long id) { return articleService.removeById(id); } // 根据ID更新文章 PutMapping public boolean updateArticle(Valid RequestBody Article article) { return articleService.updateById(article); } // 根据ID查询文章详情 GetMapping(/{id}) public Article getArticleById(PathVariable Long id) { return articleService.getById(id); } // 分页条件查询文章列表 GetMapping(/page) public IPageArticle getArticlePage( RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String title, RequestParam(required false) Integer status) { PageArticle page new Page(pageNum, pageSize); LambdaQueryWrapperArticle wrapper new LambdaQueryWrapper(); wrapper.like(title ! null !title.isEmpty(), Article::getTitle, title); wrapper.eq(status ! null, Article::getStatus, status); wrapper.orderByDesc(Article::getCreateTime); // 按创建时间倒序 return articleService.page(page, wrapper); } }4.2 前端文章管理页面与 API 调用创建文章列表页面ArticleListView.vue包含查询表单和分页表格。创建文章编辑/新增组件ArticleEdit.vue使用 Element Plus 的Dialog和表单。在request.js中统一处理请求和响应例如添加加载状态、错误提示。使用 Pinia 管理文章列表状态避免组件间频繁传递 props。由于篇幅限制这里不展开全部前端代码但核心思路是使用el-table和el-pagination展示分页数据。使用el-form和el-dialog进行文章的创建和编辑。通过封装好的request调用后端的/api/article/page、/api/article等接口。利用 Pinia Store 集中管理文章列表数据、分页参数和加载状态使状态在多个组件间共享和同步。4.3 联调关键点与常见问题排查前后端联调是项目开发中最易出错的环节。以下是一些关键检查点和常见问题问题现象可能原因检查方式解决方案前端控制台报错Network Error或CORS错误1. 后端服务未启动。2. 后端端口被占用。3. CORS 配置不正确。1. 检查后端控制台是否启动成功。2. 访问http://localhost:8080/api/user/list看是否返回 JSON。3. 查看浏览器开发者工具 Network 面板看请求是否被 Blocked。1. 启动后端服务。2. 修改application.yml中的server.port。3. 检查WebConfig中的allowedOrigins是否包含前端地址。请求返回 4041. 请求 URL 路径错误。2. 后端 Controller 的RequestMapping路径不匹配。1. 核对前端request.js中的baseURL和具体请求路径。2. 核对后端 Controller 类和方法上的注解路径。修正 URL 路径。确保后端有对应的GetMapping、PostMapping等。请求返回 400 (Bad Request)1. 前端传递的参数格式错误如 JSON 格式不对。2. 后端实体类字段与前端传参名不匹配。3.Valid校验失败。1. 查看浏览器 Network 面板检查Request Payload。2. 查看后端控制台日志是否有绑定异常或校验错误信息。1. 确保前端传递的是正确的 JSON 对象。2. 使用JsonProperty或保持字段名一致。3. 根据后端校验注解调整前端输入。请求返回 500 (Internal Server Error)后端代码运行时异常如空指针、SQL 错误。查看后端控制台打印的完整异常堆栈信息。根据堆栈信息定位代码行修复 BUG。通常是 Service 或 Mapper 层的问题。前端页面空白或组件不显示1. Vue 组件引入或注册错误。2. 路由配置错误。3. JS 语法错误导致编译失败。1. 检查浏览器控制台 Console 面板是否有 JS 错误。2. 检查 Vue Devtools 扩展看组件树是否正确。1. 修正 JS 语法错误。2. 检查组件路径和export default。3. 检查路由path和component配置。排查顺序建议遇到联调问题首先看浏览器控制台的报错网络错误、JS错误然后看后端控制台的日志SQL、异常信息最后结合两者分析。善用console.log和System.out.println进行调试。5. 项目打包与部署准备开发完成后需要将项目构建成可部署的产物。5.1 后端 Spring Boot 项目打包Spring Boot 使用 Maven 可以很方便地打包成可执行的 JAR 文件。确保pom.xml中有 Spring Boot 打包插件通常创建项目时已自动添加build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build在项目根目录blog-backend下执行 Maven 打包命令# 清理并打包跳过测试 mvn clean package -DskipTests打包成功后在target目录下会生成blog-backend-0.0.1-SNAPSHOT.jar文件。这个 JAR 包包含了所有依赖和嵌入式 Tomcat 服务器。运行 JAR 包java -jar target/blog-backend-0.0.1-SNAPSHOT.jar你可以通过修改application.yml或使用命令行参数来指定生产环境的配置例如java -jar blog-backend-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod然后创建一个application-prod.yml文件来配置生产环境的数据库连接等。5.2 前端 Vue 项目打包Vite 项目打包后生成的是静态资源HTML、CSS、JS。在blog-frontend目录下执行构建命令npm run build构建完成后会在项目根目录生成dist文件夹里面包含了所有优化和压缩后的静态文件。部署前端静态资源你可以将dist文件夹内的所有文件放到 Nginx、Apache 等 Web 服务器的目录下。上传到对象存储如阿里云 OSS、腾讯云 COS并配置 CDN。使用 Spring Boot 的静态资源服务不推荐用于生产通常前后端独立部署。5.3 生产环境部署注意事项数据库使用独立的 MySQL 实例配置强密码并考虑主从复制、定期备份。后端配置将数据库密码、Redis 密码等敏感信息移出代码使用环境变量或配置中心如 Apollo、Nacos。配置正确的server.servlet.context-path如果有多服务。配置日志级别和输出路径便于问题追踪。考虑使用 JVM 参数调整堆内存大小。前端配置修改request.js中的baseURL为生产环境的后端 API 地址如https://api.yourdomain.com。构建时可以通过.env.production环境变量文件来注入配置。跨域生产环境中更安全的做法是在 Nginx 等反向代理层统一处理 CORS或者让前后端使用相同域名通过 Nginx 路由区分从而避免跨域。安全后端 API 需要添加认证如 JWT和授权如 Spring Security。对用户输入进行严格的校验和过滤防止 SQL 注入和 XSS 攻击。接口考虑限流和防刷。6. 总结与扩展方向通过这个从零开始的博客管理项目我们实践了 Spring Boot 和 Vue 3 全栈开发的核心流程从项目创建、依赖配置、数据库设计、实体与 Mapper 编写、Service 业务封装、RESTful API 设计到前端项目搭建、组件开发、状态管理、API 调用最后到项目打包和部署考量。每个步骤都力求解释清楚“为什么这么做”而不仅仅是“怎么做”。这个项目是一个起点你可以在此基础上继续深化用户认证与授权集成 Spring Security 或 Sa-Token实现登录、注册、权限控制RBAC。文件上传实现博客封面图、用户头像的上传功能可集成阿里云 OSS 或本地存储。博客功能完善增加文章分类、标签、评论、点赞、搜索集成 Elasticsearch等功能。前端体验优化引入 Markdown 编辑器如bytemd/vue-next、实现文章详情页、优化路由懒加载。部署与运维学习使用 Docker 容器化部署使用 Nginx 配置反向代理和负载均衡搭建 CI/CD 流水线。开发过程中最重要的是养成查看日志、善用调试工具和阅读官方文档的习惯。当遇到问题时清晰的错误信息、浏览器的开发者工具和后端的控制台日志是你最好的帮手。 30款热门AI模型一站整合DeepSeek/GLM/Claude 随心用限时 5 折。 点击领海量免费额度