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 Boot | 2.7.18 (LTS) | 提供核心框架、自动配置、内嵌Web服务器 |
| MyBatis-Plus | 3.5.x | 增强的 MyBatis 框架,简化 CRUD 操作 | |
| MySQL Driver | 8.0.x | 连接 MySQL 数据库 | |
| Lombok | 1.18.x | 通过注解简化 Java Bean 的编写(如 getter/setter) | |
| Spring Boot Starter Validation | 2.7.x | 提供参数校验功能 | |
| 前端 | Node.js | >= 16.0.0 | JavaScript 运行时环境 |
| Vue | 3.3.x | 前端核心框架 | |
| Vite | 4.4.x | 下一代前端构建工具,提供极速的开发服务器 | |
| Vue Router | 4.2.x | 官方路由管理器 | |
| Pinia | 2.1.x | Vue 官方推荐的状态管理库 | |
| Element Plus | 2.3.x | 基于 Vue 3 的桌面端组件库 | |
| Axios | 1.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> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis-Plus 起步依赖 (替换 MyBatis Framework) --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- Validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!-- 测试 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</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?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';2.3 创建实体类、Mapper 与 Service
MyBatis-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 BaseMapper<User> { // 无需编写任何方法,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 IService<User> { // 可以在此定义特殊的业务方法,例如根据用户名查找用户 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 ServiceImpl<UserMapper, 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 List<User> listUsers() { // 调用 Service 的 list() 方法,查询所有未逻辑删除的用户 return userService.list(); } }2.5 解决跨域问题
由于前端项目运行在另一个端口(如localhost:5173),浏览器出于安全考虑会阻止跨域请求。我们需要在后端配置 CORS (跨源资源共享)。
创建一个配置类WebConfig.java:
package 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 vue@latest 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 和 Axios
Element 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 class="user-list-container"> <h2>用户列表</h2> <el-table :data="userList" style="width: 100%" stripe border> <el-table-column prop="id" label="ID" width="80"></el-table-column> <el-table-column prop="username" label="用户名"></el-table-column> <el-table-column prop="nickname" label="昵称"></el-table-column> <el-table-column prop="email" label="邮箱"></el-table-column> <el-table-column prop="createTime" label="创建时间"> <template #default="scope"> {{ formatDate(scope.row.createTime) }} </template> </el-table-column> <el-table-column label="操作" width="180"> <template #default="scope"> <el-button size="small" @click="handleEdit(scope.row)">编辑</el-button> <el-button size="small" type="danger" @click="handleDelete(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 id="app"> <nav> <router-link to="/">首页</router-link> | <router-link to="/users">用户管理</router-link> </nav> <router-view /> </div> </template>3.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`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章表';- 创建实体类
Article.java:
package 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; }- 创建 Mapper
ArticleMapper.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 IPage<Article> getArticlePage( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String title, @RequestParam(required = false) Integer status) { Page<Article> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Article> 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是否包含前端地址。 |
| 请求返回 404 | 1. 请求 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> <groupId>org.springframework.boot</groupId> <artifactId>spring-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.active=prod然后创建一个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 折。 👉 点击领海量免费额度
