From 12a782bbf0d8790e2fa08e308f903a8cb948f37b Mon Sep 17 00:00:00 2001 From: SaaS Frontend Agent Date: Sat, 8 Aug 2026 00:24:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A2=84=E8=A7=88=E6=95=B0=E9=87=8F=20?= =?UTF-8?q?preview=5Fcount=20=E4=B8=8A=E9=99=90=E6=A0=A1=E9=AA=8C=20+=20Wo?= =?UTF-8?q?rker=20=E5=B9=B6=E5=8F=91=E6=8F=90=E5=8D=87=E8=87=B3=204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CreatePreviewGenerationTaskRequest 新增 preview_count 字段(1-10,默认1) - 路由日志增加 preview_count 记录 - Docker Compose Worker 并发默认从 1 提升至 4 - Worker 资源限制调整为 4C8G 以支持并行渲染 - 新增 11 个单元测试覆盖 preview_count 校验及并发配置 --- apps/api/app/api/routes/generation_preview.py | 3 +- apps/api/app/schemas/generation_task.py | 6 + infra/docker/compose.yml | 13 +- .../test_1286_preview_count_validation.py | 130 ++++++++++++++++++ 4 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 tests/unit/test_1286_preview_count_validation.py diff --git a/apps/api/app/api/routes/generation_preview.py b/apps/api/app/api/routes/generation_preview.py index 2246df9a1..3072a8f7b 100755 --- a/apps/api/app/api/routes/generation_preview.py +++ b/apps/api/app/api/routes/generation_preview.py @@ -270,10 +270,11 @@ def create_preview_generation_task( """ user_id = authenticated_user.user.id logger.info( - "[预览生成] 接收请求: user_id=%s, template_id=%s, asset_count=%d", + "[预览生成] 接收请求: user_id=%s, template_id=%s, asset_count=%d, preview_count=%d", user_id, request.template_id, len(request.asset_ids), + request.preview_count, ) # 预检查队列限流 diff --git a/apps/api/app/schemas/generation_task.py b/apps/api/app/schemas/generation_task.py index a5889dc2c..475c859a8 100755 --- a/apps/api/app/schemas/generation_task.py +++ b/apps/api/app/schemas/generation_task.py @@ -146,6 +146,12 @@ class CreatePreviewGenerationTaskRequest(BaseModel): default_factory=dict, description="自定义BGM配置,覆盖模板BGM设置。支持 enabled/source/asset_id/preset_id/audio_url/volume 等字段", ) + preview_count: int = Field( + default=1, + ge=1, + le=10, + description="预览视频生成数量,范围 1-10,默认 1", + ) @model_validator(mode="after") def _check_template_id(self) -> "CreatePreviewGenerationTaskRequest": diff --git a/infra/docker/compose.yml b/infra/docker/compose.yml index 3a6cbe5d0..b9e7178f5 100644 --- a/infra/docker/compose.yml +++ b/infra/docker/compose.yml @@ -15,7 +15,7 @@ # API_PORT - API 端口映射 (staging: 8000, production: 8001) # WEB_PORT - Web 端口映射 (staging: 3001, production: 3002) # GENERATED_FILES_HOST_DIR - 生成文件的主机目录 -# WORKER_CONCURRENCY - Worker 并发数 (默认: 1) +# WORKER_CONCURRENCY - Worker 并发数 (默认: 4) # WORKER_MAX_TASKS_PER_CHILD - Worker 每个子进程最大任务数 (默认: 100) # # 重要: @@ -109,7 +109,7 @@ services: environment: APP_ENV: ${APP_ENV:-staging} APP_VERSION: ${APP_VERSION:-unknown} - WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-1} + WORKER_CONCURRENCY: ${WORKER_CONCURRENCY:-4} WORKER_MAX_TASKS_PER_CHILD: ${WORKER_MAX_TASKS_PER_CHILD:-100} GENERATED_FILES_DIR: /app/generated GENERATED_FILES_URL_PREFIX: /generated-files @@ -136,14 +136,15 @@ services: # 资源限制建议(生产环境建议启用) # ========================================= # 注意: Worker 需要处理视频,建议分配更多资源 + # 并发 4 时需要 4C8G 以上,确保视频渲染不 OOM deploy: resources: limits: - cpus: '2.0' - memory: 2g + cpus: '4.0' + memory: 8g reservations: - cpus: '0.5' - memory: 1G + cpus: '1.0' + memory: 2G # ========================================= # Web 服务(Nginx + 前端静态文件) diff --git a/tests/unit/test_1286_preview_count_validation.py b/tests/unit/test_1286_preview_count_validation.py new file mode 100644 index 000000000..ccc1da34f --- /dev/null +++ b/tests/unit/test_1286_preview_count_validation.py @@ -0,0 +1,130 @@ +"""预览数量 preview_count 校验测试。 + +验证 CreatePreviewGenerationTaskRequest 中 preview_count 字段: +- 默认值为 1 +- 范围 1-10 +- 超出范围报错 +- Worker 并发配置验证 +""" + +import pytest + + +class TestPreviewCountValidation: + """preview_count 字段校验""" + + def test_default_preview_count_is_1(self): + """默认 preview_count 为 1""" + from app.schemas.generation_task import CreatePreviewGenerationTaskRequest + + req = CreatePreviewGenerationTaskRequest( + template_id="tmpl_123", + asset_ids=["asset_1"], + ) + assert req.preview_count == 1 + + def test_preview_count_min_valid(self): + """preview_count=1 合法""" + from app.schemas.generation_task import CreatePreviewGenerationTaskRequest + + req = CreatePreviewGenerationTaskRequest( + template_id="tmpl_123", + asset_ids=["asset_1"], + preview_count=1, + ) + assert req.preview_count == 1 + + def test_preview_count_max_valid(self): + """preview_count=10 合法(上限)""" + from app.schemas.generation_task import CreatePreviewGenerationTaskRequest + + req = CreatePreviewGenerationTaskRequest( + template_id="tmpl_123", + asset_ids=["asset_1"], + preview_count=10, + ) + assert req.preview_count == 10 + + def test_preview_count_middle_value(self): + """preview_count=5 合法""" + from app.schemas.generation_task import CreatePreviewGenerationTaskRequest + + req = CreatePreviewGenerationTaskRequest( + template_id="tmpl_123", + asset_ids=["asset_1"], + preview_count=5, + ) + assert req.preview_count == 5 + + def test_preview_count_zero_raises(self): + """preview_count=0 报错(低于下限)""" + from app.schemas.generation_task import CreatePreviewGenerationTaskRequest + from pydantic import ValidationError + + with pytest.raises(ValidationError): + CreatePreviewGenerationTaskRequest( + template_id="tmpl_123", + asset_ids=["asset_1"], + preview_count=0, + ) + + def test_preview_count_negative_raises(self): + """preview_count=-1 报错""" + from app.schemas.generation_task import CreatePreviewGenerationTaskRequest + from pydantic import ValidationError + + with pytest.raises(ValidationError): + CreatePreviewGenerationTaskRequest( + template_id="tmpl_123", + asset_ids=["asset_1"], + preview_count=-1, + ) + + def test_preview_count_exceeds_max_raises(self): + """preview_count=11 报错(超过上限 10)""" + from app.schemas.generation_task import CreatePreviewGenerationTaskRequest + from pydantic import ValidationError + + with pytest.raises(ValidationError): + CreatePreviewGenerationTaskRequest( + template_id="tmpl_123", + asset_ids=["asset_1"], + preview_count=11, + ) + + def test_preview_count_large_value_raises(self): + """preview_count=100 报错""" + from app.schemas.generation_task import CreatePreviewGenerationTaskRequest + from pydantic import ValidationError + + with pytest.raises(ValidationError): + CreatePreviewGenerationTaskRequest( + template_id="tmpl_123", + asset_ids=["asset_1"], + preview_count=100, + ) + + +class TestWorkerConcurrencyConfig: + """Worker 并发配置验证""" + + def test_default_worker_concurrency_is_4(self): + """Worker 默认并发为 4""" + from packages.config import WorkerSettings + + settings = WorkerSettings() + assert settings.worker_concurrency == 4 + + def test_worker_concurrency_configurable(self): + """Worker 并发可通过环境变量配置""" + from packages.config import WorkerSettings + + settings = WorkerSettings(worker_concurrency=8) + assert settings.worker_concurrency == 8 + + def test_worker_concurrency_is_int(self): + """Worker 并发为整数类型""" + from packages.config import WorkerSettings + + settings = WorkerSettings() + assert isinstance(settings.worker_concurrency, int) -- 2.54.0