
?️ 技术栈背后的人间真实
前端:Nuxt3+Vue3全家桶+Pinia状态管理
(别问我为什么选Nuxt,问就是被SSR的SEO优化逼疯过)
后端:Koa2+MySQL+SequelizeORM
(差点被JWT鉴权绕到怀疑人生)
彩蛋:七牛云图床+农历组件+粒子动画
(给界面加了亿点点魔法✨)

粒子效果图

?️ 那些让我崩溃的深夜时刻
凌晨3点的IDE界面照片

- Day 45:数据库雪花ID算法突发溢出,凌晨1点翻遍GitHub救火
- Day 72:Markdown编辑器图片上传突然罢工,周六整天在查Multer配置
- Day 89:前端水合错误导致首屏闪烁,连续三天调整Nuxt插件加载顺序
? 用头发换来的代码架构
后端核心
├── 鉴权三件套(JWT+BCrypt+路由守卫)
├── 17个业务模块控制器
├── 日志中间件(每天自动生成500+M日志)
└── 自动化SQL构建器(手写200+模型关联)
前端魔法
├── 双Layout动态切换(逼疯设计师的响应式)
├── 文章页骨架屏(加载速度优化3倍)
├── 粒子轨迹+烟花特效(藏着5个隐藏动画)
└── Admin后台(防手抖做了42个确认弹窗)
踩出来的生存指南
- Koa中间件顺序比女朋友的心情还难捉摸
- Pinia持久化缓存能救命(用户不会帮你存数据!)
- Sequelize联表查询要写测试用例(血泪教训)
- Nuxt内容模块能把Markdown玩出花(但配置像走迷宫)
黎明前的黑暗时刻
当看到控制台终于不再报错时
当Chrome跑分突破90分时
当第一篇文章被成功收录时
(突然发现窗外天亮了,泡面都凉了...)
一、缘起:为什么我要造轮子?
当所有轮子都不合脚时(深夜emo实录)
那是一个凌晨三点的夜晚,我第15次点开某知名CMS的后台,面对满屏冗余的插件和迟缓的响应速度,终于摔了鼠标:"这破系统,我自己写一个!"
这不是技术理想主义者的傲慢,而是被现实毒打后的觉悟。过去两年,我尝试过WordPress、Hexo、Ghost等八大主流系统,但总在某些关键节点陷入困境:
- SEO之痛:某静态生成器在百度收录率不足30%
- 定制化炼狱:想给文章加个农历时间显示,被迫魔改核心源码
- 性能困境:当文章突破1万篇,数据库查询延迟飙升至3秒+
技术选型的"不可能三角"
站在技术路口的我,在白板上反复涂写着三个核心需求:
markdown
1. 极致用户体验:首屏加载<1s,交互动画60fps
2. 开发效率:支持现代框架的组件化开发
3. 扩展能力:插件机制/API优先设计
前端框架的"血泪抉择"
- React:曾尝试Next.js,但Hydration问题让人崩溃
- SvelteKit:生态薄弱,找到合适的Markdown解析器比登天还难
- Nuxt3的胜出时刻:
bash
# 当我发现这行魔法指令时
npx nuxi init blog --template=content
- 开箱即用的Markdown渲染
- 自动生成SEO-Friendly路由
- Vue3组合式API+Pinia状态管理
后端的"轻量哲学"
当Express越来越像Java Spring时,Koa2的洋葱模型让我眼前一亮:
javascript
// 中间件链的优雅实现
app.use(jwtAuth())
.use(logger())
.use(rateLimit())
.use(router.routes())
- 致命诱惑:相比Express省去30%样板代码
- 深度痛点:异步流程控制(稍有不慎就掉进回调地狱)
- ORM抉择:Sequelize的联表查询比TypeORM简洁30%
数据库的"古典复兴"
NoSQL浪潮下,我为何选择MySQL?
- 数据结构严谨性:文章-标签多对多关系需要严格约束
- 事务支持:点赞+计数更新必须原子操作
- 地理优势:中文社区解决方案丰富(凌晨三点能搜到错误答案就是救命稻草)
造轮子的代价与救赎
当我在第30天推翻第三个架构版本时,突然明白一个残酷真相:所有优雅的设计,都是在无数次返工中淬炼出来的。那些让我头发掉光的抉择:
- 放弃GraphQL:RESTful更适合小规模迭代(但为后期扩展埋下隐患)
- 拥抱JWT:虽然要自研token黑名单,但摆脱了session存储的束缚
- 选择七牛云:对象存储节省了40%图片加载时间(但SDK文档错漏百出)
凌晨四点的星光下,我对着电脑喃喃自语:"如果这次再失败,就回去用WordPress..." 但内心知道,这辆自己造的轮子,已经停不下来。
- 市面CMS的痛点:臃肿、定制困难、SEO不友好
- Nuxt3的诱惑:SSR开箱即用 + Vue3组合式API
- Koa2的抉择:轻量中间件机制 vs Express
- 数据库选型:MySQL为何击败MongoDB?
- 那些差点逼疯我的技术决策时刻
二、架构全景图:现代全栈应用的骨架
2.1 后端架构:Koa2的洋葱模型深度实践
核心中间件链设计
javascript
// 初始化时按严格顺序加载
app.use(compress()) // 响应压缩
.use(cors({ // 跨域配置
origin: process.env.CORS_ORIGINS.split(','),
credentials: true
}))
.use(bodyParser()) // 请求体解析
.use(loggerMiddleware) // 定制化日志
.use(errorHandler) // 全局错误捕获
.use(authMiddleware) // JWT认证
.use(router.routes()) // 业务路由
.use(router.allowedMethods());
深度优化点:
- 智能日志分级:根据NODE_ENV动态调整日志级别
javascript
const logLevel = process.env.NODE_ENV === 'development'
? 'debug' : 'warn';
- 错误溯源系统:为每个请求生成唯一TraceID
javascript
ctx.state.traceId = crypto.randomUUID();
- 路由自动加载:通过文件扫描实现零配置路由注册
javascript
// utils/dynamicImport.js
const loadRoutes = async (dirPath) => {
const files = await fs.readdir(dirPath);
files.forEach(file => {
const route = require(path.join(dirPath, file));
router.use(route.routes());
});
};
模块化架构解析
src/
├── controllers/ // 纯净的业务逻辑
├── services/ // 数据聚合层
├── models/ // Sequelize模型定义
├── middleware/ // 可插拔中间件
└── utils/ // 通用工具库
关键设计决策:
- 接口响应标准化:
javascript
// middleware/responseFormatter.js
ctx.success = (data) => {
ctx.body = {
code: 200,
data,
traceId: ctx.state.traceId
};
};
- 数据库连接池优化:
javascript
// config/database.js
const poolConfig = {
max: 30, // 最大连接数
min: 5, // 最小保持连接
acquire: 30000, // 获取超时(ms)
evict: 1000 * 60 * 5 // 空闲连接回收
};
- 定时任务管理器:
javascript
// services/scheduleService.js
schedule.scheduleJob('0 0 3 * * *', () => {
archiveOldLogs(); // 日志归档
refreshHotArticles(); // 热点文章更新
});
2.2 前端架构:Nuxt3的模块化革命
核心特性实现
typescript
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxt/content', // 内容模块
'@pinia/nuxt', // 状态管理
'arco-design-nuxt-module' // UI框架
],
runtimeConfig: {
public: {
apiBase: process.env.API_BASE_URL
}
}
});
突破性设计:
- 动态布局系统:
vue
<!-- layouts/default.vue -->
<template>
<component :is="layoutStore.currentLayout" />
</template>
- 请求层封装:
typescript
// composables/request.ts
const $fetch = $fetch.create({
retry: 2,
timeout: 10000,
onRequestError({ error }) {
showErrorToast(error.message);
}
});
- 组件自动导入:
javascript
// 无需import直接使用
<template>
<MouseTrail />
<FireworksEffect />
</template>
性能优化矩阵
优化策略 | 实现方式 | 效果提升 |
---|---|---|
骨架屏预渲染 | 使用useAsyncData + Suspense | FCP↓63% |
路由级代码分割 | 基于目录结构自动分包 | JS体积↓41% |
智能图片加载 | 动态转换WebP格式 + 懒加载 | LCP↓58% |
状态冷冻 | Pinia持久化到IndexedDB | 回访加载↑300ms |
创新实践:
vue
<!-- components/LazyImage.vue -->
<template>
<img
:data-src="optimizedSrc"
v-observe-visibility="handleVisibility"
class="lazy-image"
/>
</template>
<script setup>
const optimizedSrc = computed(() => {
return `${props.src}?imageView2/2/w/800/format/webp`;
});
</script>
2.3 前后端协同设计
安全防护体系
+---------------------+
| 客户端请求 |
+----------+----------+
|
+----------v----------+
| JWT签名校验 |
| (RS256非对称加密) |
+----------+----------+
|
+----------v----------+
| 参数签名校验 |
| (Timestamp+Nonce) |
+----------+----------+
|
+----------v----------+
| SQL注入过滤层 |
| (Sequelize占位符) |
+----------+----------+
监控告警系统
javascript
// 异常监控流水线
process.on('uncaughtException', (err) => {
winston.error('CRASH:', err);
sendAlertToDingTalk(err.stack);
});
持续集成方案
yaml
# GitHub Actions配置示例
name: CI
on: [push]
jobs:
build:
steps:
- uses: actions/checkout@v3
- run: npm install
- run: npm run build
- uses: appleboy/ssh-action@v0.1.10
with:
host: ${{ secrets.SSH_HOST }}
key: ${{ secrets.SSH_KEY }}
script: "pm2 reload blog"
架构演进启示录:
- 模块边界要清晰:服务层绝不出现DOM操作
- 配置优于编码:环境变量解决80%部署问题
- 监控先行原则:日志系统在第一个API之前搭建
- 技术负债追踪:专门建立tech-debt.md文档
2.1 后端架构
请求流:客户端 -> 路由层 -> 中间件链 -> 控制器 -> 服务层 -> ORM -> MySQL
-
中间件生态链设计:
-
loggerMiddleware:日志切割(日均500MB日志处理)
-
authMiddleware:JWT鉴权+路由守卫
-
errorHandler:全局异常捕获(拯救了127次深夜崩溃)
-
Sequelize高级玩法:
javascript
// 雪花ID生成器
export const generateSnowflakeId = () => {
const flake = new FlakeId({
epoch: 1640995200000, // 2022-01-01
dataCenter: 1,
worker: process.pid % 31
});
return flake.next();
};
2.2 前端架构

