资讯动态

博客系统重构实战:从Django到MySQL与Redis的工程化升级

发布时间:2026/10/22 7:55:44 来源:尧图企业网站定制
在软件学院创新实训的第二阶段我没有换新题目而是选择把上一轮做的个人博客推翻重写于是便有了这个项目代号“个人博客2”。说是“2”其实更像一次脱胎换骨的重构第一版能跑但数据模型混乱、渲染层把用户输入的 HTML 直接塞进页面、搜索接口在文章数量上来之后明显变慢这类问题在第一轮课程答辩时就被我自己和同学吐槽了个遍。二轮实训开始后我给自己定的目标很明确——不求功能花哨而是把一个博客系统该有的内容管理、用户体系、检索能力全部做到结构清晰、能安全上线、经得起访问量增长。这篇文章适合两类人看一类是正在做实训或课程设计、想找个“完整但不过度复杂”的项目参考的同学另一类是已经搭过个人博客、但处于“能打开页面就行”阶段、想进一步理解工程化细节的开发者。我会把选题思路、需求拆分、技术选型、核心模块实现以及我在开发过程中排查过的最典型的几个故障完整写下来尽量用当时实际的操作过程说话。1. 选题为什么创新实训我没有换新题执意把博客推翻重做1.1 博客这个“老题目”为什么是实训的完整训练场实训选题阶段很多同学倾向于做“看起来新”的东西比如推荐系统、微信小程序、AI 对话机器人。这类题目确实容易在答辩时讲出吸引人的亮点但一个问题在于它们覆盖面窄很多时候核心代码只集中在某一层——推荐系统重点在算法和数据处理小程序重点在交互适配很难让你同时练到数据建模、权限控制、前后端协作、部署运维这些工程能力。博客不一样。它的业务功能听起来普通但从工程角度看正好覆盖了一条完整链路文章写入和展示涉及存储设计分类、标签涉及多对多关系用户注册登录涉及认证与安全评论涉及表单校验和 CSRF 防护搜索涉及索引优化页面访问涉及缓存最后还有静态资源、反向代理、HTTPS 这一套上线流程。把这些全部走通你的收获比做一个“亮点单一”的项目要充分得多。第一版博客只花了不到三周就写完了当时用的是一个简单的后端框架加 SQLite所有的业务逻辑都堆在路由函数里模板也直接拼接字符串。能演示、能写文章、能登录但代码走到两个月之后自己都看不下去了。所以到了第二期我决定不新增什么大功能模块而是把这些基础能力重新设计一遍。1.2 “重做”和“新增功能”的取舍为什么不选择在原有项目上不断打补丁我当时的判断是第一版的数据表结构已经明显不适合扩展。比如文章和分类是直接用外键关联的但我当时居然没有建独立的标签表而是在文章表里用一个逗号分隔的字段存标签导致后面想按标签聚合文章时只能写出极其别扭的查询。修修补补当然也可以但基础设施层的设计问题会在每一次新增功能时反复反弹。与其带着一套别扭的结构继续堆代码不如花两周时间把地基重新打好这也是“个人博客2”这个项目名真正想表达的意思——它不是为了凑版本号而是从架构层面重新来一遍。当时我也给自己定了一条原则这个版本不追求引入多少新框架、新工具所有技术都是在“确实有需要”时才加。这一点在下一节的技术选型里会具体体现。2. 先当产品拆需求再做技术选型避免“边写边想”2.1 需求拆解成三层读者端、作者端、运维端许多人做博客一上来就建表、写路由写到一半才发现评论没有审核机制、搜索接口响应慢、管理员想看访问量却找不到入口。我的建议是先别把自己当成开发者而是当成产品经理把需求摊开列成三层。读者端首页文章列表、文章详情、按分类和标签筛选、关键词搜索、文章归档、评论与回复。每篇文章需要有发布时间、浏览量这些基础元信息详情页要能正常展示代码块和图片。作者端登录后台、撰写和编辑文章、设置分类和标签、管理评论删除不适合的内容、发布或下架文章。“私密文章”这类功能如果时间紧可以不做但“置顶”和“草稿”是很常用的值得保留。运维端配置日志、数据库备份、静态资源处理、部署上线流程、环境区分。这一层第一版完全没考虑只会在本地跑这次必须在架构里预先留好位置。这三层都列全之后数据库表就比较好画了用户表、文章表、分类表、标签表和中间表、评论表。其中文章表里存作者外键、分类外键以及与标签表的多对多关系。这里不推荐把标签直接塞在文章表里因为后续按标签聚合、统计标签文章数都会很难写。2.2 技术栈最终配置与选择理由我最终采用的技术栈如下层次选择理由后端框架Python 的 Django自带 Admin 后台、用户认证、ORM省去重复造轮子数据库MySQL比 SQLite 更适合并发写入与全文索引部署也不复杂缓存Redis用于热点文章缓存、浏览量计数前端方案服务端模板渲染 局部交互博客以内容展示为主不需要强行上前后端分离部署Linux Nginx Gunicorn经典组合资料多排错思路清晰先说 Django。第一版为了让“框架轻”而选择了微框架结果用户认证、表单校验、ORM 都要自己拼。博客这类项目用户体系是最容易出安全漏洞的地方与其自己写一套不如直接用 Django 内置的认证框架它把密码哈希、会话管理、权限装饰器都封装好了而且经过了大量生产环境验证。再说为什么没上前后端分离。我的页面包括首页、详情、后台编辑交互密集度很低主要是表单提交和文章渲染。用 Vue 或 React 把全部页面重写成 SPA意味着要做 API 设计、状态管理、跨域处理、Token 刷新这一整套额外工作对博客这种以长文阅读为主的场景反而增加了复杂度。最终前端采用模板渲染只在中后台的编辑页引入了一点组件化的 JavaScript聚焦在 Markdown 编辑体验上。整条链路下来新引入的组件都各自承担明确职责没有一个是为了“堆技术栈”而加的。3. 核心模块逐个落地文章、用户、搜索的实现方案3.1 文章编辑器推荐“双存储”的方式原文与渲染结果分开文章是博客的心脏这一块的存储设计直接影响后续维护成本。我在第一版里犯的错是用户提交 Markdown 文本后直接调用一个渲染函数把 Markdown 转成 HTML 存进数据库。表面看没啥问题但后来想调整样式或换渲染引擎时才发现原始 Markdown 已经没了只能从 HTML 反向解析基本不可能无损恢复。这一版我改成“双存储”数据库里同时保留两个字段一个是原始的 Markdown 内容一个是渲染后的 HTML。文章详情页直接从 HTML 字段读取避免每次请求都做一次 Markdown 转换编辑文章时加载 Markdown 原文让作者可以继续修改。这样既兼顾了渲染速度又保留了源文件的可编辑性。Markdown 渲染的另一个关键是安全性。大部分 Markdown 库默认是允许用户输入 HTML 标签的如果直接透传恶意用户可以在文章里塞进一段 JavaScript 脚本其他读者打开页面时脚本就会执行。这个问题的完整排查过程我放在第 4 节细说但设计阶段就要确定思路渲染后一定要做“标签白名单过滤”只允许 p、h1-h6、pre、code、blockquote、a、img、ul、ol 这类常规标签并且把 script、iframe、object 全部滤掉。3.2 用户认证与评论防 CSRF把框架自带的能力用足用户模块没有自己造轮子直接用 Django 内置的 User 模型和认证视图。这个选择帮我省掉了大量安全细节密码默认用 PBKDF2 哈希登录状态用服务端 Session 管理不在前端存 Token。踩坑点在评论提交那里——评论表单是跨页面提交的没有正确带上 CSRF Token 的话所有 POST 请求都会被框架拦下来。我当时花了两个小时排查一个“明明配置了登录评论却总是报 403”的问题最后发现是模板里漏写了模板标签提交表单时没有携带 CSRF token。这个教训很直白框架的安全机制都在但你要知道它在哪个环节生效否则会把它当成 bug。权限控制方面文章的新增、编辑、删除操作都在 Django Admin 管理页里完成。这部分我做的额外设计是把“作者权限”和“管理员权限”分开普通用户可以注册登录后发表评论但只有被标记为作者角色的账号能进入后台。实操上只需给用户组配置不同的权限集合不需要自己写复杂的判断逻辑。3.3 搜索从“能搜”到“搜得快”的三步演进搜索功能看似简单实际做到可用远比想象中麻烦。第一版的做法是用 SQL 的模糊查询在文章标题和内容里匹配关键词。文章只有几十篇时毫无问题但等我把测试数据灌到几千篇之后接口响应时间肉眼可见地上升。原因很简单模糊查询无法走普通索引每次搜索都要扫全表数据一旦多起来就是灾难。第二步我加上了 MySQL 全文索引并对中文搜索使用了合适的分词器。这里有一个重要细节MySQL 默认全文索引对中文分词支持不好需要配置 ngram 分词器才能让“个人博客”这样的中文短语被正确检索。第三步是对搜索结果做缓存。这不是指把整个搜索页面缓存住而是把一段时间内的高频搜索词和结果对应关系放到 Redis 里相同的搜索词在短时间内的重复请求直接走缓存。这个优化让搜索结果页的平均响应时间从几百毫秒降到几十毫秒而且代码改动很小。搜索这一节的体会是千万不要等到用户反馈“慢了”再做优化而是在数据量还没有暴涨时就把索引和缓存设计好后面就不用返工。4. 三个记忆深刻的故障及其完整排查思路这一节我要详细写三个我认为最有代表性的问题排查过程。每个问题都不是我第一次写完代码就暴露的而是在上线测试或数据量增长后逐渐浮出水面。把排查的思路记录下来比直接记住答案更有价值。4.1 一个 XSS 漏洞Markdown 渲染器把 script 标签放行了现象是我在一篇文章里输入一段用于测试的 HTML 内容包括scriptalert(xss)/script和img srcx onerroralert(1)保存后打开详情页页面真的弹出了对话框。当时我的第一判断是“渲染环节没有做任何标签过滤”但我没有直接改代码而是先确认了问题出在哪一层。我打开浏览器开发者工具查看页面渲染后的 DOM确认 script 标签仍然存在随后我登录云服务器在数据库里直接查文章字段发现存进去的 HTML 已经包含了原始 script。这说明问题发生在 Markdown 转 HTML 的渲染阶段而不是模板输出阶段。确认根因后我做出的修复是给渲染管道增加一个白名单过滤器只保留允许的标签和属性同时把带有异常协议的链接比如javascript:清掉所有资源类标签的地址也做协议校验。修复之后我用同一批测试载荷重新提交、重读页面脚本不再执行图片的 onerror 事件也没有触发。这次排查给我的经验是安全问题的排查要先分清楚“入口、存储、输出”三个节点在哪一个被突破。很多人做安全修复时习惯直接在模板层加过滤但如果数据库里已经存进了危险内容只靠模板输出过滤就会经常出现漏网之鱼最好在“存进去之前”就把危险内容拦截掉。4.2 登录态时断时续CSRF 校验失败与 Session 过期现象很诡异注册、登录都正常但从后台页面提交文章时经常出现“CSRF 校验失败”的报错有时刷新一下又好了。我一开始怀疑是 Cookie 过期时间设置太短于是去检查会话配置发现过期时间根本没问题。接着我开始看服务器日志发现报错的请求都来自同一个后台编辑页面而这个页面里有一段用 JavaScript 动态拼接出来的表单。问题一下就清晰了动态生成的表单没有包含 Django 模板里自动注入的 CSRF Token 字段。修复方式是在前端动态创建表单时从 Cookie 中读取 CSRF 值并加入表单的隐藏字段。这个操作涉及一个小细节Cookie 和表单字段的 Token 必须一致框架校验时才不会拒绝。这轮排查真正教会我的是不要一遇到 HTTP 403 就盯着权限配置看。403 可能来自身份权限也可能来自跨站请求伪造校验。前后端交互越复杂越要先确认请求头上到底带没带齐服务器要求的三样东西——Cookie、CSRF Token、Referer 或 Origin。4.3 文章一多详情页和搜索接口一起变慢当测试数据量超过五千篇时有两个接口的响应时间明显变长文章详情页的浏览量更新操作以及关键词搜索。详情页的问题在数据库查询次数。第一版实现里文章详情页需要查询文章本身、作者、分类、标签、评论列表而这些查询分散在多个地方加载一次页面可能触发六条以上 SQL 语句。用数据库查询日志一查就发现了这个“N1 查询”问题。解决方法是把关联数据一次性查询出来再用 ORM 的预取机制把作者、标签和评论都一起取回。搜索的问题前面已经提到过最后用全文索引加 Redis 缓存解决。这轮排查我总结出一个判断方法慢未必是“一个点慢”而是数据量大了以后原本没被注意到的多次查询和全表扫描集中爆发。排查时先打开慢查询日志把实际执行的 SQL 捞出来看执行计划比凭感觉猜“是不是服务器性能不行”要高效得多。5. 从本地跑通到真正上线部署与性能调优记录5.1 一套代码跑两个环境通过配置文件区分第一版项目里数据库连接地址、调试开关、静态文件路径全部写死在一个配置文件里每次打包上传到服务器都要改代码非常容易漏。这一版我做了环境区分本地开发环境使用一套配置线上生产环境使用另一套配置通过系统环境变量或启动参数切换。生产环境最关键的一个开关是关闭调试模式。把调试模式关掉之后框架不会再返回详细的错误堆栈给访客而是记录到日志文件里。这对安全很有必要因为错误页面包含的路径、数据库字段名等细节在攻击者眼里都是信息收集的入口。5.2 Nginx 和 Gunicorn 的分工谁该干什么部署时我没有用 Docker而是选择最基础的 Nginx Gunicorn 组合。分工很简单用户请求先到 Nginx如果请求的是图片、CSS、JavaScript 这类静态文件Nginx 直接从磁盘返回如果请求的是动态页面接口Nginx 把请求转发给后面运行的 Gunicorn 进程。Nginx 的关键配置是静态文件的路径映射location /static/ { alias /srv/blog/static/; } location / { proxy_pass http://127.0.0.1:8001; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }Gunicorn 这边需要注意 worker 数量的设置。太多导致内存超占太少则并发一高就响应迟缓。我这边用的是gunicorn config.wsgi:application -w 4 -b 127.0.0.1:8001 --timeout 604 个 worker 对于一个日访问量几百到几千的博客来说完全够用。服务进程用守护进程工具来管理崩了会自动拉起省去自己写 shell 脚本监控的麻烦。5.3 缓存策略不把 Redis 当摆设而是用在刀刃上Redis 缓存我主要用在三个地方热门文章详情页的 HTML 片段缓存、浏览量计数、搜索结果缓存。文章详情页的缓存逻辑是这样的访客第一次访问某篇文章时从数据库读出来并渲染完整页面然后把渲染结果存入 Redis有效时间设为十分钟。十分钟内再有访问直接返回缓存数据库压力大幅下降。浏览量计数则不走页面缓存而是先写入 Redis 的计数器再定时批量写回数据库避免每次点击都直接更新数据库行。必须强调的是缓存不是加得越多越好。评论和文章管理后台这类对一致性要求高的功能就绝对不能开长时间缓存否则作者刚发的评论读者半个小时内都看不到。缓存一定要按数据的“新鲜度容忍度”来区分对待。6. 项目收尾复盘哪些做法最有效哪些教训最贵6.1 分层写代码别把视图函数变成“万能垃圾桶”第二版里我刻意把代码分成三层视图层负责接收请求和返回响应业务逻辑层负责文章处理、评论校验这类核心规则数据访问层只负责 ORM 查询。第一版最大的坏味道是路由函数又长又乱一个函数里既有查询、又有权限判断、还有模板渲染。重构之后每个函数长度明显缩短后面加新功能时也更容易知道改哪个文件。这种分层写起来会感觉“多写了文件”但维护到后期就会发现省回来的时间远多于多写的时间。实训项目只有一个月时可能感觉不明显如果把项目延续半年以上这种差距会非常巨大。6.2 Git 提交规范是我差点放弃、最后收获最大的习惯第一版时我的 Git 提交信息基本是“update”或者“fix”到后来想回退某个改动根本不知道当时改了啥。第二版我强制自己按照“类型:描述”的格式写提交信息比如feat: 增加置顶文章功能、fix: 修复搜索接口 ngram 分词报错。坚持两周后回看历史时能非常快地定位到具体提交配合版本回退再也不用担心改坏了代码无法恢复。给实训同学的建议是提交频率宁可高一点也不要把一周的改动攒成一次提交。每完成一个小的可运行节点就提交一次写清楚做了什么这是性价比最高的项目保险。6.3 文档虽小关键时刻能救命实训项目答辩前我花了一个下午给项目写了一份完整的说明文档内容包括运行环境要求、安装依赖的命令、数据库迁移步骤、启动命令、部署流程、目录结构说明。后来我在一台全新服务器上重新部署这套博客照着文档一步步执行三十分钟就完成了。如果没有这份文档又要凭记忆一个个装依赖、对配置至少得折腾半天。技术项目的价值不只在于“能跑”还在于别人接手时能看懂、能部署。给未来的自己留一份能照着执行的文档这是我在这个项目里做得最值的一件事。回看整个“个人博客2”技术上算不上有多深但它让我完整地体验了一遍“从选项到上线、从踩坑到优化”的真实流程。如果你也在做类似的实训项目我建议不必追求功能数量上的炫技而是把内容管理、用户安全、搜索性能、部署运维这几条主线真正打通。认真做好一个传统项目比浅尝辄止地摆弄十个新项目学到的东西要多得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