feat: add async image task API

This commit is contained in:
QiuSW
2026-07-08 22:08:48 +08:00
parent c98f713762
commit 25a4080177
26 changed files with 1531 additions and 48 deletions
+78 -1
View File
@@ -11,9 +11,11 @@
-> 宝塔 / Nginx
/static/ -> STATIC_ROOT
/media/ -> MEDIA_ROOT(MVP 本地媒体;后续可换对象存储)
/api/v1/generate/* -> Gunicorn 长请求池(图片同步,超时更长)
/api/v1/generate/image/tasks* -> Gunicorn 普通请求池(异步提交/轮询,短请求)
/api/v1/generate/* -> Gunicorn 长请求池(旧同步生成,超时更长)
其他路径 -> Gunicorn 普通请求池(用户端 / admin / 余额 / 充值)
-> cmhub Django 单体
-> cmhub image-task worker(management command,后台处理异步生图)
-> MySQL 8.4
-> Django shared cache(MVP 可用 MySQL DatabaseCache;高并发换 Redis/Memcached)
```
@@ -112,6 +114,12 @@ STATIC_URL=/static/
STATIC_ROOT=/www/wwwroot/cmhub/staticfiles
MEDIA_ROOT=/www/wwwroot/cmhub/media
MEDIA_URL=/media/
PUBLIC_BASE_URL=https://cmhub.example.com
MEDIA_PUBLIC_BASE_URL=https://cmhub.example.com
IMAGE_TASK_RETENTION_HOURS=24
GENERATED_IMAGE_RETENTION_HOURS=72
IMAGE_TASK_REAPER_INTERVAL_SECONDS=60
IMAGE_TASK_LEASE_SECONDS=600
DJANGO_CACHE_BACKEND=django.core.cache.backends.db.DatabaseCache
DJANGO_CACHE_LOCATION=cmhub_cache
@@ -136,6 +144,7 @@ python3.12 -c "from cryptography.fernet import Fernet; print(Fernet.generate_key
- 当前注册策略为免邮箱验证,注册登录不依赖邮件服务;T-608 后注册成功会赠送 100 点,生产必须保留或收紧 `ACCOUNT_SIGNUP_RATE_LIMIT`,并使用共享 Django cache 承载限流计数;若后续启用密码找回、通知或恢复邮箱验证,再把 `DJANGO_EMAIL_BACKEND` 改为真实 SMTP / 邮件服务并配置 `DJANGO_DEFAULT_FROM_EMAIL`。
- `PAYMENT_CALLBACK_MODE=sdk` 必须配齐微信 / 支付宝商户配置;未配齐时先保持 `mock`。
- 宝塔 / Nginx 已强制 HTTPS 时,`DJANGO_SECURE_SSL_REDIRECT=false` 即可;全站 HTTPS 稳定后再把 `DJANGO_SECURE_HSTS_SECONDS` 调大,避免 HSTS 误锁域名。
- T-614 异步生图 worker 没有 request 对象,生产必须配置 `MEDIA_PUBLIC_BASE_URL` 或 `PUBLIC_BASE_URL` 为公开 HTTPS 域名,否则异步轮询成功时可能返回相对 `/media/...` URL。
## 五、初始化数据库与静态文件
@@ -262,6 +271,42 @@ gunicorn config.wsgi:application \
systemd 单元可分别命名为 `cmhub-web.service` 和 `cmhub-generate.service`。服务的 `WorkingDirectory` 指向 `/www/wwwroot/cmhub`,`ExecStart` 使用上面的两条 Gunicorn 命令,环境变量由项目根目录 `.env` 在 Django settings 中读取。
异步生图 worker 示例:
```bash
python3.12 manage.py run_image_tasks \
--worker-id cmhub-image-worker-1 \
--sleep-seconds 1
```
建议单独托管为 `cmhub-image-worker.service`。该 worker 从数据库 `image_generation_task` 表抢 `queued` 任务,使用 MySQL `select_for_update(skip_locked)` 标记 `running`,执行成功后写 `succeeded` 和稳定 `result_url`;失败或上游超时会调用计费层退点并写 `failed`。worker 循环会按 `IMAGE_TASK_REAPER_INTERVAL_SECONDS` 扫描租约或心跳过期的 `running` 任务,默认判失败并幂等退点,不默认重排队。
systemd 单元示例:
```ini
[Unit]
Description=cmhub image task worker
After=network.target mysql.service
[Service]
Type=simple
WorkingDirectory=/www/wwwroot/cmhub
ExecStart=/usr/bin/python3.12 manage.py run_image_tasks --worker-id cmhub-image-worker-1 --sleep-seconds 1
Restart=always
RestartSec=5
User=www
Group=www
[Install]
WantedBy=multi-user.target
```
部署异步生图时至少需要同时运行:
- `cmhub-web.service`:用户端、admin、余额、模型目录、异步提交 / 轮询等短请求。
- `cmhub-generate.service`:旧同步生成接口,保留给老客户端。
- `cmhub-image-worker.service`:新异步任务真正调上游生成图片。
## 八、宝塔 / Nginx 配置
宝塔中新建站点并绑定域名和 SSL 后,在站点 Nginx 配置中加入或调整:
@@ -285,6 +330,28 @@ server {
add_header Cache-Control "public";
}
location = /api/v1/generate/image/tasks {
proxy_pass http://127.0.0.1:8001;
proxy_set_header Host $host;
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 https;
proxy_connect_timeout 30s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
location ^~ /api/v1/generate/image/tasks/ {
proxy_pass http://127.0.0.1:8001;
proxy_set_header Host $host;
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 https;
proxy_connect_timeout 30s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
location /api/v1/generate/ {
proxy_pass http://127.0.0.1:8002;
proxy_set_header Host $host;
@@ -313,6 +380,7 @@ server {
- `/static/` 必须指向 `STATIC_ROOT`,否则 Bootstrap/qrcode 本地资源在 `DEBUG=False` 下 404。
- `/media/` 是 MVP 本地图片结果访问路径;如果改对象存储,应同步更新 `MEDIA_URL` 和存储配置。
- `/api/v1/generate/image/tasks` 与 `/api/v1/generate/image/tasks/{task_id}` 必须写在 `/api/v1/generate/` 前面,确保异步提交 / 轮询走普通 web 池;旧同步生成接口继续走长请求池。
- 宝塔若已在外层配置 HTTP 到 HTTPS 跳转,`DJANGO_SECURE_SSL_REDIRECT=false` 即可;如果由 Django 负责跳转,再设为 `true`。
## 九、上线前验证
@@ -335,6 +403,13 @@ python3.12 manage.py test apps.api --noinput --keepdb --verbosity 2
python3.12 manage.py test apps.portal --noinput --keepdb --verbosity 2
```
异步生图 worker 单次验证:
```bash
python3.12 manage.py run_image_tasks --once --worker-id smoke-worker
python3.12 manage.py run_image_tasks --reap-only
```
AI smoke:
```bash
@@ -362,6 +437,8 @@ python3.12 manage.py smoke_ai_generation image
- HTTPS 已由宝塔 / Nginx 强制跳转;如由 Django 强制跳转则设置 `DJANGO_SECURE_SSL_REDIRECT=true`。HSTS 只在确认全站 HTTPS 后启用。
- `STATIC_ROOT` 已 `collectstatic`,Nginx 可访问 `/static/portal/vendor/bootstrap/bootstrap.min.css` 和 `/static/portal/vendor/qrcode/qrcode.js`。
- `MEDIA_ROOT` 或对象存储可访问生成图片 URL。
- `MEDIA_PUBLIC_BASE_URL` / `PUBLIC_BASE_URL` 已配置为客户端可访问的 HTTPS 域名;异步生图成功返回的 `result.image_url` 可公网下载。
- `cmhub-image-worker.service` 已启动并设置开机自启;`journalctl -u cmhub-image-worker.service` 无持续异常。
- `DJANGO_CACHE_BACKEND` 为共享 cache,不是 `LocMemCache`。
- MySQL 是 8.4 / InnoDB / `utf8mb4`。
- 真实支付 SDK 与商户配置齐全后,`PAYMENT_CALLBACK_MODE=sdk`。