聊到全栈项目练手,博客系统几乎是我见过最适合把前端、后端、数据库、部署一次串起来的项目,没有之一。你不需要去理解一个陌生的业务规则,因为博客的每个功能你作为一个读者早就用过无数遍:写文章、发评论、存草稿、改标签。做这样一个全栈博客系统,会逼着你把整个技术链路完整走一遍,而且最终交付的是一个有真实用途的产物,不是那种一删就完事的练习Demo。这篇博文整理的是我从零开始做全栈博客系统整个过程中的设计思路、技术选型、落地代码和踩坑记录,适合那些已经学完语言基础、但还不太清楚怎么开始第一个完整项目的人参考。
1. 这个项目到底值不值得做
1.1 为什么博客系统是“全栈”的完美试验田
很多人学完前端学后端,最后发现自己只会写碎片:会用Vue画一个列表页,会写几个登录接口,但真要独立做一个网站时,脑子里一团浆糊。博客系统恰好把全栈所需的核心板块全部覆盖了:前端交互、后端接口、数据库持久化、文件上传、用户认证、评论互动、部署上线。它就像一个微缩版的内容平台,每一个模块都是真实的业务场景,而不是教科书里的玩具案例。
打个比方,如果你一门心思只写接口,那你永远不知道一个正常的前端页面是怎么消费这些数据的;如果你只做前端,你也不知道接口返回的JSON到底怎么产生的。博客系统逼着你同时站在两端看问题,文章从编辑器里写出来,到存进数据库,再到被读者打开渲染成一个完整页面,这条完整链路会让你对整个体系有真正的体感。做完这个项目,你再去接触电商系统、社区论坛、企业官网,会发现很多模块的思路都是相通的。
更关键的是,博客系统的需求边界足够清晰,不像“做一个商城”那样需要纠结一大堆SKU、库存、支付回调这些业务复杂度。博客的核心场景就是“写”和“读”,这让你可以把注意力集中在技术实现本身,而不是被业务规则淹没。如果你已经有了一些编程基础,正愁不知道该做什么练手项目,博客系统是我个人最推荐的起点。
1.2 需求定位:先想清楚你要什么
动手写代码之前,我建议你先花半天时间把需求列出来,而不是直接打开IDE就建工程。博客系统可大可小,小到只有一个文章列表加详情页,大到支持多用户、评论通知、草稿箱、标签管理、全文搜索、访客统计。功能太多做不完,功能太少又没有学习价值,所以第一步是把需求按优先级拆成版本。
第一版本是最小可用产品,必须包含:文章发布、文章列表、文章详情、Markdown渲染。这四条是博客的骨架,先把它们打通,意味着你已经拥有一个能“写、存、读”的闭环。第二阶段往上加交互层:评论、标签、归档、搜索。第三阶段做管理能力:用户登录、后台发布、草稿箱、文件上传。更后面的东西比如RSS订阅、站点地图、访问统计、文章置顶,都属于锦上添花。
我建议你和最初的我一样,不要一上来就想要全部功能,先做一个能给自己日常记笔记用的产品,再慢慢迭代。下面这个表格是我当时整理需求时用的对照表,它能帮助你一眼看到每个模块对应的技术难点,心里大概有个底:
| 功能模块 | 涉及技术点 | 难度 |
|---|---|---|
| 文章发布 | 富文本/Markdown编辑器、表单校验、图片上传 | 中 |
| 文章列表与详情 | 分页、路由、Markdown渲染、代码高亮 | 低 |
| 评论系统 | 用户认证、关联查询、防XSS、防刷 | 中上 |
| 用户登录注册 | JWT或Session、密码加密、权限拦截 | 中 |
| 标签与分类 | 多对多关系、SQL关联查询 | 中 |
| 后台管理 | 前端路由守卫、后端接口鉴权 | 中 |
| 部署上线 | Nginx、域名、HTTPS、数据库迁移 | 中 |
这里我要强调一句:项目做出来的第一版一定不要追求大而全,而是要把“文章从发布到展示”这条主干路径做到足够顺滑。你后面遇到的所有高级功能,本质上都是在这条主路上挂分支,主干通畅了,挂分支才有意义。
2. 技术选型:我为什么选这组方案
2.1 前后端不分离 vs 前后端分离,选哪个
做全栈选技术栈时,第一个绕不开的问题就是:到底用传统服务端渲染,还是目前主流的前后端分离。传统方案的代表是JSP、Thymeleaf、模板引擎,后端把页面模板和数据组装好之后,直接返回给浏览器一套完整HTML;前后端分离则是后端只提供纯JSON接口,前端用Vue、React这类框架单独构建页面。两种方案都能做出博客,但适用场景完全不同。
传统服务端渲染的明显优势是首屏快、对搜索引擎友好,因为页面内容直接出现在HTML里,不需要浏览器再跑一遍JavaScript去动态渲染。缺点则是前后端代码耦合在同一个工程里,开发时前端改一下样式,后端开发也要跟着启动整个项目,协作体验比较差。如果这个博客主要是你自己写给自己看,传统方案完全够用,而且部署简单。
但我的选择是前后端分离,因为我的目标不仅仅是“做一个能用的博客”,而是“做一个能展示自己全栈能力的项目”。分离式架构下,前端工程和后端工程可以独立开发、独立部署,接口文档也能沉淀下来,这更接近目前主流团队的协作模式。你如果学会这一套,找工作或者接手公司项目时会更快上手。不过我也要说清楚,分离式项目如果对SEO有强需求,后期要补很多方案,比如预渲染或瀑布流式客户端渲染。个人博客还好,你如果打算让搜索引擎大量收录,这个痛点后面有的是活儿做。
2.2 数据库与ORM:MySQL加MyBatis-Plus,还是JPA?
文章、用户、评论、标签这些数据有明显的关联关系,我毫不犹豫选了MySQL。有些人觉得MongoDB这种文档数据库更灵活,存文章结构很自由。但博客的评论和文章之间存在强关联,用户和文章之间存在归属关系,如果统统塞进文档里,后面做列表、做统计、做聚合查询都会非常别扭。关系型数据库这种“表结构清晰、事务可靠、查询能力稳定”的特性,完全压过了“动态字段”带来的那点便利。
ORM框架我最终选了MyBatis-Plus,原因很简单:可控。它的CRUD封装能帮我省掉大量重复的单表操作,同时复杂查询我又能直接写SQL,不至于像JPA那样遇到坑时一头钻进黑盒里。JPA开发效率确实高,尤其在对象模型和表结构非常匹配的时候,但它的懒加载、N+1问题、一级缓存和二级缓存这些概念,对初学者来说太重了。MyBatis-Plus的思路更直白:需要简单的,直接调用现成方法;需要复杂的,自己写XML里的SQL语句。这种“中间路线”对我这种既想快速落地又想理解每一步原理的人来说,最舒服。
当然,这不是说Java系就是唯一答案。现在很多全栈项目也用Node.js的Express或者NestJS,Python的FastAPI也是很好的选择。选型没有绝对的对错,关键是你要清楚你的语言生态里哪个ORM最顺手。我的建议是,如果你以后想往企业级开发走,Java加MyBatis-Plus的组合值得体验一次;如果你更看重开发效率和轻量部署,Node.js全栈也完全能做出一个漂亮的博客系统。怕就怕什么都想要,今天用这个框架明天换那个,最后项目烂尾。
2.3 Markdown编辑器与渲染:博客的灵魂
博客系统里有三样东西决定用户愿不愿意长期用:写文章是否顺手、打开文章是否够快、代码片段是否清晰。其中Markdown处理和渲染是最核心的一环。前端编辑器我推荐直接用成熟组件,而不是自己从头做一个。个人项目完全没有必要在这里重复造轮子,选择Vditor、Toast UI或者单纯的textarea加markdown-it都可以。我自己的实现用的是textarea加markdown-it的方案,因为组件依赖少,渲染逻辑完全掌握在自己手里。
这里有一个非常关键的决策点:后端到底应该存原始Markdown文本,还是存转好的HTML。我见过有人为了省事,前端提交的时候直接把markdown转成HTML字符串存库,结果后面想换编辑器、想改主题样式、想做全文搜索的时候全傻眼了。正确做法是数据库里存原始Markdown文本,展示的时候再根据页面主题动态渲染HTML。原始文本是最可靠的数据资产,需要什么格式随时能生成;如果你只存HTML,想反推原始Markdown就非常麻烦。
代码高亮是我强烈建议第一版就要加的,没有高亮的代码块基本等于一堆黑压压的纯文本,阅读体验非常差。我在前端渲染流程里引入了highlight.js,配合当前的博客主题做了一版高亮样式,实测下来代码块阅读体验提升非常明显,读者看技术文章时确实离不开这个。
2.4 用户认证与权限:到底需要多复杂
博客系统至少会有一个管理员自己,如果开放注册,还需要普通用户身份的区分。用户认证我推荐用JWT或者Session加Redis,两者各有优劣。JWT的优点是无状态,后端不需要存会话信息,水平扩展时非常方便;缺点是踢人难、登出逻辑麻烦,token一旦泄漏在过期之前很难远程吊销。传统Session的优点是服务端可以随时删除会话,配合Redis也能做到多实例共享,缺点是需要额外维护会话存储。
个人博客项目的流量和管理员数量都很有限,其实用哪种方案都行,我更推荐先理解清楚它们的原理再动手。我自己的选择是JWT加HttpOnly Cookie:前端把token塞进Cookie里,浏览器每次请求自动带上,这样前端JavaScript拿不到token,能降低被XSS脚本窃取的风险。网上很多教程喜欢把token存在localStorage里,然后通过Authorization请求头发送,这样写起来确实直观,但安全性要差一点,因为只要页面上有一个渲染漏洞,攻击者就能把token偷走。这个细节,我建议全栈开发者在第一次做项目时就养成良好习惯。
权限控制上最重要的一句话:前端隐藏按钮不等于后端安全。也就是说,页面上你可以根据用户角色决定显示“编辑”还是“删除”,但后端接口必须再次校验角色,否则别人绕过前端直接调你的删除接口,照样可以删数据。我在写后台接口时,专门定义了一个拦截器,对所有带权限要求的接口做统一校验,而不是在每一个Controller方法里重复判断。
3. 实操过程:从数据库表到一条文章发布的完整链路
3.1 数据库表设计:文章的“骨架”
我们直接上手设计数据库表。博客最核心的表是文章表,它的字段设计要注意几个容易踩坑的点:标题、摘要、正文、状态、发布时间、更新时间、作者ID。状态字段建议用字符串而不是布尔值,因为一篇文章的状态至少有三个:草稿、已发布、已删除,布尔值根本无法表达。摘要字段不要随手省略,列表页如果直接截取正文字段,性能会很差,而且会把Markdown语法的原始字符暴露出来,十分影响观感。
用户表要包含用户名、邮箱、密码哈希、角色、创建时间。关键点是密码绝对不允许明文存储,至少要用BCrypt这类自带盐值的算法做哈希,我在项目里用的就是Spring Security自带的BCryptPasswordEncoder。标签表很简单,字段就是名称和别名。文章和标签是多对多关系,所以还需要一张关联表,单独存储文章ID和标签ID。
下面是一段简化后的核心建表SQL,你可以直接当作项目初始版本参考:
CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, password_hash VARCHAR(100) NOT NULL, role VARCHAR(20) NOT NULL DEFAULT 'USER', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE article ( id BIGINT PRIMARY KEY AUTO_INCREMENT, author_id BIGINT NOT NULL, title VARCHAR(200) NOT NULL, summary VARCHAR(500) NOT NULL DEFAULT '', content MEDIUMTEXT NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'DRAFT', published_at DATETIME NULL, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted TINYINT NOT NULL DEFAULT 0, INDEX idx_author (author_id), INDEX idx_status_published (status, published_at) ); CREATE TABLE tag ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE, slug VARCHAR(100) NOT NULL UNIQUE ); CREATE TABLE article_tag ( article_id BIGINT NOT NULL, tag_id BIGINT NOT NULL, PRIMARY KEY (article_id, tag_id) );索引设计这一块要啰嗦两句。文章列表页最常见的排序是“已发布且按发布时间倒序”,所以我在status和published_at两个字段上建了联合索引。个人博客的数据量通常不大,索引不要加太多,每张表多那么三五个索引,反而会拖慢写入速度。删除策略我用的是逻辑删除,也就是deleted字段标记,而不是物理DELETE,这样就算误删了文章也能抢救回来。
3.2 后端接口:把写文章变成一次HTTP请求
数据库表设计好之后,下一步就是把对文章的操作映射成接口。REST风格是我一直在用的方式,核心接口基本是固定的几套动作:获取文章列表、获取文章详情、新建文章、更新文章、删除文章,再加上用户注册和登录。这里有个容易犯的错误:新建文章和更新文章要分开处理,新建时没有ID,更新时必须校验文章是否存在且当前用户是否有权限操作。
下面是关键接口的整理:
| 动作 | 请求方式 | 路径 | 说明 |
|---|---|---|---|
| 获取已发布文章列表 | GET | /api/articles?page=1&size=10 | 只返回已发布文章 |
| 获取文章详情 | GET | /api/articles/{id} | 根据ID返回文章内容 |
| 新建文章 | POST | /api/articles | 需要登录,身份为作者 |
| 更新文章 | PUT | /api/articles/{id} | 需要登录,校验作者身份 |
| 删除文章 | DELETE | /api/articles/{id} | 逻辑删除 |
| 用户注册 | POST | /api/auth/register | 用户名、邮箱、密码 |
| 用户登录 | POST | /api/auth/login | 成功后返回JWT |
| 发布评论 | POST | /api/articles/{id}/comments | 可开放,也可仅登录用户可用 |
后端示例我用Spring Boot的Controller写法来展示,逻辑非常简单:接收请求、校验参数、调用Service处理业务、返回结果。
@RestController @RequestMapping("/api/articles") public class ArticleController { @PostMapping public Result<ArticleVO> create(@RequestBody @Valid ArticleCreateRequest request) { ArticleVO vo = articleService.create(request); return Result.success(vo); } @PutMapping("/{id}") public Result<ArticleVO> update(@PathVariable Long id, @RequestBody @Valid ArticleUpdateRequest request) { ArticleVO vo = articleService.update(id, request); return Result.success(vo); } }实际开发时,我会在Service层加事务注解,因为保存文章的同时还要同步文章和标签的关联关系,任何一步失败都应该把整批操作回滚。这里有个新手极容易踩的坑:先插文章拿到自增ID,再插文章和标签的关联关系,如果第二步抛异常而事务没生效,就会留下脏数据。所以事务一定要加在“跨多张表操作”的方法上。
3.3 前端页面:从Markdown源码到渲染页面的路径
前端我用的是Vite加Vue 3,路由设计很简单:/是首页列表,/article/:id是文章详情,/admin是后台管理。很多人做前端时容易陷入“拼命堆组件”的状态,但我建议路由和页面结构尽量保持清晰,一个页面只负责一个核心职责。首页是列表页,它只需要请求接口拿到文章标题、摘要、发布时间,然后渲染成卡片列表;详情页才是重头戏,因为它要拉取Markdown文本并完成渲染。
Markdown渲染是整个详情页的核心链路,我的前端实现逻辑可以分为四步:先从后端拿到原始Markdown字符串,然后调用marked或者markdown-it把它解析成HTML,再经过DOMPurify做白名单过滤,最后交给highlight.js处理代码块高亮,插入页面DOM。这四步缺一不可。为什么必须加DOMPurify这一步?因为markdown允许你写原始HTML标签,如果某个用户写了恶意脚本,你直接把渲染结果放进页面里,那就是一个现成的XSS攻击入口。个人博客如果只有自己发文章风险还小,一旦开放评论功能,这类过滤就是硬性门槛。
下面这段代码就是我项目里封装的渲染函数,核心逻辑一目了然:
import { marked } from 'marked'; import DOMPurify from 'dompurify'; import hljs from 'highlight.js'; function renderMarkdown(raw) { const dirtyHtml = marked.parse(raw); const cleanHtml = DOMPurify.sanitize(dirtyHtml); const container = document.createElement('div'); container.innerHTML = cleanHtml; container.querySelectorAll('pre code').forEach((block) => { hljs.highlightElement(block); }); return container.innerHTML; }这段逻辑放在浏览器端执行是有代价的,用户打开文章时首先要等接口返回数据,然后在浏览器里做一次完整解析。如果想要首屏更快,可以后续做成服务端渲染,或者在后端预先把渲染结果缓存下来。但我第一次做的版本选择了浏览器端渲染,因为它最容易调试、最直观,也足够满足个人博客的访问压力。
3.4 用户系统与评论:博客的互动层
用户系统和评论功能放在一起做,是因为评论天然依赖登录信息。注册登录的界面别做得花里胡哨,用户名、邮箱、密码三个字段就够了,剩下的核心都在后端逻辑里。注册时先判断用户名和邮箱是否已经存在,然后对密码做哈希处理,最后插入用户表。登录时根据用户名找到用户,用BCrypt校验密码,校验通过后生成JWT,把它写入HttpOnly Cookie。
评论功能的难点反而不在存储,而在数据关联。评论表里需要存文章ID、用户ID、评论内容、父评论ID和创建时间。评论列表加载时,要根据文章ID先查出所有评论,再把评论和用户头像、用户名绑定起来,如果评论量大了,还要处理分页和无限嵌套。我的第一版只做了单层评论,没有做嵌套回复,因为嵌套评论的数据结构复杂度会明显上升。对于个人博客,单层评论加一个时间排序完全够用。
写评论接口时我特别提一个点:一定要做用户身份校验,而不是把用户ID放在前端传过来的参数里。也就是说,后端从Cookie或请求头里解析出当前登录用户,而不是信任请求体里的userId字段,否则别人可以伪造参数,替任何人发评论甚至删评论。这种身份主体应从会话上下文中拿,而不是从可变的请求参数里拿,属于全栈接口设计的基础素养。
4. 上线部署与性能优化:本地跑通只是开始
4.1 部署方案对比:服务器、Docker还是平台挂载
本地跑通项目以后,很多人会松一口气,但对我们做全栈的人来说,真正让系统变成完整项目的,是把它部署到公网,让朋友或者读者可以访问。部署方案我体验过三种:直接在服务器上用Nginx加进程守护跑前后端、用Docker Compose编排所有服务、用云平台直接托管。三种方式适合不同阶段,没有绝对好坏。
如果你只有一台轻量云服务器,并且想尽快看到效果,最省事的方式是:后端应用打成jar包,在服务器上用systemd托管,前端构建后的静态文件交给Nginx直接托管。前端页面里所有API请求走相对路径,Nginx再把特定路径反向代理到后端端口。这套方案的好处是非常直观,任何一部分出问题都能直接查看日志和进程状态。缺点是以后想迁移环境,得重新配置一遍。
如果你和我一样玩过一阵之后嫌手动配置太繁琐,可以切到Docker Compose。用一份docker-compose.yml把MySQL、Redis、后端、前端Nginx全部编排起来,服务器上一条命令启动全部服务,迁移和备份都很舒服。Docker的缺点是有学习成本,但你一旦习惯了镜像和容器,部署项目就变得像搭积木一样。我的建议是先用手动部署理解每一层发生了什么,再上Docker,这样踩坑的时候你能准确判断问题在应用层还是容器层。
无论用哪种方式,有几个通配的部署细节必须注意:数据库的密码不要硬编码在代码里,要用环境变量管理;HTTPS证书可以通过Let‘s Encrypt免费申请,配置好自动续期;公网服务器上的防火墙策略必须收敛,只暴露80、443和SSH端口。这些细节决定了你的博客能不能稳定跑上一年而不出幺蛾子。
4.2 我踩过的性能坑:静态资源、缓存、数据库连接
个人博客在低并发下基本不会出现性能问题,但有两个机制性的坑容易让项目显得很业余。第一个是静态资源全都走后端应用。前端构建出来的几个MB的JS文件、CSS文件,如果都经过后端应用读取再返回,应用每次都要消耗线程处理这些IO,并发一大马上变慢。正确方案是让Nginx直接托管前端静态目录,图片也尽量走单独路径或者对象存储,后端只负责输出JSON接口。这一条改动,对响应速度的提升几乎是立竿见影的。
第二个坑是N+1查询。文章列表页里每篇文章要显示几个标签,初学者很可能写成一个循环:先查文章列表,再根据文章ID一条条查标签。如果一页有十篇文章,数据库就要执行十一条查询。文章少的时候无感,文章一旦过百,页面延迟会非常明显。我的做法是把文章的ID收集起来,用一条IN查询把所有文章的标签查出来,然后在内存里完成组装,把数据库查询次数从十一降到了二。这个思路在所有列表开发里都通用。
除了这两个问题,我还在第一版里加入了Redis缓存:文章详情页的渲染结果缓存十分钟,热门标签列表缓存一小时。设置缓存时要注意缓存失效和文章更新的同步,否则用户编辑完文章之后,页面还是旧内容,体验会非常割裂。最简单的方案是,文章更新接口成功时主动删除对应缓存,下次有人访问时再重新生成。缓存这东西,用不好比不用还糟糕,宁可先别急着加。
5. 常见问题与排查技巧实录
5.1 我遇到过的四个疑难问题速查表
做全栈博客过程中,有些问题非常典型,单独靠报错信息往往很难定位原因。这里我把自己遇到过的,以及身边朋友做类似项目时反复踩的坑整理成一张速查表,建议你直接保存下来:
| 现象 | 产生原因 | 解决办法 |
|---|---|---|
| 首页刷新后404 | 前端用了History路由,Nginx没有配置fallback | Nginx中添加try_files指令,让未匹配路径回退到index.html |
| Markdown里代码块没有高亮 | highlight.js初始化时没有扫描动态渲染的内容 | 渲染完成后调用hljs.highlightElement,或者使用vue等框架的钩子 |
| 图片上传后访问报404 | 静态资源映射路径和实际存储路径不一致 | 检查后端是否注册了本地或对象存储的访问映射,以及权限 |
| 登录后接口偶尔报401 | JWT过期时间设置太短,或服务端时钟不一致 | 明确token有效期,并在前端响应拦截器里做统一刷新处理 |
| 评论里中文显示乱码 | MySQL表或连接字符串字符集不对 | 数据库统一设置为utf8mb4,JDBC连接串加characterEncoding=UTF-8 |
这里我想单独说一讲首页刷新404这个坑,因为它真的会让很多人卡住至少一天。当你使用Vue Router的History模式时,浏览器访问/article/1,服务器看到的请求路径是一个具体路径,而服务器上其实并没有这个文件,所以返回404。Nginx需要把这类路径全部映射到前端入口文件:
location / { try_files $uri $uri/ /index.html; }加了这条之后,前端的路由才会重新接管URL。你这个坑如果不提前知道,等部署之后再排查会很容易摸不着头脑。
5.2 安全与健壮性:评论区和文件上传的隐性坑
安全话题在个人项目里特别容易被忽略,但一旦出事就是大事。我见过不少人做博客系统,开放评论功能之后完全没有任何过滤,直接把用户提交的内容用v-html塞进页面,这等于给攻击者开了一扇大门。我再次重申:所有用户产生的内容,在插入页面之前都要做白名单清洗。DOMPurify之类的库能处理绝大多数XSS攻击,但前提是你真的用了它,并且用到用户输入的入口上。
文件上传也是重灾区。博客后台难免要传头像、传图片,最常见的错误是前端只判断了文件后缀名,然后私自把文件存到一个谁都读得到的目录。攻击者可能会上传一个伪装成图片的脚本文件,一旦被服务器解析就会出大问题。我的做法是:文件扩展名和服务端读取出的MIME Type双重校验,只允许jpg、png、webp这几种;文件内容不强信任,文件名一律由服务端重新生成随机字符串加时间戳;上传目录禁止执行脚本。这样即使有人强行上传了恶意文件,服务器也不会把它当代码去执行。
还有一个没有写在速查表里的问题,就是数据库连接池耗尽。很多人做全栈项目时直接用配置文件的默认连接池参数,但个人项目虽然流量小,后台日志打印、定时任务可能会把连接池占满,导致正常访问时连接超时。排查方法很简单,如果后台日志频繁出现“Connection is not available”之类的字样,就要检查连接池最大连接数设置,同时可以通过临时调大的方式来验证是不是这个原因。
尤其在开发模式下,建议把接口层的重要操作日志打印出来,比如谁登录了、谁删了文章、谁上传了文件。这些日志平时看着没用,一旦服务器被攻击或者数据异常,就是找问题的最快线索。6. 写在最后的一点个人建议
做完全栈博客系统并上线之后,我最大的感受是:一个项目能不能真正体现你的水平,更多取决于你如何处理边界情况,而不是你写出了多少行代码。很多人在简历里写“独立开发全栈博客系统”,但实际项目里没有做XSS过滤、没有处理Nginx刷新404、没有考虑JWT存储的安全位置,这些细节面试官一问一个准。你如果在做这个项目时把这些隐藏问题都解决到位,整个项目的含金量会完全不一样。
从功能角度,我觉得下一步可以继续补全文搜索、自动备份和访问统计。搜索如果需要好的中文分词,可以试试Meilisearch或者OpenSearch,数据量大一点的时候很有用;自动备份用定时脚本或者云数据库的备份策略,都很成熟;访问统计如果用第三方平台可能会被广告拦截器过滤掉,最好自己做一个埋点接口,数据掌握在自己手里。这些扩展方向每一个都值得单独深入研究,但前提是先让第一版完整、稳定地跑起来。
最后分享一个很多教程不会说的心得:给博客好好写README、画一张简单的架构图,然后把它部署到公网放上自己的几篇原创文章,这个过程本身才是全栈项目真正的体验。你不需要在V1.0里加入所有想象中应该在的东西,做出一个能用、够安全、结构清晰的产品,然后让它不断生长,这就已经很好了。