从零搭建个人博客平台:毕业设计中的技术选型与工程实践
毕业设计选「个人博客」看似人畜无害,真动手才发现:功能越堆越多,代码越写越乱,最后把 README 写成忏悔录。本文用“踩坑复盘”的方式,把前后端分离博客的完整链路拆给你看,哪儿该偷懒、哪儿该细抠,一目了然。
1. 毕业设计常见三大坑
功能堆砌无架构
很多同学把“炫酷”当需求:点赞、打赏、站内信、夜间模式……写到一半发现接口耦合、数据库字段随意扩展,最后牵一发动全身。安全性直接裸奔
评论存原始 HTML、管理后台/admin路径无鉴权、JWT 永不过期,答辩现场被老师一句“XSS 怎么防”问得原地社死。部署=手动 FTP
本地跑得好好的,上云后 502 不断;SQLite 锁库、端口冲突、环境变量写死,日志还找不到,只能对着宝塔面板发呆。
2. 技术栈选型:别被“新”字洗脑
前后端分离已成默认选项,但“怎么分”仍有讲究。下面用一张表把常见方案对比清楚,毕业设计直接抄作业即可。
| 维度 | 静态生成 (Hugo/Hexo) | SSR (Next/Nuxt) | SPA+CSR API (Vue+Node) |
|---|---|---|---|
| SEO | 极好 | 极好 | 需 SSR 或预渲染 |
| 动态交互 | 差 | 好 | 极好 |
| 学习成本 | 低 | 中 | 高 |
| 运维成本 | 最低(丢 CDN 就行) | 中(要 Node 服务) | 高(DB+缓存+反向代理) |
| 适合毕设 | 想速成 | 想炫技 | 想展示全栈能力 |
结论:
- 只想“能跑就行”→ 静态生成
- 想拿优秀且时间充裕 → SPA+API,本文后续围绕此展开
数据库同理:
- SQLite:本地开发省事儿,毕设答辩完直接关机,但高并发写会锁库
- PostgreSQL:云厂商免费额度足够,毕设级别放心用,Docker 一行命令拉起来
3. 核心模块实现细节
3.1 用户认证:JWT 不是银弹
- 存 RefreshToken 到 httpOnly Cookie,防 XSS
- AccessToken 设 15 min 过期,Redis 存黑名单,支持后台踢人
- 注册时邮件验证码,用 nodemailer + 免费邮箱 SMTP,毕业设计足够
3.2 Markdown 渲染:别直接innerHTML
- 后端统一用
marked+dompurify过滤,返回纯 HTML - 代码高亮用
prismjs,服务端渲染完缓存,减轻前端压力 - 数学公式可选
KaTeX,CDN 引入即可,不必自己搭构建链
3.3 评论防刷:毕业设计也得上限流
- 同一 IP 每 60 s 最多 3 次评论,Redis
INCR计数器实现 - 内容先过敏感词库,再 DOMPurify 清洗,最后入库
- 嵌套评论用闭包表(closure table)模型,查询一条 SQL 即可整树,答辩可吹“无限层级”
4. Clean Code 示例:Express 路由片段
下面给出一个“文章模块”精简骨架,含关键注释,可直接放进论文附录。
// routes/post.js const express = require('express'); const router = express.Router(); const db = require('../models/db'); const auth = require('../middleware/auth'); const { body, validationResult } = require('express-validator'); /** * 创建文章 * POST /api/posts * 权限: 登录用户 */ router.post('/', auth.required, // 1. 鉴权 [ body('title').trim().isLength({ min: 1 }), // 2. 校验 body('content').trim().isLength({ min: 1 }) ], async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(422).json({ errors: errors.array() }); } const { title, content, tags } = req.body; try { // 3. 参数化查询防注入 const sql = ` INSERT INTO posts (title, content, author_id, tags, created_at) VALUES ($1, $2, $3, $4, NOW()) RETURNING id`; const { rows } = await db.query(sql, [title, content, req.user.id, tags]); return res.status(201).json({ id: rows[0].id }); } catch (e) { console.error(e); return res.status(500).json({ msg: 'db error' }); } }); module.exports = router;要点:
- 路由层只做“收包-校验-转发”,业务下沉 service
- SQL 预编译,拒绝拼接
- 统一错误码,前端好匹配
5. 性能瓶颈 & 安全措施速览
冷启动
Serverless 场景下首次请求要拉依赖,可把node_modules打包进镜像,或换常驻实例并发读写
文章列表页加LIMIT+OFFSET深度分页会慢,用“上一页 ID”游标方式;热门文章放 Redis 缓存 5 min,读多写少场景命中率极高XSS 过滤
所有富文本统一过dompurify,并设置内容安全策略(CSP)响应头:default-src 'self'; style-src 'unsafe-inline'CSRF 防护
用 SameSite=Strict Cookie 基本够用;若想更保险,再加一层双重 Cookie 令牌
6. 生产环境避坑指南
忽略 HTTPS
浏览器默认拦截混合内容,评论头像无法加载;云厂商免费证书一键下发,别再给自己找“此站点不安全”大红条不做输入校验
把 express-validator 当摆设,数据库里躺满 2 MB 的 emoji 标题,MySQL 直接Incorrect string value炸库日志缺失
线上 500 返回啥?不知道。Winston 或 Pino 按天滚动,错误栈直接丢进独立文件,答辩老师问“你怎么排查”时,把日志翻出来即可忘记备份
SQLite 直接复制文件即可;PG 用pg_dump定时导到对象存储,毕业设计虽然数据小,但“误删库”故事永不过时把 .env 一并打包镜像
泄露 JWT 密钥=全站裸奔;用云平台的“密钥管理”注入,本地开发才加载.env.example
7. 把博客演进为轻量级 CMS
毕设结束代码吃灰?不妨多想一步:
- 把用户角色拆成“管理员/作者/访客”,权限粒度到按钮级
- 文章加“专栏”与“专题”两种聚合,数据库表结构预留
category_id,serial_id - 页面动态配置:利用 JSON Schema 描述轮播、友情链接,后台拖拽生成,秒变小型 CMS
- 插件化钩子:在渲染管线里预留
beforeRender钩子,后续可接入广告、推荐位,商业化也能接得住
8. 小结
把“个人博客”当成一次最小化的全栈练兵:
- 先让架构跑“通”,再让代码“干净”,最后让部署“稳”
- 每步都留扩展点,毕设答辩能吹,未来迭代不翻车
下一步,你会给博客加上多租户,还是直接拆成 headless CMS?评论区等你脑洞。