Compare commits

..

2 Commits

Author SHA1 Message Date
用户CI Test 4437a08d4f style(domain): format generation_task.py with black
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 1m0s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m31s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
2026-07-09 15:37:02 +08:00
用户CI Test f5f54b98b6 fix(generation): 修复生成任务状态机缺失导致状态永不更新的问题
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Failing after 8s
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m38s
CI/CD Pipeline / Build & Push Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Runtime Images (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
## 问题根因
generate_video Celery 任务完全没有状态更新逻辑:
- 任务开始执行时,不更新 GenerationTask 状态为 running
- 任务成功时,不更新状态为 completed
- 任务失败时,不更新状态为 failed
- GenerationTask 领域模型缺少 mark_processing/mark_completed/mark_failed 方法

结果:任务状态永远停留在 pending,前端查询时一直显示"生成中"或超时显示失败,即使视频实际上已经生成成功了。

## 修复方案

### 1. GenerationTask 领域模型增加完整状态机
- 新增 TERMINAL_STATUSES 终态集合
- 新增 _VALID_TRANSITIONS 合法状态转换表
- 新增 transition_to() 通用状态转换方法(含合法性校验)
- 新增 mark_processing() — pending → running,设置 started_at
- 新增 mark_completed(result_count) — running → completed,设置 completed_at/progress/result_count
- 新增 mark_failed(error_message) — pending/running → failed,设置 error_message/completed_at
- 新增 mark_cancelled() — pending/running → cancelled
- 新增 mark_pending_from_failed() — failed → pending(用于重试)
- 新增 is_terminal/is_completed/is_failed/is_running 属性

状态机:
  pending → running → completed
              ↘ failed → pending (重试)
       ↘ cancelled

### 2. generate_video Celery 任务增加状态更新
- 新增 _update_task_status() 辅助函数:独立 session + 异常隔离
- 任务开始时:调用 mark_processing() 更新为 running
- 任务成功时:调用 mark_completed() 更新为 completed,传入视频数量
- 任务失败时:在 except 块中调用 mark_failed() 记录错误信息
- _create_video_record_and_dedup 返回值改为 int,方便统计成功数量

状态更新使用独立数据库 session,且所有状态更新操作都包裹在 try/except 中,确保不会因为状态更新失败导致整个任务异常。

### 3. 新增状态机单元测试
新增 tests/unit/test_generation_task_status.py,覆盖 42 个测试用例:
- 初始状态验证
- mark_processing 正常/异常路径
- mark_completed 正常/异常路径
- mark_failed 正常/异常路径
- mark_cancelled 正常/异常路径
- mark_pending_from_failed 重试路径
- transition_to 通用方法
- 完整流转路径(成功、失败、重试、取消)

## 影响范围
- packages/domain/generation_task.py — 领域模型状态机方法
- apps/worker/worker_app/tasks/generation.py — Celery 任务状态更新
- tests/unit/test_generation_task_status.py — 新增单元测试

## 验证方式
- 42 个状态机单元测试全部通过
- 现有 8 个生成相关测试全部通过
- 现有 15 个生成 API 集成测试全部通过
- 现有 6 个编辑计划 worker 失败测试全部通过
- 现有 25 个生成视频管理测试全部通过
2026-07-09 15:34:35 +08:00
4 changed files with 2 additions and 247 deletions
+2 -4
View File
@@ -164,8 +164,7 @@ jobs:
USE_IN_MEMORY_DB: "true"
run: |
set -eu
PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/unit -q \
--cov=apps --cov-report=term --cov-report=xml
PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/unit -q
- name: Start PostgreSQL for integration tests
shell: sh
@@ -210,8 +209,7 @@ jobs:
run: |
set -eu
pip install -q pytest-rerunfailures
PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration -q --timeout=60 -x --reruns 2 --reruns-delay 1 -m "not performance" \
--cov=apps --cov-append --cov-report=term --cov-report=xml --cov-fail-under=50
PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration -q --timeout=60 -x --reruns 2 --reruns-delay 1 -m "not performance"
- name: Run API performance baseline tests
shell: sh
-235
View File
@@ -1,235 +0,0 @@
# CI 必需环境变量清单
> 本文档整理小虾 SaaS 项目中所有从环境变量读取的配置项,明确哪些是 CI 测试必须的、哪些是可选的。
> 最后更新:2026-07-09
## 目录
- [一、配置来源说明](#一配置来源说明)
- [二、CI 必需环境变量(P0](#二ci-必需环境变量p0)
- [三、可选环境变量(有默认值)](#三可选环境变量有默认值)
- [四、测试专用环境变量](#四测试专用环境变量)
- [五、Worker 服务环境变量](#五worker-服务环境变量)
- [六、当前 CI 配置对照](#六当前-ci-配置对照)
---
## 一、配置来源说明
项目的环境变量配置主要来自以下几处:
| 来源 | 文件路径 | 说明 |
|------|---------|------|
| API 主配置 | `apps/api/app/config.py` | pydantic `Settings` 类,API 服务核心配置 |
| Worker 配置 | `apps/worker/worker_app/core/config.py` | pydantic `WorkerSettings` 类,Worker 服务配置 |
| 共享配置 | `packages/shared/config.py` | pydantic `SharedSettings` 类,API + Worker 共享配置 |
| 直接读取 | 各模块中 `os.environ` / `os.getenv` | 散落在各业务模块中的直接读取 |
> **注意**pydantic-settings 配置默认 `case_sensitive=False`,即环境变量名不区分大小写,但习惯上使用大写。
---
## 二、CI 必需环境变量(P0
以下变量是 CI 运行测试**必须配置**的,缺失会导致测试启动失败或核心功能异常。
| 变量名 | 用途说明 | 默认值 | 影响范围 |
|--------|---------|--------|---------|
| `DATABASE_URL` | 数据库连接字符串 | `postgresql+psycopg://postgres:postgres@localhost:5432/xiaoxia_saas` | 集成测试、 Alembic 迁移验证 |
| `USE_IN_MEMORY_DB` | 是否使用内存数据库(SQLite) | `false` | 单元测试(设为 `true` 可跳过 PostgreSQL 依赖) |
| `JWT_SECRET_KEY` | JWT 签名密钥,**无安全默认值**,必须显式设置 | `None`(启动校验失败) | 所有涉及认证的 API 测试 |
> **说明**
> - 单元测试通过 `USE_IN_MEMORY_DB=true` 使用 SQLite 内存数据库,无需 PostgreSQL
> - 集成测试需要真实 PostgreSQL,需设置 `DATABASE_URL`
> - `JWT_SECRET_KEY` 在测试文件中通过 `os.environ.setdefault()` 设置了测试用默认值,CI 中可不额外配置,但生产环境必须配置
---
## 三、可选环境变量(有默认值)
以下变量都有合理的默认值,CI 中可以不配置,使用默认值即可。
### 3.1 应用基础配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `APP_NAME` | 应用名称 | `xiaoxia-saas` |
| `APP_VERSION` | 应用版本号 | `0.1.61` / `unknown` |
| `ENVIRONMENT` | 运行环境标识 | `development` |
| `DEBUG` | 是否开启调试模式 | `true` |
| `APP_BASE_URL` | 应用基础 URL(用于生成邮件链接等) | `http://localhost:3000` |
| `API_HOST` | API 服务绑定地址 | `0.0.0.0` |
| `API_PORT` | API 服务端口 | `8000` |
| `API_PREFIX` | API 路由前缀 | `/api/v1` |
| `APP_ENV` | 环境标识(用于加载 .env.{env} 文件) | `development` |
| `LOG_LEVEL` | 日志级别 | `INFO` |
### 3.2 数据库连接池配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `DATABASE_POOL_SIZE` | 连接池大小 | `20` |
| `DATABASE_MAX_OVERFLOW` | 最大溢出连接数 | `10` (API) / `40` (Worker) |
| `DATABASE_POOL_TIMEOUT` | 获取连接超时时间(秒) | `30` |
| `DATABASE_POOL_RECYLE` / `DATABASE_POOL_RECYCLE` | 连接回收时间(秒) | `3600` |
| `AUTO_CREATE_SCHEMA` | 是否自动创建表结构 | `false` |
### 3.3 Redis / Celery 配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `REDIS_URL` | Redis 连接地址 | `redis://localhost:6379/0` |
| `REDIS_MAX_CONNECTION` | Redis 最大连接数 | `50` |
| `ENABLE_REDIS_SESSIONS` | 是否启用 Redis 会话存储 | `false` |
| `CELERY_BROKER_URL` / `BROKER_URL` | Celery Broker 地址 | `redis://localhost:6379/0` |
| `CELERY_RESULT_BACKEND` / `RESULT_BACKEND` | Celery 结果后端 | `redis://localhost:6379/1` |
### 3.4 JWT 配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `JWT_ALGORITHM` | JWT 签名算法 | `HS256`(隐式默认) |
| `JWT_ACCESS_TOKEN_EXPIRE_MINUTES` | Access Token 过期时间(分钟) | `30`(隐式默认) |
| `JWT_REFRESH_TOKEN_EXPIRE_DAYS` | Refresh Token 过期时间(天) | `30`(隐式默认) |
| `JWT_SECRET_KEY_OLD` | 旧 JWT 密钥(用于密钥轮换) | `None` |
| `SECRET_ROTATION_DAYS` | 密钥轮换建议天数 | `90` |
### 3.5 邮件配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `ENABLE_EMAIL_DELIVERY` | 是否启用邮件发送 | `false` |
| `SMTP_HOST` | SMTP 服务器地址 | `smtp.gmail.com` |
| `SMTP_PORT` | SMTP 端口 | `587` |
| `SMTP_USER` | SMTP 用户名 | `""`(空) |
| `SMTP_PASSWORD` | SMTP 密码 | `""`(空) |
| `SMTP_FROM_EMAIL` | 发件人邮箱 | `""`(空) |
| `SMTP_FROM_NAME` | 发件人名称 | `小虾 SaaS` |
| `SMTP_USE_TLS` | 是否使用 TLS | `true` |
### 3.6 OSS 阿里云存储配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `OSS_ENDPOINT` | OSS Endpoint | `oss-cn-hangzhou.aliiyuncs.com` |
| `OSS_ACCESS_KEY_ID` | OSS Access Key ID | `""`(空) |
| `OSS_ACCESS_KEY_SECRET` | OSS Access Key Secret | `""`(空) |
| `OSS_BUCKET_NAME` | OSS Bucket 名称 | `xiaoxia-autocut` |
| `OSS_DIRECT_UPLOAD_MAX_MB` / `MAX_UPLOAD_SIZE_MB` | 直传最大文件大小(MB | `2000` |
| `OSS_DIRECT_UPLOAD_EXPIRE_SECONDS` | 直传签名过期时间(秒) | `900` |
### 3.7 CosyVoice 语音合成配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `COSYVOICE_API_KEY` | CosyVoice API Key | `""`(空) |
| `COSYVOICE_BASE_URL` | CosyVoice API 地址 | `https://dashscope.aliyuncs.com/api/v1/services/aigc/text2audio` |
| `COSYVOICE_MODEL` | CosyVoice 模型 | `cosyvoice-v1` |
| `COSYVOICE_VOICE` | 默认音色 | `longxiaochun` |
| `COSYVOICE_SAMPLE_RATE` | 采样率 | `22050` |
| `COSYVOICE_FORMAT` | 输出格式 | `mp3` |
### 3.8 CORS 配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `CORS_ORIGINS_RAW` | CORS 允许的源(逗号分隔) | `http://localhost:3000,http://localhost:5173,http://localhost:8000` |
### 3.9 文件存储 / 生成文件配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `GENERATED_FILES_DIR` | 生成文件本地存储目录 | `/app/generated` |
| `GENERATED_FILES_URL_PREFIX` | 生成文件访问 URL 前缀 | `/generated-files` |
| `VIDEO_OUTPUT_DIR` | 视频输出目录 | `{tempdir}/video_output` |
| `PUBLIC_API_BASE_URL` | 公开 API 基础 URL | `https://api.xiaoxiajianji.com` |
### 3.10 监控 / 指标配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `METRICS_AUTH_TOKEN` | Prometheus 指标接口认证 Token | `""`(空,不启用认证) |
### 3.11 内部 API 配置
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `INTERNAL_API_KEYS` | 内部 API 调用密钥列表(逗号分隔) | `""`(空) |
---
## 四、测试专用环境变量
以下变量仅在测试或冒烟测试脚本中使用。
| 变量名 | 用途说明 | 默认值 | 使用位置 |
|--------|---------|--------|---------|
| `SMOKE_TEST_PASSWORD` | 冒烟测试用的测试账号密码 | `changeme` | `scripts/smoke_*.py` |
| `MIGRATION_SINCE_REVISION` | 迁移安全检查的起始版本 | `None` | `scripts/check_migration_safety.py` |
| `MIGRATION_DIFF_AGAINST` | 迁移 diff 对比的目标分支/版本 | `None` | `scripts/check_migration_safety.py` |
---
## 五、Worker 服务环境变量
以下变量主要用于 Worker(Celery)服务,CI 的单元/集成测试通常不涉及。
| 变量名 | 用途说明 | 默认值 |
|--------|---------|--------|
| `WORKER_NAME` | Worker 名称 | `xiaoxia-saas-worker` |
| `WORKER_CONCURRENCY` | Worker 并发数 | `4` |
| `WORKER_MAX_TASKS_PER_CHILD` | 每个子进程最大任务数 | `1000` |
---
## 六、当前 CI 配置对照
当前 `.gitea/workflows/ci-cd.yml``validate` job 配置的环境变量:
| 变量名 | CI 配置值 | 是否必需 | 备注 |
|--------|----------|---------|------|
| `DATABASE_URL` | `postgresql+psycopg://postgres:postgres@127.0.0.1:5432/xiaoxia_saas` | ✅ 是 | Job 级别配置 |
| `USE_IN_MEMORY_DB` | `"false"`Job 级) / `"true"`(单元测试 step 级) | ✅ 是 | 单元测试 step 覆盖为 `true` |
| `JWT_SECRET_KEY` | (未配置) | ⚠️ 测试内置 | 测试文件中通过 `setdefault` 设置了测试密钥 |
### 6.1 CI 环境变量现状评估
-**数据库配置完备**DATABASE_URL + USE_IN_MEMORY_DB 已正确配置
-**JWT 密钥**:测试代码内置默认值,CI 可正常运行
- ⚠️ **缺少 Redis 配置**:但当前测试不依赖 Redis,使用默认值即可
- ⚠️ **缺少邮件/OSS/语音配置**:均为可选,CI 中使用空默认值不影响核心测试
### 6.2 建议后续补充
如果未来测试覆盖到以下功能,需要在 CI 中补充对应配置:
1. **Redis 相关测试** → 配置 `REDIS_URL`
2. **邮件发送测试** → 配置 `ENABLE_EMAIL_DELIVERY` 及 SMTP 相关变量
3. **OSS 上传测试** → 配置 OSS 相关变量(或使用 mock)
4. **语音合成测试** → 配置 CosyVoice 相关变量(或使用 mock
---
## 附录:环境变量读取位置索引
### pydantic Settings 类
- `apps/api/app/config.py``Settings` 类(API 主配置)
- `apps/worker/worker_app/core/config.py``WorkerSettings` 类(Worker 配置)
- `packages/shared/config.py``SharedSettings` 类(共享配置)
### 直接 os.environ / os.getenv 读取
| 变量名 | 文件位置 |
|--------|---------|
| `VIDEO_OUTPUT_DIR` | `apps/worker/video_processing/video_compose_service.py``apps/worker/worker_app/tasks/compose_video.py` |
| `INTERNAL_API_KEYS` | `apps/api/app/api/routes/auth.py` |
| `APP_ENV` / `ENV` | `apps/api/app/api/routes/auth.py`、各 config.py 的 `get_settings()` |
| `GENERATED_FILES_DIR` | `apps/worker/worker_app/tasks/generation.py``apps/api/main.py``scripts/cleanup_generated_files.py` |
| `GENERATED_FILES_URL_PREFIX` | `apps/worker/worker_app/tasks/generation.py``apps/api/main.py``apps/api/app/core/storage.py` |
| `PUBLIC_API_BASE_URL` | `apps/worker/worker_app/tasks/generation.py` |
| `METRICS_AUTH_TOKEN` | `apps/api/app/middleware/prometheus_metrics.py` |
| `APP_VERSION` | `apps/api/app/middleware/prometheus_metrics.py` |
| `SMOKE_TEST_PASSWORD` | `scripts/smoke_*.py` |
| `MIGRATION_SINCE_REVISION` | `scripts/check_migration_safety.py` |
| `MIGRATION_DIFF_AGAINST` | `scripts/check_migration_safety.py` |
| `DATABASE_URL` | `alembic/env.py` |
-5
View File
@@ -1,8 +1,3 @@
[pytest]
pythonpath = . apps/api apps/worker
testpaths = tests
# ===== 覆盖率配置 =====
# 覆盖率统计范围(供 --cov 使用时的默认源)
# 注意:addopts 不默认开启 --cov,避免影响本地开发调试
# CI 中通过命令行参数显式开启:--cov=apps --cov-report=term --cov-report=xml --cov-fail-under=50
-3
View File
@@ -24,9 +24,6 @@ celery==5.4.0
# 对象存储
oss2==2.18.4
cryptography==46.0.5
# 覆盖系统预装的旧版pyOpenSSL,与cryptography 46.0.5兼容
pyOpenSSL==26.2.0
# HTTP 客户端
httpx==0.27.2