ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

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

JavaEE博客系统实战:SpringBoot+Vue全链路骨架 简介本资源是一套完整的JavaEE课程期末大作业实战项目面向高校计算机专业学生及SpringBootVue初学者提供可直接运行的前后端分离博客系统解决方案。项目采用SpringBoot构建后端服务Vue CLI搭建前端界面整合SSMPSpringSpringMVCMyBatisPageHelper技术栈覆盖用户管理、文章发布、分类标签、评论互动等核心博客功能适合作为课程设计、实训考核或技术栈整合练习。压缩包共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.yml里spring.datasource.url后面跟着?useSSLfalseserverTimezoneGMT%2B8这串参数时你就知道这不是玩具项目。2. 数据库建模与后端服务启动从 SQL 脚本到可运行的 REST API2.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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 文章表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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;提示article表未设外键约束如FOREIGN KEY (user_id) REFERENCES user(id)这是 SpringBoot 项目常见做法——将关联逻辑交给 MyBatis-Plus 的TableField和 Service 层处理避免数据库级约束增加部署复杂度。若需强一致性可在application.yml中开启spring.jpa.hibernate.ddl-autovalidate进行启动校验。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.ymlspring: datasource: url: jdbc:mysql://localhost:3306/m_blog?useSSLfalseserverTimezoneGMT%2B8characterEncodingutf8 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 直接运行主类适合开发调试在 IDEA 中打开blog_demo目录等待 Maven 自动导入依赖约 1~2 分钟找到com.example.blog.BlogDemoApplication.java右键 →Run BlogDemoApplication.main()控制台输出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类解析username和role。若 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 中的 vue2.6.14、element-ui2.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.json中scripts定义了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.roleVuex 中持久化的用户角色由/api/user/info接口返回后 commit 到 state注意Admin.vue中的el-menu组件通过v-ifuserRole 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) // 允许携带 cookieJWT 通常不用但登录态需 .maxAge(3600); } }参数说明allowedOrigins必须写具体域名*在allowCredentialstrue时被禁止maxAge3600表示预检请求缓存 1 小时减少 OPTIONS 请求次数。方案二Vue CLI 代理更贴近生产部署逻辑修改vue.config.jsmodule.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.yml中jwt.secret与前端utils/request.js的token解析密钥不一致检查后端JwtTokenUtil.java的SECRET_KEY是否等于 yml 中的jwt.secret值发布文章时报500 Internal Server Error日志显示NullPointerException前端提交的categoryId为null后端ArticleService.save()未做空值校验在ArticleController.java的save()方法前添加NotNull注解或在 Service 层if (article.getCategoryId() null) article.setCategoryId(0L);登录页输入正确账号密码返回{code:400,msg:用户名或密码错误}数据库user表中password字段存储的是明文而代码使用BCryptPasswordEncoder校验执行 SQL 更新密码UPDATE user SET password$2a$10$ZQVJqYbKfFgH7XpLmNtOeOqRrSsTtUuVvWwXxYyZzAaBbCcDdEeFf WHERE usernameadmin;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 rootlocalhost数据库用户名/密码错误检查application.yml中spring.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.yml中url的serverTimezoneGMT%2B8是否正确Windows 系统必须加%2B代替关闭 Windows 防火墙临时测试java.sql.SQLException: Cannot load connection class because of underlying exceptionJDBC 驱动版本不匹配查看pom.xml中mysql-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.vuetemplate div el-button typeprimary clickdialogVisible true新增分类/el-button el-table :datacategories el-table-column propname label分类名称/el-table-column el-table-column label操作 template slot-scope{row} el-button sizemini clickhandleEdit(row)编辑/el-button /template /el-table-column /el-table el-dialog title新增分类 :visible.syncdialogVisible el-input v-modelform.name placeholder请输入分类名称/el-input div slotfooter el-button clickdialogVisible false取消/el-button el-button typeprimary clicksubmitForm确定/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仅继承BaseMapperArticle所有 SQL 由 MyBatis-Plus 动态生成。若需快速扩展字段如给article表加view_count浏览量只需三步在Article.java实体类中添加字段TableField(view_count) private Integer viewCount 0; // 默认值 0在ArticleMapper.java中添加自定义方法无需 XMLUpdate(UPDATE article SET view_count view_count 1 WHERE id #{id}) int incrementViewCount(Param(id) Long id);在ArticleService.java中调用public void updateViewCount(Long id) { articleMapper.incrementViewCount(id); }优势避免手写 XML 映射文件降低出错率Update注解直接执行原生 SQL性能优于LambdaUpdateWrapper的链式调用。5.3 生产环境部署 checklist阿里云 ECS 实操要点将项目部署到阿里云 ECSCentOS 7时需按顺序执行# 1. 安装 JDK 11SpringBoot 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.activeprod app.log 21 # 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.activeprod激活生产配置可单独建application-prod.ymlNginx 的location /api/必须以/结尾否则代理路径错误try_files $uri $uri/ /index.html解决 Vue Router history 模式下的 404 问题。本文还有配套的精品资源点击获取
返回列表