我的被窝 · 个人博客系统

一个属于自己的内容阵地,从 0 到 1 跑通 Web 全链路,实现纯个人风格前后台博客

一、项目背景

2022 年底,在大二学完动态网页 与数据库基础之后,我需要一个能"把所学全部串起来"的实战项目。"我的被窝"这个名字来源于我希望拥有一个属于自己的、温暖的小角落——一个可以记录生活、整理学习笔记、分享作品的内容阵地。

选博客系统作为全栈入门项目,是因为它足够"小而全":既有前台的内容展示,又有后台的数据管理;既要处理静态资源,又要处理动态接口;既涉及用户权限,又涉及富文本与评论交互,它能完整覆盖 Web 开发的全链路。

定位:这不是一个追求功能大而全的 SaaS 产品,而是一个"一个人能独立完成的全栈 MVP"——它让我第一次真正实践"一个网页从浏览器发起到数据库返回再到页面渲染"的完整旅程。

二、技术选型与架构设计

技术选型的核心原则是"够用、好学、能跑通"。作为第一个全栈项目,没有引入 React/Vue 这类前端框架,而是选择了服务端渲染(SSR)的传统栈,把注意力集中在理解 HTTP、模板与数据库上。

层级 技术 选型理由
后端框架 Express Node.js 最轻量 Web 框架,路由/中间件模型清晰,上手成本低,社区资源丰富
模板引擎 art-template 性能高、语法接近原生 HTML,支持 include 公共组件复用,适合 SSR 场景
数据库 MongoDB 文档型存储,文章/评论结构灵活无需建表,MongoDBCompass 操作直观
前端交互 jQuery DOM 操作与 AJAX 简洁,避免在框架学习上分散精力,聚焦业务逻辑
UI 框架 Bootstrap 响应式栅格 + 现成组件(模态框、表单、分页),快速搭建美观后台
选 jQuery 而不是 Vue,是因为当时2022年这个阶段我更想搞清楚"数据是怎么从后端流到前端的",技术选型的第一优先级是"学习成本最低、最快跑通全链路",而非"生产环境最优"。后面我主要在TOB网站项目实践了Vue的全流程。

2.1 整体技术架构

# 浏览器(jQuery + Bootstrap) │ HTTP 请求(GET / POST) ▼ Express 应用(路由 + 中间件) ├── 静态资源:public/(CSS/JS/图片/LOGO) ├── 路由层:routes/(前台 home/article + 后台 admin) ├── 控制层:controller/(业务逻辑 + 权限校验) └── 模板渲染:art-template → HTML 输出 │ mongoose 操作 ▼ MongoDB(users / articles / comments / messages)

2.2 前后台架构

系统分为前台(访客视角)后台(管理员视角)两套独立的页面与路由,共用同一套数据库。前台专注内容呈现与互动,后台专注内容生产与管理。

页面/模块 核心职责
前台 首页文章列表 分页展示最新文章,封面图 + 摘要 + 标签
文章详情页 正文渲染、目录锚点跳转、评论区
百宝箱 个人收藏的资源、工具、书单整理
留言板 访客公开留言,无需登录即可提交
作品集 展示个人设计/开发作品的卡片墙
后台 用户管理 用户增删改查、角色分配(普通/管理员)
文章管理 文章发布、编辑、删除、状态切换
分页模块 通用分页组件,复用于列表与后台

2.3 数据库 ER 设计

数据库围绕"用户 - 文章 - 评论 - 留言"四类核心实体设计,用户与文章为一对多,文章与评论为一对多,留言板独立。

users · 用户
PK _id
username
password (MD5)
role (0/1)
email
avatar
articles · 文章
PK _id
FK author → users
title
content
cover
tags
createdAt
comments · 评论
PK _id
FK article → articles
FK user → users
content
createdAt
messages · 留言
PK _id
nickname
content
contact (可选)
createdAt
设计要点:评论与文章通过 articleId 关联,查询文章详情时通过 populate 联表取出评论及评论者昵称,避免前端二次请求。

三、核心功能实现

3.1 文章 CRUD

文章的增删改查是后台的核心。通过 Express 路由 + mongoose 模型完成数据操作,art-template 渲染后台列表与编辑页。

// routes/admin/article.js —— 文章发布 router.post('/add', async (req, res) => { const { title, content, cover, tags } = req.body; await Article.create({ title, content, cover, tags, author: req.session.user._id // 从会话取作者 }); res.redirect('/admin/articles'); }); // 文章列表分页查询 router.get('/', async (req, res) => { const page = +req.query.page || 1; const size = 5; const total = await Article.countDocuments(); const list = await Article.find() .sort({ createdAt: -1 }) .skip((page - 1) * size) .limit(size) .populate('author', 'username'); res.render('admin/article-list', { list, page, total, size }); });

3.2 用户权限与登录拦截

用户分为普通用户(role=0)管理员(role=1)两种角色。登录成功后将用户信息存入 session,通过中间件统一拦截未登录与权限不足的访问。

