ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flask+SQLite轻量建站:WorkBuddy日更博客实操指南

Flask+SQLite轻量建站:WorkBuddy日更博客实操指南 1. 项目概述为什么一个轻量级建站工具值得你花三天时间实操一遍WorkBuddy 不是另一个“AI一键生成网站”的营销噱头它本质是一个面向开发者和内容创作者的本地优先、命令行驱动的静态站点生成器内核基于 Flask SQLite 构建但刻意剥离了传统 Web 框架的部署复杂度。我第一次看到它是在一个极客小众论坛里有人用它三分钟搭起个人技术博客第二天就上线了带搜索、分类、RSS 的完整站点——不是用 Next.js 或 Hugo 那种预编译方案而是真正在本地跑着一个轻量 Flask 实例实时响应 Markdown 编辑、自动刷新页面、SQLite 记录访问统计连数据库文件都直接存放在项目根目录下。这正是标题里“从零建站并日更实操记录”的真实含义它不追求高并发或企业级架构而是把“写一篇新文章 → 保存 → 网页立刻可见 → 数据自动落库 → 第二天还能查阅读趋势”这个闭环压缩到最短路径。关键词里反复出现的Flask和SQLite并非凑数——Flask 提供最小可行的 HTTP 服务骨架SQLite 则承担内容存储、元数据管理、甚至简易用户行为埋点比如每篇文章的阅读次数二者加起来不到 500 行核心代码却撑起了整套工作流。而WorkBuddy这个名字本身就在暗示它的定位不是老板、不是平台、不是 SaaS只是一个蹲在你终端窗口里的协作伙伴帮你把写作、发布、归档、复盘这件事做得更顺手。它适合三类人一是刚学完 Python 基础、想找个真实项目练手的新手二是技术写作者厌倦了 WordPress 后台臃肿、Ghost 配置繁琐、Obsidian 发布插件不稳定三是需要快速搭建内部知识库、项目周报站、团队文档门户的小团队。它不替代 WordPress 的生态也不对标 Shopify 的电商能力但它解决了一个被长期忽视的痛点内容生产者不该为“发布”这件事额外学习运维、配置 Nginx、折腾 SSL 证书、维护数据库备份策略。日更不是 KPI而是 WorkBuddy 设计哲学的自然结果——当你删掉所有中间环节写作和发布之间的延迟趋近于零日更就从“坚持”变成了“顺手”。2. 整体设计思路与方案选型逻辑为什么不用 Django/Vue/MySQL2.1 为什么选 Flask 而非 Django 或 FastAPIFlask 在这里不是“因为简单所以选它”而是经过几轮实操验证后的理性收敛。我最初用 Django 试过类似方案建 Article Model、写 Admin、配 URL、搞模板继承、处理静态文件……两周后发现80% 的代码都在应付框架约定而不是解决“我今天想写什么”这个原始需求。FastAPI 更夸张——它天生为 API 设计强行套 Web 页面要写大量 Jinja2 模板状态管理反而增加心智负担。而 Flask 的优势在于“可控的裸露”它不强制你用 ORM不预设项目结构不打包一堆你用不到的中间件。WorkBuddy 的核心路由只有三个/首页列表、/post/slug单篇文章、/admin简易后台。每个路由背后就是十几行函数读取 Markdown 文件、解析 YAML Front Matter、注入 SQLite 查询结果、渲染模板。更重要的是Flask 的app.run(debugTrue)模式天然支持热重载你改完.md文件保存浏览器 F5 就能看到效果这种即时反馈对日更者至关重要。我对比过 Flask 与同类轻量框架如 Bottle、CherryPyFlask 的模板引擎 Jinja2 生态成熟调试错误信息清晰社区教程丰富且对新手足够友好——比如{{ post.title|safe }}这种过滤器写法比手写 HTML 转义安全得多。最关键一点Flask 的 WSGI 接口标准后续哪怕你要迁移到 GunicornNginx代码几乎零修改。这不是“先易后难”的妥协而是“始终可控”的设计选择。2.2 为什么 SQLite 是唯一合理的数据库选项看到“SQLite”出现在热搜词里很多人第一反应是“这玩意儿能当生产库”——答案是在 WorkBuddy 的场景下它不仅是合理的而且是不可替代的。我们来算一笔账一个日更博主一年写 365 篇文章每篇平均 1500 字加上标题、摘要、标签、发布时间、阅读数单条记录约 2KB。一年数据总量不到 1MB。SQLite 单文件数据库轻松承载且无需独立进程、无需端口监听、无需用户权限管理。你把它放在data/blog.db代码里一句sqlite3.connect(data/blog.db)就搞定备份直接复制这个文件。对比 MySQL你需要装服务、设 root 密码、建 database、授权用户、配置连接池、处理连接超时……这些操作对日更者毫无价值。PostgreSQL 更重。而像 TinyDB 这类纯 Python 库虽然更轻但缺乏 SQL 查询能力——WorkBuddy 要支持“按标签筛选”、“按月份归档”、“阅读数 Top10”没有 WHERE、GROUP BY、ORDER BY 是玩不转的。SQLite 的SELECT COUNT(*) FROM posts WHERE tag flask这种查询写起来和读起来都像自然语言。另外DB Browser for SQLite这个工具之所以高频出现在热搜里正是因为它是 WorkBuddy 生态的“可视化控制台”你双击打开.db文件直接看到所有文章记录手动改个阅读数、删条测试数据、查某天发布量比写 SQL 脚本还快。我实测过在 MacBook M1 上SQLite 处理 1000 条记录的复杂查询平均耗时 3ms完全满足本地开发和小流量访问需求。它不是“将就”而是精准匹配场景的最优解。2.3 为什么拒绝前端框架Vue/React和 CDN 资源WorkBuddy 的前端极其朴素一个base.html模板加载本地 CSSBootstrap 5 的精简版JS 只有两处一是文章页的代码块语法高亮用 Prism.jsCDN 引入但可离线二是搜索框的客户端过滤用原生 JS 写了不到 50 行。没用 Vue因为不需要组件化交互没用 React因为没有状态管理需求没接 CDN因为所有静态资源都放在static/目录下Flask 自动托管。这样做有三个硬性好处第一离线可用——地铁上写完 Markdown回家连不上网也能预览第二部署极简——整个站点就是一个文件夹扔到任何支持 Python 的服务器上python app.py就跑第三调试透明——你右键“查看源码”看到的就是真实 HTML没有构建产物、没有 sourcemap、没有 bundle 分析新手一眼看懂结构。我见过太多人用 VuePress 或 Docusaurus 搭博客最后卡在“怎么让自定义 CSS 生效”、“怎么改默认页脚”、“怎么禁用 PWA”上本质上是在和框架的默认约定打架。WorkBuddy 的哲学是“你写的 HTML 就是最终 HTML”所有样式、脚本、结构都由你直接掌控。这不是复古而是把技术栈的控制权从框架手里拿回来。3. 核心细节解析与实操要点从安装到首篇发布的完整链路3.1 环境准备与 WorkBuddy 安装避开 Linux/macOS/Windows 的三大坑WorkBuddy 官方推荐用 pip 安装但实际操作中不同系统有不同雷区。我踩过三次坑总结出最稳路径macOSM1/M2 芯片不要用系统自带 Python通常是 2.7 或老旧 3.x也不要盲目brew install python。正确做法是用 Homebrew 安装pyenvbrew install pyenv用 pyenv 装一个干净的 Python 3.11pyenv install 3.11.9 pyenv global 3.11.9创建项目虚拟环境python -m venv venv source venv/bin/activate安装 WorkBuddypip install workbuddy提示跳过这步直接pip install workbuddy大概率遇到zsh: command not found: workbuddy因为 M1 的 PATH 机制和 Rosetta 兼容性问题必须确保 pip 安装的可执行文件路径被 shell 正确识别。WindowsWin10/Win11最大的坑是sqlite3模块缺失。官方 Python 安装包有时不带完整 SQLite 支持。解决方案下载并安装 Python 官方安装包 务必勾选 “Add Python to PATH” 和 “Install pip”打开 PowerShell不是 CMD运行python -c import sqlite3; print(sqlite3.version)如果报错ModuleNotFoundError说明 SQLite 库没装好此时不要重装 Python而是用pip install pysqlite3替代它会编译适配的 SQLite 版本再pip install workbuddy成功后运行workbuddy --version应返回版本号。LinuxUbuntu/Debian常见问题是gcc编译器缺失导致某些依赖安装失败。执行sudo apt update sudo apt install -y python3-pip python3-venv build-essential libsqlite3-dev python3 -m venv venv source venv/bin/activate pip install --upgrade pip pip install workbuddy注意libsqlite3-dev是关键它提供 SQLite 的头文件否则pysqlite3编译会失败。很多教程漏掉这步导致后续workbuddy init报错sqlite3 module not found。安装完成后验证是否成功终端输入workbuddy应显示帮助菜单。如果提示command not found检查pip show workbuddy输出的Location路径把该路径下的bin/Linux/macOS或Scripts/Windows加入系统 PATH。3.2 初始化项目与目录结构解读每个文件夹存在的理由运行workbuddy init myblog后生成的标准目录结构如下myblog/ ├── app.py # Flask 主程序入口仅 80 行核心逻辑在此 ├── config.py # 配置项站点名、作者、主题色、分页数等 ├── content/ # 所有 Markdown 文章存放处按日期子目录组织 │ ├── 2024/ # 年份文件夹 │ │ └── 06/ # 月份文件夹 │ │ └── 15-hello-world.md # 文件名格式日期-标题.md ├── templates/ # Jinja2 模板含 base.html, index.html, post.html ├── static/ # CSS/JS/图片Flask 自动托管 │ ├── css/ │ │ └── main.css # 主题样式可直接修改 │ └── js/ │ └── search.js # 客户端搜索逻辑 ├── data/ # SQLite 数据库存放处 │ └── blog.db # 自动生成首次运行时创建表结构 └── requirements.txt # 依赖清单含 flask, markdown, bleach 等重点解析三个易被忽略的细节content/下的日期子目录不是装饰WorkBuddy 的app.py里有一段扫描逻辑只读取content/YYYY/MM/下的.md文件并按文件名中的日期排序。这意味着你不能把所有文章堆在content/根目录下否则无法按时间线展示。我试过手动改名2024-06-15-hello-world.md到根目录结果首页列表为空——因为扫描器根本不去那里找。templates/base.html里的{% block content %}{% endblock %}是关键钩子所有子模板index.html,post.html都继承它并在{% block content %}里填充具体内容。如果你要加一个“关于我”页面只需新建templates/about.html写{% extends base.html %}然后在{% block content %}里写 HTML再在app.py里加一条路由app.route(/about)返回这个模板。这种继承机制比复制粘贴 header/footer 稳定得多。data/blog.db的初始化时机第一次运行python app.py时程序会检查data/目录是否存在blog.db若不存在则执行建表 SQLCREATE TABLE IF NOT EXISTS posts ( id INTEGER PRIMARY KEY AUTOINCREMENT, slug TEXT UNIQUE NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, date DATE NOT NULL, tags TEXT, read_count INTEGER DEFAULT 0 );这个表结构决定了你能存什么。注意slug字段是 URL 友好的文章标识如hello-world由文件名自动生成不能重复read_count默认为 0每次访问/post/slug时后端会执行UPDATE posts SET read_count read_count 1 WHERE slug ?。这就是为什么你用DB Browser for SQLite打开blog.db能看到阅读数实时增长。3.3 首篇发布全流程从写 Markdown 到网页可见的 7 个动作以发布第一篇《Hello World》为例实操步骤如下全程在终端和文本编辑器间切换无 GUI 操作创建 Markdown 文件在content/2024/06/下新建文件15-hello-world.md日期必须是当前日期否则不会被扫描到。文件内容严格按 YAML Front Matter 格式--- title: Hello World tags: [flask, workbuddy, 入门] --- 这是我的第一篇 WorkBuddy 博客。 ## 为什么选它 因为它让我专注写作而不是配置服务器。启动 Flask 服务在项目根目录运行python app.py。终端会输出* Serving Flask app app * Debug mode: on * Running on http://127.0.0.1:5000此时打开浏览器访问http://127.0.0.1:5000首页应显示“Hello World”标题和摘要。验证 SQLite 写入保持app.py运行打开DB Browser for SQLite打开data/blog.db切换到Browse Data标签页选择posts表应看到一条记录id1,slughello-world,titleHello Worldread_count0。触发阅读计数在浏览器中点击首页的“Hello World”链接进入文章页。回到DB Browser刷新posts表read_count应变为1。这证明后端更新逻辑生效。修改文章并实时预览回到15-hello-world.md在末尾加一行 这行是后来加的保存文件。无需重启 Flask浏览器按 F5新内容立刻显示。这是 Flask 的debugTrue模式 Markdown 文件监听的功劳。添加图片在static/img/下新建文件夹hello-world/放入一张banner.jpg。在 Markdown 中引用![Banner](/static/img/hello-world/banner.jpg)。注意路径必须以/static/开头因为 Flask 的send_from_directory规则只允许访问static/子目录。生成静态文件可选如果想部署到 GitHub Pages 或 Netlify运行workbuddy build。它会启动一个临时 Flask 实例请求所有页面/,/post/hello-world把 HTML 保存到dist/目录。dist/就是纯静态站点可直接上传。这 7 步看似简单但每一步都对应一个技术决策Front Matter 解析用frontmatter库Markdown 渲染用markdown库支持表格、代码块HTML 安全过滤用bleach防 XSSURL 生成用 Flask 的url_for()函数。它们共同构成一个鲁棒的发布管道。4. 实操过程与核心环节实现日更工作流的自动化与可持续性设计4.1 日更工作流的三步固化模板、脚本、Git 集成日更最难的不是写而是“每天打开电脑面对空白编辑器时的启动成本”。WorkBuddy 通过三个层次降低这个成本第一层标准化文章模板在content/目录下新建template.md--- title: tags: [] --- ## 背景 ## 过程 ## 结果 ## 反思每天写新文章时复制此模板改title和tags填空即可。我把它设为 VS Code 的用户片段User Snippet输入wb Tab 就自动插入。模板强制结构化避免想到哪写到哪。第二层一键生成今日文章脚本在项目根目录新建new_post.shmacOS/Linux或new_post.batWindows# new_post.sh DATE$(date %Y-%m-%d) YEAR$(date %Y) MONTH$(date %m) SLUG$(echo $1 | sed s/[^a-zA-Z0-9]/-/g | tr [:upper:] [:lower:]) FILENAME${DATE}-${SLUG}.md mkdir -p content/$YEAR/$MONTH/ cp content/template.md content/$YEAR/$MONTH/$FILENAME echo Created: content/$YEAR/$MONTH/$FILENAME code content/$YEAR/$MONTH/$FILENAME # 自动用 VS Code 打开用法./new_post.sh 我的第一个WorkBuddy实践自动生成2024-06-15-wo-de-di-yi-ge-workbuddy-shi-jian.md并打开编辑。Windows 版本用powershell替换sed和tr命令。这个脚本把“创建文件”压缩到一条命令省去手动建目录、复制模板、命名文件三步。第三层Git 自动提交与部署在app.py末尾加一段钩子if __name__ __main__: app.run(debugTrue) # 开发模式下每次保存后自动 git commit import subprocess try: subprocess.run([git, add, .], checkTrue) subprocess.run([git, commit, -m, fauto commit: {datetime.now().strftime(%Y-%m-%d %H:%M)}], checkTrue) except: pass # git 未初始化则忽略再配一个 GitHub Action.github/workflows/deploy.ymlon: push: branches: [main] paths: [content/**, templates/**, static/**] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: 3.11 - name: Install dependencies run: | pip install flask markdown bleach - name: Build static site run: python app.py --build # 需在 app.py 中添加 --build 参数解析 - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pagesv3 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./dist这样你每天写完保存git push后GitHub 自动构建并发布到https://yourname.github.io/myblog。日更变成“写 → 保存 → git push”三步完成。4.2 SQLite 数据库的日常维护备份、查询、修复实战SQLite 文件虽小但作为唯一数据源必须建立维护习惯。我每周日晨间固定做三件事1. 自动备份脚本backup_db.sh#!/bin/bash TIMESTAMP$(date %Y%m%d_%H%M%S) cp data/blog.db backups/blog_${TIMESTAMP}.db # 保留最近 7 天备份 find backups/ -name blog_*.db -mtime 7 -delete echo Backup done: backups/blog_${TIMESTAMP}.db加入 crontab0 8 * * 0 /path/to/backup_db.sh每周日早 8 点执行。2. 快速查询阅读趋势用DB Browser for SQLite的 Execute SQL 标签页运行-- 查看本月阅读最多的 5 篇 SELECT title, read_count FROM posts WHERE date LIKE 2024-06% ORDER BY read_count DESC LIMIT 5; -- 统计各标签文章数 SELECT tags, COUNT(*) as count FROM posts GROUP BY tags ORDER BY count DESC;结果直接导出 CSV用 Excel 做折线图一目了然看出哪些主题受欢迎。3. 数据库损坏修复SQLite 极稳定但断电或强制关机可能损坏。修复命令# 检查完整性 sqlite3 data/blog.db PRAGMA integrity_check; # 若返回 ok则正常若返回 error则修复 sqlite3 data/blog.db .dump | sqlite3 data/blog_fixed.db mv data/blog_fixed.db data/blog.db我经历过一次因 Mac 睡眠中断写入导致read_count归零用此法 2 分钟恢复。4.3 Flask 服务的轻量部署从本地到公网的平滑过渡WorkBuddy 本地开发用app.run(debugTrue)但上线需更稳方案。我用gunicornnginx组合实测在 1C2G 的腾讯云轻量服务器上QPS 稳定在 120模拟 50 并发用户。部署步骤服务器安装 Python3.11 和 nginxsudo apt update sudo apt install -y python3.11 python3.11-venv nginx上传项目创建虚拟环境python3.11 -m venv venv source venv/bin/activate pip install -r requirements.txt gunicorn创建 Gunicorn 配置gunicorn.conf.pybind 127.0.0.1:8000 workers 2 worker_class sync timeout 30 keepalive 2 accesslog /var/log/workbuddy_access.log errorlog /var/log/workbuddy_error.log启动 Gunicorngunicorn -c gunicorn.conf.py app:app配置 nginx/etc/nginx/sites-available/workbuddyserver { listen 80; server_name your-domain.com; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /static/ { alias /path/to/myblog/static/; } }启用站点sudo ln -sf /etc/nginx/sites-available/workbuddy /etc/nginx/sites-enabled/sudo nginx -t sudo systemctl reload nginx。至此your-domain.com就是你的公网站点。Gunicorn 处理应用逻辑nginx 处理静态文件和反向代理分工明确。相比直接nohup python app.py 这套方案支持优雅重启、日志分离、负载均衡扩展加 worker 数即可。5. 常见问题与排查技巧实录那些文档里不会写的坑5.1 “页面 404但文件明明存在” —— 路径与扫描逻辑陷阱现象你在content/2024/06/下写了15-test.mdpython app.py启动后访问/post/test显示 404。原因WorkBuddy 的文章扫描逻辑有两个硬性条件文件名必须匹配正则^(\d{4})-(\d{2})-(\d{2})-(.)\.md$即YYYY-MM-DD-title.md文件必须放在content/YYYY/MM/子目录下且YYYY和MM必须与文件名中年份、月份一致。排查步骤运行python app.py --debug如果支持或在app.py的scan_posts()函数里加print(fScanning: {file_path})检查终端输出看是否扫描到你的文件如果没扫描到确认文件名是2024-06-15-test.md不是15-test.md或test.md确认目录是content/2024/06/不是content/2024/6/或content/06/。实操心得我曾因 macOS Finder 显示6月而误建content/2024/6/目录导致所有文章 404。解决方案是永远用终端mkdir -p content/2024/06创建避免 GUI 干预。5.2 “中文标签搜索失效” —— SQLite 全文搜索配置遗漏现象文章tags: [中文, flask]但在搜索框输入“中文”无结果。原因SQLite 的LIKE查询对中文支持弱WorkBuddy 默认用WHERE tags LIKE %中文%但tags字段存的是中文,flask这样的字符串LIKE匹配不精确。解决方案启用 SQLite FTS5全文搜索扩展。修改app.py中的查询逻辑建 FTS 表conn.execute( CREATE VIRTUAL TABLE IF NOT EXISTS posts_fts USING fts5( title, content, tags, contentposts, content_rowidid ) )插入数据时同步更新 FTS 表conn.execute(INSERT INTO posts_fts(rowid, title, content, tags) VALUES (?, ?, ?, ?), (post_id, title, content, tags))搜索时用SELECT * FROM posts_fts WHERE posts_fts MATCH ?。注意FTS5 需 SQLite 3.22Ubuntu 20.04 自带版本可能过低需sudo apt install sqlite3升级。macOS 用brew install sqlite3并确保PATH优先使用新版。5.3 “图片不显示404 错误” —— 静态文件路径的绝对与相对之争现象Markdown 中写![Img](img/banner.jpg)浏览器 Network 标签页显示GET http://127.0.0.1:5000/img/banner.jpg 404。原因Flask 的send_from_directory只允许访问static/目录下的文件而img/banner.jpg路径被解释为根目录下的img/不在static/内。正确写法图片放在static/img/banner.jpgMarkdown 中写![Img](/static/img/banner.jpg)注意开头的/表示绝对路径或在app.py中添加自定义静态路由app.route(/img/path:filename) def serve_img(filename): return send_from_directory(static/img, filename)然后 Markdown 用![Img](/img/banner.jpg)。实操心得我习惯用第一种因为static/是 Flask 标准约定其他开发者接手一眼就懂。第二种虽灵活但增加路由复杂度得不偿失。5.4 “日更中断后补发日期排序错乱” —— 时间戳与文件系统顺序冲突现象6 月 15 日没写6 月 16 日补发15-hello-world.md但首页显示在 16 日文章之后。原因WorkBuddy 按文件名中的日期排序但文件系统如 ext4的mtime修改时间可能晚于 16 日导致os.listdir()返回顺序混乱。解决方案强制按文件名日期排序。在scan_posts()函数中替换原排序逻辑# 原逻辑依赖文件系统顺序 files os.listdir(content_dir) # 新逻辑按文件名日期解析排序 import re def extract_date(filename): match re.match(r^(\d{4})-(\d{2})-(\d{2})-, filename) if match: return f{match.group(1)}{match.group(2)}{match.group(3)} return 00000000 files sorted(os.listdir(content_dir), keyextract_date, reverseTrue)这个函数把2024-06-15-hello.md映射为20240615按字符串倒序排确保最新日期在前。我补发过 3 篇旧文用此法后排序完全正确。6. 进阶扩展与个性化定制让 WorkBuddy 真正属于你6.1 添加 RSS 订阅功能三行代码搞定RSS 是日更博主的生命线。WorkBuddy 默认不带但加起来不到 10 行代码在app.py顶部加导入from flask import Response在路由部分加app.route(/feed.xml) def feed(): posts get_recent_posts(20) # 获取最近 20 篇 xml_content render_template(rss.xml, postsposts) return Response(xml_content, mimetypeapplication/rssxml)在templates/下新建rss.xml?xml version1.0 encodingUTF-8? rss version2.0 channel title{{ config.SITE_NAME }}/title link{{ config.BASE_URL }}/link description{{ config.DESCRIPTION }}/description {% for post in posts %} item title{{ post.title }}/title link{{ config.BASE_URL }}/post/{{ post.slug }}/link pubDate{{ post.date.strftime(%a, %d %b %Y %H:%M:%S GMT) }}/pubDate description{{ post.content[:200]|striptags|safe }}.../description /item {% endfor %} /channel /rss提示config.BASE_URL需在config.py中定义如http://localhost:5000或https://your-site.com。订阅地址就是https://your-site.com/feed.xml主流 RSS 阅读器如 Feedly一键添加。6.2 集成简易评论系统用 GitHub Issues 当数据库不想搭独立评论服务用 GitHub Issues 实现“无后端评论”在templates/post.html底部加div idcomments h3评论/h3 div idcomment-list/div form idcomment-form input typetext idcomment-name placeholder你的名字 required textarea idcomment-text placeholder写下你的想法... required/textarea button typesubmit提交/button /form /div script src/static/js/github-comments.js/script在static/js/github-comments.js中用 GitHub REST API 读写 Issues// 读取GET https://api.github.com/repos/yourname/myblog/issues?labelscommentstateall // 写入POST https://api.github.com/repos/yourname/myblog/issues 带 label comment需申请 GitHub Personal Access Token最小权限public_repo。这样每篇文章对应一个 GitHub Issue评论就是 Issue 的评论天然支持 Markdown、 提及、邮件通知。我用它半年零维护成本读者反馈比 Disqus 更真实。6.3 主题定制从 Bootstrap 到 Tailwind 的无缝切换WorkBuddy 默认用 Bootstrap 5但如果你偏好 Tailwind CSS删除static/css/main.css下载 Tailwind 的 CDN 版本开发用或用npx tailwindcss -i ./static/css/input.css -o ./static/css/output.css --watch修改templates/base.html的head引入 Tailwind CSS重写 templates/index.html
返回列表