我见过太多人做个人博客,要么用WordPress套个主题草草了事,要么拿起Hexo写两篇就吃灰。真正想练手、想掌握前后端分离开发全流程的人,最后还是绕不开Spring Boot加Vue这套组合。手头这套博客系统,就是我当时从零搭起来、后来反复重构过的完整项目,包含源码、数据库脚本和配套文档,拿来学习、改造成自己的作品都挺好用。
这套系统解决的核心痛点很实在:前端Vue负责页面交互和渲染,后端Spring Boot提供干净的接口服务,两边通过JSON格式的数据沟通,互不干扰。你不需要在页面里混写Java代码,也不用担心模板引擎的各种限制,功能拆得清清楚楚,扩展起来特别顺手。
尤其适合三类人:一是刚学完Spring Boot基础、想找个完整项目练手的人;二是正在做毕业设计、需要一个能讲清楚原理的系统的学生;三是想搭一个真正属于自己的技术博客、又不想被现成框架绑死的开发者。接下来的内容,我会先讲整体设计思路,再拆解核心功能模块和数据库建模,然后走一遍实操流程,最后把开发中容易踩的坑都列出来。
1. 内容整体设计与思路拆解
1.1 为什么是Spring Boot + Vue这个组合
个人博客系统听上去功能不多,无非是写文章、贴标签、挂评论,但真要做得顺手、代码不烂尾,技术选型得认真琢磨。我最早也考虑过直接用JSP加Servlet的传统玩法,理论上也能跑通,但写起来太累:页面里混着Java代码,改个样式得整个页面重新编译,前端稍复杂一点就寸步难行。后来换成Thymeleaf模板引擎,比JSP干净一些,但本质上还是服务端渲染的路子,页面交互全靠后台拼接好再返给浏览器,遇到异步刷新、局部更新这些需求,代码会变得很别扭。
Spring Boot加Vue这套组合的好处在于,前后端彻底分离,各干各的活。后端只用Spring Boot写API接口,接收请求、处理业务逻辑、返回JSON数据;前端Vue负责页面展示、路由跳转、状态管理,完全不用关心数据是怎么存进数据库的。两边通过HTTP协议通信,定义好接口规范就能并行开发。我在实际开发中感受最深的一点是,前端改交互逻辑时,后端接口完全可以不动,只调整页面组件和数据绑定就行,效率提升特别明显。
1.2 技术栈选型参考
这套系统主流程用的技术栈是Spring Boot 2.7加Vue 2.6,当时选这个版本组合主要考量稳定性和生态成熟度。Spring Boot 2.7还在维护期内,依赖兼容性很好;Vue 2.6虽然不如Vue 3新,但Element UI组件库在这个版本下最顺手,网上能查到的踩坑经验也最多。如果你现在从头搭,选Spring Boot 3加Vue 3也完全没问题,只是注意Spring Boot 3要求JDK 17以上,MyBatis-Plus等相关依赖也要选对应新版本。
后端持久层我用的是MyBatis-Plus,选它而不是原生MyBatis,最大的优点是内置了通用的增删改查方法,单表操作几乎不用写SQL,省下大量样板代码。代码生成器还能根据数据库表直接生成实体类、Mapper接口和Service层代码,建好表之后几分钟就能把基础代码骨架拉起来。权限认证这块用JWT加拦截器实现,登录成功后签发一个Token,前端每次请求带上,后端拦截器校验身份,不用在服务端存Session,做前后端分离项目这个方案是标配。
前端部分只用了Vue生态里最核心的几个库:Vue Router做页面路由,Axios发HTTP请求,Element UI提供现成的UI组件。状态管理方面,这个项目规模用不上Vuex大动干戈,非要用的话可以加,但大部分页面之间只需要通过路由参数传值就够了。Markdown编辑器选择了mavon-editor,它基于CodeMirror开发,支持实时预览、图片上传,各种扩展语法也比较齐全。还有一个细节容易被忽略——代码高亮用的highlight.js,配合编辑器一起集成,文章展示效果才能到位。
2. 核心功能模块拆解与技术要点
2.1 文章管理与Markdown渲染链路
文章管理是整个系统的心脏,需要想清楚的最核心问题是内容以什么形态存储。我最后采用的方案是:数据库存Markdown源文本,前端展示时再渲染成HTML。为什么这么设计?因为Markdown源文本方便编辑、版本对比和迁移,博客文章以后要是想换平台发布,拿源文件直接能走;而渲染成HTML则交给前端统一处理,后端只需要把原始文本原封不动地传给前端就行。
后端的文章接口只做两件事:接收文章标题、分类、标签、内容等字段入库,查询时按条件分页返回列表或详情。需要注意的一个坑是,文章内容字段要用LongText类型(对应MySQL的longtext),不然Markdown文本长一点就存不进去,SQL会直接报错。另外,状态字段建议设计成草稿和已发布两种,默认走草稿,点发布时才把状态改成1,这对博客写作场景很实用,文章写到一半存草稿,第二天接着改,不用一股脑全公开。
前端渲染Markdown的链路是:mavon-editor加载文章内容后,内部先把Markdown编译成HTML,再交给highlight.js对代码块做高亮处理。这个过程中最容易出的问题,是代码块里嵌套了HTML标签时被编辑器误解析,显示错乱。我当时的处理方式是在markdown-it的配置参数里开启html选项,同时用highlight.js做好代码块识别,实测下来很多特殊符号都能正确显示。预览效果和最终文章详情页要保持一致,这里建议文章详情页也直接用mavon-editor的只读模式渲染,而不是自己写一套解析逻辑,省心不少。
2.2 分类标签体系与归档查询
写博客的人应该都有这种体会:文章一多,没有分类和标签的话,找以前的文章简直是大海捞针。这套系统里分类和标签是分开设计的,语义上也有区别。分类是树形结构,最典型的场景是技术、随笔、读书笔记这种大方向,层级可控;标签则是扁平结构,可以很灵活,比如一篇讲JWT认证的文章,能同时挂上"Spring Boot""安全""JWT"好几个标签,方便交叉检索。
表结构上,文章和分类是多对一关系,文章表存分类ID字段;文章和标签是多对多关系,需要一张中间表article_tag关联。用MyBatis-Plus实现关联查询时,我踩过一个坑:如果直接关联查多对多数据,Page分页插件统计总数会不准确,因为分页是在SQL层面执行的,而关联查询结果集的行数不等于文章数。我的解决方法是分开查,先查文章列表分页数据,再根据文章ID集合批量查标签,然后在前端组装好再渲染,这样分页统计就完全没问题了。
归档功能是按时间维度组织的,前端需要展示年份加月份的分组列表,类似"2025年1月(3篇)"这种效果。实现方式可以后端直接按日期分组统计,也可以前端拿全量文章列表自行分组。考虑到后端只需要一条group by日期字段的SQL,我建议放在后端做,一年或半年内博客文章量通常不大,直接在文章列表接口加一个归档统计的聚合接口就行。
2.3 评论系统的三层设计
评论功能看上去就是一张表存留言,但要把体验做好,有三层设计需要搞定。第一层是数据存储,评论表至少要包含文章ID、评论人昵称、邮箱、评论内容、父评论ID、评论时间、状态字段。其中父评论ID是用来支持楼层回复的,用户A评论,用户B回复A,这条回复记录的父评论ID就指向A的评论ID。前端展示时,先渲染所有顶级评论,再递归找出每个顶级评论下的子评论,形成楼中楼效果。
第二层是防骚扰,这个一般容易被忽略。评论如果不加审核,垃圾广告很容易就刷进来。我的做法是:状态字段支持待审核和已通过两种值,新评论默认待审核,只有管理后台审核通过后才在前端页面显示。发送评论的接口还加了简单的前置校验,昵称和邮箱必填,邮箱格式要正确,评论区内容不能为空。心里有数就行,不搞太复杂,能挡住大部分无脑垃圾评论就够了。
第三层是操作体验,提交评论用异步请求,成功后前端把新评论追加到列表里,不用刷新页面。这里有个体验细节:用户评论提交成功后,最好立即清空输入框并给出成功提示,而不是等着刷新之后看结果。还有一个细节是评论时间不能直接显示"2025-01-15 15:30:00"这么硬的时间字符串,建议做一个简单的相对时间转换,比如"2分钟前"、"3天前",阅读体验会好很多。
2.4 后台管理的权限划分
前台页面和后台管理共用一套Spring Boot接口,但权限控制必须分开。登录用户分为管理员和普通用户两种角色,管理后台所有接口都要求登录后访问,而管理类操作(发文章、删评论、管理分类标签)只允许管理员角色执行。实现上用了两个自定义注解,一个标记需要登录的接口,另一个标记需要管理员权限的接口,通过Spring AOP切面统一拦截处理。
权限这块我在实际项目中吃过亏,一开始图省事,只做了登录校验,没区分角色,结果任何人注册后都能跑到后台发文章改设置。后来重构时把用户表加了角色字段,用枚举类型存ADMIN和USER,拦截器里判断角色不匹配就返回403状态码,问题就彻底解决了。前端同步处理,管理后台的路由在Vue Router中配置了meta属性记录需要的角色信息,路由守卫里校验不通过直接跳转登录页。双端都做校验,才能保证接口不被绕过,前端隐藏按钮只是提升体验,不能作为安全屏障。
3. 项目结构设计与数据库建模
3.1 后端包结构与分层规范
后端工程按标准的三层结构来划分,Controller层接收参数和返回结果,Service层封装业务逻辑,Mapper层对接数据库,分包命名遵循统一规范就能保证后期好维护。我习惯的包结构是:controller、service、mapper、entity、dto、vo、config、common、utils。entity里放数据库表映射的实体类,dto放接收前端参数的传输对象,vo放返回给前端的数据视图对象,这样分层的好处是参数校验和响应结构不会跟数据库实体搅在一起。
在写代码时有一个容易忽视的分层规范:Controller层只做参数接收、调用Service、统一处理返回,不写任何具体的业务逻辑;复杂的业务处理都放在Service层。常见新手会犯的毛病是,在Controller里直接new一个QueryWrapper拼查询条件做数据筛选,看上去方便,其实把业务逻辑散落到控制层了,后面加需求改查询规则时,要找半天代码在哪里。各层得各司其职,这样出问题才能最快定位到具体层。
统一的接口返回结构这一块,我建议在一开始就定义好,不然后期改起来很伤。我用的标准是:code字段表示业务状态码,20000表示成功,其他数字表示各种业务异常;message字段存放返回提示信息;data字段存放业务数据。前端Axios封装了响应拦截器,判断code不是20000就直接弹错误提示,省去每个接口单独处理错误。分页查询的返回结果也是统一结构,用自定义的PageResult对象包两层:data是当前页的记录列表,total是总记录数。
3.2 前端路由与页面组织
Vue前端工程结构,pages目录下按功能模块划分了多个页面组件,分别是首页、文章详情页、分类页、标签页、归档页、关于页、登录页、后台管理相关页面。路由配置和页面一一对应,前台页面都走游客可以访问的路径,后台管理的路由统一挂在/admin路径下,并由路由守卫做登录和权限判断。
首页比较典型的布局是:顶部导航栏显示博客名称和菜单项,左侧主体区域按时间倒序列出文章卡片,右侧侧边栏放个人简介、文章分类、标签云、最新评论这些模块。文章详情页左侧是正文内容区,底部是评论区域,右侧是文章目录导航。这种布局在博客圈里用得很多,用户浏览体验比较成熟,不需要自己凭空设计交互。页面组件按单文件组件的方式来写,模板、脚本、样式都放在同一个vue文件里,方便定位和修改。
Axios请求封装是前端一个很关键的基础模块,我在utils/request.js中统一配置了baseURL和请求超时时间,并在请求拦截器里把登录后的Token从localStorage取出,加到请求头Authorization字段。响应拦截器统一处理后端返回的code状态码,遇到登录过期就清掉本地登录信息并跳转到登录页。这样封装一次,所有页面组件里只需要调用封装好的request方法,不用重复处理各种异常分支。
3.3 数据库表设计与字段说明
数据库我用的是MySQL 8.0,字符集统一设置成utf8mb4,排序规则选择utf8mb4_general_ci。用utf8mb4而不是utf8,是因为它完整支持emoji表情和生僻字的存储,评论内容或者文章标题里偶尔会有人用特殊字符,用utf8的话这种数据写入会直接报错。数据库名我定为blog_system,主要包含5张核心表:用户表、文章表、分类表、标签表、评论表,另外还有一张文章标签关联表。
文章表是最核心的一张表,字段包括主键id、标题title、摘要summary、正文内容content、封面图cover_image、分类ID category_id、状态status、浏览量view_count、是否置顶is_top、创建时间create_time、更新时间update_time。摘要字段容易被忽略,但很实用,列表页直接展示它,不用截取正文内容,既省流量又能保证显示效果。浏览量字段直接存数字,每次访问详情时加一,简单有效,等流量大了再考虑Redis缓存计数器。
用户表字段包括主键id、用户名username、密码password、昵称nickname、邮箱email、头像avatar、角色role、创建时间create_time。密码存储明文是大忌,我用的方案是BCrypt加密存储,注册时加密,登录时用BCrypt校验,就算数据库泄露了,密码原文也不会直接暴露。分类表和标签表结构相对简单,分类表是主键加分类名、描述、排序号;标签表是主键加标签名。中间表article_tag只需要两个外键字段和主键id即可。
3.4 数据库初始化SQL脚本设计
项目配套的数据库脚本文件包含全量建表语句和初始测试数据。执行方式分两种:如果是全新环境,直接执行完整的初始化脚本,表和测试数据一口气搞定;如果是给已有库升级或补数据,可以单独执行增量脚本。初始化脚本里我会按顺序执行建库、建表、插入默认数据,这样一把跑通不依赖顺序。
建表语句要特别注意字段类型和索引的设计。文章表的content字段我前面提到必须用longtext,标题和摘要用varchar类型并设置合适长度。索引方面,文章表的分类ID创建普通索引,状态字段也建索引,因为后台列表查询经常按状态过滤;文章表和时间相关的查询多用create_time做排序,给这个字段建索引能明显加快列表查询速度。评论表的外键字段、文章标签关联表的两列都要建索引,不然数据量上来之后关联查询会变得很慢。
默认数据这部分我准备了管理员账号和几篇示例文章,管理员账号初始密码经过BCrypt加密之后写入脚本,并且我会在文档里醒目标注第一次登录后要尽快修改密码。示例文章主要用来填充页面展示效果,覆盖了不同分类和标签。这样下载源码后执行脚本,启动项目就能直接看到完整的前台效果和后台管理的数据操作。
4. 实操过程与核心环节实现
4.1 本地环境准备与启动流程
实操第一步是准备开发环境。JDK需要1.8或以上版本,我用的JDK 8最稳定;MySQL用5.7或8.0都可以;Node.js建议14以上版本,低了npm安装依赖容易报错。后端用IDEA打开工程,等待Maven下载依赖时用Navicat连接MySQL,在Navicat里直接执行数据库初始化脚本,建好库和表。前端用VS Code打开vue目录,打开终端执行npm install安装依赖,这个过程是全网最磨人的环节,经常因为网络原因装到一半失败,建议配置国内npm镜像源,实测下载速度能快好几倍。
后端启动要改的配置在application.yml文件里,主要改数据库连接的用户名、密码、URL地址这三项。端口号默认配的8080,前端开发服务器端口8081,前后端联调时需要配置代理转发。前端项目根目录下的vue.config.js里配置了devServer.proxy,把/api路径开头的请求都代理转发到http://localhost:8080,这样前端页面里发的请求就不会出现跨域问题。前端工程启动命令是npm run serve,启动后浏览器访问localhost:8081就能看到博客首页。后端启动很简单,运行主类里的main方法,控制台看到Tomcat started的日志就说明启动成功了。
启动过程中最容易遇到的一个问题是数据库连不上,表现为启动日志报Communications link failure或者Access denied。前者一般是MySQL没启动或端口不对,后者是用户名密码或权限配置错误。逐一排查配置项,基本上都能解决。Maven依赖如果下载不下来,先确认IDEA的Maven settings配置文件是否指向了国内镜像仓库,这个配置对了能省很多时间。
4.2 后端核心接口开发示例
文章分页列表接口是后台系统最常用的接口,以它为例把实现思路串一遍。Controller层接收页码current、每页大小size、以及可选的关键字keyword参数,调用Service层的分页方法。Service层构造MyBatis-Plus的LambdaQueryWrapper,根据keyword非空判断来动态拼标题的模糊查询条件,按置顶字段和创建时间排序,然后调用Mapper层的selectPage方法。查询结果用PageResult对象封装好返回前端的统一格式。
@GetMapping("/article/list") public Result<PageResult<ArticleVO>> list(@RequestParam(defaultValue = "1") int current, @RequestParam(defaultValue = "") String keyword) { Page<Article> page = new Page<>(current, 5); LambdaQueryWrapper<Article> wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(keyword), Article::getTitle, keyword) // 非空才拼接条件 .orderByDesc(Article::getIsTop) // 置顶文章排前面 .orderByDesc(Article::getCreateTime); // 新文章在前 articleService.page(page, wrapper); return Result.success(PageResult.build(page)); }这段代码里的一个关键设计是wrapper.like方法的第一个布尔参数,这个参数是false时该条查询条件自动不拼进SQL,这样就能在一个方法里同时处理有关键字和无关键字两种情况,代码简洁且安全。用LambdaQueryWrapper而不是字符串写字段名,能避免字段名拼写错误在编译期才暴露的问题。
文章详情接口也用类似方式,按ID查单条记录,如果查不到就返回业务异常信息。作者在后台编辑文章的时候,接口接收的是一个ArticleDTO,DTO字段比实体类多一些,比如标签ID列表和分类ID都要通过DTO传入。创建文章时要先插入主表记录拿到文章ID,再循环插入文章标签关联数据。这里要注意的是,在循环里逐条insert单条数据性能尚可,但如果标签非常多,可以用MyBatis-Plus的批量插入方法一次搞定。
4.3 前端页面关键实现
前端列表页的核心逻辑是调用后端分页接口获取文章数据,渲染到页面上。这段逻辑放在mounted生命周期里执行,页面初始加载时调用一次loadArticles方法,方法内部通过this.$http.get请求接口,拿到数据后更新页面里的文章数组和分页组件当前页数。分页组件的事件绑定到handlePageChange方法,页码变化时更新当前页码参数再重新请求接口。这里有一个小细节,列表页的加载状态要处理好,请求发出后显示loading遮罩,数据返回后关掉,体验上不会让人觉得点一下没反应。
文章详情页除了加载文章内容,还要处理Markdown渲染、浏览量加一、加载评论列表这三个并行任务。Markdown渲染这部分我直接用的封装好的mavon-editor组件,设置只读模式,把Markdown源文本绑定进去,组件内部自动渲染。浏览量加一调的是后端单独的接口,每次进入详情页时请求一次。评论列表加载放在页面底部,用户登录状态下还能看到评论输入框和提交按钮。
getArticleDetail(id) { this.$http.get(`/article/detail/${id}`).then(res => { const data = res.data; this.article = data; // 文章加载完成后再渲染评论列表 this.getComments(id); }) }编写前端组件时我有一条实用建议:凡是页面里需要的格式化逻辑(比如时间转换、浏览量大于一万显示成w单位),都抽到utils公共方法里,不要在组件里临时写一遍。多个页面复用同一个格式化工具时,以后要改格式只动公共方法一个地方,不用满项目去找散落的代码。
4.4 文章发布与管理后台流程
管理后台的编辑器页面与前台的阅读体验是不同的,需要用mavon-editor的编辑模式,左边写Markdown,右边实时预览效果。发布文章时填写的字段有标题、分类、标签、封面图、摘要、正文内容。标签字段用Element UI的input-tag组件处理,用逗号分隔输入多个标签,保存前转换成标签ID列表传给后端。
编辑已有文章和新建文章的逻辑都复用了同一个编辑页,区别只是在路由参数里多一个文章ID。如果带ID进来,就先把文章详情查询出来填充到表单里,再把关联的标签ID列表转换成标签名数组回显到tag组件。保存时根据是否有ID决定走新增还是更新接口。这种复用思路在后台系统的各个模块里普遍适用,能减少大量重复代码。
管理后台的整体布局采用左侧菜单加右侧内容区域的经典结构,左侧菜单按功能分组:文章管理、分类管理、标签管理、评论管理、系统设置。文章列表页提供按标题搜索、按分类筛选、按状态筛选三个条件,操作列提供编辑、置顶/取消置顶、删除三个按钮。所有列表操作都加了一个二次确认弹窗,防止手滑点错造成数据误删,这个习惯强烈建议保持,尤其是删除操作,误删几篇文章的心痛谁都懂。
5. 常见问题与排查技巧实录
5.1 跨域请求与代理配置的坑
前后端分离开发第一个绕不开的问题就是跨域。浏览器同源策略会拦截不同端口之间的Ajax请求,前端端口8081请求后端8080端口,默认情况下必然被拦。解决方式有两种,第一种是前端配置代理,第二种是后端开启跨域支持。我把两种都配了,因为开发环境和生产环境遇到的场景不一样:开发环境靠前端devServer代理转发,生产环境部署在同域下就不存在跨域问题,但为了保险还是给后端写了允许跨域的配置类。
后端开启跨域支持,写一个配置类实现WebMvcConfigurer接口,重写addCorsMappings方法,允许所有的请求路径跨域访问。需要注意allowedOriginPatterns和allowedOrigins的区别,后者不允许携带通配符,如果需求里要求允许所有来源访问,用allowedOriginPatterns("")而不是allowedOrigins(""),不然实际请求时会被浏览器拦截掉,这个坑我当时排查了挺久才定位到。
@Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); }还有一种容易出现的情况是,前端代理配好了,但请求的URL路径没带/api前缀,导致代理规则没生效。我在代理规则里专门加了一条对/api路径的匹配,然后前端所有接口请求都封装在utils/request.js里,baseURL统一写成/api前缀下的地址,从根上避免这个问题。建议你也采用这种方式,统一管理请求前缀。
5.2 Token过期与登录态失效的处理
JWT Token的有效期、前端登录态管理是一个需要注意设计的细节。Token过期后,用户再操作后台某接口,后端拦截器直接返回未认证的状态码。如果前端不及时处理,用户会停留在页面上,点了按钮没反应,很困惑。我前端的做法是,在响应拦截器里统一判断HTTP状态码为401时,清空localStorage里的Token数据,弹出登录过期提示,然后跳转到登录页。
这里要注意一个是Token过期后用户正在编辑的文章内容,直接跳转登录页会把内容丢光,体验很差。我踩过这个坑后调整了策略:跳转登录页前,先把当前页面的路由记录到sessionStorage里,登录成功后再自动跳回原来的页面。这样一个简单的优化,能让用户觉得系统很人性化,细节决定体验。Token里我还加入了签发时间字段,前端在操作敏感接口时如果发现Token快过期了,可以提前提醒用户重新登录,避免写了一半突然被打断。
还有一个容易被忽视的细节是,登录接口返回的Token要分清楚放哪个存储:localStorage是持久化的,关掉浏览器再打开还在;sessionStorage是会话级的,浏览器关掉就没了。个人博客系统里设置记住登录的就放localStorage,没设置的就放sessionStorage。但要注意,XSS攻击可以读取localStorage里的Token,所以防止XSS和Token过期检测要一起做,前端不要用v-html直接渲染不可信的用户输入内容,后端在保存评论和用户资料时也要过滤掉危险的HTML标签。
5.3 图片上传与存储策略
文章内容里配图几乎是必备的,小到封面图片,大到正文里的截图演示,都需要一个可靠的图片上传方案。我一开始用本地存储,定义一个upload目录,上传接口把文件保存到磁盘,访问时通过静态资源映射返回。这个方案在开发环境没问题,但部署到服务器后文件路径对不上、磁盘空间不足、备份困难这些麻烦事一件接一件。
后来我把上传目标改为云存储,用对象存储服务保存图片,上传成功后返回一个外链地址,存入文章字段或封面图字段。对象存储的好处是容量基本不用考虑,访问速度快,支持CDN加速,而且不用担心服务器重启丢失文件。整体改造的思路是后端上传接口统一返回URL字符串,前端拿到URL后直接填入图片地址字段,不需要感知后端的存储细节。如果你不想引入云服务,用Nginx静态文件目录存图片也是可行方案,但要做好目录权限控制和容量预警。
图片上传还有一个安全点要关注:后端必须校验上传文件的类型和大小。只允许图片扩展名(jpg、png、gif、webp),文件大小限制在5MB以内,最好再用文件头的magic number校验文件真实类型,防止有人把木马文件伪装成图片上传到服务器上。这块实现不复杂,但能帮你避免很多安全问题,值得认真做。
5.4 调试技巧与性能优化
想起排查接口问题时一个提升效率的小技巧:后端接口开发和联调阶段,F12打开浏览器开发者工具,在Network标签页观察接口请求的状态码和响应时间,很快就能判断出问题是出在前端、后端还是网络层。看到接口200但页面没数据,多半是前端渲染逻辑问题;看到不返回数据,往往是后端拼接数据逻辑有bug,再打开控制台看打印的SQL语句和报错信息就行。如果接口数据正常但页面展示慢,大概率是前端渲染性能问题,肉眼看不出来就加console.time打点,逐个排查数据链路每个环节的耗时。
性能优化方面,个人博客的量级一般不会太大,主要做三个优化就够用。第一个是数据库查询优化,列表页和详情页的SQL尽量走索引,不用select *,只查需要的字段。第二个是前端懒加载,路由配置里用import函数动态引入页面组件,首屏只加载当前页面需要的JS,而不是一次性打包全部页面代码,首屏加载速度会有明显改善。第三个是静态资源走缓存,给Nginx配置上图片和静态JS/CSS的缓存策略。
Web服务器部署这块,我把前端build产物放在Nginx的html目录下,后端打成jar包用systemd守护运行,Nginx做反向代理把/api请求转发到本地8080端口。这样配置的好处是,前后端处于同一个域名下,自然不存在跨域问题,同时Nginx自带静态文件性能优化能力,用起来很省心。
6. 项目扩展与后续开发建议
博客系统写到能用只是第一步,真正让它成为你自己的作品,还需要结合使用场景做扩展。先说我个人最推荐加的模块:友情链接管理,这个功能技术上就是一张表加增删改查,但能让博客和同行站点互相引流,社区氛围一下就出来了。其次是网站公告或者公告栏组件,发布重要信息时不用改代码,后台填条数据就能生效。
还有一个经常被人忽视的扩展点是搜索引擎优化,常见做法包括:给文章详情页动态生成title和description元信息、文章URL使用友好的拼音或ID、生成sitemap.xml文件提交给搜索引擎收录。这套系统做的过程中,我给这些细节都预留了字段位置(比如每篇文章可以填独立的SEO关键字),后续要做的时候不用改表结构,直接补逻辑就行。
技术层面如果你想升级体验,可以考虑接入Redis做热点数据的缓存和浏览量计数器,流量起来之后能明显减轻数据库压力;搜索功能可以接入搜索引擎中间件或者直接用数据库的全文索引,都比在代码里遍历查询like要高效。前端可以考虑升级到Vue 3的组合式API,组件复用逻辑更清晰,但重构成本会比较高,建议想清楚再动手。
这套项目断断续续维护了差不多一年,最大的感受是:功能做着做着就会自然生长,从最早只有最基础的文章发布,到后来加了评论审核、标签聚合、归档统计、图片上传,每一步都是使用中真实遇到的需求。做个人博客最珍贵的地方在于,它逼着你把全栈链路都摸一遍,从数据库设计到后端接口再到前端交互,每一个环节出了问题都得自己解决。这个过程中踩过的坑、总结的思路,比项目本身更值钱。
以此为基础,你可以任意改造成自己风格的博客系统,换主题、加功能、改逻辑,都由你说了算。开发中遇到问题,建议先打开配套文档查常见问题部分,再回到源码里对照排查,很多时候思路就通了。