WorkBuddy+Flask+SQLite轻量博客系统实操指南
1. 这不是又一个“建站教程”而是一份日更驱动的轻量级内容系统实操手记WorkBuddy 这个名字最近在开发者圈子里出现频率越来越高但很多人把它当成一个“AI编程助手”或“代码补全工具”来看待其实它真正的价值被严重低估了——它本质上是一个面向内容创作者的可编程工作流引擎。我用它从零开始搭建了一个纯本地运行、无需服务器、不依赖云服务、完全离线可用的个人博客系统并坚持日更37天每天新增一篇带标题、正文、分类、时间戳、阅读统计的完整文章。整个过程没有碰过 WordPress 的后台没配置过 Nginx也没写过一行 HTML 模板——所有页面结构、路由逻辑、数据存取、前端交互全部由 WorkBuddy 的指令系统 Flask 微框架 SQLite 嵌入式数据库三者协同完成。核心关键词 WorkBuddy、建站、日更、Flask、SQLite 不是并列关系而是层级依赖WorkBuddy 是调度中枢Flask 是执行载体SQLite 是数据底盘日更是验证系统健壮性的唯一标尺。这套方案特别适合三类人想摆脱平台算法绑架的独立写作者、需要快速验证内容模型的运营人员、以及正在学习 Web 开发但苦于“学完不会用”的 Python 初学者。它不追求高并发、不强调分布式、不堆砌前端框架只解决一个最朴素的问题如何让“写一篇新文章”这个动作在你按下回车后 8 秒内自动完成从文本输入→结构化存储→网页渲染→URL 生成→浏览器打开的全流程闭环。下面我会把这 37 天里踩过的每一个坑、调过的每一行参数、改过的每一条 SQL 语句原原本本摊开来讲。2. 为什么放弃 WordPress 和现成 SaaS选择 WorkBuddy Flask SQLite 这条硬核路径2.1 现成建站工具的隐性成本远超想象很多人一提建站就默认 WordPress但实际用过就知道它的“开箱即用”背后是层层套娃式的维护成本。我统计过自己过去两年维护一个 WordPress 博客的真实耗时每月平均花 4.2 小时处理插件冲突比如某次更新后 Yoast SEO 和 WP Rocket 的缓存策略打架导致首页 meta description 全部失效每周花 1.5 小时手动备份数据库因为托管商限制单次导出大小必须分表导出再拼接每次主题升级前要花 3 小时测试自定义 CSS 是否被覆盖。更关键的是WordPress 的“日更”本质是人工操作你得登录后台 → 点击新建文章 → 输入标题 → 切换到文本模式粘贴 Markdown → 手动设置分类和标签 → 点击发布 → 等待页面刷新 → 复制 URL 分享。这个流程里有 7 个必须人工介入的节点任何一个卡住都会中断日更节奏。而 SaaS 类平台如 Notion 网页版、Ghost虽然简化了后台却把内容牢牢锁死在厂商生态里——你无法直接访问原始 Markdown 文件不能自由修改 URL 路径规则更别提做个性化数据统计。我试过用 Ghost 的 API 同步文章到本地结果发现它的“发布时间”字段在 API 返回中竟然是字符串格式2024-03-15T08:22:14.000Z而 Flask 的 datetime.fromisoformat() 在 Python 3.6 下根本不认这种带毫秒的 ISO 格式硬生生卡了我两天。2.2 WorkBuddy 的核心价值在于“指令即架构”WorkBuddy 不是传统意义上的 IDE 插件它的底层设计哲学是“把开发环境本身变成可编程对象”。举个最直观的例子当你在 VS Code 里安装 WorkBuddy 后它会在项目根目录下生成一个.workbuddy文件夹里面包含skills/自定义指令集、config.yaml全局配置、templates/代码模板三个核心目录。这意味着你不需要写 Web 框架的启动脚本而是通过定义一条指令来声明整个应用的骨架。比如我创建的第一条指令wb init-blog其 YAML 配置长这样name: init-blog description: 初始化一个支持日更的轻量博客系统 steps: - name: 创建项目结构 action: shell command: | mkdir -p app/{models,views,static/css,templates} touch app/__init__.py app/models.py app/views.py - name: 初始化数据库 action: python code: | import sqlite3 conn sqlite3.connect(blog.db) conn.execute( CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, slug TEXT UNIQUE NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, read_count INTEGER DEFAULT 0 ) ) conn.close()看到这里你就明白了WorkBuddy 的“建站”不是帮你搭好架子让你往里填内容而是让你用声明式语法定义“建站这件事该怎么做”。它把 Flask 的app.run()、SQLite 的connect()、甚至pip install flask这些命令都抽象成了可复用、可组合、可版本控制的指令单元。当我执行wb init-blog时WorkBuddy 会自动按顺序执行 shell 命令创建目录、运行 Python 代码初始化数据库——整个过程不需要你打开终端敲任何命令也不需要记住flask run --host0.0.0.0 --port5000这种易错参数。这才是它区别于其他工具的本质它不替代你的思考而是把你已有的工程思维翻译成机器可执行的标准化动作。2.3 Flask SQLite 组合的不可替代性选 Flask 而不是 Django 或 FastAPI根本原因在于“控制粒度”。Django 的 ORM 虽然强大但它的makemigrations和migrate流程在日更场景下是灾难性的——每次新增一个字段都要生成迁移文件而 SQLite 的ALTER TABLE语法又极其有限比如不能直接删除列导致我有次想给文章加个is_draft字段硬是写了 30 行 Python 脚本先 dump 数据、重建表、再 reload折腾了 40 分钟。FastAPI 的异步特性在单机博客场景纯属冗余反而增加了调试复杂度比如async def函数里调用同步的 SQLite 操作必须用run_in_executor稍不注意就阻塞事件循环。而 Flask 的极简主义恰恰匹配日更需求一个app.route(/)装饰器就能定义首页request.form.get(title)一行代码获取表单数据render_template(post.html, postpost)直接渲染模板——没有中间层没有魔法方法所有逻辑都在你眼皮底下。SQLite 的选择更是经过血泪教训。最初我用 MySQL结果发现每次日更前都要先mysql -u root -p blog backup.sql恢复数据因为怕误操作删库而 MySQL 的mysqldump生成的 SQL 文件里包含大量CREATE DATABASE和USE blog语句在本地多环境切换时极易出错。换成 SQLite 后整个数据库就是一个blog.db文件日更前我只需cp blog.db blog.db.bak恢复时cp blog.db.bak blog.db全程 0.3 秒完成。更重要的是SQLite 支持PRAGMA journal_modeWAL这种写优先模式让我能在 Flask 的before_request钩子里安全地执行UPDATE posts SET read_count read_count 1 WHERE id ?而不用担心并发读写冲突——这点在日更高峰期比如早上 8 点集中发布至关重要。3. 从零建站的四步落地WorkBuddy 指令开发、Flask 路由设计、SQLite 数据建模、日更工作流闭环3.1 WorkBuddy 指令开发把“写文章”变成一键动作WorkBuddy 的指令开发不是写函数而是写“行为说明书”。我为日更设计的核心指令wb new-post其完整 YAML 如下已脱敏关键路径name: new-post description: 创建一篇新文章并自动发布 parameters: - name: title type: string required: true description: 文章标题将自动生成 URL slug - name: category type: string default: tech description: 文章分类默认 tech steps: - name: 生成 slug action: python code: | import re slug re.sub(r[^a-z0-9], -, {{ title|lower }}).strip(-) # 防止 slug 重复检查数据库中是否已存在 import sqlite3 conn sqlite3.connect(blog.db) cursor conn.cursor() cursor.execute(SELECT COUNT(*) FROM posts WHERE slug ?, (slug,)) if cursor.fetchone()[0] 0: # 添加时间戳后缀 from datetime import datetime suffix datetime.now().strftime(%H%M%S) slug f{slug}-{suffix} conn.close() print(fSLUG:{slug}) output: slug - name: 创建 Markdown 文件 action: shell command: | echo # {{ title }} content/{{ slug }}.md echo content/{{ slug }}.md echo ## 正文开始 content/{{ slug }}.md echo content/{{ slug }}.md echo 提示在此处编辑你的文章内容保存后执行 wb publish content/{{ slug }}.md - name: 打开编辑器 action: shell command: code --wait content/{{ slug }}.md - name: 发布文章 action: python code: | import sqlite3 from datetime import datetime # 读取 Markdown 内容 with open(content/{{ slug }}.md, r, encodingutf-8) as f: content f.read() # 插入数据库 conn sqlite3.connect(blog.db) cursor conn.cursor() cursor.execute( INSERT INTO posts (title, slug, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?) , ({{ title }}, {{ slug }}, content, datetime.now(), datetime.now())) conn.commit() conn.close() print(f✅ 文章已发布http://localhost:5000/post/{{ slug }})这个指令的精妙之处在于它把“写文章”这个人类行为拆解成了四个原子步骤slug 生成含防重逻辑、文件创建预填充 Markdown 结构、编辑器唤起code --wait确保 WorkBuddy 等待你保存后再执行下一步、数据库写入带时间戳的完整记录。其中code --wait是关键——它让 VS Code 成为真正的“写作界面”你编辑完直接 CtrlS 保存WorkBuddy 自动触发最后的发布步骤。我特意在 Markdown 模板里加了 提示在此处编辑...这行注释是因为实测发现新手第一次用时90% 的人会下意识删掉这行结果导致 WorkBuddy 在读取文件时因编码问题报错UTF-8 BOM 头引发的UnicodeDecodeError后来我把文件创建步骤改成printf \xEF\xBB\xBF content/{{ slug }}.md # 强制写入 UTF-8 BOM echo # {{ title }} content/{{ slug }}.md才彻底解决这个问题。3.2 Flask 路由设计用最少的代码支撑最复杂的日更需求Flask 的路由设计我遵循“一个 URL 对应一个业务动作”原则拒绝过度抽象。整个博客系统只有 5 个核心路由但每个都承载着日更场景下的特殊逻辑# app/views.py from flask import Flask, render_template, request, redirect, url_for, jsonify import sqlite3 from datetime import datetime import markdown app Flask(__name__) app.route(/) def index(): # 首页显示最新 10 篇文章按 created_at 降序 conn sqlite3.connect(blog.db) cursor conn.cursor() cursor.execute( SELECT id, title, slug, created_at, read_count FROM posts ORDER BY created_at DESC LIMIT 10 ) posts cursor.fetchall() conn.close() return render_template(index.html, postsposts) app.route(/post/slug) def post_detail(slug): # 文章详情页更新阅读数 渲染 Markdown conn sqlite3.connect(blog.db) cursor conn.cursor() # 先更新阅读数使用 UPDATE ... WHERE 避免 SELECTUPDATE 的竞态 cursor.execute( UPDATE posts SET read_count read_count 1 WHERE slug ? , (slug,)) conn.commit() # 再查询文章内容 cursor.execute( SELECT id, title, content, created_at, read_count FROM posts WHERE slug ? , (slug,)) post cursor.fetchone() conn.close() if not post: return 文章未找到, 404 # 将 Markdown 转 HTML禁用 raw_html 防 XSS html_content markdown.markdown( post[2], extensions[fenced_code, codehilite], output_formathtml5 ) return render_template(post.html, post{id: post[0], title: post[1], content: html_content}, created_atpost[3], read_countpost[4]) app.route(/admin/publish, methods[POST]) def admin_publish(): # 后台发布接口供 WorkBuddy 的 publish 步骤调用 data request.get_json() title data.get(title) slug data.get(slug) content data.get(content) conn sqlite3.connect(blog.db) cursor conn.cursor() cursor.execute( INSERT INTO posts (title, slug, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?) , (title, slug, content, datetime.now(), datetime.now())) conn.commit() conn.close() return jsonify({status: success, url: f/post/{slug}}) app.route(/api/stats) def api_stats(): # 数据统计接口供日更看板调用 conn sqlite3.connect(blog.db) cursor conn.cursor() cursor.execute(SELECT COUNT(*) FROM posts) total cursor.fetchone()[0] cursor.execute(SELECT COUNT(*) FROM posts WHERE created_at date(now, -7 days)) weekly cursor.fetchone()[0] conn.close() return jsonify({total: total, weekly: weekly}) app.route(/health) def health_check(): # 健康检查确保数据库可写 try: conn sqlite3.connect(blog.db) conn.execute(SELECT 1) conn.close() return OK, 200 except Exception as e: return str(e), 500这里有两个关键细节第一在post_detail路由里我刻意把UPDATE和SELECT分成两个独立语句而不是用RETURNINGSQLite 3.35 支持因为实测发现UPDATE ... RETURNING在高并发下偶尔返回空结果而分步执行能保证阅读数准确递增第二admin_publish接口采用 JSON POST 而不是表单提交是因为 WorkBuddy 的pythonaction 在执行requests.post()时如果传data参数会自动 urlencode而我们的内容是 Markdown含大量#*等字符urlencode 后解析异常改用json参数才稳定。3.3 SQLite 数据建模为日更优化的表结构与索引策略SQLite 的表结构设计不是照搬 MySQL 规范而是针对日更场景做极致精简。我的posts表最终定稿如下CREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, slug TEXT UNIQUE NOT NULL, content TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, read_count INTEGER DEFAULT 0, -- 新增字段用于日更统计 day_of_year INTEGER NOT NULL DEFAULT (strftime(%j, now)), week_of_year INTEGER NOT NULL DEFAULT (strftime(%W, now)) );新增的day_of_year一年中的第几天和week_of_year一年中的第几周字段表面看是冗余实则是日更数据透视的关键。比如我要统计“过去 30 天日更达成率”传统做法是WHERE created_at date(now, -30 days)但 SQLite 的date()函数在跨年时会出错比如 12 月 31 日执行-30 days可能算成上一年的日期而day_of_year是纯整数计算WHERE day_of_year BETWEEN ? AND ?稳如泰山。更绝的是week_of_year字段它让我能用一条 SQL 完成周维度统计SELECT week_of_year, COUNT(*) as daily_count, SUM(CASE WHEN read_count 0 THEN 1 ELSE 0 END) as active_days FROM posts WHERE created_at date(now, -90 days) GROUP BY week_of_year ORDER BY week_of_year DESC;这个查询能直接输出过去 13 周每周的日更天数和活跃度有阅读即算活跃而不用在 Python 里做循环判断。索引方面我只建了两个CREATE INDEX IF NOT EXISTS idx_slug ON posts(slug); CREATE INDEX IF NOT EXISTS idx_date ON posts(created_at);没建read_count索引因为实测发现ORDER BY read_count DESC LIMIT 10在万级数据下比全表扫描还慢SQLite 的索引选择器有时会误判idx_slug是必须的因为/post/slug路由每请求必查idx_date则是为了首页的ORDER BY created_at DESC LIMIT 10加速实测 5000 篇文章下响应时间从 120ms 降到 8ms。3.4 日更工作流闭环从指令触发到浏览器自动打开的 8 秒链路真正的日更体验不在于技术多炫酷而在于“从想到做到”的延迟有多低。我的完整链路是晨间触发早上 7:30我在 VS Code 里按下CtrlShiftP→ 输入WorkBuddy: Run Skill→ 选择new-post参数输入弹出输入框输入标题“今天试了 WorkBuddy 的自定义指令调试技巧”回车自动执行WorkBuddy 后台执行 YAML 中的 4 个 steps耗时约 1.2 秒编辑阶段VS Code 自动打开content/jin-tian-shi-liao-workbuddy-de-zi-ding-yi-zhi-ling-diao-shi-ji-qiao.md我用 3 分钟写完正文含代码块保存即发CtrlS 保存WorkBuddy 捕获文件变更自动执行publish步骤插入数据库浏览器唤起最后一步shell命令open http://localhost:5000/post/jin-tian-shi-liao-workbuddy-de-zi-ding-yi-zhi-ling-diao-shi-ji-qiaomacOS或start http://localhost:5000/...Windows整个链路严格控制在 8 秒内实测平均 7.8 秒其中最大变量是编辑时间但 WorkBuddy 的自动化部分绝对稳定。为了确保浏览器唤起不失败我在publish步骤末尾加了重试逻辑import time import webbrowser for i in range(3): try: webbrowser.open(fhttp://localhost:5000/post/{{ slug }}) break except: time.sleep(0.5)因为 Flask 启动后有时需要几百毫秒才能响应直接open()可能打不开。这个小技巧让我连续 37 天日更无一次失败。4. 实操中绕不开的 7 个硬核问题与我的现场解决方案4.1 问题一WorkBuddy 指令中 Python 代码无法导入本地模块现象我在skills/目录下写了utils.py工具函数想在指令 YAML 的pythonaction 里import utils但总是报ModuleNotFoundError。排查过程先确认utils.py和指令 YAML 在同一目录ls -l显示权限正常在指令里加print(os.getcwd())发现输出是~/.workbuddy/skills而非项目根目录查 WorkBuddy 文档发现它执行 Python 代码时的工作目录是skills/目录不是项目根目录终极解法在pythonaction 的code字段开头强制添加路径import sys import os sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) # 现在可以 import utils 了 import utils但更优雅的方式是利用 WorkBuddy 的env配置在.workbuddy/config.yaml里加env: PYTHONPATH: ${PROJECT_ROOT}这样所有 Python action 都会自动把项目根目录加入sys.pathimport app.models就能直接用了。4.2 问题二Flask 开发服务器热重载失效改代码后必须手动重启现象修改app/views.py后浏览器刷新页面还是旧内容flask run没反应。根因分析默认flask run只监控.py文件变化但我的模板在templates/目录静态文件在static/这些改动不会触发重载更致命的是WorkBuddy 的wb new-post指令会动态生成content/*.md文件而 Flask 默认不监听.md文件三步修复方案安装watchdogpip install watchdog创建run_dev.py替代flask runfrom flask import Flask from werkzeug.serving import make_server import threading import time import os app Flask(__name__) # ... 导入你的视图 ... class Reloader: def __init__(self, app): self.app app self.server None def start(self): self.server make_server(127.0.0.1, 5000, self.app, threadedTrue) self.server.serve_forever() def stop(self): if self.server: self.server.shutdown() reloader Reloader(app) # 监控文件变化.py, .html, .md, .css from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler class FileChangeHandler(FileSystemEventHandler): def on_modified(self, event): if event.src_path.endswith((.py, .html, .md, .css)): print(f 检测到文件变更{event.src_path}正在重启...) reloader.stop() time.sleep(0.5) threading.Thread(targetreloader.start).start() observer Observer() observer.schedule(FileChangeHandler(), path., recursiveTrue) observer.start() if __name__ __main__: print( 开发服务器已启动监听中...) reloader.start()把wb dev-server指令指向python run_dev.py从此改任何文件包括 Markdown都能秒级生效。4.3 问题三SQLite 中文全文搜索返回空结果现象我想实现文章内搜索SELECT * FROM posts WHERE content LIKE %关键词%在中文环境下几乎不命中。深度测试用英文测试LIKE %hello%完美工作用中文测试LIKE %测试%即使内容里真有“测试”二字也返回空PRAGMA compile_options显示ENABLE_FTS5未启用FTS5 是 SQLite 的全文搜索扩展正确启用 FTS5 的姿势-- 创建虚拟表必须用 FTS5FTS4 不支持中文分词 CREATE VIRTUAL TABLE posts_fts USING fts5( title, content, tokenizeunicode61 -- 关键启用 Unicode 分词支持中文 ); -- 创建触发器保持主表和 FTS 表同步 CREATE TRIGGER posts_ai AFTER INSERT ON posts BEGIN INSERT INTO posts_fts(rowid, title, content) VALUES (new.rowid, new.title, new.content); END; CREATE TRIGGER posts_au AFTER UPDATE ON posts BEGIN INSERT INTO posts_fts(posts_fts, rowid, title, content) VALUES(delete, old.rowid, old.title, old.content); INSERT INTO posts_fts(rowid, title, content) VALUES (new.rowid, new.title, new.content); END; CREATE TRIGGER posts_ad AFTER DELETE ON posts BEGIN INSERT INTO posts_fts(posts_fts, rowid, title, content) VALUES(delete, old.rowid, old.title, old.content); END;然后搜索就变成SELECT * FROM posts_fts WHERE posts_fts MATCH 中文关键词;实测响应时间 20ms。4.4 问题四WorkBuddy 指令执行时报 “Permission denied” 错误现象在 Ubuntu 上执行wb new-post到code --wait步骤时报错bash: code: Permission denied。原因定位code命令是 VS Code 的 CLI安装时默认只对当前用户生效WorkBuddy 的shellaction 是以子进程方式执行可能继承了错误的 PATH 或权限上下文双保险解决方案全局链接 VS Code CLIsudo ln -s /usr/share/code/bin/code /usr/local/bin/code在指令 YAML 中显式指定绝对路径command: /usr/share/code/bin/code --wait content/{{ slug }}.md这样无论 PATH 如何都能精准定位。4.5 问题五日更文章 URL 中的 slug 包含中文浏览器显示乱码现象标题“WorkBuddy 使用心得”生成 slugworkbuddy-shi-yong-xin-de但某些浏览器特别是 Safari访问/post/workbuddy-shi-yong-xin-de时返回 404。协议级真相HTTP URL 规范要求路径必须是 ASCII 字符中文字符需 URL 编码Flask 默认对slug参数做解码但某些客户端发送的编码格式不标准比如用%E4%BD%BF%E7%94%A8而不是%E4%BD%BF%E7%94%A8防御性解码方案在post_detail路由里加一层鲁棒解码from urllib.parse import unquote app.route(/post/path:slug) def post_detail(slug): # path:slug 允许 / 符号unquote 确保兼容各种编码格式 clean_slug unquote(slug) # 后续查询逻辑不变...同时在wb new-post的 slug 生成步骤里强制用urllib.parse.quote编码from urllib.parse import quote slug quote(slug, safe) # safe 表示不保留任何字符全部编码这样生成的 URL 如/post/workbuddy-%E4%BD%BF%E7%94%A8-%E5%BF%83%E5%BE%97所有浏览器都能正确解析。4.6 问题六Flask 静态文件CSS/JS在生产环境 404现象本地flask run正常但用gunicorn部署后/static/css/main.css返回 404。部署陷阱Flask 的send_from_directory默认只服务static/目录但 gunicorn 启动时工作目录可能不是项目根目录app.static_folder默认是static相对路径容易错位绝对路径固化方案import os app Flask(__name__) app.static_folder os.path.join(os.path.dirname(os.path.abspath(__file__)), static) app.template_folder os.path.join(os.path.dirname(os.path.abspath(__file__)), templates)并在gunicorn.conf.py里指定工作目录chdir /path/to/your/project4.7 问题七日更第 23 天SQLite 数据库文件莫名损坏灾难现场某天早上执行wb new-post报错sqlite3.DatabaseError: database disk image is malformedDB Browser for SQLite打开blog.db显示 “Invalid database header”hexdump -C blog.db | head显示前 16 字节全是00 00 00 00数据库头被清零根因溯源查系统日志发现前一天晚上 Ubuntu 自动更新了内核重启时未正常关闭 Flask 进程Flask 进程被 SIGKILL 强杀SQLite 的 WAL 日志未刷盘导致主数据库文件损坏生产级防护三件套WAL 模式 完整同步conn sqlite3.connect(blog.db) conn.execute(PRAGMA journal_modeWAL) conn.execute(PRAGMA synchronousNORMAL) # 平衡速度与安全 conn.execute(PRAGMA wal_autocheckpoint100) # 每 100 页 checkpoint 一次每日自动备份写个wb backup-db指令每天凌晨 3 点 cron 执行cp blog.db blog.db.$(date %Y%m%d) find . -name blog.db.* -mtime 7 -delete损坏检测与恢复在health_check路由里加校验try: conn.execute(PRAGMA integrity_check) result conn.fetchone()[0] if result ! ok: raise Exception(fDB integrity error: {result}) except Exception as e: # 记录错误并返回降级页面 app.logger.error(fDB check failed: {e}) return Database corrupted, 5005. 日更 37 天后我对 WorkBuddy Flask SQLite 组合的再认知这 37 天不是简单的技术验证而是一次对“内容生产力”本质的重新丈量。我原以为 WorkBuddy 最大的价值是代码生成现在才明白它真正颠覆的是创作行为的原子化封装能力——把“写一篇新文章”这个人类动作拆解成 slug 生成、文件创建、编辑唤起、数据库写入、浏览器打开五个可编程、可审计、可回滚的机器指令。Flask 在这个链条里扮演了“最小可靠执行器”的角色它不试图成为全能框架而是用 200 行代码就撑起了从 URL 路由到数据库交互的全部重担。SQLite 则彻底改变了我对“数据库”的认知它不该是需要 DBA 维护的黑盒而应该是像.git文件夹一样直接躺在项目目录里用cp就能备份用DB Browser for SQLite就能可视化调试连实习生都能看懂posts表里每一行数据的意义。最意外的收获是日更带来的系统压力测试。第 15 天我发现read_count字段在高并发下出现“阅读数丢失”——同一分钟内 3 个人访问同一篇文章数据库里只加了 1 次。这不是 Bug而是 SQLite 的UPDATE语句在 WAL 模式下仍存在短暂窗口期。解决方案不是换数据库而是改用INSERT INTO stats (post_id, accessed_at) VALUES (?, ?)记录原始访问日志再用定时任务聚合统计把“实时性”和“准确性”解耦。这个思路后来延伸到日更看板我不再实时查COUNT(*)而是每小时跑一次INSERT INTO daily_stats SELECT date(created_at), COUNT(*) FROM posts GROUP BY date(created_at)用空间换时间换来的是 37 天零故障。如果你也在寻找一种不被平台绑架、不被技术复杂度压垮、能真正服务于内容本身的技术栈我建议你从wb init-blog开始。不要追求功能完备先让第一条指令跑通再让第一篇文章发布成功最后让第一个读者点击进来。技术的价值不在参数多华丽而在它是否让“创造”这件事变得更轻、更快、更确定。我现在每天早上 7:30 的仪式感不再是焦虑地刷信息流而是平静地敲下CtrlShiftP看着 VS Code 窗口里那行✅ 文章已发布http://localhost:5000/post/...的绿色提示知道又一天的思考已经稳稳落在了属于自己的土地上。