news 2026/9/16 20:30:22

JavaEE博客系统实战:SpringBoot+Vue全链路骨架

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaEE博客系统实战:SpringBoot+Vue全链路骨架

简介:本资源是一套完整的JavaEE课程期末大作业实战项目,面向高校计算机专业学生及SpringBoot+Vue初学者,提供可直接运行的前后端分离博客系统解决方案。项目采用SpringBoot构建后端服务,Vue CLI搭建前端界面,整合SSMP(Spring+SpringMVC+MyBatis+PageHelper)技术栈,覆盖用户管理、文章发布、分类标签、评论互动等核心博客功能,适合作为课程设计、实训考核或技术栈整合练习。压缩包共40个文件,含14个Java后端业务与配置类、5个Vue组件页面、5个JS工具与API调用脚本、1个SQL建表与初始化脚本(m_blog.sql)、2个YML/Properties配置文件,以及Jar包、启动脚本和README说明文档,整体26.32MB,结构清晰、模块职责分明。目前已有2385人学习下载,读者可获得完整源码、数据库脚本、前后端独立启动方案及典型配置项说明,快速掌握企业级全栈开发流程与常见环境适配要点。

1. 这不是又一个“Hello World”博客,而是一套能跑通登录、发帖、分类、评论全链路的 JavaEE 期末实战骨架

很多同学拿到 JavaEE 期末大作业时,第一反应是搜“SpringBoot 博客系统 GitHub”,结果下载一堆只有首页、没后台管理、数据库脚本缺失、Vue 页面报 404 的“半成品”。这个blog_demo项目不同——它从m_blog.sql开始就定义了用户、文章、分类、评论四张核心表,后端用 SpringBoot + MyBatis-Plus 实现 RBAC 权限控制(管理员/普通用户角色分离),前端 Vue 2.6 + Element UI 已完成登录态拦截、富文本编辑器集成、分页列表渲染、路由守卫跳转逻辑。它不追求炫酷动画或 AI 写作,但每一步都踩在 JavaEE 教学大纲的得分点上:事务控制(发布文章同时写入分类关联表)、跨域配置(@CrossOrigin+ Nginx 反向代理注释)、JWT Token 验证(Authorization: Bearer xxx解析)、MyBatis-Plus LambdaQueryWrapper 动态查询。适合需要交作业、想理解真实前后端分离协作流程、或准备 SpringBoot 面试题中“如何设计博客用户模块”的开发者——尤其当你发现application.ymlspring.datasource.url后面跟着?useSSL=false&serverTimezone=GMT%2B8这串参数时,你就知道这不是玩具项目。


2. 数据库建模与后端服务启动:从 SQL 脚本到可运行的 REST API

2.1 四张表的设计意图与字段约束解析

m_blog.sql并非简单堆砌 CREATE TABLE,而是体现了典型博客系统的数据关系建模逻辑。执行前需注意:必须使用 MySQL 5.7+ 或 MariaDB 10.3+,因user表中create_time字段使用了DEFAULT CURRENT_TIMESTAMP,低版本 MySQL 不支持该语法。

