1. 为什么我选择 WorkBuddy + Flask + SQLite 这套组合
1.1 从"建站"这件事的真实门槛说起
很多人一提建站,脑子里第一反应就是 WordPress。确实,WordPress 生态成熟、插件多、主题多,但它的代价是:你得维护 PHP 环境、得盯着插件更新、得忍受一堆用不上的功能拖慢速度。另一条路是 Shopify 这类 SaaS 建站,开箱即用,但每个月的订阅费和交易抽成是实打实的成本,而且数据不在自己手里,想改点底层逻辑基本没戏。
我这次要做的站,需求其实很朴素:一个轻量的内容站,能自己控制数据结构,能日更,能本地跑起来,部署成本越低越好。这种需求下,WordPress 太重,Shopify 太贵且不自由,纯静态站又满足不了"日更 + 数据查询"的诉求。所以我选了 Flask + SQLite 这条"源码建站"的路子——用 Python 写后端,SQLite 存数据,整个项目就是一个文件夹,拷到哪都能跑。
那 WorkBuddy 在这里扮演什么角色?简单说,它是我用来"加速开发"的助手。从建表、写路由、调样式到排查报错,我把大量重复性的、模板化的编码工作交给它,自己专注在业务逻辑和内容运营上。这套组合实测下来,一个人维护一个日更站完全扛得住。
1.2 三种建站路线的取舍对比
在动手之前,我把三条主流路线拉出来做了个对比,这也是我建议你先想清楚的地方:
| 维度 | WordPress | Shopify 类 SaaS | Flask + SQLite 自建 |
|---|---|---|---|
| 上手门槛 | 低,装完就能用 | 极低,注册即用 | 中,需要懂点 Python |
| 数据掌控 | 一般,存在 MySQL | 差,数据在平台 | 完全自主,一个文件 |
| 月成本 | 服务器 + 域名 | 订阅费 + 抽成 | 仅服务器 + 域名 |
| 定制自由度 | 受插件限制 | 受平台限制 | 想怎么改就怎么改 |
| 维护负担 | 插件/主题更新烦 | 平台负责 | 自己负责,但很轻 |
| 适合场景 | 博客、企业站 | 电商 | 轻量工具站、数据站 |
我选第三条路的核心理由是:这个站的核心价值在于"数据 + 匹配逻辑",而不是"页面展示"。WordPress 和 Shopify 擅长的是展示和交易,而我需要的是一个能跑自定义算法的后端。Flask 的轻量恰好匹配这种需求——它不替你做决定,你想怎么组织代码就怎么组织。
1.3 WorkBuddy 到底帮我省了什么
这里得说清楚,WorkBuddy 不是"一键建站"的魔法按钮。它更像一个随时在线的结对程序员。我实际用下来,它在这几个环节价值最大:
- 脚手架生成:Flask 的项目结构、蓝图划分、配置文件模板,我描述需求它直接给,省去查文档的时间。
- SQLite 建表与查询:字段设计、索引、UPDATE 语句这些容易写错的地方,让它生成初稿我再改,效率翻倍。
- 报错排查:Python 的报错栈有时候很绕,把错误贴给它,基本能定位到具体行。
- 前端样式:我不擅长 CSS,页面布局和响应式调整基本靠它给方案。
但要注意,它给的东西必须自己过一遍。我踩过的坑是:它生成的 SQLite 查询有时候没考虑并发写入,直接用在日更场景下会偶发锁库。这类问题它不会主动提醒你,得靠你自己的经验去补。
2. 环境搭建:从零把架子立起来
2.1 Python 环境与依赖安装的实操细节
第一步是 Python 环境。我强烈建议用虚拟环境,别图省事直接全局装。原因很简单:Flask 项目依赖一旦和系统里其他项目的包版本冲突,排查起来能让你怀疑人生。
# 创建项目目录 mkdir workbuddy-site && cd workbuddy-site # 创建虚拟环境(Windows 用 python -m venv venv) python3 -m venv venv # 激活虚拟环境 source venv/bin/activate # Windows: venv\Scripts\activate # 安装核心依赖 pip install flask flask-sqlalchemy如果你用 VSCode 开发,记得在设置里把 Python 解释器指向venv里的那个,否则终端里装的包和编辑器识别的对不上,会出现"明明装了却提示找不到模块"的经典问题。这个坑我见过太多新手栽进去。
依赖清单我建议单独维护一个requirements.txt:
Flask==3.0.0 Flask-SQLAlchemy==3.1.1版本号锁死是有必要的。Flask 3.x 和 2.x 在某些 API 上有差异,不锁版本,换台机器部署时可能直接跑不起来。
2.2 项目目录结构怎么划分才不乱
一个能长期日更维护的项目,目录结构必须一开始就规划好。我见过太多人把所有代码堆在一个app.py里,写到两千行之后自己都找不到函数在哪。我的划分方式是这样的:
workbuddy-site/ ├── app/ │ ├── __init__.py # 应用工厂 │ ├── models.py # 数据模型 │ ├── routes/ │ │ ├── main.py # 主页面路由 │ │ └── admin.py # 后台管理路由 │ ├── templates/ # Jinja2 模板 │ └── static/ # CSS/JS/图片 ├── instance/ │ └── site.db # SQLite 数据库文件 ├── config.py # 配置文件 ├── requirements.txt └── run.py # 启动入口这里有个关键点:SQLite 数据库文件放在instance/目录下。Flask 官方推荐这么做,因为这个目录默认不会被纳入版本控制,也不会被 Web 服务器直接暴露出去。你要是把.db文件放在static/里,那等于把整个数据库公开下载,这是新手最容易犯的致命错误。
2.3 用 WorkBuddy 生成应用工厂骨架
应用工厂模式(Application Factory)是 Flask 项目结构化的基础。我让 WorkBuddy 生成初版,然后自己调整:
# app/__init__.py from flask import Flask from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy() def create_app(): app = Flask(__name__, instance_relative_config=True) app.config.from_object('config.Config') db.init_app(app) from app.routes.main import main_bp from app.routes.admin import admin_bp app.register_blueprint(main_bp) app.register_blueprint(admin_bp, url_prefix='/admin') with app.app_context(): db.create_all() return appinstance_relative_config=True这行很关键,它让 Flask 知道去instance/目录找配置和数据库。db.create_all()放在应用上下文里执行,第一次启动时会自动建表,省去手动执行 SQL 的麻烦。
注意:
db.create_all()只会在表不存在时创建,它不会修改已存在表的结构。如果你后期改了模型字段,得手动迁移或者删库重建。生产环境建议上 Flask-Migrate,但个人小站直接删库重建也够用。
3. 数据层设计:SQLite 建表与查询优化
3.1 内容站的数据模型怎么设计
假设我这个站是内容型的,核心就是"文章"这个实体。但光有文章不够,还得有分类、标签、发布状态这些维度。我的模型设计如下:
# app/models.py from app import db from datetime import datetime class Article(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) summary = db.Column(db.String(500)) category = db.Column(db.String(50), index=True) tags = db.Column(db.String(200)) status = db.Column(db.String(20), default='draft') created_at = db.Column(db.DateTime, default=datetime.utcnow) updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)几个设计决策值得说明:
slug加唯一索引:URL 里用 slug 比用 id 友好,加索引是为了查询快。日更站文章多了之后,没索引的查询会明显变慢。category加索引:分类页是高频查询入口,索引能显著提速。status区分草稿和发布:日更场景下,我经常提前写好几篇存草稿,到点再发布。这个字段是刚需。updated_at用onupdate:自动更新时间戳,省得每次改文章手动写。
3.2 SQLite 的并发写入问题与应对
SQLite 有个众所周知的特性:默认情况下同一时刻只允许一个写操作。这在日更个人站场景下通常不是问题,但如果你同时开着后台编辑页面和定时发布脚本,就可能撞上database is locked错误。
我的应对方案是开启 WAL 模式(Write-Ahead Logging):
# 在 create_app 里加一段 from sqlalchemy import event @event.listens_for(db.engine, "connect") def set_sqlite_pragma(dbapi_conn, connection_record): cursor = dbapi_conn.cursor() cursor.execute("PRAGMA journal_mode=WAL") cursor.execute("PRAGMA synchronous=NORMAL") cursor.close()WAL 模式允许读写并发,读操作不会被写操作阻塞。synchronous=NORMAL是在安全性和性能之间的折中,对个人站来说完全够用。实测下来,开了 WAL 之后,后台编辑和前台访问同时进行基本不会再报锁库错误。
提示:WAL 模式会额外生成
-wal和-shm两个文件,这是正常的,别手动删。备份数据库时要三个文件一起拷,或者先执行PRAGMA wal_checkpoint(TRUNCATE)把日志合并回主文件。
3.3 用 DB Browser for SQLite 做可视化管理
命令行查数据库效率太低,我日常用DB Browser for SQLite这个免费工具。它能直接打开.db文件,可视化地浏览表结构、执行 SQL、导出数据。
几个我常用的操作:
- 浏览数据:直接看表内容,改错数据不用写 UPDATE 语句。
- 执行 SQL:复杂查询先在工具里调通,再写进代码。
- 导出 CSV:做数据备份或者迁移时很方便。
有个细节要注意:用 DB Browser 打开数据库时,如果 Flask 应用正在运行,可能因为文件锁导致写入失败。我的习惯是改数据前先停掉应用,改完再启动。或者干脆用工具只读浏览,写操作走应用接口。
4. 路由与页面:把功能串起来
4.1 前台展示路由的实现
前台核心就三个页面:首页列表、文章详情、分类页。用 Flask 的蓝图组织:
# app/routes/main.py from flask import Blueprint, render_template, abort from app.models import Article main_bp = Blueprint('main', __name__) @main_bp.route('/') def index(): page = request.args.get('page', 1, type=int) pagination = Article.query.filter_by(status='published')\ .order_by(Article.created_at.desc())\ .paginate(page=page, per_page=10, error_out=False) return render_template('index.html', pagination=pagination) @main_bp.route('/article/<slug>') def article_detail(slug): article = Article.query.filter_by(slug=slug, status='published').first() if not article: abort(404) return render_template('article.html', article=article)分页用 Flask-SQLAlchemy 自带的paginate,省得自己算 offset 和 limit。error_out=False是为了让超出范围的页码返回空列表而不是 404,体验更好。
4.2 后台管理:日更的效率关键
日更站最影响效率的就是后台。我的后台设计原则是:发布一篇文章的操作步骤越少越好。核心路由:
# app/routes/admin.py from flask import Blueprint, request, redirect, url_for, render_template from app import db from app.models import Article from datetime import datetime admin_bp = Blueprint('admin', __name__) @admin_bp.route('/article/new', methods=['GET', 'POST']) def new_article(): if request.method == 'POST': title = request.form['title'] article = Article( title=title, slug=generate_slug(title), content=request.form['content'], summary=request.form.get('summary', ''), category=request.form.get('category', 'uncategorized'), tags=request.form.get('tags', ''), status=request.form.get('status', 'draft') ) db.session.add(article) db.session.commit() return redirect(url_for('admin.article_list')) return render_template('admin/edit.html')generate_slug是我自己写的小函数,把中文标题转成拼音或者用时间戳兜底。这里 WorkBuddy 帮我生成了初版,但它默认用英文标题逻辑处理,中文会全部变成空字符串,我改成"拼音 + 时间戳"的组合才解决。
4.3 模板层的复用技巧
Jinja2 模板最忌讳每个页面都从头写。我用base.html做骨架,其他页面继承:
<!-- templates/base.html --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>{% block title %}我的站{% endblock %}</title> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> </head> <body> <nav>{% include 'partials/nav.html' %}</nav> <main>{% block content %}{% endblock %}</main> <footer>{% include 'partials/footer.html' %}</footer> </body> </html>导航和页脚用include抽出来,改一处全站生效。这个结构看着简单,但日更站页面一多,复用带来的维护效率提升非常明显。
5. 部署上线与日更流程固化
5.1 本地跑通到服务器部署的过渡
本地开发用flask run就够了,但部署到服务器不能这么干。Flask 自带的开发服务器性能差且不适合生产。我的方案是Gunicorn + Nginx:
# 服务器上安装 pip install gunicorn # 启动(4 个 worker 进程) gunicorn -w 4 -b 127.0.0.1:8000 "app:create_app()"-w 4是 worker 数量,经验值是 CPU 核数 × 2 + 1。个人小站 2 到 4 个足够。Nginx 做反向代理,把 80 端口的请求转发到 8000。
这里有个 SQLite 的坑要提醒:多 worker 模式下,多个进程同时写 SQLite 更容易触发锁。所以 WAL 模式在部署环境里更重要。如果日更频率高、并发写多,可以考虑把写操作集中到一个进程,或者干脆换 PostgreSQL。但对个人站来说,WAL + 低并发,SQLite 完全扛得住。
5.2 日更流程的标准化
日更最怕的是"每次都要重新想一遍怎么发"。我把流程固化成固定动作:
- 打开后台
/admin/article/new - 填标题、正文、分类、标签
- 状态选"草稿",保存
- 预览确认无误后,改成"发布"
- 前台刷新验证
这套流程跑顺之后,一篇千字文章从写到发不超过 15 分钟。关键是把"预览"这一步保留住——我踩过的坑是直接发布后发现排版错乱,又得改回来,反而更慢。
5.3 数据备份:日更站的生命线
SQLite 的好处是备份极其简单——就是一个文件。但简单不代表可以不做。我的备份策略是:
# 每天凌晨备份,保留最近 30 天 cp instance/site.db backups/site_$(date +%Y%m%d).db find backups/ -name "site_*.db" -mtime +30 -delete写成 cron 任务自动跑。注意备份前最好先 checkpoint 一下 WAL 日志,否则备份出来的文件可能缺最新数据:
sqlite3 instance/site.db "PRAGMA wal_checkpoint(TRUNCATE);"6. 常见问题与排查实录
6.1 高频报错速查表
| 报错信息 | 常见原因 | 解决方向 |
|---|---|---|
database is locked | 并发写入冲突 | 开启 WAL 模式,减少同时写操作 |
no such table | 建表未执行或路径错 | 检查db.create_all()是否执行,数据库路径是否正确 |
ModuleNotFoundError | 虚拟环境未激活 | 确认终端和编辑器用的是同一个解释器 |
TemplateNotFound | 模板目录结构错 | 检查templates/是否在应用根目录 |
| 中文 slug 为空 | 编码处理缺失 | 用拼音库或时间戳兜底生成 |
| 静态文件 404 | 路径拼接错误 | 用url_for('static', ...)而非硬编码 |
6.2 几个 WorkBuddy 使用中的实操心得
用 WorkBuddy 辅助开发这段时间,我总结了几个让它更好用的技巧:
- 描述需求要具体:说"帮我写个 Flask 路由"和说"帮我写个 Flask 路由,接收 slug 参数,查 Article 表,找不到返回 404",后者给的代码基本能直接用。
- 让它解释而不是只给代码:遇到不熟悉的写法,追问一句"这段为什么这么写",能学到不少。
- 生成的 SQL 一定要审:尤其是 UPDATE 和 DELETE,它偶尔会漏 WHERE 条件,直接跑就是灾难。
- 版本差异要主动说明:告诉它你用的是 Flask 3.x,否则它可能按 2.x 的写法给,某些 API 已经变了。
6.3 性能优化的几个实用点
日更站文章积累到几百篇后,我做了这几件事提速:
- 给高频查询字段加索引:
slug、category、status都加了。 - 列表页只查必要字段:用
db.session.query(Article.id, Article.title, ...)代替Article.query,减少数据传输。 - 静态资源加缓存头:Nginx 配置里给 CSS/JS 设长缓存。
- 首页做简单缓存:用 Flask-Caching 缓存首页 60 秒,日更场景下完全够用。
这些优化做完,首页加载从 800ms 降到 200ms 以内,体验提升很明显。
7. 我在这套流程里踩过的坑
最后说几个只有真正跑起来才会遇到的问题。
第一个坑是时区。datetime.utcnow()存的是 UTC 时间,前台直接显示会差 8 小时。我一开始没注意,文章发布时间全是错的。解决办法是存 UTC,展示时转本地时区,或者干脆统一用本地时间存。我选了后者,简单直接。
第二个坑是 slug 冲突。两篇文章标题一样,生成的 slug 就撞了,插入时报唯一约束错误。我的处理是生成 slug 时检查是否已存在,存在就加个短随机后缀。
第三个坑是 WAL 文件膨胀。长时间运行后-wal文件可能涨到几十 MB。定期执行 checkpoint 能控制住,我把它加进了每日备份脚本里。
第四个坑是后台没有登录保护。我第一版后台是裸奔的,任何人访问/admin都能发文章。后来加了简单的 session 登录才补上。个人站也别省这一步,被爬虫扫到就麻烦了。
这套 WorkBuddy + Flask + SQLite 的组合,我从建站到稳定日更跑了几个月,最大的感受是:轻量方案的上限取决于你对细节的把控。框架本身不复杂,真正拉开差距的是那些边角问题的处理——锁库、时区、备份、安全。把这些都理顺了,一个人维护一个日更站,真的不难。