feat(core): 火山人像素材库审核(绿/红盾)接入 + 端到端验证

- assets_client.py:照搬 AirDrama,volcengine SDK + AK/SK 签名(凭证走 settings.ASSETS_API)。
- review.py:真人资产送审/轮询编排。一团队一组(AssetReviewGroup,OneToOne),全 best-effort 不破坏主流程。
- 模型:AssetReviewGroup + Asset.review_status/review_remote_id/review_error(迁移 assets/0003)。
- 集成:真人基础资产生成后 transaction.on_commit 静默送审(services.generate_base_asset kind==person)。
- 端点 poll-reviews + 序列化暴露 review_status;前端基础资产趴每8s轮询、人物卡渲染 审核✓(绿)/审核✗·重生(红)/审核中,审核终态后停轮询。
- e2e 已验:送审→建组→processing→active(真拿到火山绿盾)。

对抗式交叉验证(2审查员,0 critical)修复:
- get_or_create_team_group 并发竞态:DB 唯一约束去重 + select_for_update 串行化远程建组,杜绝重复建组/丢送审。
- create_asset 返回空 Id 不再标 processing(否则卡死黄);processing 超 15 分钟兜底判 failed,未知 Status 记日志,防永久 processing + 无限轮询。

凭证暂借 AirDrama,.env 改 ASSETS_API_* 两行即可换;ASSETS_API_ENABLED=false 可一键关停。回归 18/18 过。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
seaislee1209
2026-06-17 10:51:23 +08:00
co-authored by Claude Opus 4.8
parent 521f5f4535
commit b30eaed838
14 changed files with 375 additions and 14 deletions
@@ -0,0 +1,45 @@
# Generated by Django 5.1.15 on 2026-06-17 02:28
import django.db.models.deletion
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0002_loginsession_userpreference'),
('assets', '0002_initial'),
]
operations = [
migrations.AddField(
model_name='asset',
name='review_error',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='asset',
name='review_remote_id',
field=models.CharField(blank=True, max_length=128),
),
migrations.AddField(
model_name='asset',
name='review_status',
field=models.CharField(blank=True, max_length=16),
),
migrations.CreateModel(
name='AssetReviewGroup',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('remote_group_id', models.CharField(max_length=128)),
('name', models.CharField(blank=True, max_length=128)),
('team', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='review_group', to='accounts.team')),
],
options={
'abstract': False,
},
),
]