release: 2026.09.14 智能剪辑批量变体P0修复+AI数字人MOV支持+代码审查重构 (#1885)
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 29s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 53s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m15s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m33s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m48s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m50s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Successful in 5m39s
AI Code Review / AI Code Review (pull_request) Successful in 6m17s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m26s
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 7s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 26s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 53h12m45s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 53h20m15s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 53h19m44s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 53h19m46s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 53h12m21s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 53h19m48s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 53h19m48s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 53h19m49s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 53h19m49s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 53h19m49s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 53h19m51s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 53h12m21s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 53h12m21s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 53h12m19s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 53h19m42s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 53h19m43s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 53h19m43s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 53h19m46s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 53h55m6s
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 2s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 2s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 29s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 29s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 53s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m15s
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Successful in 2m33s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m48s
CI/CD Pipeline / Validate - Style (pull_request) Successful in 2m50s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Successful in 5m39s
AI Code Review / AI Code Review (pull_request) Successful in 6m17s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 7m26s
CI/CD Pipeline / CI Gate (pull_request) Successful in 3s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 7s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 26s
CI/CD Pipeline / Deploy Production (pull_request) Failing after 53h12m45s
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Failing after 53h20m15s
CI/CD Pipeline / PR Build Web Image (pull_request) Failing after 53h19m44s
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 53h19m46s
CI/CD Pipeline / Build Production API Image (pull_request) Failing after 53h12m21s
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Failing after 53h19m48s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Failing after 53h19m48s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 53h19m49s
CI/CD Pipeline / Build Staging Web Image (pull_request) Failing after 53h19m49s
CI/CD Pipeline / Build Staging API Image (pull_request) Failing after 53h19m49s
CI/CD Pipeline / Check push changed paths (pull_request) Failing after 53h19m51s
CI/CD Pipeline / Build Production Worker Image (pull_request) Failing after 53h12m21s
CI/CD Pipeline / Build Production Web Image (pull_request) Failing after 53h12m21s
CI/CD Pipeline / Canary Release to Production (pull_request) Failing after 53h12m19s
CI/CD Pipeline / ACR Image Cleanup (pull_request) Failing after 53h19m42s
CI/CD Pipeline / Staging API Integration Tests (pull_request) Failing after 53h19m43s
CI/CD Pipeline / Staging E2E Tests (pull_request) Failing after 53h19m43s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Failing after 53h19m46s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Failing after 53h55m6s
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
"""CI中自动修复代码格式(Python: black + isort | Frontend: prettier),并推送回原分支。
|
||||
"""CI中自动修复代码格式(Python: black + isort + ruff | Frontend: prettier),并推送回原分支。
|
||||
|
||||
- PR事件:所有PR只要Code Quality因格式问题失败,自动修复并push回源分支
|
||||
- Push事件(develop/main):自动修复并push回原分支,保持主干格式永远正确
|
||||
- 防循环:修复commit带 [skip ci-format-check] 标记,检测到该标记则跳过修复
|
||||
- 只修格式(black/isort/prettier),ruff逻辑类错误不动
|
||||
- black/isort/prettier 修格式;ruff check --fix --unsafe-fixes 自动修复
|
||||
ruff 可修复的 lint 规则(含 F401 未使用 import 等 unsafe fix)
|
||||
- ruff 目标范围与 validate_style.sh 的检查范围对齐:apps packages tests
|
||||
(alembic/scripts 不在 ruff 检查范围内,不做修复)
|
||||
当code quality检查因格式问题失败时触发。
|
||||
"""
|
||||
|
||||
@@ -135,7 +138,7 @@ def get_pr_head_branch(pr_number, api_url, token):
|
||||
|
||||
|
||||
def fix_python(target_py_files, scan_mode):
|
||||
"""修复 Python 文件格式 (black + isort)"""
|
||||
"""修复 Python 文件 (black 格式化 + isort 排序 + ruff lint 自动修复)"""
|
||||
if not target_py_files:
|
||||
print("没有需要修复的 Python 文件,跳过")
|
||||
return
|
||||
@@ -146,14 +149,48 @@ def fix_python(target_py_files, scan_mode):
|
||||
result = run(f"python3 -m black {target_str}", check=False)
|
||||
print(result.stdout[-500:] if result.stdout else "")
|
||||
if result.returncode != 0:
|
||||
print("black执行失败,但继续尝试isort", file=sys.stderr)
|
||||
print("black执行失败,但继续尝试isort/ruff", file=sys.stderr)
|
||||
|
||||
print()
|
||||
print("--- isort 排序 ---")
|
||||
result = run(f"python3 -m isort {target_str}", check=False)
|
||||
print(result.stdout[-500:] if result.stdout else "")
|
||||
if result.returncode != 0:
|
||||
print("isort执行失败", file=sys.stderr)
|
||||
print("isort执行失败,继续尝试ruff", file=sys.stderr)
|
||||
|
||||
# ruff lint 自动修复
|
||||
# 与 validate_style.sh 的检查范围对齐:只修 apps/packages/tests
|
||||
# (alembic 在 pyproject.toml 中被 exclude,scripts 不在 ruff 检查范围内)
|
||||
ruff_scopes = ("apps/", "packages/", "tests/")
|
||||
ruff_files = [f for f in target_py_files if f.startswith(ruff_scopes)]
|
||||
if scan_mode != "incremental":
|
||||
ruff_targets = "apps packages tests"
|
||||
elif ruff_files:
|
||||
ruff_targets = " ".join(ruff_files)
|
||||
else:
|
||||
ruff_targets = ""
|
||||
|
||||
if ruff_targets:
|
||||
# ruff 由 style job 的 requirements-dev.txt 安装;不可用时跳过(不阻断 black/isort 的修复)
|
||||
avail = run("python3 -m ruff --version", check=False)
|
||||
if avail.returncode != 0:
|
||||
print("ruff 不可用,跳过 ruff 自动修复", file=sys.stderr)
|
||||
else:
|
||||
print()
|
||||
print("--- ruff lint 自动修复 (--fix --unsafe-fixes) ---")
|
||||
# --unsafe-fixes 用于启用 F401(未使用 import)等 ruff 归类为 unsafe 的自动修复;
|
||||
# 安全性由修复后重跑的完整 CI(单测/构建/staging 健康检查)兜底
|
||||
result = run(
|
||||
f"python3 -m ruff check {ruff_targets} --fix --unsafe-fixes",
|
||||
check=False,
|
||||
)
|
||||
print(result.stdout[-1500:] if result.stdout else "")
|
||||
if result.returncode != 0:
|
||||
# 可能是仍有不可自动修复的 lint 错误(留待 style check 再次拦截),或修复过程出错
|
||||
print("ruff 自动修复后仍有未修复项或执行失败,剩余问题由 style check 继续拦截", file=sys.stderr)
|
||||
else:
|
||||
print()
|
||||
print("增量模式且无 apps/packages/tests 范围内的 Python 变更,跳过 ruff 自动修复")
|
||||
|
||||
|
||||
def fix_frontend(target_fe_files, scan_mode, repo_root):
|
||||
@@ -334,7 +371,7 @@ def main():
|
||||
# 提交修复
|
||||
run("git clean -fd")
|
||||
run("git add -u")
|
||||
run('git commit -m "style: auto-format with black + isort + prettier [skip ci-format-check]"')
|
||||
run('git commit -m "style: auto-format with black + isort + ruff + prettier [skip ci-format-check]"')
|
||||
|
||||
# 推送(head_branch已从ensure_git_repo获取)
|
||||
print(f"\nPR来源分支: {head_branch}")
|
||||
|
||||
@@ -209,7 +209,7 @@ rollback() {
|
||||
--restart unless-stopped \
|
||||
--cpus 2 \
|
||||
--memory 2g \
|
||||
--health-cmd "sh -c \"grep -q celery /proc/1/cmdline || exit 1\"" \
|
||||
--health-cmd "sh -c \"for pid in /proc/[0-9]*/cmdline; do if grep -ql celery \"$pid\" 2>/dev/null; then exit 0; fi; done; exit 1\"" \
|
||||
--health-interval 30s \
|
||||
--health-timeout 10s \
|
||||
--health-retries 3 \
|
||||
@@ -408,7 +408,7 @@ docker run -d \
|
||||
--restart unless-stopped \
|
||||
--cpus 2 \
|
||||
--memory 2g \
|
||||
--health-cmd "sh -c \"grep -q celery /proc/1/cmdline || exit 1\"" \
|
||||
--health-cmd "sh -c \"for pid in /proc/[0-9]*/cmdline; do if grep -ql celery \"$pid\" 2>/dev/null; then exit 0; fi; done; exit 1\"" \
|
||||
--health-interval 30s \
|
||||
--health-timeout 10s \
|
||||
--health-retries 3 \
|
||||
|
||||
@@ -310,7 +310,7 @@ docker run -d \
|
||||
--restart unless-stopped \
|
||||
--cpus 2 \
|
||||
--memory 2g \
|
||||
--health-cmd "sh -c \"grep -q celery /proc/1/cmdline || exit 1\"" \
|
||||
--health-cmd "sh -c \"for pid in /proc/[0-9]*/cmdline; do if grep -ql celery \"$pid\" 2>/dev/null; then exit 0; fi; done; exit 1\"" \
|
||||
--health-interval 30s \
|
||||
--health-timeout 10s \
|
||||
--health-retries 3 \
|
||||
|
||||
@@ -192,7 +192,7 @@ rollback() {
|
||||
-e PUBLIC_API_BASE_URL=https://staging-api.xiaoxiajianji.com \
|
||||
-v "$GENERATED_DIR:/app/generated" \
|
||||
--restart unless-stopped \
|
||||
--health-cmd "sh -c \"grep -q celery /proc/1/cmdline || exit 1\"" \
|
||||
--health-cmd "sh -c \"for pid in /proc/[0-9]*/cmdline; do if grep -ql celery \"\$pid\" 2>/dev/null; then exit 0; fi; done; exit 1\"" \
|
||||
--health-interval 30s \
|
||||
--health-timeout 10s \
|
||||
--health-retries 3 \
|
||||
@@ -501,7 +501,7 @@ docker run -d \
|
||||
-e PUBLIC_API_BASE_URL=https://staging-api.xiaoxiajianji.com \
|
||||
-v "$GENERATED_DIR:/app/generated" \
|
||||
--restart unless-stopped \
|
||||
--health-cmd "sh -c \"grep -q celery /proc/1/cmdline || exit 1\"" \
|
||||
--health-cmd "sh -c \"for pid in /proc/[0-9]*/cmdline; do if grep -ql celery \"\$pid\" 2>/dev/null; then exit 0; fi; done; exit 1\"" \
|
||||
--health-interval 30s \
|
||||
--health-timeout 10s \
|
||||
--health-retries 3 \
|
||||
|
||||
@@ -305,7 +305,7 @@ docker run -d \
|
||||
-e PUBLIC_API_BASE_URL=https://staging-api.xiaoxiajianji.com \
|
||||
-v "$GENERATED_DIR:/app/generated" \
|
||||
--restart unless-stopped \
|
||||
--health-cmd "sh -c \"grep -q celery /proc/1/cmdline || exit 1\"" \
|
||||
--health-cmd "sh -c \"for pid in /proc/[0-9]*/cmdline; do if grep -ql celery \"$pid\" 2>/dev/null; then exit 0; fi; done; exit 1\"" \
|
||||
--health-interval 30s \
|
||||
--health-timeout 10s \
|
||||
--health-retries 3 \
|
||||
|
||||
Executable
+159
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""素材库计数重算脚本(Issue #1776)。
|
||||
|
||||
用法:
|
||||
# Dry-run: 输出差异清单,不执行修改
|
||||
python scripts/recount_asset_counts.py --dry-run
|
||||
|
||||
# 执行修正
|
||||
python scripts/recount_asset_counts.py
|
||||
|
||||
# 只处理指定项目
|
||||
python scripts/recount_asset_counts.py --project-id <project_id>
|
||||
|
||||
# 只处理指定素材库
|
||||
python scripts/recount_asset_counts.py --library-id <library_id>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# 添加项目根目录到 path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from sqlalchemy import create_engine, func
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from packages.adapters.sqlalchemy_impl.models import AssetLibraryModel, AssetModel
|
||||
|
||||
|
||||
def get_db_session() -> Session:
|
||||
"""创建数据库 session。"""
|
||||
import os
|
||||
|
||||
database_url = os.getenv("DATABASE_URL")
|
||||
if not database_url:
|
||||
print("ERROR: DATABASE_URL environment variable not set")
|
||||
sys.exit(1)
|
||||
engine = create_engine(database_url)
|
||||
return Session(engine)
|
||||
|
||||
|
||||
def check_discrepancies(session: Session, project_id: str | None = None, library_id: str | None = None) -> list[dict]:
|
||||
"""检查素材库计数差异。
|
||||
|
||||
返回列表,每项包含:
|
||||
- library_id: 素材库 ID
|
||||
- library_name: 素材库名称
|
||||
- recorded_count: 记录的计数
|
||||
- actual_count: 实际计数
|
||||
- delta: 差异 (actual - recorded)
|
||||
"""
|
||||
query = session.query(AssetLibraryModel)
|
||||
if project_id:
|
||||
query = query.filter(AssetLibraryModel.project_id == project_id)
|
||||
if library_id:
|
||||
query = query.filter(AssetLibraryModel.id == library_id)
|
||||
|
||||
libraries = query.all()
|
||||
discrepancies = []
|
||||
|
||||
for lib in libraries:
|
||||
# 查询实际计数(排除 deleted)
|
||||
actual_count = (
|
||||
session.query(func.count(AssetModel.id))
|
||||
.filter(
|
||||
AssetModel.asset_library_id == lib.id,
|
||||
AssetModel.status != "deleted",
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
actual_size = (
|
||||
session.query(func.coalesce(func.sum(AssetModel.file_size), 0))
|
||||
.filter(
|
||||
AssetModel.asset_library_id == lib.id,
|
||||
AssetModel.status != "deleted",
|
||||
)
|
||||
.scalar()
|
||||
or 0
|
||||
)
|
||||
recorded_count = int(lib.asset_count or 0)
|
||||
recorded_size = int(lib.total_size or 0)
|
||||
|
||||
if actual_count != recorded_count or actual_size != recorded_size:
|
||||
discrepancies.append(
|
||||
{
|
||||
"library_id": lib.id,
|
||||
"library_name": lib.name,
|
||||
"project_id": lib.project_id,
|
||||
"kind": lib.kind,
|
||||
"recorded_count": recorded_count,
|
||||
"actual_count": actual_count,
|
||||
"count_delta": actual_count - recorded_count,
|
||||
"recorded_size": recorded_size,
|
||||
"actual_size": actual_size,
|
||||
"size_delta": actual_size - recorded_size,
|
||||
}
|
||||
)
|
||||
|
||||
return discrepancies
|
||||
|
||||
|
||||
def fix_discrepancies(session: Session, discrepancies: list[dict]) -> int:
|
||||
"""修正素材库计数。返回修正数量。"""
|
||||
fixed = 0
|
||||
for d in discrepancies:
|
||||
session.query(AssetLibraryModel).filter(AssetLibraryModel.id == d["library_id"]).update(
|
||||
{
|
||||
AssetLibraryModel.asset_count: d["actual_count"],
|
||||
AssetLibraryModel.total_size: d["actual_size"],
|
||||
}
|
||||
)
|
||||
fixed += 1
|
||||
session.commit()
|
||||
return fixed
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="素材库计数重算脚本(Issue #1776)")
|
||||
parser.add_argument("--dry-run", action="store_true", help="只输出差异清单,不执行修正")
|
||||
parser.add_argument("--project-id", type=str, help="只处理指定项目")
|
||||
parser.add_argument("--library-id", type=str, help="只处理指定素材库")
|
||||
args = parser.parse_args()
|
||||
|
||||
session = get_db_session()
|
||||
|
||||
try:
|
||||
discrepancies = check_discrepancies(session, args.project_id, args.library_id)
|
||||
|
||||
if not discrepancies:
|
||||
print("✅ 所有素材库计数一致,无需修正")
|
||||
return
|
||||
|
||||
# 输出差异清单
|
||||
print(f"发现 {len(discrepancies)} 个素材库计数不一致:\n")
|
||||
print(f"{'Library ID':<40} {'Name':<20} {'Recorded':<10} {'Actual':<10} {'Delta':<10}")
|
||||
print("-" * 90)
|
||||
for d in discrepancies:
|
||||
print(
|
||||
f"{d['library_id']:<40} {d['library_name'][:20]:<20} {d['recorded_count']:<10} {d['actual_count']:<10} {d['count_delta']:+<10}"
|
||||
)
|
||||
|
||||
total_delta = sum(d["count_delta"] for d in discrepancies)
|
||||
print(f"\n总计差异: {total_delta:+d}")
|
||||
|
||||
if args.dry_run:
|
||||
print("\n[DRY-RUN] 未执行修正。移除 --dry-run 参数以执行修正。")
|
||||
else:
|
||||
print("\n正在执行修正...")
|
||||
fixed = fix_discrepancies(session, discrepancies)
|
||||
print(f"✅ 已修正 {fixed} 个素材库计数")
|
||||
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -57,7 +57,7 @@ if [ "$TARGET_ENV" = "staging" ]; then
|
||||
fi
|
||||
|
||||
# 共用 secrets 直接导出(如果存在)
|
||||
SHARED_SECRETS="OSS_ACCESS_KEY_ID OSS_ACCESS_KEY_SECRET COSYVOICE_API_KEY DASHSCOPE_API_KEY MEDIAKIT_API_KEY"
|
||||
SHARED_SECRETS="OSS_ACCESS_KEY_ID OSS_ACCESS_KEY_SECRET COSYVOICE_API_KEY DASHSCOPE_API_KEY MEDIAKIT_API_KEY WECHAT_APP_ID WECHAT_APP_SECRET"
|
||||
for var in $SHARED_SECRETS; do
|
||||
value="${!var:-}"
|
||||
# 已经在环境中了,无需额外操作
|
||||
|
||||
@@ -201,7 +201,7 @@ docker run -d \
|
||||
--restart unless-stopped \
|
||||
--cpus 2 \
|
||||
--memory 2g \
|
||||
--health-cmd "sh -c \"grep -q celery /proc/1/cmdline || exit 1\"" \
|
||||
--health-cmd "sh -c \"for pid in /proc/[0-9]*/cmdline; do if grep -ql celery \"$pid\" 2>/dev/null; then exit 0; fi; done; exit 1\"" \
|
||||
--health-interval 30s \
|
||||
--health-timeout 10s \
|
||||
--health-retries 3 \
|
||||
|
||||
Reference in New Issue
Block a user