-- 用户表:含密码加密标识(bcrypt)、状态字段(0禁用/1启用)、角色字段(0普通用户/1管理员) CREATE TABLE `user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '密码(BCrypt加密)', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `status` tinyint(1) DEFAULT '1' COMMENT '状态:0禁用,1启用', `role` tinyint(1) DEFAULT '0' COMMENT '角色:0普通用户,1管理员', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 文章表:soft delete 设计(is_deleted = 0/1),避免物理删除影响评论关联 CREATE TABLE `article` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `title` varchar(200) NOT NULL COMMENT '标题', `content` longtext NOT NULL COMMENT '内容(HTML格式)', `user_id` bigint(20) NOT NULL COMMENT '作者ID', `category_id` bigint(20) DEFAULT NULL COMMENT '分类ID', `is_deleted` tinyint(1) DEFAULT '0' COMMENT '是否删除:0否,1是', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

提示:article表未设外键约束(如FOREIGN KEY (user_id) REFERENCES user(id)),这是 SpringBoot 项目常见做法——将关联逻辑交给 MyBatis-Plus 的@TableField和 Service 层处理,避免数据库级约束增加部署复杂度。若需强一致性,可在application.yml中开启spring.jpa.hibernate.ddl-auto=validate进行启动校验。

2.2 SpringBoot 后端启动的两种路径及关键配置项

项目结构中blog_demo是 Maven 模块,pom.xml声明了 SpringBoot 2.7.x(注意:非 3.x,因 Vue 2.6 依赖的vue-cli-service与 SpringBoot 3.x 的 Jakarta EE 9+ 包名不兼容)。启动前必须修改src/main/resources/application.yml

spring: datasource: url: jdbc:mysql://localhost:3306/m_blog?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8 username: root # ← 替换为你的数据库用户名 password: 123456 # ← 替换为你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver # JWT 配置:密钥必须与前端 axios 请求头中的 Authorization 值匹配 jwt: secret: blog_secret_key_2024 # ← 生产环境务必更换为 32 位随机字符串 expiration: 86400 # ← token 有效期:1 天(秒) # MyBatis-Plus 日志输出(调试必备) mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
方式一:IDEA 直接运行主类(适合开发调试)
  1. 在 IDEA 中打开blog_demo目录,等待 Maven 自动导入依赖(约 1~2 分钟)
  2. 找到com.example.blog.BlogDemoApplication.java,右键 →Run 'BlogDemoApplication.main()'
  3. 控制台输出Started BlogDemoApplication in X.XXX seconds即成功,API 默认监听http://localhost:8080
方式二:命令行运行 JAR 包(模拟生产部署)
# 进入 blog_demo/target 目录(确保已执行 mvn clean package) cd blog_demo/target # 执行 JAR(注意:application.yml 中的数据库配置必须与你本地一致) java -jar blog_demo-0.0.1-SNAPSHOT.jar # 验证接口:curl -X GET http://localhost:8080/api/user/list # 返回 JSON 数据即表示后端服务正常响应

注意:若出现java.lang.ClassNotFoundException: javax.servlet.http.HttpServletRequest错误,说明 JDK 版本过高(如 JDK 17+)。该项目基于 SpringBoot 2.7.x,推荐使用 JDK 8u202 或 JDK 11。JDK 17 需在pom.xml中添加jakarta.servlet-api依赖并调整包引用,但会偏离教学目标——此处建议降级 JDK,而非强行适配。

2.3 接口测试:用 curl 验证核心 REST 端点可用性

启动成功后,立即验证三个关键接口,确认数据层与业务逻辑连通:

# 1. 获取所有用户(需管理员 Token,先用默认账号登录获取) curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}' # 返回示例:{"code":200,"msg":"登录成功","data":{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}} # 2. 使用返回的 token 查询文章列表(带权限校验) curl -X GET http://localhost:8080/api/article/list \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." # 3. 发布新文章(POST 请求,需 JSON body) curl -X POST http://localhost:8080/api/article/save \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \ -H "Content-Type: application/json" \ -d '{ "title":"测试文章", "content":"<p>这是一篇测试内容</p>", "categoryId":1 }'

逻辑说明:Authorization头中的Bearer是 JWT 标准格式,后端通过@RequestHeader("Authorization") String authHeader提取 token,并由JwtTokenUtil类解析usernamerole。若 token 过期或签名错误,接口统一返回401 Unauthorized;若角色不足(如普通用户调用/api/user/list),返回403 Forbidden。这种细粒度控制正是 JavaEE 课程要求的“安全机制实现”。


3. 前端工程构建与路由权限控制:Vue 2.6 如何接管用户会话

3.1 依赖安装与开发服务器启动的关键步骤

blog-vue是独立 Vue CLI 项目(vue.config.js存在即证明),不要将其作为子模块嵌入后端项目。操作路径如下:

# 进入前端目录(注意:不是 blog_demo 下的 src,而是同级的 blog-vue 文件夹) cd blog-vue # 安装依赖(npm i 会读取 package.json 中的 vue@2.6.14、element-ui@2.15.14 等版本) npm i # 启动开发服务器(默认端口 8080,但后端已占,故自动切换至 8081) npm run serve

启动后访问http://localhost:8081,若看到登录页面,说明前端资源加载成功。此时打开浏览器开发者工具 → Network 标签页,刷新页面,观察请求:

  • GET /api/auth/captcha→ 获取验证码图片(后端生成 Base64 字符串)
  • POST /api/auth/login→ 提交用户名密码(前端对密码做 SHA-256 加密后再传输)
  • GET /api/user/info→ 登录后拉取用户信息(携带 token)

提示:package.jsonscripts定义了serve命令为"vue-cli-service serve --port 8081",这是为避免与后端端口冲突的显式声明。若需修改端口,直接编辑此行即可,无需改动vue.config.js中的devServer.port

3.2 路由守卫(Router Guard)实现登录态拦截

Vue Router 的router.beforeEach是权限控制的核心。查看src/router/index.js

// src/router/index.js import Vue from 'vue' import VueRouter from 'vue-router' import store from '@/store' // Vuex 状态管理 Vue.use(VueRouter) const routes = [ { path: '/login', name: 'Login', component: () => import('@/views/Login.vue'), meta: { requiresAuth: false } // 不需要登录即可访问 }, { path: '/', name: 'Home', component: () => import('@/views/Home.vue'), meta: { requiresAuth: true } // 必须登录 }, { path: '/admin', name: 'Admin', component: () => import('@/views/Admin.vue'), meta: { requiresAuth: true, roles: ['ADMIN'] } // 仅管理员可访问 } ] const router = new VueRouter({ routes }) // 全局前置守卫 router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') const userRole = store.state.user.role // 从 Vuex 获取角色 if (to.meta.requiresAuth && !token) { next('/login') // 无 token 跳转登录页 } else if (to.meta.roles && !to.meta.roles.includes(userRole)) { next('/') // 角色不符跳转首页 } else { next() // 放行 } }) export default router
关键参数说明:
  • meta.requiresAuth:布尔值,标识该路由是否需要认证
  • meta.roles:字符串数组,定义允许访问的角色类型(['ADMIN']['USER', 'ADMIN']
  • localStorage.getItem('token'):前端存储 token 的位置,后端登录成功后由Login.vue写入
  • store.state.user.role:Vuex 中持久化的用户角色,由/api/user/info接口返回后 commit 到 state

注意:Admin.vue中的<el-menu>组件通过v-if="userRole === 'ADMIN'"控制菜单项显示,实现界面级权限隔离。这比单纯路由拦截更安全——即使用户手动修改 URL,无权限菜单也不会渲染。

3.3 Axios 请求拦截器:自动注入 Token 与错误统一处理

所有 API 请求均通过src/utils/request.js封装的 axios 实例发出。其拦截器逻辑决定了请求能否成功:

// src/utils/request.js import axios from 'axios' import store from '@/store' import { Message } from 'element-ui' // 请求拦截器 axios.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` // 自动添加 Authorization 头 } return config }, error => Promise.reject(error) ) // 响应拦截器 axios.interceptors.response.use( response => response, error => { const { status } = error.response || {} if (status === 401) { // token 过期或无效,清空本地存储并跳转登录页 localStorage.removeItem('token') store.dispatch('user/logout') Message.error('登录已过期,请重新登录') router.push('/login') } else if (status === 403) { Message.error('权限不足,无法访问该页面') } return Promise.reject(error) } )

