当前位置: 首页 > news >正文

SpringBoot 3 + Vue 3 前后端分离博客系统:从零搭建到部署完整指南

🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度

很多同学在准备毕业设计或者面试项目时,常常苦于找不到一个结构清晰、技术栈主流、能跑起来的完整项目。自己从零搭建又耗时耗力,网上找到的源码要么环境复杂,要么依赖缺失,跑起来一堆报错。

今天分享一个基于 SpringBoot 3 和 Vue 3 的博客管理系统,这是一个典型的前后端分离项目,涵盖了用户管理、文章发布、分类标签、评论等核心功能。项目结构清晰,代码规范,依赖完整,半小时内就能在你的本地环境成功运行。无论是用于学习前后端分离架构,还是作为毕业设计、丰富个人简历,都是一个非常不错的选择。

本文将手把手带你完成从环境准备、项目导入、数据库配置到前后端启动的全过程,并提供完整的源码获取方式。即使你是刚接触 SpringBoot 或 Vue 3 的新手,也能跟着步骤顺利搭建。

1. 项目概述与技术栈

1.1 什么是博客管理系统?

博客管理系统(Blog Management System)是一个用于创建、发布、管理和展示网络文章(博客)的Web应用。它通常包含后台管理界面供作者操作,以及前台页面供读者浏览。一个完整的博客系统通常具备以下核心模块:

  • 用户认证与授权:管理员登录、权限控制。
  • 文章管理:文章的增、删、改、查(CRUD)。
  • 分类与标签管理:对文章进行归类。
  • 评论管理:用户对文章的评论与回复。
  • 数据统计:简单的文章浏览数、评论数统计。

1.2 为什么选择 SpringBoot + Vue3 前后端分离?

  • SpringBoot:作为Java后端开发的“事实标准”,它极大地简化了Spring应用的初始搭建和开发过程,内嵌Tomcat,提供自动配置和起步依赖,让开发者能快速构建独立、生产级的应用。
  • Vue 3:是当前最主流的前端框架之一,相比于Vue 2,它在性能(基于Proxy的响应式系统)、TypeScript支持、组合式API等方面有显著提升,开发体验更好,更适合构建现代复杂的单页面应用(SPA)。
  • 前后端分离:这是一种现代化的架构模式。后端(SpringBoot)专注于提供RESTful API处理业务逻辑和数据持久化;前端(Vue 3)负责页面渲染和用户交互。两者通过HTTP接口(通常是JSON格式)进行通信。这种架构的好处是职责清晰、便于并行开发、前端可以独立部署,且后端API可以被多种客户端(Web、移动端)复用。

本项目的完整技术栈如下:

  • 后端:SpringBoot 3.x, Spring Security(安全框架), JWT(令牌认证), MyBatis-Plus(数据访问层增强), MySQL, Maven。
  • 前端:Vue 3, Vue Router(路由), Pinia(状态管理), Element Plus(UI组件库), Axios(HTTP客户端), Vite(构建工具)。
  • 开发工具:IntelliJ IDEA(后端), Visual Studio Code(前端), Navicat或DBeaver(数据库)。

2. 环境准备与项目结构

在开始之前,请确保你的本地开发环境已就绪。

2.1 必备软件安装

  1. JDK:版本 17 或以上。SpringBoot 3.x 需要 JDK 17+ 的支持。安装后配置好JAVA_HOME环境变量。
  2. Node.js:版本 16 或以上,推荐 LTS 版本。它将同时安装 npm(Node包管理器)。安装后可以在命令行输入node -vnpm -v检查。
  3. Maven:版本 3.6 或以上。用于管理后端项目的依赖和构建。安装后配置好MAVEN_HOME环境变量,并添加bin目录到PATH
  4. MySQL:版本 5.7 或 8.0。用于数据存储。请记住你安装时设置的 root 用户密码。
  5. IDE
    • 后端:IntelliJ IDEA(社区版或旗舰版)。
    • 前端:Visual Studio Code,并安装 Vue 官方扩展(Volar)。

2.2 获取项目源码

你可以通过提供的链接或网盘地址获取完整的项目源码压缩包。解压后,你会看到类似如下的目录结构:

