行业资讯
📅 2026/9/1 2:43:16
Vue+SpringBoot个人博客实战复盘:从选型到部署的完整指南
简介这是一套面向Web全栈初学者与进阶开发者的VueSpringBoot个人博客项目源码聚焦前后端分离架构实践帮助开发者系统掌握现代Java Web与前端工程化开发流程。资源共537个文件涵盖98个Java后端业务与配置类、202个Vue组件及逻辑JS、107个HTML模板页、60个CSS样式文件以及SVG/ICO等静态资源整体压缩包仅4.34MB轻量易读。已有54人学习下载适合用于课程设计、技术面试准备或个人作品集搭建。项目结构规范含完整RESTful API接口、Vuex状态管理、Vue Router路由控制、axios请求封装、Spring Security权限校验及JPA数据访问层预览可见nginx.conf部署配置、editormd富文本编辑器集成、多种CSS主题如ambiance.css、typo.css和动画支持体现真实博客系统的功能完整性与工程可维护性。 前段时间整理硬盘翻出一个名为“一个基于VueSpringBoot的个人博客项目.zip”的压缩包解压后看着里面熟悉的前后端目录突然觉得这种“看起来人人都会做、但真正做完整并不容易”的练手项目其实特别适合写一篇完整的复盘。个人博客在技术上不算新鲜但它的覆盖面非常广前端要处理路由、状态管理、列表渲染、交互体验后端要处理鉴权、分页、文件上传、数据库设计再加上前后端联调和部署整个流程走完基本能摸清一个商业项目的完整链路。如果你正在学Vue和SpringBoot想做毕设或者想给简历加一个拿得出手的实战项目这个项目模板都能直接拿来用。这篇文章我不会只贴代码而是会把你从零到一搭这个项目时最容易卡住的环节比如版本选择、目录结构、JWT鉴权、跨域代理、Docker部署、各种诡异报错全部摊开讲清楚。每个选择背后都会解释为什么这么做踩过的坑也会单独整理成速查表。内容比较多建议先收藏再慢慢看。1. 动手前先把技术选型和模块结构定明白1.1 为什么是Vue加SpringBoot而不是其他组合很多新手一上来就问“用什么框架好”其实个人博客这种体量的项目核心诉求是快速落地、结构清晰、方便扩展Vue加SpringBoot恰好在这三点上都很稳。Vue的优势在于渐进式你不需要一开始就引入全家桶可以先只用vue-router做页面跳转等需要全局共享用户信息时再加Vuex或Pinia。它的响应式机制和组件化写法对个人开发者非常友好写一个文章列表页不需要太多模板代码。而SpringBoot的价值在于“约定大于配置”内置Tomcat、自动配置数据源、依赖启动器一拉就齐不需要像传统SSM那样写一堆XML。个人博客的接口数量不多但涉及文章、分类、标签、评论、用户等多个资源SpringBoot的分层结构恰好能把职责理清楚。还有一个现实原因招聘市场上Vue和SpringBoot的需求量大面试常考做这个项目的过程本身就是在帮自己过一遍高频考点这点后文提到的一些细节里会体现。1.2 项目功能模块与页面结构梳理拿到标题里的zip之前先别急着写代码把功能模块盘清楚。我做的这个项目把功能拆成了三块前台展示文章列表、文章详情、分类筛选、标签聚合、关于页面、站内搜索。后台管理管理员登录、文章发布与编辑、分类和标签维护、个人资料设置。公共支撑图片上传、JWT鉴权拦截、全局异常处理、日志记录。前台是门户追求的是加载速度和阅读体验后台是工具追求的是操作效率公共支撑是底子决定项目安不安全、稳不稳定。这种拆法还有一个好处就是前后端接口可以直接按模块划分比如前台接口统一用/blog前缀后台接口统一用/admin前缀后文鉴权拦截器就靠这个前缀区分哪些接口需要登录。1.3 技术栈清单与版本选择建议版本问题是我最想先说的因为我在这个项目上被“版本太高”坑过两次。先交代一下当时用的完整技术栈前端Vue 2.6用vue-cli 4构建没用Vite主要考虑当时项目脚手架稳定、vue-router 3、Vuex 3、axios、element-ui、marked渲染Markdown、highlight.js代码高亮。后端SpringBoot 2.3.7、JDK 8、MyBatis-Plus 3.4、MySQL 5.7、JWTjjwt 0.9.1、Swaggerknife4j。部署前端Nginx、Docker Compose编排后端与MySQL。如果你是现在新建项目我建议用Vue 3加Vite加Pinia后端用SpringBoot 2.7或3.x加JDK 17。但有一个原则要记住不要无脑追最新版本。SpringBoot 3.x要求JDK 17以上如果你的机器装的是JDK 8那老老实实用SpringBoot 2.x否则一启动就报UnsupportedClassVersionError排查半天才发现是JDK不匹配纯浪费时间。热词里提到“springboot版本太高”大概率就是这个原因。2. 后端SpringBoot核心功能实现2.1 工程初始化与IDEA建项目时的几个坑后端工程我推荐用IDEA的Spring Initializr创建但热词里有人提到“idea新建项目没有springboot 3.4.3选项”这里解释一下Spring Initializr页面显示的版本是它默认的联网版本不是固定不可变的。你可以在创建后直接改pom.xml里的parent版本号前提是对应版本的依赖都能在中央仓库拉到。没有合适的版本选项时还容易漏掉一个小地方——IDEA的Server URL默认是start.spring.io国内网络环境下拉取慢或失败很常见。我一般会先手工在pom.xml里把依赖写好再让Maven去拉。用阿里云镜像仓库能解决大多数拉包失败问题repositories repository idaliyun/id urlhttps://maven.aliyun.com/repository/public/url /repository /repositoriespom.xml里的核心依赖就五样spring-boot-starter-web、spring-boot-starter-validation、mybatis-plus-boot-starter、mysql-connector-java、jjwt。如果要做Swagger再加上knife4j-spring-boot-starter。代码尽量少用lombok的Data虽然省事但面试里经常被问“你知道Data的坑吗”搞清getter/setter和equals/hashCode的生成规则再决定用不用也不迟。2.2 数据库设计与表关系个人博客的数据量级不会很大但表结构一定要设计清楚不然写到后面会乱。我建的表有这些表名主要字段说明t_userid, username, password, nickname, avatar, create_time管理员账号字段不要带plain密码校验逻辑t_articleid, title, summary, content, cover_image, category_id, create_time, update_time, status文章主体status用于草稿和发布t_categoryid, category_name, description分类一个分类对应多篇文章t_tagid, tag_name标签纯列表t_article_tagarticle_id, tag_id文章和标签的多对多关联表t_commentid, article_id, nickname, content, create_time评论先做游客评论登录评论属于扩展文章表和分类表用外键逻辑关联但物理上我没建外键约束。原因很简单个人博客读写压力不大外键约束会影响插入删除效率而且后台删除分类时需要自己写逻辑判断“该分类下是否有文章”这比数据库抛外键异常更好处理。MyBatis-Plus里表名的下划线命名会自动映射到实体类的驼峰属性配合逻辑删除字段还可以做到删除文章后用户端自动不可见。2.3 文章接口怎么分页、怎么返回统一结构后端接口设计最忌讳每个接口返回格式不一致。我在项目里封装了一个统一返回体Result里面包含code、message、data三个字段。code为200表示成功401表示未登录500表示服务端异常。前端axios拦截器拿到code后统一处理不用每个页面重复写错误提示。文章列表接口是核心中的核心我用了MyBatis-Plus的Page分页插件前端传入pageNum和pageSize后端返回总条数和当前页数据。这里有几个细节值得注意列表页不需要返回文章正文content只返回summary即可减少网络传输量。按分类和标签筛选时用LambdaQueryWrapper的eq方法拼接条件避免判断空条件时的尴尬。排序用create_time倒序同时加一个status等于1的过滤条件保证草稿不会出现在前台。public Result getArticlePage(Integer pageNum, Integer pageSize, Long categoryId, Long tagId) { PageArticle page new Page(pageNum, pageSize); LambdaQueryWrapperArticle wrapper new LambdaQueryWrapper(); wrapper.eq(Article::getStatus, 1) .eq(categoryId ! null, Article::getCategoryId, categoryId) .orderByDesc(Article::getCreateTime); articleMapper.selectPage(page, wrapper); return Result.success(page); }先按条件过滤再排序是MyBatis-Plus最常见的写法。关键是eq方法第一个参数传布尔值条件不成立时自动忽略这个条件避免了手写if判断的重复代码。2.4 登录鉴权与JWT无状态认证个人博客一般就一个管理员但鉴权逻辑不能省因为这是面试必问的点。我用的方案是JWT加拦截器用户登录成功后后端生成一个包含userId和username的token返回给前端前端存到localStorage里每次请求在请求头加Authorization字段后端拦截器校验token有效性。JWT由三部分组成Header、Payload、Signature。Header指明加密算法Payload放用户信息Signature用密钥对前两部分签名。这样做的好处是服务端不需要保存session天然适合前后端分离也方便以后扩展成多端登录。public String generateToken(Long userId, String username) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 24)) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }密码存储我用了MD5加盐严格来说SHA-256加盐更安全或者直接用BCrypt但个人博客的威胁模型有限MD5加随机盐已经比明文存储强太多。重点是盐不能写死最好每个用户独立生成一个盐存储在用户表里。密码校验时先取出盐再对输入密码加盐哈希比较。拦截器实现上要注意注册/登录接口要放行/admin/**的后台接口必须校验/blog/**前台接口可以放行。如果是SpringBoot 2.x自定义拦截器实现HandlerInterceptor在preHandle里校验token再通过WebMvcConfigurer注册拦截路径。2.5 接口文档与Swagger集成后端接口写完最好接一个Swagger省得每次联调都让前端同事来问“这个接口参数叫什么”。我用knife4j它对Swagger的UI做了增强界面比原版好看不少而且在SpringBoot 2.3.7下兼容性稳定。配置的时候注意几件事要写Docket的select路径只扫描Controller包的接口不要扫到不需要暴露的内部类。开启注解后Controller方法上要写ApiOperation标注接口含义类上写Api描述模块。不写的话Swagger页面全是英文方法名等于没配。如果集成了JWT可以在Swagger里配置全局token参数方便直接在文档页面调试需要登录的接口。热词里提到“springboot解决pdf xss攻击”虽然场景不同但文章内容预览也可能遇到XSS问题。尤其是后台富文本编辑器提交的内容如果直接在前端v-html渲染很容易被注入恶意脚本。我的处理办法是后端做一层过滤把script标签、onload事件等危险内容转义。前端也要对用户输入内容做一次清理双重保障。3. 前端Vue核心功能实现3.1 环境准备npm、vue-cli、node_modules报错前端部分先从环境说起。热词里有一条“the project can not found node_modules”这几乎是每个Vue新人都会遇到的坑。原因很简单项目的依赖是记录在package.json里的但实际的node_modules文件夹不会跟着项目一起提交。你从git或压缩包拿到项目后必须自己执行npm install把依赖安装到本地。npm install本身也有坑。默认源是npmjs官方源国内下载速度慢、容易超时。我的做法是先永久切换源到淘宝镜像npm config set registry https://registry.npmmirror.com然后再执行npm install基本一分钟左右能装完。如果已经安装了依赖但node_modules损坏优先删除整个依赖目录再重装rm -rf node_modules npm cache clean --force npm install前端工程创建推荐几个方式Vue 2项目用vue create命令Vue 3项目推荐用npm create vite。vue create会自动帮你装好vue-router和Vuex但版本选择时要注意Vue 2对应vue-router 3Vue 3对应vue-router 4两者API差别不小别混着来。3.2 路由设计与权限控制前端路由是整个项目的骨架。前台页面按模块拆分后台页面单独放在/admin路径下。路由懒加载是必做的优化每个路由组件单独打包成chunk首屏只加载首屏需要的文件const ArticleDetail () import(/views/ArticleDetail.vue)路由守卫我用在登录校验。只有路由meta里配置requiresAuth为true的页面才需要检查token比如后台管理页。没有token就跳转到登录页这个逻辑写在全局前置守卫里比每个页面单独判断省事得多。vue-router还有一个高频考点是路由传参。列表页跳到详情页通常用动态路由传id访问路径是/article/123。详情页里通过this.$route.params.id读取。这里要注意一个坑从文章列表连续点击不同文章时组件实例会被复用不会重新走created钩子。解决办法是在文章详情组件里watch $route对象的变化变化时重新拉取数据。3.3 文章列表与详情页的数据交互文章列表页的核心是axios请求后端接口。我封装了一个request.js统一设置baseURL和请求拦截器。请求拦截器里读取localStorage中的token加到请求头响应拦截器里统一处理后端返回的code如果code是401就清除本地token并跳转登录页。service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config })列表页的组件通信要处理好分页。我用了el-pagination组件页码变化时重新调用接口接口返回的total用于渲染总条数。搜索筛选和分类筛选共用同一个请求方法参数变化时把页码重置为1否则会出现“你在第三页搜了个关键词但列表是空的”这种低级bug。详情页除了展示文章内容还有上一篇下一篇和评论列表。评论提交我用了一个表单校验评论内容长度在1到200之间昵称必填。前端校验只是用户体验后端Controller同样要用Validated做参数校验防止绕过前端直接curl提交脏数据。3.4 后台管理富文本编辑器与图片上传后台管理页面是后台管理能力的集中体现我实现了三个核心功能登录、文章发布、分类维护。登录页用el-form做表单校验提交时调用登录接口成功后把token和用户昵称存到localStorage再跳转到后台首页。文章发布页用mavon-editor作为Markdown编辑器因为它同时支持编辑和预览而且是中文界面。编辑器的内容直接绑定到文章的content字段通过v-model双向同步到表单对象里。图片上传是后台最容易被低估的功能。前端用el-upload组件上传时要设置name字段和后端接口一致否则后端拿不到文件。上传成功后后端返回图片URL前端把URL插入到编辑器当前光标位置这样文章里就能正常显示图片。el-upload action/admin/upload namefile :on-successhandleUploadSuccess el-button上传图片/el-button /el-upload后端接收MultipartFile文件后要做三件事校验文件类型只允许jpg、png、gif、webp、限制文件大小我设为5MB、生成不重复的文件名用UUID加时间戳。文件保存路径可以放在服务器本地或OSS个人项目保存到服务器本地通过nginx映射访问就够了。3.5 computed、watch与keep-alive的实战区别热词里反复出现“vue computed”说明这是前端面试的必考点。在实际项目里computed用来做派生状态它依赖的响应式数据变化时会自动重新计算而且有缓存。比如文章详情页的阅读时长依赖content内容计算得到computed: { readingTime() { const words this.article.content.replace(/[#*\-\s]/g, ).length return Math.max(1, Math.ceil(words / 300)) } }watch则适合处理异步或开销较大的操作比如监听路由参数变化重新拉文章详情。两者并不是完全替代关系computed不能执行异步操作watch可以。热词里还有“vue keep-alive切换路由子组件el-table滚回头部”这个坑我也踩过。后台文章列表有几十条数据你往下滚动到了第20条切到别的菜单再切回来表格滚动位置回到了顶部。原因很简单组件被销毁后重新创建DOM是全新的。解决办法是用keep-alive缓存组件状态但直接用keep-alive缓存整个后台页面又会导致表格数据不是最新的。折中方案是只在列表页缓存activated钩子里重新拉取数据activated() { this.fetchList() }要注意的是如果列表数据不需要每次切换都刷新就别在这里拉接口否则缓存等于白做。4. 前后端联调、部署与问题排查4.1 开发环境联调跨域代理配置前后端分离项目最烦的就是跨域问题。浏览器同源策略会拦截不同端口号下的请求前端开发服务器运行在8080端口后端运行在9090端口直接请求必然跨域。两种解决方法我都用过推荐优先用Vue的devServer代理module.exports { devServer: { proxy: { /api: { target: http://localhost:9090, changeOrigin: true } } } }这样前端代码里请求/api/blog/article开发服务器会把请求转发到http://localhost:9090/api/blog/article。浏览器看请求是同源的就不会拦截。这个方案不需要后端配合开发环境最方便。但生产环境就不能靠它了生产环境直接由Nginx把/api前缀的请求反向代理到后端服务。如果你接手的是一个已经部署的项目后端接口地址和前端页面地址不同域名那就得在后端加跨域配置Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8080) .allowedMethods(*) .allowedHeaders(*); } }CORS配置最注意allowedOrigins不能写*同时又想带cookie这样会被浏览器拒绝。个人博客如果没有跨域登录需求建议不要开启allowCredentials避免增大安全面。4.2 Docker部署SpringBoot后端部署这块我觉得是整个项目里含金量最高的部分因为很多人写代码很溜但一到部署就发怵。我用了Docker Compose统一管理后端和数据库先说后端最简DockerfileFROM openjdk:8-jre-alpine COPY target/blog-server.jar /app.jar ENTRYPOINT [java, -jar, /app.jar]构建的时候先执行Maven打包mvn clean package -DskipTests docker build -t blog-server .如果数据库也容器化docker-compose.yml里把后端和MySQL编排在一起方便一键启动。有一个细节要注意后端连接数据库的地址不能写成localhost因为容器内的localhost指向容器自己要写成服务名比如dbDocker Compose会做DNS解析version: 3 services: db: image: mysql:5.7 environment: - MYSQL_ROOT_PASSWORD123456 - MYSQL_DATABASEblog_db volumes: - ./mysql-data:/var/lib/mysql server: build: . ports: - 9090:8080 depends_on: - db environment: - SPRING_DATASOURCE_URLjdbc:mysql://db:3306/blog_db热词提到“docker部署springboot项目”我补充一个真实经验容器启动时SpringBoot会等MySQL就绪但depends_on只控制启动顺序不控制MySQL是否可连接。如果后端启动比MySQL快会报数据库连接失败。解决方案是在后端启动命令里加一个等待脚本比如sh -c until nc -z db 3306; do sleep 1; done; java -jar /app.jar。或者更省事让SpringBoot数据源增加自动重连配置失败后重试几次。4.3 Nginx部署Vue前端前端构建产物是纯静态文件用Nginx提供服务最合适不过。构建命令npm run build构建完之后dist目录就是最终产物把它上传到服务器Nginx配置一个server块指向该目录。这里的关键难题是vue-router的history模式。前端路由路径是/article/123但服务器上并不存在这个物理文件直接刷新页面会404。解决办法是配合try_files把请求回退到index.htmlserver { listen 80; server_name your-domain.com; root /var/www/blog/dist; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://localhost:9090; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这样前端路由刷新后Nginx会找到index.html由前端路由继续处理。/api前缀的请求反代到后端服务实现前后端在同一域名下互通。4.4 高频报错与排查清单前端和后端联调过程中我整理了高频报错排查表按影响程度排序现象可能原因解决思路启动SpringBoot报UnsupportedClassVersionErrorJDK版本太低升级JDK到对应版本或降级SpringBoot版本页面白屏且控制台报SyntaxError前端代码压缩后语法不兼容低版本浏览器配置browserslist用babel转译ES6文章接口返回401token未携带或token过期检查前端请求拦截器重新登录npm install报network/socket错误默认源连接超时切换镜像源重试后端接口返回500空指针数据库表字段与实体类不对应检查驼峰映射配置和字段名图片上传失败nginx的client_max_body_size限制太小调整为大于5m刷新前端页面404history路由未配置try_files添加try_files回退到index.html评论区昵称乱码数据库字符集不是utf8mb4建库时指定charsetutf8mb4有几个排查技巧值得单独说。后端日志永远看完整堆栈不要只看第一行错误信息往往真正原因在Caused by里。前端排查用Vue Devtools可以实时看组件状态和Vuex数据。热词里提到“vue devtools插件下载”这个插件确实重要装好之后能直接看到响应式数据和路由状态排查问题效率翻倍。如果Chrome商店下不了也可以用源码构建或找离线安装包但要注意来源安全。4.5 安全加固与性能优化清单个人博客虽然流量不大但安全底线不能丢。我在项目上线前做了这几件事密码传输加一层简单的前端加密避免明文走HTTP传输。虽然不是绝对安全但至少不能让抓包的人一眼看到明文密码。后端全局过滤XSS脚本对请求参数中的script、iframe、onerror等关键字进行转义。文章内容在后端渲染成HTML时也做一次过滤防止存储型XSS。对管理后台接口做简单的防暴力破解连续输错5次密码锁定该IP十分钟。用AOP加Reids计数器就能实现不用太复杂。接口层校验入参长度和格式防止提交超大content打爆数据库。性能优化方面个人博客最有效的三板斧是文章列表接口开启MyBatis-Plus分页并且只查需要的字段静态资源图片、JS、CSS交给Nginx直接返回配置缓存头详情页用浏览器缓存文章不常更新可以设置一小时强缓存。Redis缓存我一开始没加因为当时的流量完全用不上后来访问量上来了才在文章详情接口加了缓存逻辑也很简单查Redis没有就查数据库后写入更新文章时删除对应缓存。这套流程跑下来之后我自己最大的感受是真正费时间的不是写CRUD而是解决环境配置、版本兼容和联调细节。你会发现很多问题不是代码逻辑难而是“诶为什么这里会这样”的环境问题。耐心一点把排查思路整理成文档下次遇到就能秒杀。最后再分享一个项目扩展的小技巧博客做出来之后可以顺手给它加一个RSS订阅、sitemap.xml生成、全站搜索关键词统计这些功能不复杂但都很适合写进简历的“项目亮点”里。面试官问你项目的时候能把这些细节讲清楚比堆技术名词加分得多。本文还有配套的精品资源点击获取