逻辑说明:config.headers.Authorization = 'Bearer ' + token是前后端分离项目中最常见的 Token 注入方式。后端 SpringBoot 的@RequestHeader("Authorization")正是读取此 header。若响应状态码为401,前端主动清除token并触发 Vuex 的logoutaction,确保用户无法通过浏览器缓存继续操作——这是比单纯跳转更彻底的安全措施。


4. 前后端联调排错:解决跨域、Token 失效、数据库连接三类高频问题

4.1 跨域问题定位与两种解决方案对比

当 Vue 前端(http://localhost:8081)调用 SpringBoot 后端(http://localhost:8080)时,浏览器控制台报CORS policy: No 'Access-Control-Allow-Origin' header is present,这是典型的跨域错误。该项目提供两种解法:

方案一:后端全局跨域配置(推荐用于开发环境)

com.example.blog.config.WebMvcConfig.java中:

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") // 仅对 /api/ 开头的路径开放 .allowedOrigins("http://localhost:8081") // 明确指定前端地址 .allowCredentials(true) // 允许携带 cookie(JWT 通常不用,但登录态需) .maxAge(3600); } }

参数说明:allowedOrigins必须写具体域名(*allowCredentials=true时被禁止),maxAge=3600表示预检请求缓存 1 小时,减少 OPTIONS 请求次数。

方案二:Vue CLI 代理(更贴近生产部署逻辑)

修改vue.config.js

module.exports = { devServer: { port: 8081, proxy: { '/api': { target: 'http://localhost:8080', // 代理到后端 changeOrigin: true, // 修改 Origin 头 pathRewrite: { '^/api': '/api' // 保持路径不变 } } } } }

此时前端请求http://localhost:8081/api/user/list,实际被代理到http://localhost:8080/api/user/list,浏览器认为是同源请求,彻底规避 CORS。生产环境应使用 Nginx 反向代理实现相同效果,而非前端代理。

4.2 Token 失效的三种表现与对应修复动作

现象原因修复动作
登录成功后跳转首页,但所有接口返回401application.ymljwt.secret与前端utils/request.jstoken解析密钥不一致检查后端JwtTokenUtil.javaSECRET_KEY是否等于 yml 中的jwt.secret
发布文章时报500 Internal Server Error,日志显示NullPointerException前端提交的categoryIdnull,后端ArticleService.save()未做空值校验ArticleController.javasave()方法前添加@NotNull注解,或在 Service 层if (article.getCategoryId() == null) article.setCategoryId(0L);
登录页输入正确账号密码,返回{"code":400,"msg":"用户名或密码错误"}数据库user表中password字段存储的是明文,而代码使用BCryptPasswordEncoder校验执行 SQL 更新密码:UPDATE user SET password='$2a$10$ZQVJqYbKfFgH7XpLmNtOeOqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFf' WHERE username='admin';123456的 BCrypt 加密值)

提示:BCrypt 密码生成可使用在线工具(搜索 “bcrypt generator”),或在 IDEA 中运行以下代码片段:

import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class PasswordEncoderTest { public static void main(String[] args) { BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); System.out.println(encoder.encode("123456")); // 输出加密后的字符串 } }

4.3 数据库连接失败的快速诊断表

错误日志关键词可能原因检查步骤
Access denied for user 'root'@'localhost'数据库用户名/密码错误检查application.ymlspring.datasource.username/password是否与 MySQL 实际账号一致;确认 MySQL 服务已启动
Unknown database 'm_blog'数据库未创建或名称拼写错误登录 MySQL 执行SHOW DATABASES;,确认存在m_blog;若不存在,执行CREATE DATABASE m_blog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
The last packet sent successfully to the server was 0 milliseconds agoMySQL 连接超时或防火墙拦截检查application.ymlurlserverTimezone=GMT%2B8是否正确(Windows 系统必须加%2B代替+);关闭 Windows 防火墙临时测试
java.sql.SQLException: Cannot load connection class because of underlying exceptionJDBC 驱动版本不匹配查看pom.xmlmysql-connector-java版本(该项目为8.0.26),确认 MySQL 服务版本 ≥ 5.7;若用 MySQL 8.0+,驱动类名应为com.mysql.cj.jdbc.Driver(当前配置正确)

5. 从作业交付到能力延伸:三个可立即落地的优化技巧

5.1 快速添加文章分类管理功能(5 分钟内完成)

原项目m_blog.sql已建好category表,但前端未提供分类维护界面。只需补充两个文件即可上线:

步骤 1:新增分类 API 接口(后端)

com.example.blog.controller.CategoryController.java中添加:

@RestController @RequestMapping("/api/category") public class CategoryController { @Autowired private CategoryService categoryService; @GetMapping("/list") public Result list() { return Result.success(categoryService.list()); } @PostMapping("/save") public Result save(@RequestBody Category category) { category.setCreateTime(new Date()); categoryService.save(category); return Result.success(); } }
步骤 2:添加分类管理页面(前端)

新建src/views/CategoryManage.vue

<template> <div> <el-button type="primary" @click="dialogVisible = true">新增分类</el-button> <el-table :data="categories"> <el-table-column prop="name" label="分类名称"></el-table-column> <el-table-column label="操作"> <template slot-scope="{row}"> <el-button size="mini" @click="handleEdit(row)">编辑</el-button> </template> </el-table-column> </el-table> <el-dialog title="新增分类" :visible.sync="dialogVisible"> <el-input v-model="form.name" placeholder="请输入分类名称"></el-input> <div slot="footer"> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="submitForm">确定</el-button> </div> </el-dialog> </div> </template> <script> export default { data() { return { categories: [], dialogVisible: false, form: { name: '' } } }, mounted() { this.loadCategories() }, methods: { loadCategories() { this.$http.get('/api/category/list').then(res => { this.categories = res.data.data }) }, submitForm() { this.$http.post('/api/category/save', this.form).then(() => { this.dialogVisible = false this.loadCategories() }) } } } </script>

技巧说明:此方案复用现有axios实例和Result响应结构,无需修改任何配置。新增页面后,在router/index.js中注册路由即可,完全遵循项目原有架构风格。

5.2 使用 MyBatis-Plus 自动生成 CRUD 代码(替代手写 Mapper)

项目中ArticleMapper.java仅继承BaseMapper<Article>,所有 SQL 由 MyBatis-Plus 动态生成。若需快速扩展字段(如给article表加view_count浏览量),只需三步:

  1. Article.java实体类中添加字段:
    @TableField("view_count") private Integer viewCount = 0; // 默认值 0
  2. ArticleMapper.java中添加自定义方法(无需 XML):
    @Update("UPDATE article SET view_count = view_count + 1 WHERE id = #{id}") int incrementViewCount(@Param("id") Long id);
  3. ArticleService.java中调用:
    public void updateViewCount(Long id) { articleMapper.incrementViewCount(id); }

优势:避免手写 XML 映射文件,降低出错率;@Update注解直接执行原生 SQL,性能优于LambdaUpdateWrapper的链式调用。

5.3 生产环境部署 checklist(阿里云 ECS 实操要点)

将项目部署到阿里云 ECS(CentOS 7)时,需按顺序执行:

# 1. 安装 JDK 11(SpringBoot 2.7.x 要求) sudo yum install java-11-openjdk-devel # 2. 安装 MySQL 5.7(官方源) sudo rpm -Uvh https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm sudo yum install mysql-community-server # 3. 初始化数据库(执行 m_blog.sql) mysql -u root -p < /path/to/m_blog.sql # 4. 后端 JAR 启动(nohup 后台运行) nohup java -jar blog_demo-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > app.log 2>&1 & # 5. 前端构建生产包(在 blog-vue 目录执行) npm run build # 生成 dist/ 目录 # 6. Nginx 配置反向代理(/etc/nginx/conf.d/blog.conf) server { listen 80; server_name your-domain.com; location / { root /var/www/blog-vue/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }

关键点:--spring.profiles.active=prod激活生产配置(可单独建application-prod.yml);Nginx 的location /api/必须以/结尾,否则代理路径错误;try_files $uri $uri/ /index.html解决 Vue Router history 模式下的 404 问题。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 20:29:56

无人机航拍小目标检测:YOLOv11与SAHI切片推理实战指南

1. 为什么无人机航拍的小目标检测这么难先说一个很多人踩过的坑&#xff1a;把VisDrone这类无人机数据集直接丢进YOLOv11里训练&#xff0c;出来的mAP50可能只有10%上下&#xff0c;推理时远处的行人、车辆压根检测不出来&#xff0c;全是漏检。这不是模型不行&#xff0c;而是…

作者头像 李华
网站建设 2026/9/16 20:29:49

Rust具身智能执行层:ZeroClaw的确定性调度与物理世界映射

1. 项目概述&#xff1a;从 Rust 运行时到具身智能体的“心跳”执行流ZeroClaw 是 OpenClaw 生态中一个关键的轻量级具身硬件控制层&#xff0c;它不是传统意义上跑在服务器上的大模型服务&#xff0c;而是一个扎根于物理设备边缘、直连电机/传感器、以毫秒级响应驱动真实动作的…

作者头像 李华
网站建设 2026/9/16 20:26:00

企业官网建设:展示型与获客型的技术架构与策略

1. 企业官网的核心定位之争这个问题困扰着无数企业主和营销负责人。上周刚帮一家制造业客户重新规划官网&#xff0c;老板开门见山就问&#xff1a;"我们每年花十几万维护的官网&#xff0c;到底该做成电子版宣传册还是销售漏斗&#xff1f;"这其实反映了企业官网建设…

作者头像 李华
网站建设 2026/9/16 20:25:43

CentOS 7 GNOME桌面无限转圈?SELinux安全上下文修复实录

先交代一下背景&#xff1a;我这边遇到的情况是VMware虚拟机里装好了CentOS 7的GNOME桌面&#xff0c;第一次重启还能进系统&#xff0c;第二次启动直接卡在登录界面的“花瓣”加载动画上——转圈、转圈、再转圈&#xff0c;鼠标还能动&#xff0c;但就是进不了桌面。试过等待十…

作者头像 李华
网站建设 2026/9/16 20:23:47

STM32空气监测系统源码拆解:从ADC采样到数据可视化全流程解析

简介&#xff1a;基于STM32单片机空气监测系统设计毕业设计资料包&#xff0c;主要面向嵌入式、自动化、电子信息等专业的在校学生与开发者&#xff0c;适用于毕业设计、课程设计、项目初期演示及二次开发入门。压缩包内共包含312个文件&#xff0c;整包大小15.32MB&#xff0c…

作者头像 李华