后端生成闸+多项修复;前端全站更新;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:
@@ -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"
|
||||
|
||||
|
||||
@@ -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)."
|
||||
)
|
||||
)
|
||||
Reference in New Issue
Block a user