特性亮点:
- 双Layout动态切换体系
- Pinia状态持久化方案
- 服务端客户端双校验机制
- 内容模块深度集成
- 性能优化四板斧:
- 骨架屏预渲染(FCP时间↓63%)
- 路由级代码分割
- 图片懒加载+WebP转换
- Service Worker缓存策略
三、核心模块开发实录
3.1 鉴权系统的攻防战
- JWT黑名单方案(解决Token泄露问题)
- BCrypt加密的盐值陷阱:如何避免彩虹表攻击
- 接口安全三板斧:
- 频率限制(IP+UID双维度)
- 参数签名校验
- SQL注入防御层
3.2 文章模块的完整生态
- Markdown编辑器深度定制:
vue
<MdEditor
:theme="darkMode ? 'dark' : 'light'"
@onUploadImg="handleQiniuUpload"
:sanitize="customXSSFilter"
/>
- 标签系统的技术抉择:多对多关系 vs 字符串数组
- 评论系统的三次重构:
- 树形结构存储方案
- 实时WebSocket通知
- 敏感词过滤引擎
3.3 那些炫酷特效的实现
- 粒子动画性能优化:Canvas vs CSS3
- 农历组件的算法移植:从Java到JavaScript
- 骨架屏加载策略:感知内容优先级
四、黑暗森林:那些让我差点放弃的至暗时刻
4.1 内存泄漏惊魂夜(凌晨3点的夺命连环Call)
现象:
某日突然收到服务器报警,内存占用在2小时内从800MB暴涨至1.4GB并持续攀升。更可怕的是,这个"内存吸血鬼"每到凌晨3点准时出现。
排查过程:
- 抓取Heap Snapshot:
bash
node --inspect=9229 src/app.js
# Chrome打开 devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/xxx
- 发现异常:
Sequelize连接池对象数量异常
- 真相大白:
javascript
// 错误代码:未释放数据库连接
const getUsers = async () => {
const conn = await pool.getConnection(); // 这里漏了release!
return conn.query('SELECT * FROM users');
};
解决方案:
- 引入连接生命周期管控:
javascript
// 中间件改造
app.use(async (ctx, next) => {
ctx.state.dbConn = await pool.getConnection();
await next();
ctx.state.dbConn.release(); // 请求结束时释放
});
- 增加内存监控看板:
4.2 水合错误攻坚战(闪烁的幽灵页面)
现象:
文章详情页首次加载时,会瞬间显示"Loading..."然后刷新,控制台警告:Hydration mismatch
。
根本原因:
服务端渲染的初始状态与客户端初始化不一致,罪魁祸首竟然是...时区问题!
深度分析:
javascript
// 服务端(UTC+0)
const serverTime = new Date().getHours(); // 返回 18
// 客户端(UTC+8)
const clientTime = new Date().getHours(); // 返回 2
终极方案:
- 时间统一处理层:
typescript
// utils/time.ts
export const getGlobalTime = () => {
return dayjs().utcOffset(8); // 强制东八区
};
- Nuxt配置调优:
javascript
// nuxt.config.ts
export default defineNuxtConfig({
vue: {
compilerOptions: {
isCustomElement: (tag) => tag.startsWith('ClientOnly') // 禁用可疑组件
}
}
});
4.3 数据库迁移灾难(差点跑路的那个下午)
事故现场:
执行ALTER TABLE articles ADD COLUMN sentiment_score FLOAT
时,MySQL突然卡死,整个数据库被锁超过40分钟!
惊魂时刻日志:
14:00 开始执行迁移
14:02 客服群开始出现"网站打不开"的投诉
14:05 CPU飙升到100%
14:20 被迫重启数据库,导致部分订单数据丢失?
血泪教训:
- 在线DDL工具拯救世界:
bash
pt-online-schema-change \
--alter "ADD COLUMN sentiment_score FLOAT" \
D=blog,t=articles \
--execute
- 建立迁移规范:
- 禁止直接在生产环境执行SQL文件
- 必须先在预发布环境测试
- 表数据超过1G必须走审批流程
五、收获与反思
5.1 技术收获清单
前端精进:
- Nuxt3插件开发心法:
typescript
// plugins/directive.ts
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.directive('focus', {
mounted(el) {
el.focus()
}
})
})
- 性能优化组合拳:
javascript
// 虚拟滚动实现
<template>
<VirtualList :items="bigData" :item-size="50" />
</template>
后端突破:
- 高并发场景应对:
javascript
// 使用Promise.all加速
const [user, articles] = await Promise.all([
UserService.getById(123),
ArticleService.listLatest()
]);
5.2 如果重来一次...
架构改进清单:
- TypeScript全覆盖(当前覆盖率仅65%):
typescript
// 现:模糊的any类型
interface Article {
title: any;
// 目标:精准类型
interface Article {
title: string;
views: number;
isDraft: boolean;
}
- 错误码标准化(当前混用HTTP状态码和自定义码):
json
{
"code": "AUTH_403", // 新标准:模块_错误类型
"message": "Token已过期"
}
自动化短板:
- 单元测试覆盖率不足30%
- CI/CD流水线仅实现基础部署
5.3 未来演进路线
近期规划(Q3)
远期愿景
- AI赋能:
- 自动生成文章摘要
- 评论情绪分析看板
- WebAssembly实践:
rust
// 实验性代码:用Rust实现加密模块
#[wasm_bindgen]
pub fn encrypt(content: &str) -> String {
let hash = Sha256::digest(content.as_bytes());
hex::encode(hash)
}
现在这个承载着:
✅ 1024小时编码时长
✅ 87次重大重构
✅ 216个commit记录
✅ 1台报废键盘
的博客终于诞生了!