DjangoBlog 项目理解与部署指南
DjangoBlog 项目理解与部署指南
项目地址:https://github.com/liangliangyy/DjangoBlog
本文档帮助你快速理解 DjangoBlog 的整体架构、核心模块、关键机制,并附上极空间(ZSpace)NAS 上的 Docker 部署实操。
📑 目录
- 一句话定位
- 一、技术栈
- 二、代码组织(Django 多 App 架构)
- 三、请求如何流转
- 四、数据模型(blog/models.py)
- 五、三个关键机制
- 六、前端架构
- 七、本地快速上手
- 八、源码阅读顺序
- 九、在极空间 NAS 上用 Docker 部署 ⭐
一句话定位
DjangoBlog 是一个基于 Django 5.2 + Python 3.10 构建的现代化博客系统,作者 liangliangyy,采用 MIT 协议开源。它的最大特色是插件系统和现代化的前端架构(HTMX + Alpine.js + Tailwind)。
一、技术栈
| 层 | 技术 |
|---|---|
| 后端 | Django 5.2、Python 3.10+、Gunicorn(gevent) |
| 数据库 | MySQL(默认)/ SQLite(可配置) |
| 缓存 | Redis / LocalMem |
| 搜索 | Whoosh / Elasticsearch(通过 haystack) |
| 前端 | Alpine.js + Tailwind CSS + HTMX + Vite 5 |
| 编辑器 | mdeditor(Markdown) |
| 反向代理 | Nginx(生产 / Docker 部署) |
二、代码组织(Django 多 App 架构)
项目由 djangoblog/(项目配置包)+ 若干业务 App 组成:
djangoblog/ ← 项目核心配置包(settings / urls / 中间件 / 信号 / 工具类)
├── blog/ ← 核心 App:文章、分类、标签、侧边栏、首页、搜索视图
├── accounts/ ← 用户账号、登录后端、OAuth 用户绑定
├── comments/ ← 评论系统(无限嵌套回复 + 邮件提醒)
├── oauth/ ← 第三方登录(Google / GitHub / Facebook / 微博 / QQ)
├── servermanager/ ← 运维:邮件发送日志、微信公众号管理、异常监控
├── owntracks/ ← 位置轨迹记录(owntracks 集成)
└── plugins/ ← 插件系统(8 个内置插件)
在 djangoblog/settings.py 的 INSTALLED_APPS 中可以看到它们全部已注册。
三、请求如何流转
入口在 djangoblog/urls.py,用 i18n_patterns 包裹(支持多语言 URL),路由分发如下:
| 路径 | 指向 |
|---|---|
/admin/ |
自定义 admin 后台(admin_site.py) |
/ |
blog.urls(首页、文章详情、分类、标签、归档) |
/ |
comments.urls / accounts.urls / oauth.urls |
/search |
haystack 的 EsSearchView(全文搜索) |
/sitemap.xml /feed/ /rss/ |
SEO 与 RSS 订阅 |
/mdeditor/ |
Markdown 编辑器上传图片等 |
/health/ |
健康检查接口 |
四、数据模型(blog/models.py)
核心模型继承自抽象基类 BaseModel,自带 id、creation_time、last_modify_time,并在 save() 里自动生成 slug(用 uuslug)。
Article(文章/页面)—— 核心模型,字段含标题、Markdown 正文、发布状态(d草稿 /p发布)、评论开关、类型(文章/独立页面)、浏览量、作者、分类、标签、排序、是否显示目录(TOC)。Category/Tag/BlogSettings(全局设置)等配套模型。
五、三个关键机制
理解了这三点,就理解了整个项目。
① 插件系统(项目灵魂)
通过钩子(hooks)实现解耦扩展。每个插件继承 BasePlugin,在 register_hooks() 里把自己挂到某个钩子上:
# plugins/view_count/plugin.py
class ViewCountPlugin(BasePlugin):
def register_hooks(self):
hooks.register('after_article_body_get', self.record_view) # 挂到钩子
def record_view(self, article, *args, **kwargs):
article.viewed() # 文章被读取时 +1 浏览量
内置 8 个插件:
| 插件 | 功能 |
|---|---|
view_count |
文章浏览计数统计 |
seo_optimizer |
SEO 优化增强 |
article_copyright |
文章版权声明(现代化样式) |
article_recommendation |
智能文章推荐(响应式卡片布局) |
external_links |
外部链接处理(自动添加图标) |
image_lazy_loading |
图片懒加载优化(淡入动画) |
reading_time |
文章阅读时间估算 |
cloudflare_cache |
Cloudflare 缓存管理 |
想加功能,新建一个
plugins/xxx/plugin.py即可,无需改动核心代码。
② Django 信号(djangoblog/blog_signals.py)
用信号实现“模型一变动,自动做一堆事”。最核心的是 model_post_save_callback(监听全局 post_save):
- 任何模型保存 → 通知百度/Google 收录(
SpiderNotify) - 评论保存 → 清缓存 + 异步发邮件(
send_comment_email) - 文章保存 → 精细化清除相关缓存(列表页、详情、分类、标签、作者、归档、侧边栏)
- 仅更新浏览量时跳过缓存清理(性能优化的小细节)
还有 send_email_signal(统一邮件发送 + 写入 EmailSendLog 日志)、oauth_user_login_signal(OAuth 登录后抓取头像)。
③ 缓存策略
项目重度依赖 Redis 缓存来扛性能。理解了“模型变动 → 信号 → 精准清缓存”这条链,就看懂了它如何保证“既快又不会读到脏数据”。
六、前端架构
不用传统 SPA,而是 HTMX(HTML over the wire)+ Alpine.js + Tailwind CSS + Vite,源码在 frontend/ 目录。可做到无刷新浏览(局部刷新),同时保持服务端渲染的 SEO 友好。支持深色模式自动切换。
七、本地快速上手
pip install -r requirements.txt # 装依赖
# 改 djangoblog/settings.py 里的 DATABASES 为你的 MySQL
python manage.py migrate # 建表
python manage.py createsuperuser # 建管理员
cd frontend && npm install && npm run build && cd .. # 构建前端
python manage.py create_testdata # (可选)生成测试数据
python manage.py runserver # 启动,访问 http://127.0.0.1:8000
八、源码阅读顺序
djangoblog/settings.py—— 看用了哪些中间件、第三方库blog/models.py—— 理解数据结构djangoblog/urls.py+blog/views.py—— 理解请求流djangoblog/blog_signals.py—— 理解“自动联动”机制plugins/*/plugin.py+djangoblog/plugin_manage/—— 理解插件钩子frontend/—— 理解前端构建
九、在极空间 NAS 上用 Docker 部署
极空间(ZSpace)是基于 Linux 的国产 NAS 系统,内置 Docker(应用中心 → Docker)。其固件较新版本还支持 Compose(项目编排) 功能。下面给出两种方式:图形界面逐容器创建 和 Compose 一键部署。
9.0 部署前必读:极空间的特殊性
| 注意点 | 说明 |
|---|---|
| 80 端口被占用 | 极空间系统自身的 Web 管理界面占用 80/443,Nginx 不能映射到这两个端口,必须改(如 8080)。 |
| 内存机型差异 | Elasticsearch 至少吃 512MB~1GB。2GB 内存机型(Z2 等)强烈建议不开 ES,只跑 MySQL + Redis + Web。 |
| 数据持久化 | 容器删了数据不能丢。所有 volume 要挂到 NAS 的共享目录,方便备份/迁移。 |
| 容器互联 | 容器间用容器名互相访问(db、redis),需放在同一个自定义 bridge 网络里。 |
| 默认密码 | 官方 docker-compose.yml 里的 MySQL 密码是 QQQQwww123!@#,生产环境务必修改。 |
9.1 方式一:用 Compose 一键部署(推荐,固件支持时)
适用于 Z2Pro / Z4Pro / Z423 等较新机型(系统设置里能找到 “Docker → 项目 / Compose”)。
第 1 步:准备目录
在极空间文件管理里,新建一个共享文件夹用来存放所有数据,例如:
/我的文件/DockerData/djangoblog/
├── docker-compose.yml ← 编排文件
├── nginx.conf ← Nginx 配置(从仓库 deploy/nginx.conf 复制)
├── mysql/ ← MySQL 数据
├── logs/ ← 应用日志
├── uploads/ ← 上传的图片
└── collectedstatic/ ← 静态文件
第 2 步:编写适配极空间的 docker-compose.yml
相比官方版本,主要改动:端口避让(nginx → 8080)、volume 全部改成本地目录(便于备份)、密码改掉。
✅ 上述配置已生成为现成文件:
deploy/docker-compose/docker-compose.zspace.yml,可直接拷到极空间使用,无需手敲。下文示例与之等价。
version: '3'
services:
db:
image: mysql:8.0
restart: always
environment:
- MYSQL_DATABASE=djangoblog
- MYSQL_ROOT_PASSWORD=请改成你自己的强密码 # ⚠️ 务必修改
volumes:
- ./mysql:/var/lib/mysql
container_name: db
redis:
restart: always
image: redis:7-alpine
container_name: redis
djangoblog:
image: liangliangyy/djangoblog:latest # 直接用官方已构建镜像,省去本机构建
restart: always
ports:
- "8000:8000"
volumes:
- ./collectedstatic:/code/djangoblog/collectedstatic
- ./logs:/code/djangoblog/logs
- ./uploads:/code/djangoblog/uploads
environment:
- DJANGO_DEBUG=False
- DJANGO_SECRET_KEY=请改成一段随机长字符串 # ⚠️ 务必修改
- DJANGO_MYSQL_DATABASE=djangoblog
- DJANGO_MYSQL_USER=root
- DJANGO_MYSQL_PASSWORD=请改成你自己的强密码 # ⚠️ 与上面 db 一致
- DJANGO_MYSQL_HOST=db
- DJANGO_MYSQL_PORT=3306
- DJANGO_REDIS_URL=redis:6379
depends_on:
- db
- redis
container_name: djangoblog
nginx:
restart: always
image: nginx:latest
ports:
- "8080:80" # ⚠️ 极空间 80 被占用,改用 8080
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./collectedstatic:/code/djangoblog/collectedstatic:ro
depends_on:
- djangoblog
container_name: nginx
💡
nginx.conf从仓库的deploy/nginx.conf复制过来即可,无需改动。
第 3 步:在极空间里启动
- 打开 应用中心 → Docker → 项目(Compose)。
- 名称填
djangoblog,路径选/我的文件/DockerData/djangoblog/。 - 把上面的
docker-compose.yml内容粘贴进去,保存并启动。 - 等待所有容器拉取镜像并启动(首次较慢)。
第 4 步:初始化(建管理员)
容器启动后,entrypoint.sh 会自动执行 migrate / collectstatic / compress / build_index。你只需创建超级管理员:
docker exec -it djangoblog python manage.py createsuperuser
# 或在极空间 Docker 界面里,进入 djangoblog 容器 → 终端,执行上面的命令
第 5 步:访问
- 博客首页:
http://极空间内网IP:8080 - 后台管理:
http://极空间内网IP:8000/admin/(或经 nginx 走 8080)
9.2 方式二:图形界面逐容器创建(老固件 / 不支持 Compose)
如果机型固件没有 Compose 入口,就在 Docker → 镜像 / 容器 里手动建。关键是先建一个自定义网络让容器互通。
- 创建网络:Docker → 网络 → 新建,名称
blog-net,类型bridge。 - 依次创建并运行容器,每个都加入
blog-net网络:
| 容器 | 镜像 | 容器名 | 端口映射 | 目录映射 | 关键环境变量 |
|---|---|---|---|---|---|
| MySQL | mysql:8.0 |
db |
(可不映射) | /我的文件/DockerData/djangoblog/mysql → /var/lib/mysql |
MYSQL_DATABASE=djangoblog、MYSQL_ROOT_PASSWORD=xxx |
| Redis | redis:7-alpine |
redis |
(可不映射) | — | — |
| Web | liangliangyy/djangoblog:latest |
djangoblog |
8000:8000 |
collectedstatic/logs/uploads 三项 |
DJANGO_MYSQL_HOST=db、DJANGO_REDIS_URL=redis:6379 等(见 9.1) |
| Nginx | nginx:latest |
nginx |
8080:80 |
nginx.conf → /etc/nginx/nginx.conf |
— |
- 启动顺序:
redis→db→djangoblog→nginx。 - 同样在
djangoblog容器终端里执行python manage.py createsuperuser。
9.3 配置说明(环境变量)
| 环境变量 | 示例 | 备注 |
|---|---|---|
DJANGO_SECRET_KEY |
随机长字符串 | 务必修改 |
DJANGO_DEBUG |
False |
生产环境保持 False |
DJANGO_MYSQL_HOST |
db |
容器名 |
DJANGO_MYSQL_PORT |
3306 |
|
DJANGO_MYSQL_DATABASE |
djangoblog |
|
DJANGO_MYSQL_USER |
root |
|
DJANGO_MYSQL_PASSWORD |
你的强密码 | 与 MySQL 容器一致 |
DJANGO_REDIS_URL |
redis:6379 |
容器名 |
DJANGO_ELASTICSEARCH_HOST |
es:9200 |
仅启用 ES 时需要 |
DJANGO_EMAIL_HOST / PORT / USER / PASSWORD |
见 docs/docker.md |
邮件(评论提醒等) |
9.4 极空间专属优化建议
- 外网访问:极空间自带远程访问(DDNS),不建议把 8080 直接暴露公网;优先用极空间的远程功能或自建反向代理 + HTTPS。
- 定时备份:用极空间的“定时备份”对
DockerData/djangoblog/整个目录做快照,MySQL 数据、上传图片、配置一并保全。 - 低配机型:不开 Elasticsearch(删掉
docker-compose.es.yml相关服务),搜索退回 Whoosh 即可,省下大量内存。 - 资源限制:在容器设置里给 MySQL、ES 限制 CPU/内存,避免拖慢 NAS 系统服务。
- 日志:
./logs已挂出,可在文件管理里直接查看应用日志排查问题。
9.5 常见问题排查
| 现象 | 原因 / 解决 |
|---|---|
| 容器起不来,提示端口占用 | 检查 8080 / 8000 是否被其他容器或极空间服务占用,换端口。 |
| Web 连不上数据库 | DJANGO_MYSQL_HOST 必须等于 MySQL 容器名 db,且两者在同一网络。 |
| 页面打开 502 | Web 容器还没初始化完(首次 migrate 较慢),稍等 1~2 分钟刷新;或看 ./logs。 |
| 静态资源 404 | 确认 collectedstatic 目录挂载正确,且 nginx 容器以只读方式挂了同一目录。 |
| 内存吃满 / NAS 卡顿 | 关掉 Elasticsearch,或给容器加内存上限。 |
9.6 配置 HTTPS 反向代理
想用域名 + HTTPS 访问博客(如
https://blog.example.com),需要解决两件事:① 证书从哪来;② Django 如何正确识别 HTTPS。
⚠️ 必须知道的坑
djangoblog/settings.py 里有两处写死、且不读环境变量的配置:
CSRF_TRUSTED_ORIGINS = ['http://example.com'] # 写死,且只是 http
# 没有 SECURE_PROXY_SSL_HEADER
后果:走 HTTPS 反代后,后台登录、发文章等所有 POST 请求都会被 Django 以 CSRF 校验拒绝(403),因为可信来源是 http://example.com,和你的 https://你的域名 对不上。
所以无论用哪种 HTTPS 方案,都必须让 Django 知道你的真实来源域名。下面给出三种方案,按省事程度排序。
方案 A:极空间自带远程访问(零配置,最省事)
极空间系统自带 DDNS + 内置 HTTPS 远程能力,完全不用碰证书。
- 极空间 Web → 系统设置 → 远程访问,开启并绑定你的极空间账号。
- 在路由器/极空间里把
djangoblog容器的8080端口加入转发或内网穿透。 - 用极空间分配的
https://xxx.zidatec.com:端口访问。
局限:域名是极空间分配的子域,且 CSRF 仍可能拦截。若后台 POST 报 403,仍需按方案 C 给 Django 打补丁,把极空间分配的
https://xxx.zidatec.com加入可信来源。
方案 B:Nginx Proxy Manager 统一反代 + 自动证书(推荐)
用一个独立的 Nginx Proxy Manager(NPM)容器统一接管 80/443、自动申请 Let's Encrypt 证书,业务容器只暴露内网端口。
# 追加到 docker-compose.yml 的 services 下(需先把极空间的 80/443 让给它,或映射到别的端口)
npm:
image: jc21/nginx-proxy-manager:latest
restart: always
ports:
- "80:80"
- "443:443"
- "81:81" # NPM 管理界面
volumes:
- ./npm/data:/data
- ./npm/letsencrypt:/etc/letsencrypt
container_name: npm
然后在 NPM 管理界面(http://极空间IP:81,默认 admin@example.com / changeme)里:
- Hosts → 添加
blog.example.com→ 转发到nginx:80(或直接djangoblog:8000)。 - SSL Certificates → 申请 Let's Encrypt 证书(需域名已解析到你的公网 IP)。
- 把 1 中的 Proxy Host 勾选强制 SSL + 使用该证书,并在 Custom Nginx Configuration 里加上:
proxy_set_header X-Forwarded-Proto $scheme;
前提:你的域名已 A 记录到家庭公网 IP,且运营商开放 80/443(国内常被封,需用 CNAME 中转服务)。
方案 C:在现有 nginx 容器里直接加 SSL(自托管证书)
适合已有证书(如自签、阿里云免费证书)的情况。把仓库的 deploy/nginx.conf 改造为监听 443:
server {
listen 443 ssl;
server_name blog.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
root /code/djangoblog/collectedstatic/;
location /static/ {
expires max;
alias /code/djangoblog/collectedstatic/;
}
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; # ⚠️ 关键:让 Django 知道是 https
proxy_set_header Host $http_host;
proxy_pass http://djangoblog:8000;
}
}
# 80 跳转到 443
server {
listen 80;
server_name blog.example.com;
return 301 https://$host$request_uri;
}
并在 docker-compose.yml 的 nginx 服务里挂载证书目录、放行 443:
nginx:
ports:
- "8080:80"
- "8443:443" # 443 也改成 8443,避免和极空间/路由冲突
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- ./ssl:/etc/nginx/ssl:ro # 证书放这里
- ./collectedstatic:/code/djangoblog/collectedstatic:ro
关键:给 Django 打补丁(解决 CSRF 403)
因为 settings.py 写死了 CSRF_TRUSTED_ORIGINS 且不读环境变量,官方镜像直接用会出问题。最干净的做法是基于官方镜像构建一个小补丁镜像,让它读环境变量 + 识别反代 HTTPS(完整文件已生成在仓库根目录的 Dockerfile.https):
# 文件名:Dockerfile.patch,放在 DockerData/djangoblog/ 下
FROM liangliangyy/djangoblog:latest
# ① 让 CSRF 可信来源从环境变量读取(多个域名用逗号分隔)
RUN sed -i "s|^CSRF_TRUSTED_ORIGINS.*|CSRF_TRUSTED_ORIGINS = os.environ.get('DJANGO_CSRF_TRUSTED_ORIGINS', 'https://blog.example.com').split(',')|" \
/code/djangoblog/djangoblog/settings.py
# ② 让 Django 信任反代传来的 X-Forwarded-Proto
RUN grep -q "SECURE_PROXY_SSL_HEADER" /code/djangoblog/djangoblog/settings.py || \
echo "SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')" \
>> /code/djangoblog/djangoblog/settings.py
然后在 docker-compose.yml 里把 image: 改为 build::
djangoblog:
build:
context: .
dockerfile: Dockerfile.patch
environment:
- DJANGO_CSRF_TRUSTED_ORIGINS=https://blog.example.com # ⚠️ 改成你的真实域名
# ...其余环境变量同 9.1
重新
docker-compose up -d --build生效。之后登录后台、发文章就不会再报 CSRF 403。
9.7 从旧环境迁移数据
场景:从旧的裸机部署、其他服务器、或另一台 NAS,把博客数据搬到极空间这个 Docker 部署上。
需要迁移的内容
| 内容 | 位置(新容器内) | 对应极空间挂载目录 |
|---|---|---|
| 数据库 | MySQL djangoblog 库 |
./mysql(容器卷,直接 dump 即可) |
| 上传图片 | /code/djangoblog/uploads |
./uploads |
| 静态文件 | /code/djangoblog/collectedstatic |
无需迁移(构建产物,启动自动生成) |
配置项(站点标题、侧边栏等)都存在数据库的
blog_blogsettings表里,跟着库一起走。
第 1 步:在旧环境导出
# 1) 导出 MySQL 数据(旧服务器上执行)
mysqldump -u root -p --single-transaction --default-character-set=utf8mb4 \
djangoblog > djangoblog.sql
# 2) 打包上传的图片
tar czf uploads.tar.gz uploads/
把 djangoblog.sql 和 uploads.tar.gz 拷到极空间,例如放到 DockerData/djangoblog/migrate/。
第 2 步:在新极空间部署里导入
先按 9.1 正常启动一次(让容器自动 migrate 建好表结构、跑起来),再导入数据覆盖:
# 1) 导入数据库
docker exec -i db mysql -u root -p你的密码 djangoblog < ./migrate/djangoblog.sql
# 2) 解压图片到挂载目录(注意权限,容器内是 root)
cd DockerData/djangoblog
tar xzf migrate/uploads.tar.gz # 解出 uploads/ 目录,会覆盖到挂载的 ./uploads
# 3) 重建搜索索引(如果用 Whoosh / ES)
docker exec -it djangoblog python manage.py build_index
# 4) 重启应用容器,清掉旧缓存
docker restart djangoblog
第 3 步:验证与注意事项
| 检查项 | 做法 |
|---|---|
| 文章 / 分类是否齐全 | 打开首页与后台,逐一核对 |
| 图片能否显示 | 看文章里的图片是否 200;若 404 多是 uploads/ 没挂对或权限不对 |
| 缓存脏数据 | docker exec -it djangoblog python manage.py clear_cache |
迁移注意事项:
- 先 migrate 再导数据:让新版本的表结构先生成,再覆盖数据;若反向(先倒数据后 migrate),跨大版本时可能因 migration 缺失而报错。
- 跨大版本升级(如从很老的 DjangoBlog):建议先在新环境
migrate完成升级,再导入老数据,然后再次migrate让 Django 把老数据结构补齐到新版本。 - 数据库引擎别混:旧的若是 SQLite,新的是 MySQL,不能直接拷文件,需用
dumpdata/loaddata走 Django 的 JSON fixture:# 旧库 python manage.py dumpdata --natural-foreign --natural-primary > data.json # 新容器 docker exec -i djangoblog python manage.py loaddata < /path/to/data.json - SECRET_KEY 尽量保持一致:跨实例迁移时,若旧站有用到基于 SECRET_KEY 的签名(如密码重置 token),保持
DJANGO_SECRET_KEY不变可避免失效。
浙公网安备 33010602011771号