DjangoBlog 项目理解与部署指南

DjangoBlog 项目理解与部署指南

项目地址:https://github.com/liangliangyy/DjangoBlog
本文档帮助你快速理解 DjangoBlog 的整体架构、核心模块、关键机制,并附上极空间(ZSpace)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

八、源码阅读顺序

  1. djangoblog/settings.py —— 看用了哪些中间件、第三方库
  2. blog/models.py —— 理解数据结构
  3. djangoblog/urls.py + blog/views.py —— 理解请求流
  4. djangoblog/blog_signals.py —— 理解“自动联动”机制
  5. plugins/*/plugin.py + djangoblog/plugin_manage/ —— 理解插件钩子
  6. 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 步:在极空间里启动

  1. 打开 应用中心 → Docker → 项目(Compose)。
  2. 名称填 djangoblog,路径选 /我的文件/DockerData/djangoblog/。
  3. 把上面的 docker-compose.yml 内容粘贴进去,保存并启动。
  4. 等待所有容器拉取镜像并启动(首次较慢)。

第 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 → 镜像 / 容器 里手动建。关键是先建一个自定义网络让容器互通。

  1. 创建网络:Docker → 网络 → 新建,名称 blog-net,类型 bridge。
  2. 依次创建并运行容器,每个都加入 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 —
  1. 启动顺序:redis → db → djangoblog → nginx。
  2. 同样在 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 远程能力,完全不用碰证书。

  1. 极空间 Web → 系统设置 → 远程访问,开启并绑定你的极空间账号。
  2. 在路由器/极空间里把 djangoblog 容器的 8080 端口加入转发或内网穿透。
  3. 用极空间分配的 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)里:

  1. Hosts → 添加 blog.example.com → 转发到 nginx:80(或直接 djangoblog:8000)。
  2. SSL Certificates → 申请 Let's Encrypt 证书(需域名已解析到你的公网 IP)。
  3. 把 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 不变可避免失效。
posted on 2026-06-14 19:55  Al_bert  阅读(23)  评论(0)    收藏  举报