blog-system/ ├── blog-backend/ # SpringBoot 后端项目 │ ├── src/ │ │ ├── main/ │ │ │ ├── java/com/example/blog/ │ │ │ │ ├── config/ # 配置类(安全、跨域等) │ │ │ │ ├── controller/ # 控制器,接收请求,返回响应 │ │ │ │ ├── entity/ # 实体类,对应数据库表 │ │ │ │ ├── mapper/ # MyBatis-Plus 的 Mapper 接口 │ │ │ │ ├── service/ # 业务逻辑层接口和实现 │ │ │ │ └── utils/ # 工具类(JWT、响应封装等) │ │ │ └── resources/ │ │ │ ├── application.yml # 主配置文件 │ │ │ └── mapper/ # MyBatis XML 映射文件(如有) │ │ └── test/ │ └── pom.xml # Maven 项目对象模型,管理依赖 └── blog-frontend/ # Vue 3 前端项目 ├── public/ # 静态资源 ├── src/ │ ├── api/ # 封装所有后端 API 请求 │ ├── assets/ # 图片、样式等资源 │ ├── components/ # 可复用的 Vue 组件 │ ├── router/ # Vue Router 路由配置 │ ├── stores/ # Pinia 状态管理 │ ├── utils/ # 前端工具函数 │ ├── views/ # 页面视图组件 │ ├── App.vue # 根组件 │ └── main.js # 应用入口文件 ├── .env.development # 开发环境变量 ├── .env.production # 生产环境变量 ├── index.html # 主 HTML 文件 ├── package.json # 项目依赖和脚本 └── vite.config.js # Vite 构建配置

这个结构清晰地展示了前后端分离的物理划分,是理解项目的第一步。

3. 后端项目配置与启动

3.1 数据库初始化

  1. 打开你的 MySQL 客户端(如命令行、Navicat)。
  2. 创建一个新的数据库,字符集建议使用utf8mb4,排序规则使用utf8mb4_general_ci
    CREATE DATABASE `blog_db` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
  3. 在项目的blog-backend/src/main/resources目录下,找到schema.sql文件(如果项目提供了)。这个文件包含了创建所有表的 SQL 语句。在 MySQL 客户端中执行这个文件。如果没有提供,通常项目会在首次启动时通过 MyBatis-Plus 的自动建表功能或 Flyway/Liquibase 来初始化,这需要在application.yml中配置。

3.2 修改后端配置文件

后端项目的核心配置在application.yml中。你需要根据你的本地环境修改以下几处关键配置:

# blog-backend/src/main/resources/application.yml server: port: 8080 # 后端服务启动端口 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver # 修改为你本地的MySQL连接信息:数据库名、用户名、密码 url: jdbc:mysql://localhost:3306/blog_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai username: root password: your_mysql_password # 替换为你的密码 jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss # MyBatis-Plus 配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台输出SQL,调试用 global-config: db-config: logic-delete-field: isDeleted # 全局逻辑删除字段名(如果实体类有) logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值 # JWT 配置(示例,具体密钥需修改) jwt: secret: yourSuperSecretKeyHereChangeThisToSomethingLongAndRandom # JWT签名密钥,务必修改! expiration: 604800 # token有效期(秒),7天 header: Authorization

特别注意jwt.secret是一个用于签名和验证 JWT Token 的密钥,在生产环境中必须使用足够复杂且保密的字符串,绝对不要使用示例中的值。

3.3 导入并启动后端项目

  1. 使用 IntelliJ IDEA 打开blog-backend文件夹。
  2. IDEA 会自动识别为 Maven 项目并开始下载依赖(观察右下角进度条)。如果网络较慢,可以配置 Maven 使用国内镜像源。
  3. 依赖下载完成后,找到主启动类,通常位于src/main/java/com/example/blog/BlogApplication.java(类名可能不同,但会有@SpringBootApplication注解)。
  4. 右键点击这个类,选择Run ‘BlogApplication’
  5. 观察控制台输出,如果没有报错,并看到类似Tomcat started on port(s): 8080Started BlogApplication in X.XXX seconds的日志,说明后端启动成功。
  6. 你可以在浏览器中访问http://localhost:8080/doc.html(如果集成了 Knife4j/Swagger)来查看和测试后端 API 接口。

4. 前端项目配置与启动

4.1 安装前端依赖

