后端生成闸+多项修复;前端全站更新;QA 审计与报告

后端:
- 新增 celery_health 生成前置闸——无 worker 在线时图片/视频生成入口
  一律 503,防"提交到 ARK 后无人轮询、结果悬空+额度冻结"的数据丢失
- 拼接导出:帧率跟随源众数、字幕逐句重映射、原声/BGM 混音修复
- 资金核算审计脚本 + genesis 账目回填 + 滞留预留清理命令
- 接入 yunqi provider 与豆包 TTS 模型(catalog/migrations/bootstrap 命令)

前端:全站页面更新(pipeline/library/products/projects/team/account 等),
新增共享 pager 分页组件

QA:刷新 function-audit 全量输出,新增 full-qa 报告
文档:BP 产品介绍资料、design/CLAUDE.md

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
zyc
2026-06-15 10:06:15 +08:00
co-authored by Claude Fable 5
parent 890cb9ab67
commit 216a711291
92 changed files with 5114 additions and 2027 deletions
+6
View File
@@ -8,6 +8,12 @@ def get_current_team(user):
return membership.team
def can_manage_team(user, team):
"""团队管理权限:仅 owner / admin 可做充值、改限额、增删成员等管理动作。"""
membership = user.team_memberships.filter(team=team, status="active").first()
return bool(membership and membership.role in {"owner", "admin"})
class TeamScopedViewSetMixin:
team_field = "team"
+57
View File
@@ -0,0 +1,57 @@
"""Celery worker 在线探测 —— 生成类任务的提交前置闸。
设计决策(2026-06-10):无 worker 时**禁止提交**图片/视频生成,而非静默降级到前端轮询。
旧行为的风险:提交到 ARK 后浏览器一关就无人轮询,生成结果悬在云端无人取回入库、
额度预扣长期冻结——等于数据丢失。额度预检挡"钱不够",这道闸挡"环境不齐"
探测用 control.ping 广播,结果带缓存:在线缓存 30s(别让每次提交都付 ping 开销),
离线只缓存 5s(worker 一启动尽快解封)。CELERY_TASK_ALWAYS_EAGER(测试/同步模式)
下任务就地执行不需要 worker,直接放行。
"""
import time
from django.conf import settings
from rest_framework.exceptions import APIException
class WorkerUnavailable(APIException):
status_code = 503
default_detail = (
"后台任务服务(Celery worker)未运行:生成结果将无人取回入库,可能造成数据丢失。"
"已暂停生成功能,请启动 worker 后重试(celery -A airshelf worker)。"
)
default_code = "worker_unavailable"
_OK_TTL = 30.0
_FAIL_TTL = 5.0
_cache = {"ok": False, "expires": 0.0}
def _ping_workers() -> bool:
from airshelf.celery import app as celery_app
# limit=1:收到第一个 worker 回包立即返回,不傻等满 timeout
return bool(celery_app.control.ping(timeout=1.0, limit=1))
def celery_worker_available() -> bool:
if getattr(settings, "CELERY_TASK_ALWAYS_EAGER", False):
return True
now = time.monotonic()
if now < _cache["expires"]:
return _cache["ok"]
try:
ok = _ping_workers()
except Exception: # noqa: BLE001 — broker 不可达等同于无 worker,一样要拦
ok = False
_cache["ok"] = ok
_cache["expires"] = now + (_OK_TTL if ok else _FAIL_TTL)
return ok
def require_worker() -> None:
"""生成类提交入口的前置检查:无 worker 直接 503,不让任务出门。"""
if not celery_worker_available():
raise WorkerUnavailable()
@@ -0,0 +1,45 @@
from django.core.management.base import BaseCommand
from apps.ai.catalog import VOLCANO_PROVIDER, YUNQI_MODELS, YUNQI_PROVIDER
from apps.ai.models import ModelConfig, ModelProvider
class Command(BaseCommand):
help = "Create or update YunQi (New API) provider with gpt-image-2, and disable Volcano image models."
def handle(self, *args, **options):
provider, _ = ModelProvider.objects.update_or_create(
name=YUNQI_PROVIDER["name"],
defaults={
"display_name": YUNQI_PROVIDER["display_name"],
"base_url": YUNQI_PROVIDER["base_url"],
"status": ModelProvider.Status.ACTIVE,
},
)
count = 0
for item in YUNQI_MODELS:
ModelConfig.objects.update_or_create(
provider=provider,
name=item["name"],
capability=item["capability"],
defaults={
"display_name": item["display_name"],
"endpoint": item["endpoint"],
"status": ModelConfig.Status.ACTIVE,
"metadata": item["metadata"],
},
)
count += 1
# 全站生图切到 YunQi:火山 image 模型停用,文本/视频模型保持不动
disabled = ModelConfig.objects.filter(
provider__name=VOLCANO_PROVIDER["name"],
capability=ModelConfig.Capability.IMAGE,
).update(status=ModelConfig.Status.DISABLED)
self.stdout.write(
self.style.SUCCESS(
f"Bootstrapped {count} YunQi model config(s); disabled {disabled} Volcano image model(s)."
)
)