缘起
一直以来都想拥有一个属于自己的博客,记录技术思考和生活感悟。在网上看了不少方案,最终决定自己动手写一个——用 Flask + SQLite 的轻量组合,部署在云服务器上。
技术选型
| 模块 | 选择 | 原因 |
|---|---|---|
| 后端框架 | Flask 3.1 | 轻量灵活,Python 生态 |
| 数据库 | SQLite + SQLAlchemy | 单用户博客,无需 MySQL |
| 前端 | 原生 HTML/CSS/JS | 无框架依赖,加载轻快 |
| Markdown | Python-Markdown + Pygments | 文章编写体验好,代码高亮 |
| 部署 | Gunicorn + Nginx | 简单可靠,反向代理静态文件 |
核心架构
应用工厂模式
def create_app(config_name=None):
app = Flask(__name__)
app.config.from_object(config_map.get(config_name))
# 初始化扩展
db.init_app(app)
login_manager.init_app(app)
csrf.init_app(app)
# 注册蓝图
app.register_blueprint(main_bp) # 公开路由
app.register_blueprint(auth_bp, url_prefix='/auth')
app.register_blueprint(admin_bp, url_prefix='/admin')
# 初始化数据库
with app.app_context():
db.create_all()
_init_admin(app)
return app
数据库模型
文章模型是核心,支持 Markdown 渲染、标签管理、分类关联:
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
slug = db.Column(db.String(200), unique=True, index=True)
content = db.Column(db.Text, nullable=False) # 原始 Markdown
tags = db.Column(db.String(500), default='') # 逗号分隔
is_published = db.Column(db.Boolean, default=True)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'))
category_id = db.Column(db.Integer, db.ForeignKey('category.id'))
def content_html(self):
import markdown
md = markdown.Markdown(extensions=['extra', 'codehilite', 'toc'])
return md.convert(self.content)
前端亮点
暗夜模式
通过 CSS 变量实现一键切换,状态保存在 localStorage:
:root {
--bg: #f5f5f5;
--font-color: #4c4948;
--card-bg: #fff;
}
[data-theme='dark'] {
--bg: #0d0d0d;
--font-color: rgba(255,255,255,0.75);
--card-bg: #141414;
}
PJAX 无刷新导航
页面切换只替换 #pjax-container 内的内容,无需整页加载:
new Pjax({
selectors: ['#pjax-container', 'title']
});
一言 · 每日随机语录
每次回到首页,从缓存池中随机挑一句诗词或名言展示:
var pool = JSON.parse(localStorage.getItem('saying_pool')) || [];
el.textContent = pool[Math.floor(Math.random() * pool.length)];
每天凌晨自动轮换 25 条,补充新句子。
NeteaseMiniPlayer v2 · 悬浮音乐播放器
在博客右下角集成了网易云音乐悬浮播放器,使用开源的 NeteaseMiniPlayer v2 组件,支持歌词显示、播放列表、随机播放等丰富功能。
基本用法
在页面中放置一个自定义元素,组件会自动初始化:
<div class="netease-mini-player"
data-playlist-id="496585734"
data-embed="false"
data-position="bottom-left"
data-lyric="true"
data-theme="auto"
data-autoplay="false"
data-default-minimized="true"
data-auto-pause="true">
</div>
通过 data-* 属性即可配置播放器行为,无需编写任何 JavaScript。
与 PJAX 配合
PJAX 只刷新 #pjax-container 内的内容,而播放器元素放在容器外面,因此 PJAX 切换页面时播放器不会中断,音乐持续播放:
<div id="pjax-container">
<!-- 页面内容,PJAX 只替换这里 -->
</div>
<!-- 播放器在容器外,PJAX 不影响 -->
<div class="netease-mini-player" ...>
</div>
音频保活
为防止浏览器在后台标签页时暂停音频,使用了一个 Web Worker 定时唤醒 AudioContext:
var audioWorker = new Worker('/static/js/audio-worker.js');
audioWorker.postMessage({ command: 'start' });
Worker 内部每 15 秒重建一次 AudioContext,配合 visibilitychange 事件,切回页面时自动恢复播放。
暗夜模式适配
播放器支持 data-theme="auto" 自动跟随系统主题。同时本博客在切换主题时也通过 JS 同步更新播放器的主题状态:
function syncPlayerTheme(theme) {
document.querySelectorAll('.netease-mini-player').forEach(function(el) {
el.setAttribute('data-theme', theme);
});
}
📖 完整文档:NeteaseMiniPlayer v2 官方文档
部署
使用 Gunicorn 启动 3 个 Worker,Nginx 反向代理并直接托管静态文件:
location /static/ {
alias /home/Blogs/app/static/;
expires 30d;
}
location / {
proxy_pass http://127.0.0.1;
}
写在最后
这个博客虽然简单,但麻雀虽小五脏俱全。从数据库设计到前端交互,从部署上线到日常维护,每一个环节都是亲手搭建的。后续还会继续完善——评论回复通知、图片管理、文章归档等功能已经在路上了。
感谢你读到这儿,欢迎在评论区留言交流 🚀
评论