简介:这是一套基于 Vue 与 SpringBoot 开发的个人博客系统源码,面向具备 Java 与前端基础、希望完整实践前后端分离与权限管理的开发者。项目后台管理页面采用 Vue 编写,其余页面使用 Thymeleaf 模板,后端整合 Shiro+Jwt 鉴权、MyBatisPlus、MySQL 与 Redis,前端使用 ElementUI、vue-router、Vuex 及 WebSocket,并支持管理员在线分配角色权限与用户端目录缓存,适合作为课程设计、毕业设计或二次开发的学习范本。压缩包共 530 个文件,约 4.27MB,以 js、html、java、css 等源码为主,另含 png、jpg 等静态图片资源及 sql 建库脚本、yml 配置与 md 说明文档,目录结构清晰,便于按模块阅读与调试。目前已有 1344 人学习下载,可帮助读者快速理解博客系统的整体架构、权限控制流程与缓存设计思路。
1. 从一份能跑起来的博客源码说起
前阵子帮一个做前端的朋友看简历项目,他丢过来一个压缩包,说“哥你帮我看看这个 blogSpringBoot 能不能写进简历”。我解压、翻目录、找配置文件,前后不到十分钟就把 MySQL 连接串改成本地、mvn spring-boot:run起后端、npm run serve起前端,浏览器一开,首页文章列表、详情页、后台登录全出来了。这就是一个典型的 Vue + SpringBoot 前后端分离个人博客:后端用 SpringBoot2 扛接口和鉴权,前端 Vue 做页面和路由,数据落 MySQL,Java 侧还拆了公共 library 模块。它适合两类人——想拿一个结构完整、能二次改的项目练手的在校开发者,以及需要快速搭个人站点、又不想从零写 CRUD 的独立开发者。下面我按“它是什么、怎么跑、怎么改”的顺序,把这份源码拆开讲。
2. 项目骨架与技术选型:前后端分离到底分在哪
2.1 技术栈清单与各层职责
这个项目的技术组合很“教科书”,但正因为标准,才适合拿来当二次开发的底座。后端是 SpringBoot2 全家桶,持久层走 MyBatis 或 JPA(看具体模块的 mapper 写法),数据库 MySQL,鉴权常见做法是 JWT 或 Session 拦截器;前端是 Vue 2 + Vue Router + Vuex + Axios,构建工具 Vue CLI。Java 侧单独抽了一个 library 模块,放公共实体、工具类、统一返回体,这样后端多个子模块能复用,不用到处复制Result类。
| 层次 | 技术 | 在这个项目里的职责 |
|---|---|---|
| 前端框架 | Vue 2 + Vue Router + Vuex | 页面渲染、路由跳转、登录态存储 |
| 前端请求 | Axios | 统一封装接口调用、携带 token |
| 后端框架 | SpringBoot2 | REST 接口、拦截器、全局异常 |
| 持久层 | MyBatis / JPA | 文章、分类、标签、评论的 CRUD |
| 数据库 | MySQL | 业务数据落库 |
| 公共模块 | Java library | 实体、工具、统一响应结构 |
选型上没什么花活,但每一层都卡在“够用且好改”的位置。比如为什么不用微服务?个人博客的流量和模块复杂度根本撑不起服务拆分的运维成本,单体 + 分层才是合格开发最可能的选择。为什么前端不直接上 Vue 3?Vue 2 的生态和示例多,新手照着改路由、改组件不容易翻车。
2.2 目录结构:一眼看清代码往哪放
拿到源码先别急着跑,先把目录扫一遍,知道改哪个文件去哪找。典型结构大致是这样(不同分支可能略有出入,但分层逻辑一致):
blogSpringBoot/ ├── blog-backend/ # SpringBoot 后端主工程 │ ├── src/main/java/ │ │ └── com/example/blog/ │ │ ├── controller/ # 接口层,ArticleController 等 │ │ ├── service/ # 业务逻辑 │ │ ├── mapper/ # 持久层接口 │ │ ├── entity/ # 数据库实体 │ │ └── config/ # 拦截器、跨域、MyBatis 配置 │ └── src/main/resources/ │ ├── application.yml # 数据库、端口、JWT 配置 │ └── mapper/ # XML 映射文件(若用 MyBatis) ├── blog-common/ # Java library 公共模块 │ └── src/main/java/... # Result、工具类、常量 └── blog-frontend/ # Vue 前端工程 ├── src/ │ ├── api/ # Axios 接口封装 │ ├── views/ # 页面组件 │ ├── router/ # 路由表 │ └── store/ # Vuex 状态 └── package.json这个结构的关键点在于blog-common被后端依赖,前端通过api/目录集中管理请求地址。改接口时,后端改 controller,前端改 api 封装,两边对齐路径就行。我一般会先看application.yml和package.json,这两个文件决定了项目能不能在你机器上跑起来。
2.3 一次请求的完整链路
理解链路比背目录有用。以“打开文章详情页”为例:Vue 路由匹配到/article/:id,组件在created里调api/article.js封装的方法,Axios 带上 baseURL 和 token 发请求;后端拦截器先校验登录态(公开接口放行),进ArticleController,调ArticleService,再走 mapper 查 MySQL,结果包装成统一Result返回;前端拿到data渲染。这条链路里任何一环配置错,页面就是空白或 404,所以排查时按“前端请求发没发出去 → 后端接没接到 → SQL 查没查到”三段定位,比瞎猜快得多。
3. 把项目跑起来:环境、配置、启动三步走
3.1 环境准备与依赖版本
跑这个项目需要 JDK 8 或 11(SpringBoot2 对 JDK 17 支持有限,别上来就用最新版,容易踩兼容坑)、Maven 3.6+、Node.js 14/16(Vue 2 项目对高版本 Node 的 OpenSSL 有兼容问题)、MySQL 5.7/8.0。先确认版本:
java -version # 期望 1.8 或 11 mvn -v # 期望 3.6+ node -v # 期望 v14 或 v16 mysql --version # 期望 5.7 或 8.0版本不对是最常见的“跑不起来”原因。Node 18+ 跑 Vue CLI 老项目经常报ERR_OSSL_EVP_UNSUPPORTED,这不是代码问题,是构建工具和 Node 的加密库对不上,后面避坑章节会讲怎么处理。
3.2 数据库初始化与 application.yml 配置
先在 MySQL 里建库,导入源码附带的.sql文件(一般在resources或项目根目录的sql/下)。建库语句常见写法:
CREATE DATABASE blog_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE blog_db; -- 然后 source 导入项目自带的初始化脚本 SOURCE /path/to/blog.sql;接着改后端配置。application.yml里重点看三块:数据源、服务端口、鉴权密钥。
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/blog_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 你的本地密码 driver-class-name: com.mysql.cj.jdbc.Driver blog: jwt: secret: 换成你自己的随机字符串 expire: 86400 # token 有效期,单位秒serverTimezone必须显式指定,否则 MySQL 8 驱动会报时区错误;secret别用源码里的默认值,二次开发部署到公网前一定要换。改完这些,后端基本就能连上库了。
3.3 后端启动与前端联调
后端在blog-backend目录下执行:
cd blog-backend mvn clean install -DskipTests # 先装公共模块和依赖 mvn spring-boot:run # 启动,看到 Started ... 即成功如果blog-common是独立模块,要先在根目录mvn install把它装进本地仓库,否则后端编译会找不到Result类。前端在blog-frontend下:
cd blog-frontend npm install # 依赖装好,网络慢可换镜像源 npm run serve # 默认 8081 或 8080,看 vue.config.js前端开发服务器通常配了代理,把/api转发到后端 8080,避免跨域。打开浏览器访问前端地址,能看到首页文章列表就说明前后端通了。登录后台用 SQL 脚本里预置的账号,或者自己往用户表插一条(密码注意用项目里的加密方式,常见是 MD5 或 BCrypt)。
提示:第一次跑建议先只启动后端,用 Postman 或浏览器直接访问
http://localhost:8080/api/article/list,确认接口返回 JSON 正常,再启动前端。这样出问题时能快速判断是后端没起还是前端代理配错。
4. 核心模块拆解:文章、鉴权、公共返回体
4.1 文章模块:从 Controller 到 Mapper 的一条线
文章是博客的主干,增删改查、分页、按分类标签筛选都在这条线上。Controller 层只做参数接收和结果包装,业务判断放 Service,SQL 放 Mapper。看一段典型的 Controller 写法:
@RestController @RequestMapping("/api/article") public class ArticleController { @Autowired private ArticleService articleService; // 分页查询文章列表,page 从 1 开始,size 每页条数 @GetMapping("/list") public Result<PageResult<Article>> list( @RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) Long categoryId) { PageResult<Article> data = articleService.pageQuery(page, size, categoryId); return Result.success(data); } // 按 id 查详情,公开接口 @GetMapping("/{id}") public Result<Article> detail(@PathVariable Long id) { return Result.success(articleService.getById(id)); } }@RequestParam的defaultValue保证前端不传分页参数时也有兜底;categoryId设为非必填,支持“全部文章”和“某分类下文章”两种场景。Service 层做分页计算(PageHelper或手写limit offset),Mapper 里对应一条带where category_id = ?的动态 SQL。改这个模块最常见的需求是加“按标签筛选”或“关键词搜索”,思路是在 Controller 加参数、Service 拼条件、XML 里加<if>判断,三层同步改,别只改一层就以为完事。
4.2 登录鉴权:拦截器 + token 的落地方式
后台管理需要登录,公开页面不需要。常见做法是写一个HandlerInterceptor,在preHandle里判断请求路径是否在白名单,不在就校验请求头里的 token。
public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行预检请求,否则跨域时浏览器直接拦掉 if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { return true; } String token = request.getHeader("Authorization"); if (token == null || !JwtUtil.verify(token)) { response.setStatus(401); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":401,\"msg\":\"未登录\"}"); return false; } return true; } }这段代码有两个容易忽略的点:一是OPTIONS预检必须放行,否则前端跨域请求全挂;二是返回 401 时要自己写 JSON,不能只return false,不然前端拿到空响应不好处理。拦截器注册在WebMvcConfig里,用addPathPatterns("/api/admin/**")圈定需要鉴权的范围,公开接口自然不受影响。token 的签发在登录接口里,校验通过后JwtUtil.generate(userId)返回给前端,前端存 localStorage,Axios 拦截器统一塞进请求头。
4.3 公共 library 模块:统一返回体为什么值得单独抽
blog-common里最核心的是统一响应结构。没有它,每个接口返回格式五花八门,前端得写一堆判断。抽出来之后,所有接口都返回同一个壳:
public class Result<T> { private Integer code; // 200 成功,401 未登录,500 异常 private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.msg = "ok"; r.data = data; return r; } public static <T> Result<T> fail(Integer code, String msg) { Result<T> r = new Result<>(); r.code = code; r.msg = msg; return r; } }配合全局异常处理器@RestControllerAdvice,Service 里抛出的业务异常会被统一捕获并转成Result.fail,前端永远只处理一种结构。这个模块单独抽成 library 的好处是:以后加新模块(比如文件上传、评论审核)直接依赖它,不用复制粘贴。二次开发时如果你要加新接口,记得返回值也走Result,别图省事直接返回实体,不然前端封装又得开特例。
5. 避坑与排查:那些让项目起不来的细节
5.1 跨域配置与前端代理打架
现象:前端调接口报CORS policy错误,或者请求发出去但后端收不到。原因通常是后端配了@CrossOrigin,前端vue.config.js又配了proxy,两套机制同时生效反而互相干扰;也可能是后端跨域配置漏了Authorization头。解决:二选一,开发环境用前端代理最省事,后端跨域配置只在生产直连时开;如果保留后端跨域,allowedHeaders要显式包含Authorization,allowCredentials和allowedOrigins不能同时用*。
5.2 Node 版本过高导致前端编译失败
现象:npm run serve报ERR_OSSL_EVP_UNSUPPORTED或digital envelope routines::unsupported。原因是 Node 17+ 换了 OpenSSL 3,Vue CLI 老版本用的 webpack 4 不兼容。解决:最稳的是把 Node 降到 16;不想降版本就在package.json的 scripts 里加NODE_OPTIONS=--openssl-legacy-provider,Windows 下用set NODE_OPTIONS=...&&的写法。这是环境问题,不是代码 bug,别去改业务逻辑。
5.3 数据库时区与字符集报错
现象:启动时报The server time zone value 'xxx' is unrecognized,或者中文文章标题存进去变问号。原因:JDBC URL 没带serverTimezone,或者库/表字符集不是utf8mb4。解决:URL 补serverTimezone=Asia/Shanghai,建库时指定utf8mb4,连接串加characterEncoding=utf8。如果已经建了库,用ALTER DATABASE blog_db CHARACTER SET utf8mb4;补救,表也要逐张改。
5.4 公共模块没装导致编译找不到类
现象:后端编译报cannot find symbol: class Result。原因:blog-common是独立 Maven 模块,没执行mvn install就没进本地仓库,主工程依赖解析不到。解决:在项目根目录先mvn clean install -DskipTests,或者 IDE 里把 common 模块加入聚合工程一起构建。这个坑在多模块项目里非常典型,血泪经验是:拿到多模块源码,先看根pom.xml的<modules>,按顺序装。
5.5 登录后接口仍返回 401
现象:前端明明登录成功、localStorage 里也有 token,但调后台接口还是 401。原因通常是 Axios 请求拦截器没把 token 塞进 header,或者 header 名字和后端读取的不一致(Authorizationvstoken),也可能是 token 过期时间配得太短。解决:打开浏览器 Network 面板看请求头到底带没带,对照后端preHandle里getHeader的字段名,两边对齐;再检查expire配置和服务器时间是否偏差过大。
6. 二次开发改造:从能跑到好用
项目跑通只是起点,真正体现功力的是怎么在它上面加东西而不把原有结构搞乱。我一般按“先加只读功能、再动写操作、最后碰鉴权”的顺序改,风险从低到高。
第一个改造点:加文章搜索。后端在ArticleController的 list 接口加keyword参数,Service 里拼title like concat('%', #{keyword}, '%'),Mapper XML 用<if test="keyword != null and keyword != ''">包住。前端在列表页加一个输入框,调接口时带上参数。验证方法很直接:搜一个确定存在的标题片段,看返回条数和内容对不对;再搜一个不存在的词,确认返回空列表而不是报错。
第二个改造点:加文章浏览量。详情接口里查完文章后article.setViews(article.getViews() + 1)再 update,注意并发下用update article set views = views + 1 where id = ?的原子写法,别先查再写,否则高并发会丢计数。前端详情页展示views字段即可。
第三个改造点:把 JWT 密钥和数据库密码外置到环境变量。application.yml里写${BLOG_JWT_SECRET:默认值},部署时用环境变量覆盖,避免密钥跟着源码泄露。这一步做完,项目才算具备上线的基本安全习惯。
| 改造需求 | 改动位置 | 验证方式 |
|---|---|---|
| 文章搜索 | Controller 加参、Service 拼条件、XML 加 if | 搜存在/不存在关键词各一次 |
| 浏览量统计 | Service 原子 update、前端展示字段 | 刷新详情页看数字递增 |
| 配置外置 | application.yml 用占位符 | 不设环境变量能跑,设了能覆盖 |
改完记得回归:公开页面不登录能看,后台不登录进不去,登录后能增删改文章。这三条过了,基本没把原有功能改坏。从那以后我每次接手这类前后端分离的博客源码,都强制先跑通“列表 → 详情 → 登录 → 发文章”这条最小闭环,再动任何一行代码,因为很多看似复杂的问题,其实只是某一步配置没对上。希望这份拆解能帮你少走点弯路,把这份源码真正变成自己能改、能讲清楚的项目。
本文还有配套的精品资源,点击获取