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 install
3.2 安装 Element Plus 和 Axios
Element Plus 是功能丰富的 UI 组件库,Axios 用于发起 HTTP 请求。
npm install element-plus axios
3.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 request
3.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 dev
Vite 开发服务器会启动,通常在 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 折。 👉 点击领海量免费额度
更多推荐
所有评论(0)