feat(ci): P2债务清理 + Production部署门禁(PyJWT升级/死代码清理/生产健康检查) #324

Merged
xiaoxia merged 1 commits from feat/ci-p2-debt-and-prod-gate into develop 2026-07-14 16:57:58 +08:00
6 changed files with 93 additions and 7 deletions
+90
View File
@@ -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
-1
View File
@@ -45,7 +45,6 @@ from packages.application.template.use_cases import (
CreateTemplateUseCase,
DeleteCategoryUseCase,
DeleteTemplateUseCase,
GetTemplateUsageUseCase,
GetTemplateUseCase,
ListCategoriesUseCase,
ListTagsUseCase,
+1 -2
View File
@@ -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
@@ -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]:
"""
从视频中均匀抽取帧
-2
View File
@@ -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 — 跳过失败素材,返回成功列表(调用方可通过日志感知失败)。
Executable → Regular
+1 -1
View File
@@ -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