From 699913023782aa65038963cb902d31f5d2245340 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 14 Jul 2026 16:53:28 +0800 Subject: [PATCH] =?UTF-8?q?feat(ci):=20P2=E5=80=BA=E5=8A=A1=E6=B8=85?= =?UTF-8?q?=E7=90=86=20+=20Production=E9=83=A8=E7=BD=B2=E9=97=A8=E7=A6=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 升级PyJWT 2.9.0 -> 2.13.0,修复pip-audit扫出的4个CVE - 清理vulture扫出的5处死代码: * templates.py: 移除未使用的GetTemplateUsageUseCase import * unified_render_service.py: 移除未使用的apply_chroma_key_if_needed import * unified_render_service.py: 移除未使用的parse_stickers_from_config import * asset_analyzer.py: 移除extract_frames未使用的max_frames参数 * generation.py: 移除_download_library_assets未使用的video_extensions参数 - Production部署门禁: * deploy-production阶段加checkout(修复通知脚本找不到的问题) * 新增Production smoke test健康检查:部署后从外部验证API健康、登录接口、Web前端 * 健康检查不通过则部署失败,预留回滚接入位置 --- .gitea/workflows/ci-cd.yml | 90 +++++++++++++++++++ apps/api/app/api/routes/templates.py | 1 - .../unified_render_service.py | 3 +- .../worker/worker_app/tasks/asset_analyzer.py | 2 +- apps/worker/worker_app/tasks/generation.py | 2 - requirements-base.txt | 2 +- 6 files changed, 93 insertions(+), 7 deletions(-) mode change 100755 => 100644 apps/api/app/api/routes/templates.py mode change 100755 => 100644 apps/worker/video_processing/unified_render_service.py mode change 100755 => 100644 apps/worker/worker_app/tasks/generation.py mode change 100755 => 100644 requirements-base.txt diff --git a/.gitea/workflows/ci-cd.yml b/.gitea/workflows/ci-cd.yml index c467a46be..fbc83a070 100644 --- a/.gitea/workflows/ci-cd.yml +++ b/.gitea/workflows/ci-cd.yml @@ -1740,6 +1740,50 @@ jobs: needs: [build-production-api, build-production-worker, build-production-web] steps: + - name: Checkout code + shell: sh + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + set -eu + python3 - <<'INNERPY' + import io, os, tarfile, time, urllib.request, urllib.error + url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz" + request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"}) + last_err = None + for attempt in range(5): + try: + with urllib.request.urlopen(request, timeout=120) as response: + archive = response.read() + break + except urllib.error.HTTPError as e: + last_err = e + if e.code >= 500 and attempt < 4: + wait = 2 ** attempt + print(f"Checkout HTTP {e.code}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + raise + except Exception as e: + last_err = e + if attempt < 4: + wait = 2 ** attempt + print(f"Checkout error: {e}, retrying in {wait}s (attempt {attempt+1}/5)...") + time.sleep(wait) + continue + else: + raise last_err + with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar: + root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/' + for member in tar.getmembers(): + name = member.name + if name == root_prefix[:-1]: + continue + if name.startswith(root_prefix): + member.name = name[len(root_prefix):] + if member.name: + tar.extract(member, '.') + INNERPY - name: Install SSH client shell: sh run: | @@ -1797,6 +1841,52 @@ jobs: echo "$DEPLOY_B64" | base64 -d | ssh -p 22222 -i "$key_path" "$production_user@$production_host" "IMAGE_TAG='${GITHUB_REF_NAME}' REGISTRY_TOKEN='${REGISTRY_TOKEN}' sh" + - name: Production smoke test (健康检查门禁) + if: success() + shell: sh + run: | + set -eu + API_BASE="https://api.xiaoxiajianji.com" + WEB_BASE="https://saas.xiaoxiajianji.com" + + echo "=== 生产部署门禁:外部健康检查 ===" + echo "等待服务启动稳定(30s)..." + sleep 30 + + echo "--- Check 1: API health endpoint ---" + for i in $(seq 1 20); do + HEALTH=$(curl -sf --max-time 10 "${API_BASE}/health") && break + echo " Attempt $i/20: not ready yet, waiting 5s..." + sleep 5 + done + if [ -z "$HEALTH" ]; then + echo "FAIL: API /health unreachable after 100s" + echo "生产环境健康检查未通过,部署失败!" + echo "(回滚机制待接入,当前需手动回滚)" + exit 1 + fi + echo "API health OK: $HEALTH" + + echo "--- Check 2: API login endpoint (expect 401/422) ---" + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -X POST "${API_BASE}/api/v1/auth/login" -H "Content-Type: application/json" -d '{"email":"smoke@test.com","password":"wrong"}') + if [ "$HTTP_CODE" != "401" ] && [ "$HTTP_CODE" != "422" ]; then + echo "FAIL: login returned HTTP $HTTP_CODE (expected 401 or 422)" + exit 1 + fi + echo "Login API OK: HTTP $HTTP_CODE" + + echo "--- Check 3: Web frontend ---" + HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "${WEB_BASE}/") + if [ "$HTTP_CODE" != "200" ]; then + echo "FAIL: web frontend returned HTTP $HTTP_CODE (expected 200)" + exit 1 + fi + echo "Web frontend OK: HTTP $HTTP_CODE" + + echo "" + echo "=== ✅ 生产环境健康检查全部通过 ===" + echo "Version: ${GITHUB_REF_NAME}" + - name: Notify CI success if: success() shell: sh diff --git a/apps/api/app/api/routes/templates.py b/apps/api/app/api/routes/templates.py old mode 100755 new mode 100644 index 73535f993..8cfc465be --- a/apps/api/app/api/routes/templates.py +++ b/apps/api/app/api/routes/templates.py @@ -45,7 +45,6 @@ from packages.application.template.use_cases import ( CreateTemplateUseCase, DeleteCategoryUseCase, DeleteTemplateUseCase, - GetTemplateUsageUseCase, GetTemplateUseCase, ListCategoriesUseCase, ListTagsUseCase, diff --git a/apps/worker/video_processing/unified_render_service.py b/apps/worker/video_processing/unified_render_service.py old mode 100755 new mode 100644 index a7fe9fb48..653ef594e --- a/apps/worker/video_processing/unified_render_service.py +++ b/apps/worker/video_processing/unified_render_service.py @@ -28,7 +28,6 @@ from dataclasses import dataclass, field from pathlib import Path from typing import Any -from video_processing.chroma_key_engine import apply_chroma_key_if_needed from video_processing.color_grade_engine import ColorGradeConfig, ColorGradeEngine from video_processing.ffmpeg_utils import ( DEFAULT_FPS, @@ -46,7 +45,7 @@ from video_processing.render_audio import RenderContext, merge_audio_video, mix_ from video_processing.render_subtitles import generate_ass_subtitles from video_processing.reverse_engine import ReverseConfig, ReverseEngine from video_processing.speed_engine import SpeedConfig, SpeedEngine -from video_processing.sticker_engine import StickerEngine, parse_stickers_from_config +from video_processing.sticker_engine import StickerEngine from video_processing.subtitle_generator import generate_ass_from_timeline from video_processing.transition_engine import TransitionEngine from video_processing.trim_engine import TrimConfig, TrimEngine, extract_trim_from_clip_config diff --git a/apps/worker/worker_app/tasks/asset_analyzer.py b/apps/worker/worker_app/tasks/asset_analyzer.py index c38a26304..ec609aa5b 100755 --- a/apps/worker/worker_app/tasks/asset_analyzer.py +++ b/apps/worker/worker_app/tasks/asset_analyzer.py @@ -179,7 +179,7 @@ class AssetAnalyzer: self._video_info = info return info - def extract_frames(self, count: int = 10, max_frames: int = 30) -> list[np.ndarray]: + def extract_frames(self, count: int = 10) -> list[np.ndarray]: """ 从视频中均匀抽取帧 diff --git a/apps/worker/worker_app/tasks/generation.py b/apps/worker/worker_app/tasks/generation.py old mode 100755 new mode 100644 index 04973fd1a..8db0609a7 --- a/apps/worker/worker_app/tasks/generation.py +++ b/apps/worker/worker_app/tasks/generation.py @@ -461,7 +461,6 @@ def _download_library_assets( asset_library_id: str = "", project_id: str = "", asset_ids: list[str] | None = None, - video_extensions: tuple = (".mp4", ".mov", ".avi", ".mkv", ".webm"), strict: bool = True, task_id: str = "", gen_task=None, @@ -480,7 +479,6 @@ def _download_library_assets( asset_library_id: 素材库 ID(可选,与 project_id 二选一) project_id: 项目 ID(可选,与 asset_library_id 二选一) asset_ids: 指定素材 ID 列表,为空则下载全部 ready 视频素材 - video_extensions: 支持的视频扩展名(保留兼容,当前按 file_type 过滤) strict: 严格模式(默认 True)。 True — 任何素材下载失败立即抛 RuntimeError; False — 跳过失败素材,返回成功列表(调用方可通过日志感知失败)。 diff --git a/requirements-base.txt b/requirements-base.txt old mode 100755 new mode 100644 index f01d9a400..20c438599 --- a/requirements-base.txt +++ b/requirements-base.txt @@ -13,7 +13,7 @@ uvicorn[standard]==0.32.0 pydantic==2.9.0 # 认证核心 -pyjwt==2.9.0 +pyjwt==2.13.0 bcrypt==4.2.0 # Redis -- 2.54.0