// 中间件:登录拦截 function requireLogin(req, res, next) { if (!req.session.user) { return res.redirect('/login?from=' + req.url); } next(); } // 中间件:管理员权限校验 function requireAdmin(req, res, next) { if (req.session.user?.role !== 1) { return res.status(403).send('权限不足,仅管理员可访问'); } next(); } // 后台路由统一挂载 app.use('/admin', requireLogin, requireAdmin, adminRouter);

3.3 评论系统

文章详情页底部嵌入评论区,登录用户可提交评论。评论通过文章 ID 关联,提交后实时追加到评论列表,并显示评论者昵称与头像。

  • 提交:POST /comment/add,携带 articleId 与 content
  • 展示:查询文章时 populate 关联 comments,按时间倒序渲染
  • 防刷:未登录跳转登录页;内容为空前端拦截,不发起请求

3.4 分页模块

分页是博客列表与后台管理的高频需求,封装为通用组件,传入当前页、总条数、每页条数即可渲染。

参数 含义 示例
page当前页码(从 URL query 取)1
size每页条数5
total总条数(countDocuments 取)23
totalPage总页数(Math.ceil 计算)5
实现细节:skip((page-1)*size).limit(size) 完成 MongoDB 分页查询;前端用 art-template 循环生成页码按钮,当前页高亮、首末页禁用上下页。

3.5 密码 MD5 加密

密码绝不能明文入库。使用 MD5 对密码进行单向散列,登录时对输入密码同样做 MD5 后与数据库比对。

// utils/crypto.js const crypto = require('crypto'); function md5(str) { return crypto.createHash('md5').update(str).digest('hex'); } // 注册:密码加密入库 const user = await User.create({ username, password: md5(password) // 不存明文 }); // 登录:加密后比对 const exist = await User.findOne({ username, password: md5(password) });
记录:MD5 在今天已不安全(可被彩虹表反查)。这是当时学习阶段的方案,后续项目已升级为 bcrypt 加盐哈希,但这个练习让我更深刻理解"加密"。

四、设计风格

功能是骨架,视觉是皮肤。除了把功能跑通,我在视觉设计上也花了大量心思——希望这个博客不仅"能用",更要"充满个人色彩"。

4.1 莫兰迪黄配色

摒弃常见的纯白 + 蓝色科技风,选了低饱和度的莫兰迪黄作为主色,营造出"午后阳光洒在被窝上"的温暖感,呼应"我的被窝"这个项目名。

莫兰迪黄 #d8caaf
暖驼色 #c4a77d
米杏色 #e8dcc4
深咖文字 #5c4a3a

4.2 圆角毛玻璃风格

卡片大量使用 backdrop-filter: blur() 实现毛玻璃质感,配合 12-16px 的大圆角,整体柔和不刺眼。

/* 毛玻璃卡片核心样式 */ .card { background: rgba(255, 255, 255, 0.65); backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px); border: 1px solid rgba(196, 167, 125, 0.3); border-radius: 14px; box-shadow: 0 8px 32px rgba(196, 167, 125, 0.15); }

4.3 个人原创 LOGO

没有用现成的图标库,而是手绘了一个专属 LOGO——一个蜷缩在被窝里的小形象,呼应"我的被窝"主题。LOGO 用在线条简洁的单色版与带渐变的彩色版两种场景,分别用于导航栏与首页大图。

设计理念:LOGO 不追求精致复杂,而追求"个人IP塑造"。它出现在浏览器 favicon、导航栏、页脚与封面图四个位置,形成统一的品牌识别。

五、项目复盘与反思

"我的被窝"是我第一个真正意义上独立完成、并部署上线的全栈项目,第一次完整地走完了"需求 → 设计 → 开发 → 部署"的整个流程。

5.1 最大的收获

  • 建立 Web 全链路理解:从浏览器发请求 → Express 路由 → 控制器 → MongoDB数据库 → 模板渲染 → 浏览器展示,每一段都自己深刻实践。
  • 理解前后台分离的本质:虽然用的是 SSR,但通过这个项目理解了"为什么后来要前后端分离"——SSR 在交互复杂时会很笨重。
  • 建立工程化意识:学会了用中间件复用逻辑、用 public 目录管理静态资源、用 session 管理登录态、用分页组件复用 UI。
  • "产品化"思维:不只是写代码、想配色、想 LOGO,还要想用户看到这个页面是什么感受、如何交互、如何优化用户体验——这开启了后来做产品的工作方式。

5.2 不足与改进方向

问题 当时方案 改进方向
密码加密 MD5(可被彩虹表反查) bcrypt 加盐哈希
登录态 session 存内存(重启丢失) Redis / JWT
前端交互 jQuery + SSR 前后端分离 + Vue/React
评论防刷 仅前端非空校验 限流 + 验证码 + 敏感词过滤
图片存储 本地 public 目录 对象存储 OSS + CDN

回看技术架构的演进理解:当时选择MongoDB是因为"文档型无需建表、上手快",但也因此缺乏关系建模的训练;后续在水土保持系统项目中切换到MySQL+严谨的ER设计,补上了这一课。

本页