AndrewBlog 开发者文档
Flask 3.1 · SQLite · machinaix.com
基于 Flask 构建的个人博客系统,支持 Markdown 写作、封面图片上传、标签分类、评论系统和管理员后台。本文档涵盖从本地开发到生产部署、日常运维与搜索引擎收录的完整流程。
技术栈
| 层次 | 技术 |
|---|---|
| Web 框架 | Flask 3.1 |
| 数据库 ORM | Flask-SQLAlchemy 3.1 + SQLite |
| 数据库迁移 | Flask-Migrate 4.0(Alembic) |
| 用户认证 | Flask-Login 0.6 |
| 表单与 CSRF | Flask-WTF 1.2 |
| Markdown 渲染 | Python-Markdown 3.7 |
| Markdown 编辑器 | EasyMDE(CDN) |
| 前端 UI | Pico.css v2(CDN) |
| 密码安全 | Werkzeug generate_password_hash |
| 频率限制 | Flask-Limiter 3.11 |
项目结构
AndrewBlog/ ├── app/ │ ├── __init__.py # 应用工厂 create_app() │ ├── config.py # 开发/生产配置 │ ├── extensions.py # db / login_manager / migrate / csrf 实例 │ ├── models.py # User / Post / Comment 数据模型 │ ├── forms.py # WTForms 表单定义 │ ├── utils.py # 文件上传 / 删除工具函数 │ └── routes/ │ ├── auth.py # 注册 / 登录 / 忘记密码 / 退出 │ ├── blog.py # 前台博客(首页 / 文章详情 / 评论) │ └── admin.py # 后台管理(文章列表 / 新建 / 编辑 / 删除) │ ├── app/templates/ │ ├── base.html # 公共布局(导航 / Flash 消息) │ ├── auth/ │ │ ├── login.html │ │ ├── register.html │ │ └── forgot_password.html │ ├── blog/ │ │ ├── index.html # 文章列表(分页) │ │ └── post_detail.html # 文章详情 + 评论 │ └── admin/ │ ├── posts.html # 文章管理列表 │ └── editor.html # 文章编辑器(EasyMDE) │ ├── scripts/ │ ├── init_db.py # 首次部署初始化(建表 + 管理员账号) │ └── db_manage.py # 数据库管理工具(查看 / 删文章 / 删用户) ├── migrations/ # Alembic 数据库迁移文件 ├── instance/ # SQLite 数据库(不提交到 Git) ├── uploads/ # 用户上传的封面图片(不提交到 Git) ├── .env # 环境变量(不提交到 Git) ├── .env.example # 环境变量示例 ├── requirements.txt └── run.py # 入口文件
路由总览
前台(博客)
| 方法 | URL | 功能 |
|---|---|---|
| GET | / | 文章列表首页(分页,每页 10 篇) |
| GET | /post/<slug> | 文章详情页(含评论,管理员可预览草稿) |
| POST | /post/<slug>/comment | 提交评论(需登录) |
| GET | /uploads/<filename> | 访问上传的封面图片 |
认证
| 方法 | URL | 功能 |
|---|---|---|
| GET/POST | /auth/register | 注册新账号 |
| GET/POST | /auth/login | 登录(支持用户名或邮箱) |
| GET/POST | /auth/forgot-password | 忘记密码(用户名 + 邮箱验证后重置) |
| GET | /auth/logout | 退出登录 |
后台管理(需管理员权限)
| 方法 | URL | 功能 |
|---|---|---|
| GET | /admin/ | 重定向到文章列表 |
| GET | /admin/posts | 文章管理列表 |
| GET/POST | /admin/post/new | 新建文章 |
| GET/POST | /admin/post/<id>/edit | 编辑文章 |
| POST | /admin/post/<id>/delete | 删除文章 |
数据模型
User
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键 |
| username | String(64) | 唯一,用户名 |
| String(120) | 唯一,邮箱 | |
| password_hash | String(256) | 哈希后的密码 |
| is_admin | Boolean | 是否管理员 |
| created_at | DateTime | 注册时间 |
Post
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键 |
| title | String(200) | 文章标题 |
| slug | String(200) | URL 标识,唯一 |
| summary | String(500) | 摘要(可选) |
| body | Text | 正文(Markdown 原文) |
| tags | String(200) | 标签(逗号分隔) |
| cover_image | String(200) | 封面图片文件名 |
| published | Boolean | 是否发布 |
| created_at / updated_at | DateTime | 时间戳 |
Comment
| 字段 | 类型 | 说明 |
|---|---|---|
| id | Integer | 主键 |
| body | Text | 评论内容 |
| post_id | Integer | 外键 → Post |
| author_id | Integer | 外键 → User |
| created_at | DateTime | 评论时间 |
本地运行
1. 克隆仓库
git clone https://github.com/ANDREW-SVIP/AndrewBlog.git
cd AndrewBlog2. 创建虚拟环境并安装依赖
#如果没有权限 先解锁脚本执行权限(PowerShell 默认限制直接运行激活脚本)
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope Process
#接着执行
python -m venv venv
# Windows
venv\Scripts\activate
# 初始化数据库
python scripts/init_db.py
# macOS / Linux
source venv/bin/activate
pip install -r requirements.txt3. 配置环境变量
cp .env.example .env编辑 .env,至少修改 SECRET_KEY:
FLASK_APP=run.py
FLASK_ENV=development
SECRET_KEY=your-long-random-secret-key-here
DATABASE_URL=sqlite:///blog.db
UPLOAD_FOLDER=uploads
MAX_CONTENT_LENGTH=52428804. 初始化数据库
flask db upgrade5. 创建管理员账号
python - <<'EOF'
from app import create_app
from app.extensions import db
from app.models import User
app = create_app()
with app.app_context():
u = User(username='admin', email='admin@example.com', is_admin=True)
u.set_password('your-password')
db.session.add(u)
db.session.commit()
print('管理员账号创建成功')
EOF6. 启动开发服务器
flask run或者直接运行:
python run.py访问 http://127.0.0.1:5000
主要功能
- Markdown 编辑器:集成 EasyMDE,支持实时预览、分屏模式、自动保存(localStorage)
- 封面图片:上传后即时预览,支持替换和删除,限制 5MB / jpg png gif webp
- 草稿系统:文章可保存为草稿,管理员登录后可预览草稿,普通访客只能看已发布文章
- 标签:逗号分隔,在文章列表和详情页展示
- 评论:登录用户可评论,按时间升序排列
- 忘记密码:通过用户名 + 注册邮箱双重验证后直接设置新密码,无需邮件
- CSRF 保护:所有 POST 表单均受 Flask-WTF CSRF 保护
生产部署建议
- 将
FLASK_ENV改为production - 使用强随机
SECRET_KEY(至少 32 位随机字符串) - 将 SQLite 替换为 PostgreSQL 或 MySQL(修改
DATABASE_URL) - 使用 Gunicorn + Nginx 作为 WSGI 服务器
- 将
uploads/目录托管到对象存储(如 OSS / S3)
pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 "app:create_app('production')"生产服务器信息
| 项目 | 内容 |
|---|---|
| 服务器 | 博客服务器 IP(SSH 端口 博客 SSH 端口) |
| 项目目录 | /www/wwwroot/andrewblog |
| 进程管理 | systemd(服务名 andrewblog) |
| WSGI 服务器 | Gunicorn,监听 127.0.0.1:8000 |
| Web 服务器 | Nginx(宝塔面板管理) |
| 数据库 | SQLite,路径 instance/blog.db |
| 日志 | /var/log/andrewblog/ |
修改代码后的发布流程
本地开发完成后
# 1. 提交代码
git add .
git commit -m "描述本次修改内容"
git push服务器拉取更新
# SSH 登录服务器
ssh -p <博客 SSH 端口> root@<博客服务器 IP>
# 进入项目目录
cd /www/wwwroot/andrewblog
# 拉取最新代码
git pull
# 如果新增了依赖包,重新安装
venv/bin/pip install -r requirements.txt
# 只有修改了 models.py(新增/删除字段或表)时才需要执行,日常发布不需要
# FLASK_ENV=production venv/bin/flask db upgrade
# 重启服务(优雅重启,不中断用户请求)
systemctl reload andrewblog验证部署成功
# 查看服务状态
systemctl status andrewblog
# 查看最新日志
tail -f /var/log/andrewblog/error.log常用运维命令
# 完全重启服务
systemctl restart andrewblog
# 停止服务
systemctl stop andrewblog
# 备份数据库
cp /www/wwwroot/andrewblog/instance/blog.db ~/backup/blog_$(date +%Y%m%d).db
# 查看实时访问日志
tail -f /var/log/andrewblog/access.log如果改动了静态文件(CSS/JS)
Nginx 缓存了静态文件,改动后需要让浏览器重新加载:
# 方法一:在文件名后加版本号(推荐)
# 例如 style.css?v=2
# 方法二:重载 Nginx
nginx -s reload访问日志解读(爬虫 & 收录验证)
日志路径:/var/log/andrewblog/access.log
查看最近 20 条日志
tail -20 /var/log/andrewblog/access.log实时监控(持续滚动)
tail -f /var/log/andrewblog/access.log日志格式说明
每一行格式如下:
IP地址 - - [时间 +0800] "请求方法 路径 协议" 状态码 响应字节数 "来源页" "User-Agent"示例:
66.249.68.32 - - [22/May/2026:14:21:21 +0800] "GET /post/markdown-cheatsheet HTTP/1.1" 200 5091 "-" "Mozilla/5.0 (compatible; Googlebot/2.1)"如何识别 Google 爬虫
特征: IP 段 66.249.x.x,User-Agent 含 Googlebot
grep -i "googlebot" /var/log/andrewblog/access.log | tail -20看到以下内容说明 Google 正在抓取:
66.249.68.32 - "GET /post/文章slug" 200 ... "Googlebot"
66.249.68.33 - "GET /sitemap.xml" 200 ... "Googlebot"收录验证: 在 Google 搜索 site:machinaix.com,有结果说明已收录。
如何识别百度爬虫
特征: User-Agent 含 Baiduspider
grep -i "baiduspider" /var/log/andrewblog/access.log | tail -20看到以下内容说明百度正在抓取:
x.x.x.x - "GET /post/文章slug" 200 ... "Baiduspider"
x.x.x.x - "GET /robots.txt" 200 ... "Baiduspider"收录验证: 在百度搜索 site:machinaix.com,有结果说明已收录。
如何确认百度主动推送成功
发布文章后,查看日志里有没有 POST /admin/post/new 或 POST /admin/post/ID/edit 的 302 响应:
grep "POST /admin" /var/log/andrewblog/access.log | tail -10看到 302 状态码说明文章发布成功,百度推送函数已自动执行。
去百度搜索资源平台「普通收录 → 数据反馈」可查看历史推送数量(数据有延迟,次日更新)。
常见爬虫 IP / UA 速查
| 爬虫 | IP 特征 | User-Agent 关键词 |
|---|---|---|
66.249.x.x | Googlebot | |
| 百度 | 180.76.x.x / 220.181.x.x | Baiduspider |
| UptimeRobot | 216.144.x.x / 69.162.x.x | UptimeRobot |
| Bing | 157.55.x.x | bingbot |
数据库管理工具(db_manage.py)
所有操作在服务器上执行:
查看数据库状态(只读,随时可用)
venv/bin/python scripts/db_manage.py status显示所有用户、文章、评论数量,不修改任何数据。
删除指定文章(按 slug)
# 预览(不会真正删除)
venv/bin/python scripts/db_manage.py delete-posts --slugs "slug1,slug2"
# 确认无误后加 --execute 执行
venv/bin/python scripts/db_manage.py delete-posts --slugs "slug1,slug2" --execute删除指定用户(按 ID)
# 预览
venv/bin/python scripts/db_manage.py delete-users --ids 2,6
# 执行(管理员账号自动跳过,不会误删)
venv/bin/python scripts/db_manage.py delete-users --ids 2,6 --executeslug 是文章 URL 中的标识符,在 status 输出里每篇文章都能看到。
垃圾注册账号处理
如何发现
定期运行 db_manage.py status 查看用户列表,重点关注:
- 用户名含俄语、阿拉伯语、随机数字
- 用户名含
http://或https://链接 - 邮箱域名可疑
如何清理
venv/bin/python scripts/db_manage.py status # 确认可疑用户 ID
venv/bin/python scripts/db_manage.py delete-users --ids 可疑ID # 预览
venv/bin/python scripts/db_manage.py delete-users --ids 可疑ID --execute # 执行已有防护措施
| 防护 | 说明 |
|---|---|
| 频率限制 | 同一 IP 每小时最多注册 5 次,登录每分钟最多 20 次 |
| 蜜罐字段 | 注册页隐藏字段,机器人自动填写后静默拦截 |
| 用户名格式 | 只允许中文、英文、数字、下划线、连字符 |
UptimeRobot 监控
UptimeRobot 监控博客可用性,宕机时自动推送告警。
查看监控状态
- 访问 uptimerobot.com 并登录
- 首页查看所有监控项:绿色 = 正常,红色 = 宕机
添加新监控
- 点击「+ Add New Monitor」
- Monitor Type 选「HTTP(s)」
- URL 填
https://machinaix.com - Monitoring Interval 选「5 minutes」
宕机后处理流程
ssh -p <博客 SSH 端口> root@<博客服务器 IP>
cd /www/wwwroot/andrewblog
systemctl status andrewblog # 查看状态
journalctl -u andrewblog -n 100 --no-pager # 查看错误日志
systemctl restart andrewblog # 重启服务Google 收录(Search Console)
查看收录情况
- 访问 search.google.com/search-console
- 选择 machinaix.com 资源 → 左侧「覆盖率」查看已收录页面
提交 Sitemap(只需做一次)
- Search Console → 左侧「站点地图」
- 输入
sitemap.xml点击提交
新文章发布后请求收录
- Search Console 顶部搜索框输入文章完整 URL
- 点击「请求编入索引」,1-3 天内生效
验证文章是否被收录
在 Google 搜索:
site:machinaix.com/post/文章slug有结果 = 已收录,无结果 = 尚未收录(等待或手动请求)。
License
MIT