合并应用宝 Worker 容器

This commit is contained in:
yml2213
2026-08-12 17:59:26 +08:00
parent cebbd94ae8
commit 1a20c367db
15 changed files with 155 additions and 110 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ logs/
*.db *.db
*.log *.log
# 前端依赖和构建产物(Docker 内重新构建) # Node 依赖和前端构建产物(Docker 内重新构建)
web/frontend/node_modules/ **/node_modules/
web/frontend/dist/ web/frontend/dist/
# geetest 临时图片 # geetest 临时图片
+7 -7
View File
@@ -18,12 +18,12 @@ ADMIN_PASSWORD=admin123
# Docker 对外端口(容器内仍监听 8800;服务器当前使用 8000) # Docker 对外端口(容器内仍监听 8800;服务器当前使用 8000)
APP_PORT=8000 APP_PORT=8000
# Docker 构建镜像源(服务器保持 true;本地测试可改为 false # Docker 构建镜像源(网络可访问官方源时保持 false;国内服务器可改为 true
USE_CHINA_MIRRORS=true USE_CHINA_MIRRORS=false
# MySQL 配置(生产环境必须设置密码;默认只映射到宿主机 127.0.0.1,供本地 dev.sh 使用) # MySQL 配置(生产环境必须设置密码;默认只映射到宿主机 127.0.0.1,供本地 dev.sh 使用)
# 服务器部署默认使用国内镜像;./dev.sh 默认临时改用 mysql:8.4,避免本地测试走国内镜像 # 默认使用官方镜像;国内服务器可按需覆盖为可用镜像加速地址
# MYSQL_IMAGE=docker.m.daocloud.io/library/mysql:8.4 # MYSQL_IMAGE=mysql:8.4
MYSQL_DATABASE=douyu_login MYSQL_DATABASE=douyu_login
MYSQL_USER=douyu_login MYSQL_USER=douyu_login
MYSQL_PASSWORD= MYSQL_PASSWORD=
@@ -60,11 +60,11 @@ COOKIE_SECURE=false
# CORS 允许的源(逗号分隔,不设则默认开发环境) # CORS 允许的源(逗号分隔,不设则默认开发环境)
# CORS_ORIGINS=https://example.com,https://www.example.com # CORS_ORIGINS=https://example.com,https://www.example.com
# 应用宝充值 WorkerDocker Compose 内网服务;生产环境必须设置为随机长密钥) # 应用宝充值 Worker与 Web 合并在同一容器;生产环境必须设置为随机长密钥)
# 生成方式: python3 -c "import secrets; print(secrets.token_urlsafe(32))" # 生成方式: python3 -c "import secrets; print(secrets.token_urlsafe(32))"
YYB_WORKER_KEY= YYB_WORKER_KEY=
# Docker 部署使用默认值;dev.sh 自动覆盖为 http://127.0.0.1:${YYB_WORKER_PORT:-8810} # Docker 与 dev.sh 均通过本机 Worker 访问,无需设置 YYB_WORKER_URL。
# YYB_WORKER_URL=http://yyb-worker:8810 # YYB_WORKER_URL=http://127.0.0.1:8810
# YYB_WORKER_TIMEOUT=30 # YYB_WORKER_TIMEOUT=30
# 本地调试 Worker(仅 dev.sh 使用,默认只绑定 127.0.0.1 # 本地调试 Worker(仅 dev.sh 使用,默认只绑定 127.0.0.1
# YYB_WORKER_PORT=8810 # YYB_WORKER_PORT=8810
+2 -2
View File
@@ -20,8 +20,8 @@ bg.jpg
fullbg.jpg fullbg.jpg
slice.jpg slice.jpg
# 前端 # Node 依赖
web/frontend/node_modules/ **/node_modules/
web/frontend/dist/ web/frontend/dist/
data/web.db data/web.db
.DS_Store .DS_Store
+23 -6
View File
@@ -10,7 +10,17 @@ RUN npm config set registry https://registry.npmmirror.com && \
COPY web/frontend/ ./ COPY web/frontend/ ./
RUN npm run build RUN npm run build
# ===== 阶段2: Python 运行时 ===== # ===== 阶段2: 构建应用宝 Worker 的 Node 依赖 =====
FROM node:20-slim AS yyb-node-builder
WORKDIR /build
COPY services/yyb-worker/package.json services/yyb-worker/package-lock.json ./
# 优先 npmmirror,失败时回退官方 registry,避免国内构建时依赖下载中断。
RUN npm config set registry https://registry.npmmirror.com && \
npm ci --omit=dev || \
(npm config set registry https://registry.npmjs.org && npm ci --omit=dev)
# ===== 阶段3: Python 运行时 =====
FROM python:3.12-slim AS runtime FROM python:3.12-slim AS runtime
ARG USE_CHINA_MIRRORS=true ARG USE_CHINA_MIRRORS=true
@@ -61,11 +71,17 @@ COPY utils/ ./utils/
COPY web/backend/ ./web/backend/ COPY web/backend/ ./web/backend/
COPY scripts/ ./scripts/ COPY scripts/ ./scripts/
# 将应用宝 Worker 合并进同一应用镜像;其 Python 依赖已纳入项目 uv.lock。
COPY services/yyb-worker/runtime/ ./services/yyb-worker/runtime/
COPY --from=yyb-node-builder /build/node_modules ./services/yyb-worker/runtime/node_modules
COPY docker-entrypoint.sh ./docker-entrypoint.sh
# 复制前端构建产物 # 复制前端构建产物
COPY --from=frontend-builder /build/dist ./web/frontend/dist COPY --from=frontend-builder /build/dist ./web/frontend/dist
# 创建数据和日志目录 # 创建数据和日志目录
RUN mkdir -p /app/data /app/logs RUN mkdir -p /app/data /app/logs /app/data/yyb-worker-jobs && \
chmod +x /app/docker-entrypoint.sh
# 环境变量 # 环境变量
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
@@ -73,12 +89,13 @@ ENV PYTHONDONTWRITEBYTECODE=1
ENV PATH="/app/.venv/bin:$PATH" ENV PATH="/app/.venv/bin:$PATH"
# 生产镜像默认关闭文件监控重载,避免 data/logs 写入触发重启循环。 # 生产镜像默认关闭文件监控重载,避免 data/logs 写入触发重启循环。
ENV UVICORN_RELOAD=false ENV UVICORN_RELOAD=false
ENV YYB_WORKER_URL=http://127.0.0.1:8810
EXPOSE 8800 EXPOSE 8800
# 健康检查 # 健康检查同时覆盖 Web API 与同容器 Worker。
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8800/api/health')" || exit 1 CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8800/api/health'); urllib.request.urlopen('http://127.0.0.1:8810/health')" || exit 1
# 启动命令 # 同一容器内启动 Web 与本机 Worker;入口脚本负责联动退出和信号转发。
CMD ["python", "-m", "uvicorn", "web.backend.main:app", "--host", "0.0.0.0", "--port", "8800", "--workers", "1"] ENTRYPOINT ["/app/docker-entrypoint.sh"]
+3 -4
View File
@@ -50,7 +50,7 @@ douyu_login_py/
### 应用宝充值 Worker ### 应用宝充值 Worker
应用宝充值使用同仓库内独立的 `yyb-worker` Docker 服务,后台不直接运行逆向脚本。两个服务使用 Compose 默认内网通信,不共享目录、Cookie 或数据库 应用宝充值 Worker 已合并到 Web 应用容器中,内部仅监听 `127.0.0.1:8810`,不发布额外端口。任务数据与应用数据一起保存在 `data/yyb-worker-jobs/`
```bash ```bash
cd /opt/douyu_login_py cd /opt/douyu_login_py
@@ -60,13 +60,12 @@ python3 -c 'import secrets; print(secrets.token_urlsafe(32))'
将生成的值写入项目 `.env` 将生成的值写入项目 `.env`
```dotenv ```dotenv
YYB_WORKER_URL=http://yyb-worker:8810
YYB_WORKER_KEY=随机密钥 YYB_WORKER_KEY=随机密钥
``` ```
随后执行 `./deploy.sh`,它会同时构建并启动后台、MySQL 和 Worker。Worker 不发布端口;扫码图片和支付二维码只经后台授权接口返回。Worker 的任务文件保存在独立 Docker volume `yyb-worker-data`服务器只需要此仓库,不需要部署 `js_reverse` 随后执行 `./deploy.sh`,它会构建并启动应用与 MySQL。Worker 不发布端口;扫码图片和支付二维码只经后台授权接口返回。服务器只需要此仓库,不需要部署 `js_reverse`
本机热更新调试使用 `./dev.sh`(或 `./deploy.sh dev`)。它会构建并启动一个仅绑定 `127.0.0.1:8810` 的开发 Worker,后端自动使用该地址;Worker 动态数据写入 `data/yyb-worker-dev/`,按 `Ctrl+C` 会停止本次启动的容器 本机热更新调试使用 `./dev.sh`(或 `./deploy.sh dev`)。它会直接启动一个仅绑定 `127.0.0.1:8810` 的开发 Worker,后端自动使用该地址;Worker 动态数据写入 `data/yyb-worker-dev/`,按 `Ctrl+C`一并停止。
**常用命令:** **常用命令:**
+4 -1
View File
@@ -110,7 +110,7 @@ cmd_reset() {
echo "" echo ""
echo " 将删除以下内容:" echo " 将删除以下内容:"
echo " • MySQL 数据卷" echo " • MySQL 数据卷"
echo " • 应用宝 Worker 任务数据(扫码会话、二维码和订单记录)" echo " • 应用宝 Worker 任务数据(扫码会话、二维码和订单记录)"
echo " • 历史Cookie (data/cookies/)" echo " • 历史Cookie (data/cookies/)"
echo " • 应用日志 (logs/)" echo " • 应用日志 (logs/)"
echo " • Docker容器和镜像" echo " • Docker容器和镜像"
@@ -138,6 +138,9 @@ cmd_reset() {
echo " 删除Cookie..." echo " 删除Cookie..."
rm -rf data/cookies/ rm -rf data/cookies/
echo " 删除应用宝 Worker 任务..."
rm -rf data/yyb-worker-jobs/ data/yyb-worker-dev/
echo " 删除日志..." echo " 删除日志..."
rm -rf logs/*.log rm -rf logs/*.log
+16 -33
View File
@@ -36,10 +36,9 @@ DB_MAX_OVERFLOW="${DB_MAX_OVERFLOW:-20}"
DB_POOL_TIMEOUT="${DB_POOL_TIMEOUT:-30}" DB_POOL_TIMEOUT="${DB_POOL_TIMEOUT:-30}"
DB_POOL_RECYCLE="${DB_POOL_RECYCLE:-1800}" DB_POOL_RECYCLE="${DB_POOL_RECYCLE:-1800}"
YYB_WORKER_PORT="${YYB_WORKER_PORT:-8810}" YYB_WORKER_PORT="${YYB_WORKER_PORT:-8810}"
YYB_WORKER_IMAGE="${YYB_WORKER_IMAGE:-douyu-yyb-worker:local}"
DEV_YYB_WORKER_URL="${DEV_YYB_WORKER_URL:-http://127.0.0.1:${YYB_WORKER_PORT}}" DEV_YYB_WORKER_URL="${DEV_YYB_WORKER_URL:-http://127.0.0.1:${YYB_WORKER_PORT}}"
YYB_WORKER_CONTAINER="${YYB_WORKER_CONTAINER:-douyu-yyb-worker-dev}"
YYB_WORKER_DATA_DIR="${YYB_WORKER_DATA_DIR:-$ROOT_DIR/data/yyb-worker-dev}" YYB_WORKER_DATA_DIR="${YYB_WORKER_DATA_DIR:-$ROOT_DIR/data/yyb-worker-dev}"
YYB_WORKER_NODE_MODULES="$ROOT_DIR/services/yyb-worker/node_modules"
LOG_DIR="$ROOT_DIR/logs" LOG_DIR="$ROOT_DIR/logs"
LOG_DATE="$(date +%Y-%m-%d)" LOG_DATE="$(date +%Y-%m-%d)"
BACKEND_LOG="$LOG_DIR/backend-$LOG_DATE.log" BACKEND_LOG="$LOG_DIR/backend-$LOG_DATE.log"
@@ -48,8 +47,7 @@ WORKER_LOG="$LOG_DIR/yyb-worker-$LOG_DATE.log"
BACKEND_PID="" BACKEND_PID=""
FRONTEND_PID="" FRONTEND_PID=""
WORKER_LOG_PID="" WORKER_PID=""
WORKER_STARTED_BY_DEV=false
stop_tree() { stop_tree() {
local pid="$1" local pid="$1"
@@ -68,10 +66,7 @@ cleanup() {
trap - EXIT INT TERM trap - EXIT INT TERM
stop_tree "$FRONTEND_PID" stop_tree "$FRONTEND_PID"
stop_tree "$BACKEND_PID" stop_tree "$BACKEND_PID"
stop_tree "$WORKER_LOG_PID" stop_tree "$WORKER_PID"
if [ "$WORKER_STARTED_BY_DEV" = true ]; then
docker stop "$YYB_WORKER_CONTAINER" >/dev/null 2>&1 || true
fi
} }
require_cmd() { require_cmd() {
@@ -143,26 +138,21 @@ mkdir -p "$YYB_WORKER_DATA_DIR"
trap cleanup EXIT INT TERM trap cleanup EXIT INT TERM
start_yyb_worker() { start_yyb_worker() {
if docker ps --format '{{.Names}}' | grep -qx "$YYB_WORKER_CONTAINER"; then if [ ! -d "$YYB_WORKER_NODE_MODULES/jsdom" ]; then
echo "本地 YYB Worker 已运行: ${DEV_YYB_WORKER_URL}" echo "正在安装本地 YYB Worker Node 依赖..."
return (cd "$ROOT_DIR/services/yyb-worker" && npm ci)
fi
if docker ps -a --format '{{.Names}}' | grep -qx "$YYB_WORKER_CONTAINER"; then
echo "发现已停止的本地 YYB Worker,正在清理旧容器..."
docker rm "$YYB_WORKER_CONTAINER" >/dev/null
fi fi
echo "正在构建本地 YYB Worker..."
YYB_WORKER_KEY="$YYB_WORKER_KEY" $COMPOSE build yyb-worker
echo "正在启动本地 YYB Worker..." echo "正在启动本地 YYB Worker..."
docker run -d --rm \ (
--name "$YYB_WORKER_CONTAINER" \ cd "$ROOT_DIR/services/yyb-worker/runtime"
-p "127.0.0.1:${YYB_WORKER_PORT}:8810" \ YYB_WORKER_KEY="$YYB_WORKER_KEY" \
-e TZ="${TZ:-Asia/Shanghai}" \ uv run --project "$ROOT_DIR" python scripts/yyb-worker.py \
-e YYB_WORKER_KEY="$YYB_WORKER_KEY" \ --host 127.0.0.1 \
-v "$YYB_WORKER_DATA_DIR:/app/config/worker-jobs" \ --port "$YYB_WORKER_PORT" \
"$YYB_WORKER_IMAGE" >/dev/null --data-dir "$YYB_WORKER_DATA_DIR" 2>&1 | tee -a "$WORKER_LOG"
WORKER_STARTED_BY_DEV=true ) &
WORKER_PID=$!
echo "等待本地 YYB Worker 就绪..." echo "等待本地 YYB Worker 就绪..."
for i in $(seq 1 15); do for i in $(seq 1 15); do
@@ -172,21 +162,14 @@ start_yyb_worker() {
fi fi
if [ "$i" = "15" ]; then if [ "$i" = "15" ]; then
echo "YYB Worker 启动超时,查看日志:" echo "YYB Worker 启动超时,查看日志:"
docker logs "$YYB_WORKER_CONTAINER" || true tail -n 100 "$WORKER_LOG" 2>/dev/null || true
exit 1 exit 1
fi fi
sleep 1 sleep 1
done done
} }
stream_yyb_worker_logs() {
# docker logs 默认只输出到终端;持续转存后保留 Worker 的异常和协议脚本输出。
(docker logs --tail 100 --follow "$YYB_WORKER_CONTAINER" 2>&1 | tee -a "$WORKER_LOG") &
WORKER_LOG_PID=$!
}
start_yyb_worker start_yyb_worker
stream_yyb_worker_logs
echo "正在启动本地 MySQL..." echo "正在启动本地 MySQL..."
MYSQL_IMAGE="$MYSQL_IMAGE" \ MYSQL_IMAGE="$MYSQL_IMAGE" \
+4 -26
View File
@@ -37,8 +37,8 @@ services:
- COOKIE_SECURE=${COOKIE_SECURE:-false} - COOKIE_SECURE=${COOKIE_SECURE:-false}
# CORS 允许的源(逗号分隔) # CORS 允许的源(逗号分隔)
- CORS_ORIGINS=${CORS_ORIGINS:-} - CORS_ORIGINS=${CORS_ORIGINS:-}
# 独立 yyb-worker 容器,通过 Compose 默认内网访问。 # Worker 已合并到本容器,通过回环地址访问。
- YYB_WORKER_URL=${YYB_WORKER_URL:-http://yyb-worker:8810} - YYB_WORKER_URL=http://127.0.0.1:8810
- YYB_WORKER_KEY=${YYB_WORKER_KEY:-} - YYB_WORKER_KEY=${YYB_WORKER_KEY:-}
# Roundcube 邮件验证码读取服务地址(不填则使用代码默认值) # Roundcube 邮件验证码读取服务地址(不填则使用代码默认值)
- MAIL_ROUNDCUBE_URL=${MAIL_ROUNDCUBE_URL:-} - MAIL_ROUNDCUBE_URL=${MAIL_ROUNDCUBE_URL:-}
@@ -50,21 +50,19 @@ services:
depends_on: depends_on:
mysql: mysql:
condition: service_healthy condition: service_healthy
yyb-worker:
condition: service_healthy
# 增加文件描述符限制,避免 "Too many open files" 错误 # 增加文件描述符限制,避免 "Too many open files" 错误
ulimits: ulimits:
nofile: nofile:
soft: 65536 soft: 65536
hard: 65536 hard: 65536
healthcheck: healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8800/api/health')"] test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8800/api/health'); urllib.request.urlopen('http://127.0.0.1:8810/health')"]
interval: 30s interval: 30s
timeout: 5s timeout: 5s
retries: 3 retries: 3
mysql: mysql:
image: ${MYSQL_IMAGE:-docker.m.daocloud.io/library/mysql:8.4} image: ${MYSQL_IMAGE:-mysql:8.4}
container_name: douyu-login-db container_name: douyu-login-db
restart: unless-stopped restart: unless-stopped
ports: ports:
@@ -88,28 +86,8 @@ services:
retries: 12 retries: 12
start_period: 20s start_period: 20s
yyb-worker:
build:
context: ./services/yyb-worker
image: ${YYB_WORKER_IMAGE:-douyu-yyb-worker:local}
container_name: douyu-yyb-worker
restart: unless-stopped
environment:
- TZ=Asia/Shanghai
- YYB_WORKER_KEY=${YYB_WORKER_KEY:?请在 .env 中设置 YYB_WORKER_KEY}
volumes:
- yyb-worker-data:/app/config/worker-jobs
networks:
- default
healthcheck:
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8810/health')"]
interval: 30s
timeout: 5s
retries: 3
volumes: volumes:
mysql-data: mysql-data:
yyb-worker-data:
networks: networks:
order-site-net: order-site-net:
+47
View File
@@ -0,0 +1,47 @@
#!/usr/bin/env sh
set -eu
# docker compose run 传入命令时直接执行,例如 migrate-mysql 的迁移脚本。
if [ "$#" -gt 0 ]; then
exec "$@"
fi
# Worker 仅监听容器回环地址,Web 后端通过 127.0.0.1 调用。
python /app/services/yyb-worker/runtime/scripts/yyb-worker.py \
--host 127.0.0.1 \
--port 8810 \
--data-dir /app/data/yyb-worker-jobs &
worker_pid=$!
web_pid=""
stop_processes() {
kill -TERM "$worker_pid" 2>/dev/null || true
if [ -n "$web_pid" ]; then
kill -TERM "$web_pid" 2>/dev/null || true
fi
wait "$worker_pid" 2>/dev/null || true
if [ -n "$web_pid" ]; then
wait "$web_pid" 2>/dev/null || true
fi
}
trap stop_processes INT TERM
python -m uvicorn web.backend.main:app --host 0.0.0.0 --port 8800 --workers 1 &
web_pid=$!
while :; do
if ! kill -0 "$worker_pid" 2>/dev/null; then
if wait "$worker_pid"; then exit_code=0; else exit_code=$?; fi
kill -TERM "$web_pid" 2>/dev/null || true
wait "$web_pid" 2>/dev/null || true
exit "$exit_code"
fi
if ! kill -0 "$web_pid" 2>/dev/null; then
if wait "$web_pid"; then exit_code=0; else exit_code=$?; fi
kill -TERM "$worker_pid" 2>/dev/null || true
wait "$worker_pid" 2>/dev/null || true
exit "$exit_code"
fi
sleep 1
done
+2
View File
@@ -24,6 +24,8 @@ dependencies = [
"scipy>=1.13.0", "scipy>=1.13.0",
"pyexecjs>=1.5.1", "pyexecjs>=1.5.1",
"pymysql>=1.1,<2", "pymysql>=1.1,<2",
"curl-cffi>=0.13",
"segno>=1.6",
] ]
[build-system] [build-system]
-18
View File
@@ -1,18 +0,0 @@
FROM node:20-bookworm-slim
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 NODE_ENV=production
RUN apt-get update \
&& apt-get install -y --no-install-recommends python3 python3-pip ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& python3 -m pip install --no-cache-dir --break-system-packages --upgrade pip
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY requirements-worker.txt ./
RUN python3 -m pip install --no-cache-dir --break-system-packages -r requirements-worker.txt
COPY runtime/ ./
RUN mkdir -p /app/config/worker-jobs
EXPOSE 8810
HEALTHCHECK --interval=30s --timeout=5s --retries=3 CMD python3 -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8810/health')"
CMD ["python3", "scripts/yyb-worker.py", "--host", "0.0.0.0", "--port", "8810"]
+4 -5
View File
@@ -1,15 +1,14 @@
# YYB Worker Runtime # YYB Worker Runtime
此目录是部署到 `douyu_login_py` 的最小 YYB 运行时。构建上下文只包含 Worker 启动所需的 Python、Node/jsdom 依赖、协议脚本和静态重放数据。 此目录是 `douyu-login` 主镜像部署的 YYB 运行时代码。构建会将 Worker 所需的 Python、Node/jsdom 依赖、协议脚本和静态重放数据纳入同一镜像
- 源码分析和协议验证工作区仍在 `js_reverse/sites/yyb`;不要将该分析仓库上传到服务器。 - 源码分析和协议验证工作区仍在 `js_reverse/sites/yyb`;不要将该分析仓库上传到服务器。
- 不要向 `runtime/config/` 放入 Cookie、订单响应、二维码或付款链接。每个任务的动态数据仅写入 Docker volume 挂载的 `/app/config/worker-jobs/<job-id>/` - 不要向 `runtime/config/` 放入 Cookie、订单响应、二维码或付款链接。每个任务的动态数据仅写入挂载目录 `/app/data/yyb-worker-jobs/<job-id>/`
- `mall-order-template.json` 仅保留请求结构与非用户常量。登录态、商品、区服、角色、支付平台、动态令牌和加密字段均由当前任务运行时填充。 - `mall-order-template.json` 仅保留请求结构与非用户常量。登录态、商品、区服、角色、支付平台、动态令牌和加密字段均由当前任务运行时填充。
- 本服务没有 Compose 端口映射,只允许 `douyu-login` 通过默认 Compose 内网的 `http://yyb-worker:8810` 访问`YYB_WORKER_KEY` 必须与后台服务配置一致。 - Worker 在应用容器内仅监听 `127.0.0.1:8810`Web 后端通过本机地址访问`YYB_WORKER_KEY` 必须与后台服务配置一致。
本地独立验证: 本地独立验证:
```bash ```bash
docker build -t douyu-yyb-worker:local services/yyb-worker YYB_WORKER_KEY=test-key uv run python runtime/scripts/yyb-worker.py --host 127.0.0.1
docker run --rm -e YYB_WORKER_KEY=test-key douyu-yyb-worker:local
``` ```
@@ -1,3 +0,0 @@
curl_cffi>=0.13
pycryptodome>=3.20
segno>=1.6
@@ -14,6 +14,7 @@ import json
import os import os
import re import re
import subprocess import subprocess
import sys
import threading import threading
import time import time
import uuid import uuid
@@ -96,7 +97,7 @@ def _start_login(job_id: str, provider: str, timeout: int) -> None:
directory = _job_dir(job_id) directory = _job_dir(job_id)
session = directory / "mall-session.json" session = directory / "mall-session.json"
qr = directory / ("qq-login.jpg" if provider == "qq" else "wechat-login.jpg") qr = directory / ("qq-login.jpg" if provider == "qq" else "wechat-login.jpg")
command = ["python3", f"scripts/login-{provider}.py", "--session", str(session), command = [sys.executable, f"scripts/login-{provider}.py", "--session", str(session),
"--qr", str(qr), "--timeout", str(timeout)] "--qr", str(qr), "--timeout", str(timeout)]
with _lock: with _lock:
job["provider"] = provider job["provider"] = provider
@@ -135,7 +136,7 @@ def _start_payment(job_id: str, selection: dict) -> None:
session = directory / "mall-session.json" session = directory / "mall-session.json"
response = directory / "mall-order-response.json" response = directory / "mall-order-response.json"
output = directory / "jsdom-order" output = directory / "jsdom-order"
command = ["python3", "main.py", "mall", "auto", "--session", str(session), command = [sys.executable, "main.py", "mall", "auto", "--session", str(session),
"--order-template", str(ROOT / "config" / "mall-order-template.json"), "--order-template", str(ROOT / "config" / "mall-order-template.json"),
"--quantity", "1", "--product-id", str(selection["product_id"]), "--quantity", "1", "--product-id", str(selection["product_id"]),
"--offer-id", str(selection["offer_id"]), "--offer-id", str(selection["offer_id"]),
@@ -149,7 +150,7 @@ def _start_payment(job_id: str, selection: dict) -> None:
job = _jobs[job_id] job = _jobs[job_id]
if job.get("status") != "success" or not response.exists(): if job.get("status") != "success" or not response.exists():
return return
pay_command = ["python3", "scripts/jsdom-pay.py", "--session", str(session), pay_command = [sys.executable, "scripts/jsdom-pay.py", "--session", str(session),
"--mall-response", str(response), "--out-dir", str(output), "--mall-response", str(response), "--out-dir", str(output),
"--zone-id", str(selection["zone_id"]), "--zone-id", str(selection["zone_id"]),
"--pf", str(selection.get("order_pf", "")), "--pf", str(selection.get("order_pf", "")),
Generated
+37
View File
@@ -200,6 +200,30 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" },
] ]
[[package]]
name = "curl-cffi"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "cffi" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b4/23/d32e113b16dbfb458bea408871ed98dd12f306a366a04215e84537e0af7e/curl_cffi-0.16.0.tar.gz", hash = "sha256:b00b423da8028eb6221e3b63bcd63d681150c07cee8b16000d1f7ea292731895", size = 238344, upload-time = "2026-08-01T13:45:12.372Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fd/fe/0c330de78421af13e6384ab948e3adbcd4c638b06b53b7fff108bf1db121/curl_cffi-0.16.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6128021320f74999ec1216c1817b2c3adcb0f334d204add1ccf18e248bf7efcb", size = 3023503, upload-time = "2026-08-01T13:44:33.452Z" },
{ url = "https://files.pythonhosted.org/packages/2e/49/3b502d0d09e427b1bdec4f7339bb115c971c9b3fdaf355d02ca06e97ad61/curl_cffi-0.16.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:edd5f6e8f122157f4d2351b0b5e48e6a1c0677a2064da71451bb30ef57af19ba", size = 2780341, upload-time = "2026-08-01T13:44:35.131Z" },
{ url = "https://files.pythonhosted.org/packages/2e/b5/b341f96f9fa12b28d1150913a1f9007a09a36757c81b4100373c5bdbf78b/curl_cffi-0.16.0-cp310-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:93615d44f23e56c1256700c2e78de4b879310f39a5621828ea7e2a5ecc04bdda", size = 12824596, upload-time = "2026-08-01T13:44:36.556Z" },
{ url = "https://files.pythonhosted.org/packages/f5/3b/d600b20bff0c55b80b156dc9be0de7c3b3ee2d29977d0a839ea703fab978/curl_cffi-0.16.0-cp310-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:3c31e71bf68a9c02a279a184ec9c0ea7c80ce1ef4f1d35073fae3124eb3a7868", size = 12647842, upload-time = "2026-08-01T13:44:38.814Z" },
{ url = "https://files.pythonhosted.org/packages/e4/45/9208864ec429558efac168088e50f8a91b947f742ee128cff55b88c7b635/curl_cffi-0.16.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:182416f07d71a342240554fa62c22e591999b78c225b21d3fe27d9f807420dd6", size = 13472637, upload-time = "2026-08-01T13:44:40.893Z" },
{ url = "https://files.pythonhosted.org/packages/eb/c8/1639a1d9c8b64d0323330b219b7207a54b14fc54982c30cb08c8cc95aa16/curl_cffi-0.16.0-cp310-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d95c0deccc2184eeee7c2aa18d07de261184b418210995aabd0d29f98717a05c", size = 12828918, upload-time = "2026-08-01T13:44:43.049Z" },
{ url = "https://files.pythonhosted.org/packages/0a/02/bcdf03ea583a445280568c9b163c10668037ee09ece62e78c15846a62df0/curl_cffi-0.16.0-cp310-abi3-manylinux_2_34_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ce1f823bc5ce675291a7cf14781496775dfae9298638c25059f2565f6a58a704", size = 12604731, upload-time = "2026-08-01T13:44:45.239Z" },
{ url = "https://files.pythonhosted.org/packages/17/8b/4ddae52044c537ace13ad7e46dded8e9340a64e0704e320455c5151108a8/curl_cffi-0.16.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e52586a9dc4ed5e75faa39be0f30353b10cdc7410bad276beb013085e974bb44", size = 12576433, upload-time = "2026-08-01T13:44:47.626Z" },
{ url = "https://files.pythonhosted.org/packages/5a/f5/38ee2f039db7832f07ce91f6a3d22d87ebfcfaa541130371ac1faa5caea9/curl_cffi-0.16.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce87f301b31147711c3aebc86fb7e16d8dc48f7e5df272c1bea760c687e28eef", size = 13240699, upload-time = "2026-08-01T13:44:49.727Z" },
{ url = "https://files.pythonhosted.org/packages/ad/03/b9df2973f1119f9d11d8fb3bf2682e5ffe5c52ef3ab89f720473c60fe97e/curl_cffi-0.16.0-cp310-abi3-win_amd64.whl", hash = "sha256:e22a8212d830108e977ff394237f637238e265f5f65037d6c1ee71ea8cc03bcb", size = 1976497, upload-time = "2026-08-01T13:44:51.839Z" },
{ url = "https://files.pythonhosted.org/packages/6b/8b/092beeb5fbe3b7370666708eb5618a9e593ffc80ecb2c97c3395158d270b/curl_cffi-0.16.0-cp310-abi3-win_arm64.whl", hash = "sha256:095fc36e4988736f31521d6fe0aa1f243dba22656b4818fc2fb3b7a547e7a9ba", size = 1711122, upload-time = "2026-08-01T13:44:53.242Z" },
{ url = "https://files.pythonhosted.org/packages/15/ea/81cf3858b256494b31a554cf76bbd345def3ea7e7a1a592cc515633b4e28/curl_cffi-0.16.0-cp313-abi3-android_24_arm64_v8a.whl", hash = "sha256:06b1c7e07af8ff7c4c5ce4086ea89cc582ebff9adff4a37cfffa5f5de5d5b943", size = 8603463, upload-time = "2026-08-01T13:44:55.003Z" },
]
[[package]] [[package]]
name = "douyu-login-py" name = "douyu-login-py"
version = "0.2.0" version = "0.2.0"
@@ -207,6 +231,7 @@ source = { editable = "." }
dependencies = [ dependencies = [
{ name = "alembic" }, { name = "alembic" },
{ name = "bcrypt" }, { name = "bcrypt" },
{ name = "curl-cffi" },
{ name = "fastapi" }, { name = "fastapi" },
{ name = "loguru" }, { name = "loguru" },
{ name = "numpy" }, { name = "numpy" },
@@ -221,6 +246,7 @@ dependencies = [
{ name = "python-multipart" }, { name = "python-multipart" },
{ name = "requests", extra = ["socks"] }, { name = "requests", extra = ["socks"] },
{ name = "scipy" }, { name = "scipy" },
{ name = "segno" },
{ name = "sqlalchemy" }, { name = "sqlalchemy" },
{ name = "uvicorn", extra = ["standard"] }, { name = "uvicorn", extra = ["standard"] },
{ name = "websockets" }, { name = "websockets" },
@@ -230,6 +256,7 @@ dependencies = [
requires-dist = [ requires-dist = [
{ name = "alembic", specifier = ">=1.18.4" }, { name = "alembic", specifier = ">=1.18.4" },
{ name = "bcrypt", specifier = ">=4.0.0" }, { name = "bcrypt", specifier = ">=4.0.0" },
{ name = "curl-cffi", specifier = ">=0.13" },
{ name = "fastapi", specifier = ">=0.110.0" }, { name = "fastapi", specifier = ">=0.110.0" },
{ name = "loguru", specifier = ">=0.7.0" }, { name = "loguru", specifier = ">=0.7.0" },
{ name = "numpy", specifier = ">=1.24.0" }, { name = "numpy", specifier = ">=1.24.0" },
@@ -244,6 +271,7 @@ requires-dist = [
{ name = "python-multipart", specifier = ">=0.0.9" }, { name = "python-multipart", specifier = ">=0.0.9" },
{ name = "requests", extras = ["socks"], specifier = ">=2.31.0" }, { name = "requests", extras = ["socks"], specifier = ">=2.31.0" },
{ name = "scipy", specifier = ">=1.13.0" }, { name = "scipy", specifier = ">=1.13.0" },
{ name = "segno", specifier = ">=1.6" },
{ name = "sqlalchemy", specifier = ">=2.0.0" }, { name = "sqlalchemy", specifier = ">=2.0.0" },
{ name = "uvicorn", extras = ["standard"], specifier = ">=0.27.0" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.27.0" },
{ name = "websockets", specifier = ">=16.0" }, { name = "websockets", specifier = ">=16.0" },
@@ -693,6 +721,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" }, { url = "https://files.pythonhosted.org/packages/99/0e/33f32a2a58987e26aec0f7df252cbbad1e90ae77bdbc76f40dd4ed0cf0ea/scipy-1.18.0-cp312-cp312-win_arm64.whl", hash = "sha256:d88363fd9d8fbd3511bd273f1a49efb2a540773ddf92a91d57498ce7dd7f3e76", size = 24351481, upload-time = "2026-06-19T15:00:11.103Z" },
] ]
[[package]]
name = "segno"
version = "1.6.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1c/2e/b396f750c53f570055bf5a9fc1ace09bed2dff013c73b7afec5702a581ba/segno-1.6.6.tar.gz", hash = "sha256:e60933afc4b52137d323a4434c8340e0ce1e58cec71439e46680d4db188f11b3", size = 1628586, upload-time = "2025-03-12T22:12:53.324Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d6/02/12c73fd423eb9577b97fc1924966b929eff7074ae6b2e15dd3d30cb9e4ae/segno-1.6.6-py3-none-any.whl", hash = "sha256:28c7d081ed0cf935e0411293a465efd4d500704072cdb039778a2ab8736190c7", size = 76503, upload-time = "2025-03-12T22:12:48.106Z" },
]
[[package]] [[package]]
name = "six" name = "six"
version = "1.17.0" version = "1.17.0"