前端项目使用 npm 管理依赖。在启动前,需要先安装所有依赖包。

  1. 使用 VS Code 打开blog-frontend文件夹。
  2. 打开 VS Code 的终端(Terminal -> New Terminal)。
  3. 在终端中,确保当前路径在前端项目根目录下,然后执行安装命令:
    npm install # 或使用淘宝镜像加速 # cnpm install
    这个命令会根据package.json文件下载所有需要的库(如 Vue、Element Plus、Axios等)到node_modules目录。

4.2 修改前端环境配置

前端需要知道后端 API 的地址。配置通常在环境变量文件中。

  1. 打开blog-frontend/.env.development文件(开发环境配置)。
  2. 修改VITE_API_BASE_URL变量,指向你刚刚启动的后端服务地址和端口。
    # .env.development VITE_API_BASE_URL=http://localhost:8080/api
    这里假设你的后端 API 统一以/api开头。请根据你后端controller的实际路径进行调整。

4.3 启动前端开发服务器

依赖安装完成后,就可以启动前端开发服务器了。

  1. 在 VS Code 的终端中(仍在项目根目录),运行启动命令:
    npm run dev
  2. 命令执行后,终端会输出本地访问地址,通常是http://localhost:5173(Vite 默认端口)。
  3. 打开浏览器,访问这个地址(如http://localhost:5173)。你应该能看到博客系统的前端登录或首页界面。

至此,前后端服务都已成功启动,一个完整的博客管理系统已经在你的本地运行起来了!

5. 核心功能模块代码详解

为了让项目不只是“跑起来”,更能“学明白”,我们深入看几个核心模块的代码实现。

5.1 后端用户登录与JWT认证

这是前后端分离项目的安全基石。流程是:用户提交用户名密码 -> 后端验证 -> 生成JWT Token返回 -> 前端存储Token并在后续请求中携带。

1. 实体类 (User.java)

// blog-backend/src/main/java/com/example/blog/entity/User.java package com.example.blog.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("sys_user") // 对应数据库表名 public class User { @TableId(type = IdType.AUTO) // 主键自增 private Long id; private String username; // 用户名 private String password; // 加密后的密码 private String nickname; // 昵称 private String avatar; // 头像地址 private String email; private Integer status; // 状态 0-正常 1-禁用 @TableField(fill = FieldFill.INSERT) // 插入时自动填充 private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) // 插入和更新时自动填充 private LocalDateTime updateTime; }

2. 登录接口 (AuthController.java)

// blog-backend/src/main/java/com/example/blog/controller/AuthController.java package com.example.blog.controller; import com.example.blog.common.Result; import com.example.blog.entity.User; import com.example.blog.service.UserService; import com.example.blog.utils.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private UserService userService; @Autowired private JwtUtil jwtUtil; @PostMapping("/login") public Result login(@RequestBody Map<String, String> loginForm) { String username = loginForm.get("username"); String password = loginForm.get("password"); // 1. 验证用户名密码 User user = userService.login(username, password); if (user == null) { return Result.error("用户名或密码错误"); } // 2. 生成JWT Token String token = jwtUtil.generateToken(user.getId().toString(), user.getUsername()); // 3. 返回用户信息和Token Map<String, Object> data = new HashMap<>(); data.put("user", user); data.put("token", token); return Result.success("登录成功", data); } }

3. JWT工具类 (JwtUtil.java) - 核心片段

// blog-backend/src/main/java/com/example/blog/utils/JwtUtil.java package com.example.blog.utils; import io.jsonwebtoken.*; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; @Component public class JwtUtil { @Value("${jwt.secret}") private String secret; // 从配置文件中注入密钥 @Value("${jwt.expiration}") private Long expiration; // 有效期 // 生成Token public String generateToken(String userId, String username) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + expiration * 1000); return Jwts.builder() .setSubject(userId) .claim("username", username) // 自定义声明 .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS512, secret) // 使用HS512算法和密钥签名 .compact(); } // 验证并解析Token public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } // ... 其他方法:验证Token是否过期、获取用户信息等 }

5.2 前端请求封装与状态管理

前端需要统一管理API请求和用户登录状态。

1. Axios请求拦截器 (request.js)

// blog-frontend/src/utils/request.js import axios from 'axios'; import { ElMessage } from 'element-plus'; import { useUserStore } from '@/stores/user'; // Pinia store // 创建axios实例 const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, // 读取环境变量中的基础URL timeout: 10000, // 请求超时时间 }); // 请求拦截器:在发送请求前统一处理,例如添加Token service.interceptors.request.use( (config) => { const userStore = useUserStore(); if (userStore.token) { // 如果存在token,将其添加到请求头中 config.headers['Authorization'] = `Bearer ${userStore.token}`; } return config; }, (error) => { console.error('Request Error:', error); return Promise.reject(error); } ); // 响应拦截器:统一处理响应数据或错误 service.interceptors.response.use( (response) => { const res = response.data; // 假设后端统一返回格式为 { code: 200, message: 'success', data: ... } if (res.code === 200) { return res.data; // 直接返回业务数据 } else { // 业务逻辑错误,例如密码错误 ElMessage.error(res.message || 'Error'); return Promise.reject(new Error(res.message || 'Error')); } }, (error) => { // HTTP状态码错误,例如 401, 404, 500 console.error('Response Error:', error.response); if (error.response && error.response.status === 401) { ElMessage.error('登录已过期,请重新登录'); // 触发登出逻辑,清空token和用户信息,跳转到登录页 const userStore = useUserStore(); userStore.logout(); window.location.href = '/login'; } else { ElMessage.error(error.response?.data?.message || '网络请求失败'); } return Promise.reject(error); } ); export default service;

2. Pinia状态管理 - 用户Store (user.js)

// blog-frontend/src/stores/user.js import { defineStore } from 'pinia'; import { loginApi, getUserInfoApi, logoutApi } from '@/api/user'; // 引入API import { removeToken, setToken } from '@/utils/auth'; // 假设有操作token的工具函数 export const useUserStore = defineStore('user', { state: () => ({ token: localStorage.getItem('token') || '', // 从本地存储初始化 userInfo: JSON.parse(localStorage.getItem('userInfo') || '{}'), }), actions: { // 登录动作 async login(loginForm) { try { const res = await loginApi(loginForm); this.token = res.token; this.userInfo = res.user; // 持久化到本地存储 setToken(res.token); localStorage.setItem('userInfo', JSON.stringify(res.user)); return Promise.resolve(res); } catch (error) { return Promise.reject(error); } }, // 获取用户信息 async getUserInfo() { if (!this.token) return; try { const res = await getUserInfoApi(); this.userInfo = res; localStorage.setItem('userInfo', JSON.stringify(res)); } catch (error) { // 获取失败,可能是token失效 this.logout(); } }, // 登出动作 logout() { this.token = ''; this.userInfo = {}; removeToken(); localStorage.removeItem('userInfo'); // 可以调用后端登出API // logoutApi(); }, }, });

5.3 文章管理功能实现

文章管理是博客的核心,涉及前后端协作的完整CRUD流程。

1. 后端文章接口 (ArticleController.java)

// blog-backend/src/main/java/com/example/blog/controller/ArticleController.java package com.example.blog.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.blog.common.Result; import com.example.blog.entity.Article; import com.example.blog.service.ArticleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/article") public class ArticleController { @Autowired private ArticleService articleService; // 新增或更新文章 @PostMapping public Result saveOrUpdate(@RequestBody Article article) { // 这里可以加入权限校验,例如只有作者本人或管理员可以操作 boolean success = articleService.saveOrUpdate(article); return success ? Result.success("操作成功") : Result.error("操作失败"); } // 分页查询文章列表 @GetMapping("/page") public Result findPage(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(defaultValue = "") String title) { Page<Article> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Article> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.like(!title.isEmpty(), Article::getTitle, title); // 标题模糊查询 queryWrapper.orderByDesc(Article::getCreateTime); // 按创建时间倒序 IPage<Article> pageResult = articleService.page(page, queryWrapper); return Result.success(pageResult); } // 根据ID查询文章详情 @GetMapping("/{id}") public Result getById(@PathVariable Long id) { Article article = articleService.getById(id); if (article != null) { // 可以在这里增加浏览数等逻辑 return Result.success(article); } return Result.error("文章不存在"); } // 根据ID删除文章(逻辑删除) @DeleteMapping("/{id}") public Result delete(@PathVariable Long id) { boolean success = articleService.removeById(id); return success ? Result.success("删除成功") : Result.error("删除失败"); } }

2. 前端文章列表页面 (ArticleList.vue)

<!-- blog-frontend/src/views/article/ArticleList.vue --> <template> <div class="article-list"> <div class="header"> <el-input v-model="searchForm.title" placeholder="请输入文章标题" style="width: 300px;" @keyup.enter="handleSearch"> <template #append> <el-button :icon="Search" @click="handleSearch" /> </template> </el-input> <el-button type="primary" :icon="Plus" @click="handleAdd">新增文章</el-button> </div> <el-table :data="tableData" style="width: 100%" v-loading="loading"> <el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="title" label="标题" show-overflow-tooltip /> <el-table-column prop="categoryName" label="分类" width="120" /> <el-table-column prop="viewCount" label="浏览" width="80" /> <el-table-column prop="createTime" label="创建时间" width="180"> <template #default="scope"> {{ formatDate(scope.row.createTime) }} </template> </el-table-column> <el-table-column label="操作" width="200" fixed="right"> <template #default="scope"> <el-button type="primary" link :icon="View" @click="handleView(scope.row)">查看</el-button> <el-button type="primary" link :icon="Edit" @click="handleEdit(scope.row)">编辑</el-button> <el-button type="danger" link :icon="Delete" @click="handleDelete(scope.row)">删除</el-button> </template> </el-table-column> </el-table> <div class="pagination"> <el-pagination v-model:current-page="pageParams.pageNum" v-model:page-size="pageParams.pageSize" :page-sizes="[10, 20, 50, 100]" :total="total" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange" @current-change="handleCurrentChange" /> </div> <!-- 新增/编辑对话框 --> <ArticleDialog v-model="dialogVisible" :form-data="currentArticle" @success="handleDialogSuccess" /> </div> </template> <script setup> import { ref, reactive, onMounted } from 'vue'; import { Search, Plus, View, Edit, Delete } from '@element-plus/icons-vue'; import { ElMessage, ElMessageBox } from 'element-plus'; import { getArticlePageApi, deleteArticleApi } from '@/api/article'; import ArticleDialog from './components/ArticleDialog.vue'; import { formatDate } from '@/utils/date'; const loading = ref(false); const tableData = ref([]); const total = ref(0); const dialogVisible = ref(false); const currentArticle = ref({}); // 查询条件 const searchForm = reactive({ title: '', }); // 分页参数 const pageParams = reactive({ pageNum: 1, pageSize: 10, }); // 获取文章列表 const fetchData = async () => { loading.value = true; try { const params = { ...pageParams, ...searchForm, }; const res = await getArticlePageApi(params); tableData.value = res.records; total.value = res.total; } catch (error) { console.error('获取文章列表失败:', error); } finally { loading.value = false; } }; // 搜索 const handleSearch = () => { pageParams.pageNum = 1; // 搜索时回到第一页 fetchData(); }; // 分页大小变化 const handleSizeChange = (val) => { pageParams.pageSize = val; fetchData(); }; // 当前页变化 const handleCurrentChange = (val) => { pageParams.pageNum = val; fetchData(); }; // 新增 const handleAdd = () => { currentArticle.value = {}; dialogVisible.value = true; }; // 编辑 const handleEdit = (row) => { currentArticle.value = { ...row }; dialogVisible.value = true; }; // 查看 const handleView = (row) => { // 跳转到文章详情页,传递文章ID // router.push(`/article/detail/${row.id}`); }; // 删除 const handleDelete = (row) => { ElMessageBox.confirm(`确定要删除文章“${row.title}”吗?`, '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning', }).then(async () => { try { await deleteArticleApi(row.id); ElMessage.success('删除成功'); fetchData(); // 刷新列表 } catch (error) { ElMessage.error('删除失败'); } }).catch(() => {}); }; // 对话框操作成功后的回调 const handleDialogSuccess = () => { dialogVisible.value = false; fetchData(); // 刷新列表 }; onMounted(() => { fetchData(); }); </script> <style scoped> .article-list { padding: 20px; } .header { display: flex; justify-content: space-between; margin-bottom: 20px; } .pagination { margin-top: 20px; display: flex; justify-content: flex-end; } </style>

6. 项目部署与打包

本地开发完成后,你可能需要将项目打包部署到服务器或用于演示。

6.1 后端打包 (SpringBoot)

SpringBoot项目使用Maven打包非常方便,会生成一个可执行的Jar文件。

  1. blog-backend目录下打开终端。
  2. 执行Maven打包命令:
    mvn clean package -DskipTests
    • clean:清理旧的构建文件。
    • package:打包项目。
    • -DskipTests:跳过测试,加快打包速度。
  3. 打包成功后,在target目录下会生成一个blog-backend-0.0.1-SNAPSHOT.jar(名称可能不同)的文件。
  4. 你可以将这个Jar文件上传到服务器,使用java -jar blog-backend-0.0.1-SNAPSHOT.jar命令运行。生产环境建议使用nohup或配置为系统服务。

6.2 前端打包 (Vue 3)

Vue项目需要打包成静态文件(HTML, JS, CSS),然后由Nginx等Web服务器托管。

  1. blog-frontend目录下打开终端。
  2. 首先,修改生产环境配置文件.env.production,将API地址指向你的线上后端服务地址。
    # .env.production VITE_API_BASE_URL=https://your-domain.com/api
  3. 执行打包命令:
    npm run build
  4. 命令执行完毕后,会在项目根目录下生成一个dist文件夹,里面就是打包好的所有静态资源。
  5. dist文件夹内的所有文件上传到你的Web服务器(如Nginx)的网站根目录,并配置Nginx将所有非静态文件请求重定向到index.html(用于支持Vue Router的history模式)。

Nginx配置示例:

server { listen 80; server_name your-domain.com; # 你的域名 root /path/to/your/dist; # dist文件夹的绝对路径 location / { try_files $uri $uri/ /index.html; # 关键配置,支持前端路由 } # 代理后端API请求,解决跨域问题(如果前后端不同域) location /api/ { proxy_pass http://localhost:8080/api/; # 代理到后端服务 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

7. 常见问题与解决方案 (FAQ)

在搭建和运行过程中,你可能会遇到以下问题:

问题现象可能原因解决方案
后端启动失败,端口被占用8080端口已被其他程序(如另一个Tomcat)使用。1. 在application.yml中修改server.port为其他端口(如 8081)。
2. 在命令行查找占用端口的进程并结束它(Windows:netstat -ano | findstr :8080,Linux/Mac:lsof -i :8080)。
前端npm install失败,网络超时npm默认源在国外,网络不稳定。1. 使用淘宝镜像:npm config set registry https://registry.npmmirror.com,然后重试。
2. 使用cnpm命令。
前端访问localhost:5173报错,无法连接到后端API前端配置的API地址 (VITE_API_BASE_URL) 错误,或后端服务未启动。1. 检查后端服务是否在localhost:8080正常运行。
2. 检查blog-frontend/.env.development文件中的VITE_API_BASE_URL是否正确指向后端地址和端口。
登录成功,但进行其他操作(如新增文章)时提示“未授权”或“Token无效”前端没有正确在请求头中携带Token,或后端Token验证失败。1. 检查前端request.js中的请求拦截器是否从Pinia store或localStorage正确获取了token并添加到Authorization头。
2. 检查后端JWT配置的secret是否一致,Token是否已过期。
数据库连接失败MySQL服务未启动,或application.yml中的数据库连接信息(url, username, password)错误。1. 确保MySQL服务已启动。
2. 仔细核对application.yml中的数据库名、用户名和密码。
3. 检查MySQL用户是否有远程连接权限(如果数据库不在本机)。
前端页面样式错乱,Element Plus组件不显示Element Plus组件未正确引入或注册。1. 检查package.json中是否有element-plus依赖。
2. 检查是否在main.js或按需导入的配置文件中正确引入了Element Plus及其样式。
打包后前端页面空白或路由失效1. 静态资源路径错误。
2. 未配置Nginx的try_files规则。
1. 检查vite.config.js中是否配置了正确的base(如果项目部署在子路径)。
2. 确保Web服务器(如Nginx)正确配置了将所有路由重定向到index.html

8. 项目扩展与优化建议

这个基础项目已经具备了核心功能,但如果你想将其用于更复杂的场景或作为毕业设计的亮点,可以考虑以下扩展方向:

  1. 增加文章审核与发布状态:为文章添加status字段(如:草稿、待审核、已发布、已拒绝),并实现管理员审核流程。
  2. 实现文章点赞、收藏功能:新建likefavorite表,记录用户与文章的互动关系。
  3. 引入富文本编辑器:将简单的文本框替换为功能强大的富文本编辑器,如WangEditorTinyMCEQuill,支持图片上传、代码高亮等。
  4. 添加文章SEO优化:为每篇文章生成独立的、对搜索引擎友好的URL(Slug),并自动生成descriptionkeywords的meta标签。
  5. 实现文件上传服务:将用户上传的头像、文章图片存储到对象存储(如阿里云OSS、腾讯云COS)或本地服务器,并做好文件管理。
  6. 集成第三方登录:使用 OAuth 2.0 协议接入微信、QQ、Github 等第三方登录方式。
  7. 加入缓存机制:对于首页文章列表、热门文章等不常变的数据,使用 Redis 进行缓存,显著提升访问速度。
  8. 完善后台权限管理:使用 RBAC(基于角色的访问控制)模型,细化权限,例如区分文章作者、栏目管理员、系统管理员等角色。
  9. 前端性能优化
    • 路由懒加载:在router/index.js中,使用() => import(‘…’)语法实现组件按需加载。
    • 图片懒加载:对于长列表中的图片,使用Intersection Observer APIvue-lazyload插件。
    • API请求防抖/节流:对搜索框等频繁触发请求的场景进行优化。
  10. 容器化部署:编写Dockerfiledocker-compose.yml文件,将前后端和数据库容器化,实现一键部署,这会是简历上的一大亮点。

这个 SpringBoot + Vue 3 博客管理系统项目,从技术选型到代码结构,都符合当前企业级开发的主流实践。通过亲手搭建和深入研究其中的代码,你不仅能掌握前后端分离开发的完整流程,更能深入理解用户认证、状态管理、组件化开发、RESTful API设计等核心概念。

🚀 30+款热门AI模型一站整合,DeepSeek/GLM/Qwen 随心用,限时 5 折。 👉 点击领海量免费额度

http://www.jsqmd.com/news/1134529/

相关文章:

  • 老旧Mac系统重生指南:突破官方限制的硬件兼容方案
  • Windows 11 右键菜单修复:3种方法恢复被折叠的 Git Bash Here 选项
  • 三重降压转换器TPS65263设计与应用全解析
  • 安卓逆向实战:从加壳特征识别到Frida动态脱壳与签名算法还原
  • SpringBoot+Vue高校毕业生就业平台:从零搭建到部署的全栈毕设实战
  • GitLab Webhook 安全配置实战:签名令牌 vs 秘密令牌,3步防重放攻击
  • Codex Skills 安装与配置指南:10个必备技能提升开发效率
  • Servlet 3.0 与 JSP 2.3 实战:5分钟构建一个带登录验证的简易留言板
  • Spring Boot+Vue课程作业管理系统毕业设计实战指南
  • 作为程序员的求职,我想还是有些职业的特殊性的。程序员职业和一般的职业有很大的不同,程序员职业有其鲜明的特点::1、个人劳动 2、产品可复制。这两个特点注定了程序员有很大个人发展的空间。
  • Linux命令行操作:从基础到高效的系统管理
  • 从Harness Engineering到Hermes Agent:构建可靠、可进化AI智能体的工程实践
  • Web应用越权漏洞检测与防护实战指南
  • 锂离子电池过压保护与STM32L031C6系统设计
  • 企业微信第三方应用扫码登录 3 步实战:Spring Boot 后端 + 域名配置避坑
  • 抖音下载终极指南:5分钟搞定无水印批量下载的开源神器
  • 免费开源版图设计工具KLayout:集成电路设计的终极解决方案
  • Hadoop Web控制台安全加固实战:绕过官方Simple认证,实现Nginx反向代理密码验证
  • 深度解析HCL AppScan全场景漏洞检测:从DAST/SAST到DevSecOps实战
  • JProfiler 13 实战:3步定位内存泄漏,结合Heap Walker与GC Root分析
  • Java健身房管理系统实战:Spring Boot+Vue全栈开发指南
  • AI工程师必备:HTML优先的Agent开发范式,超越Figma AI的设计到代码自动化
  • 飞书Webhook签名验证实战:从原理到Node.js完整实现
  • DC-DC降压转换与I2C数字控制电源系统设计
  • MetaMask 13.37.0 安全配置进阶:5项关键设置防范钓鱼与资产丢失
  • tar 命令 5 个高级参数详解:--exclude、-C、--strip-components 实战场景
  • 设计系统 Token 工程化:从定义到 CI 校验的完整链路
  • TongWeb 7.0 连接池配置优化:10个关键参数详解与性能压测对比
  • Office Online Server 2016 WOPI 集成实战:SpringBoot 服务 5 步对接文档预览
  • Java+Vue+Spring Boot+MySQL课程作业管理系统:从部署到二次开发全指南