
blog程序怎么用:从准备到完成的实操步骤
“blog 程序怎么用”通常有两种意思:使用现成的博客平台发布文章,或自己安装、运行一套博客程序。前者适合尽快开始写作;后者适合需要掌控数据、页面和部署方式的场景。本文先说明如何选择,再用一个可运行的 Flask 示例演示从准备、初始化、发布到访问的完整流程。
这个示例刻意保持简单:访客可以浏览文章,作者通过服务器命令发布文章;文章内容按纯文本显示,不支持用户提交 HTML。它适合学习、内部记录或低流量个人博客的原型,不包含后台登录、多用户权限、富文本编辑、评论和生产级运维设施。不要把“能运行”误认为“已经适合直接公开部署”。
一、先判断适不适合自己
现成平台适合什么情况
- 只想写作和发布:托管博客平台通常已经提供编辑器、主题、账号管理和备份,省去服务器维护。
- 需要自己控制内容与部署:可考虑 WordPress 等成熟程序,或静态站点生成器。前者通常带数据库和后台,后者一般把文章构建成静态文件。
- 想学习开发、功能非常定制:可以自行编写博客程序,但需要自己承担身份验证、权限、备份、升级和安全维护。
选择前确认几个现实问题:文章是否需要多人协作;是否要评论、搜索和定时发布;是否必须使用自有域名;是否有服务器和维护时间;内容能否迁移。如果只是个人写作,优先选维护成本低的方案,通常比从零造轮子更合适。
本文示例的适用边界
下面的程序仅提供文章列表、文章详情和本机命令行发布。没有 Web 发布后台,因此远程访客不能直接写入文章;SQLite 数据库是单个文件,适合小型、低并发应用,不等于适合任意规模。示例也没有自动分页、图片上传、全文搜索和多用户权限。需要这些能力时,应评估成熟程序或在明确需求后扩展,而不是把示例直接当成完整内容管理系统。
二、准备运行环境
需要安装 Python 3.10 或更新版本。先创建项目目录和虚拟环境:
mkdir simple-blog
cd simple-blog
python -m venv .venv
激活虚拟环境。不同系统的命令略有区别:
# macOS 或 Linux
source .venv/bin/activate
# Windows PowerShell
.venv\Scripts\Activate.ps1
安装 Flask,并记录依赖版本。下面命令会把当前环境中安装的包写入文件;部署时也可以进一步固定经过测试的版本。
python -m pip install Flask
python -m pip freeze > requirements.txt
新建 app.py、templates 目录和两个模板文件。最终目录结构如下:
simple-blog/
├── app.py
├── requirements.txt
└── templates/
├── index.html
└── post.html
三、编写博客程序
1. 创建应用和数据库
将以下内容保存为 app.py。数据库路径可通过 BLOG_DB 环境变量指定;未设置时使用当前目录下的 blog.db。程序使用 SQLite 参数化查询,不把用户输入拼接进 SQL。
import os
import sqlite3
from pathlib import Path
import click
from flask import Flask, abort, g, render_template
app = Flask(__name__)
# 默认写入项目当前目录。正式部署时建议明确指定持久化目录。
app.config["BLOG_DB"] = os.environ.get("BLOG_DB", "blog.db")
def get_db():
"""每个请求使用自己的数据库连接。"""
if "db" not in g:
db_path = Path(app.config["BLOG_DB"])
db_path.parent.mkdir(parents=True, exist_ok=True)
g.db = sqlite3.connect(db_path)
g.db.row_factory = sqlite3.Row
return g.db
@app.teardown_appcontext
def close_db(error=None):
db = g.pop("db", None)
if db is not None:
db.close()
@app.cli.command("init-db")
def init_db():
"""创建文章表;重复执行不会删除已有数据。"""
db = get_db()
db.execute(
"""
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT NOT NULL,
created_at TEXT NOT NULL
)
"""
)
db.commit()
click.echo("数据库初始化完成。")
@app.cli.command("publish")
@click.argument("title")
@click.argument("file", type=click.Path(exists=True, dir_okay=False))
def publish(title, file):
"""从 UTF-8 文本文件读取正文并发布文章。"""
title = title.strip()
if not title or len(title) > 200:
raise click.ClickException("标题不能为空,且不能超过 200 个字符。")
body = Path(file).read_text(encoding="utf-8")
if not body.strip():
raise click.ClickException("正文不能为空。")
if len(body) > 200000:
raise click.ClickException("正文超过示例程序的 200000 字符限制。")
db = get_db()
# SQLite 的 CURRENT_TIMESTAMP 使用 UTC 时间,显示时按 UTC 理解。
cursor = db.execute(
"""
INSERT INTO posts (title, body, created_at)
VALUES (?, ?, CURRENT_TIMESTAMP)
""",
(title, body),
)
db.commit()
click.echo(f"文章已发布,编号:{cursor.lastrowid}")
@app.get("/")
def index():
posts = get_db().execute(
"""
SELECT id, title, created_at
FROM posts
ORDER BY id DESC
"""
).fetchall()
return render_template("index.html", posts=posts)
@app.get("/posts/<int:post_id>")
def show_post(post_id):
post = get_db().execute(
"""
SELECT id, title, body, created_at
FROM posts
WHERE id = ?
""",
(post_id,),
).fetchone()
if post is None:
abort(404)
return render_template("post.html", post=post)
if __name__ == "__main__":
# 仅供本机开发;正式部署不要开启调试模式。
app.run()
这里的标题和正文长度限制是本示例在发布命令中自行定义的应用限制,不是 Flask 或 SQLite 的通用参数限制,也不代表适用于所有博客。长度以 Python 字符串长度检查,不能直接当成字节数、数据库容量或所有语言下的“字数”。如果业务需要其他限制,应明确需求并同步调整输入校验、数据库和界面。
2. 添加文章列表模板
新建 templates/index.html:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>我的博客</title>
</head>
<body>
<header>
<h1><a href="/">我的博客</a></h1>
</header>
<main>
{% if posts %}
<ul>
{% for post in posts %}
<li>
<a href="{{ url_for('show_post', post_id=post['id']) }}">
{{ post['title'] }}
</a>
<time>{{ post['created_at'] }} UTC</time>
</li>
{% endfor %}
</ul>
{% else %}
<p>还没有文章。</p>
{% endif %}
</main>
</body>
</html>
3. 添加文章详情模板
新建 templates/post.html:
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ post['title'] }} - 我的博客</title>
<style>
.body { white-space: pre-wrap; overflow-wrap: anywhere; }
</style>
</head>
<body>
<header>
<p><a href="/">返回首页</a></p>
</header>
<main>
<article>
<h1>{{ post['title'] }}</h1>
<time>{{ post['created_at'] }} UTC</time>
<div class="body">{{ post['body'] }}</div>
</article>
</main>
</body>
</html>
Flask 的 Jinja 模板对 HTML 模板默认启用自动转义,因此正文中的尖括号等特殊字符会作为文本显示,而不是被浏览器当成 HTML 执行。模板没有使用 safe 过滤器。white-space: pre-wrap 用来保留普通文本的换行;它不会把 Markdown 转成排版,也不提供富文本功能。若以后允许富文本或 HTML,必须采用经过审查的 HTML 清洗方案,并限制允许的标签、属性和链接协议,不能仅靠转义或一个正则表达式解决。
四、初始化并发布第一篇文章
确保仍在虚拟环境中,并且当前目录是项目目录。初始化数据库:
flask --app app init-db
准备一个 UTF-8 编码的正文文件 first-post.txt,例如:
这是我的第一篇文章。
正文按纯文本保存,空行和换行会保留。
通过 Flask 命令发布。命令参数里的标题建议加引号,以便包含空格或中文标点:
flask --app app publish "第一篇文章" first-post.txt
如果发布成功,终端会显示文章编号。启动开发服务器:
flask --app app run
打开终端提示的本机地址,通常是 http://127.0.0.1:5000/。首页会显示文章列表,点击标题进入详情页。直接访问不存在的文章编号会得到 404 页面;文章编号由 SQLite 自动分配,不需要手动编造。
五、发布、编辑和删除的实际操作
发布新文章
每篇文章保存为一个 UTF-8 文本文件,再执行 publish 命令即可。它会新增记录,不会覆盖同名文章,也不会检查是否有重复标题。发布前先预览文件内容,确认编码和换行符合预期。
编辑文章
本示例没有编辑命令或网页后台。要修改文章,需要编写额外命令或使用 SQLite 管理工具,并在操作前备份数据库。直接改数据库虽然能完成任务,但容易误改记录;编辑功能应增加输入校验、明确的文章编号检查和失败处理,不能把数据库文件当普通文本编辑。
删除文章
示例没有删除入口,避免在没有身份验证和权限控制的情况下暴露破坏性操作。需要删除功能时,应实现受保护的管理界面或只允许可信运维人员执行的命令,并在执行前核对文章编号。公开的 GET 链接不应直接执行删除;网页表单还需考虑 CSRF 防护。
六、部署前要理解的安全边界
当前代码不提供管理员认证
发布命令在运行程序的本机或服务器终端执行,不经过网站登录。谁能登录这台机器、运行相应命令,谁就可能发布内容。因此不要把终端权限交给不可信人员,也不要为了方便把命令包装成无鉴权的公开 HTTP 接口。若要增加 Web 后台,需要设计身份验证、角色权限、会话保护、CSRF 防护、登录限速和安全的密码存储;这不是在路由上加一个密码框就完成了。
只接受纯文本,不等于过滤所有风险
自动转义能降低这两个模板中常见的 HTML 注入和脚本执行风险,但它不是通用安全保证。不要对不可信正文使用 safe,不要把内容拼进 JavaScript、CSS 或未经处理的 URL 上下文。模板、前端脚本或输出方式改变后,安全性质也可能改变。
开发服务器不用于生产
flask --app app run 是开发用途。对外提供服务时,应使用适合部署环境的 WSGI 服务器和反向代理,并正确配置 HTTPS、访问日志、进程管理和防火墙。不要在生产环境开启 Flask 调试器;调试模式可能泄露信息并带来严重风险。部署方式应根据操作系统、平台和流量选择,不能把本机运行命令当作生产部署方案。
数据库文件需要持久化和备份
SQLite 数据库位于 BLOG_DB 指定路径。若部署平台会清理临时磁盘,数据库可能在重启或重新部署后消失。应选择持久化存储目录,并定期备份数据库文件;备份是否可用,需要通过恢复演练确认。对正在频繁写入的数据库,备份方式应遵循 SQLite 的备份建议,不能假设任何时刻直接复制文件都适合所有场景。
并发与规模有边界
SQLite 使用文件存储,部署简单,但写入并发和多进程场景需要谨慎评估。本文没有做分页:首页会一次读取全部文章,文章持续增长时应增加分页或查询限制。高并发、多实例部署或复杂查询需求,可能需要改用服务型数据库并处理连接池、迁移和备份。不存在仅凭一个固定文章数就能判断是否必须更换数据库的通用阈值,实际取决于写入频率、查询模式、硬件和可接受的维护成本。
七、常见错误与排查
- 提示找不到 Flask:确认虚拟环境已激活,并在同一环境执行
python -m pip install Flask。也可用python -m pip show Flask检查安装位置。 - 提示找不到 app:确认命令在包含
app.py的目录执行,并写成flask --app app。如果文件名不同,--app后要对应修改。 - 提示数据库表不存在:先运行
flask --app app init-db。初始化命令创建表,不会自动导入文章。 - 页面显示模板不存在:检查目录名是否为
templates,并确认模板文件在该目录内,而不是与app.py同级。 - 正文乱码:以 UTF-8 保存文本文件,并让发布命令用 UTF-8 读取。不要只通过改网页编码猜测文件的实际编码。
- 数据库写入到了意外位置:相对路径以启动进程的当前工作目录为基准。部署时可设置绝对路径,例如
BLOG_DB=/var/lib/simple-blog/blog.db,并确认运行账户有目录读写权限。 - 修改程序后旧数据看起来不见了:首先核对
BLOG_DB和当前工作目录是否改变。不同路径会对应不同数据库文件,通常不是数据自动迁移或丢失。
八、从示例走向可维护的博客
确定要长期使用后,可以按优先级逐步补齐功能:先加分页、文章编辑和删除,再增加可靠的管理员认证与操作审计;需要排版时,选择 Markdown 渲染并检查链接与原始 HTML 策略;需要图片时,限制文件类型和大小,将上传文件存放在不可执行的位置;需要搜索时,根据数据量选择数据库查询或专门的搜索方案。每项功能都要同时考虑权限、错误处理、备份和升级。
发布前还应检查页面标题、站点名称、时区和域名;配置 robots、站点地图或统计工具属于后续需求,不是博客能够运行的前提。对现有成熟程序,优先使用官方文档和受支持的插件,及时更新,并在升级前备份。自行维护时,依赖版本、数据库结构和部署配置都应纳入版本管理或运维记录。
总结
使用博客程序的关键不是先写代码,而是先判断需求、维护能力和数据归属。本文的 Flask 示例可以完成数据库初始化、命令行发布、列表浏览和详情查看,适合作为可运行的学习起点。它明确只处理纯文本、没有 Web 管理后台,也没有生产级认证和运维能力。准备公开使用前,先补齐访问控制、持久化备份、部署配置和规模评估;如果这些工作超出需求,选用维护成熟的现成博客程序往往更稳妥。



