真人素材库V3 | 虚拟人像库 | app/admin build

This commit is contained in:
2026-07-07 13:32:07 +08:00
parent 5ffe8e0893
commit 64714c6d42
7 changed files with 394 additions and 184 deletions
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -28,7 +28,7 @@
}
})();
</script>
<script type="module" crossorigin src="/assets/index-BJj5fRkV.js"></script>
<script type="module" crossorigin src="/assets/index-Bo_aUzmj.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-D7ShJUt4.css">
</head>
<body>
+4
View File
@@ -57,6 +57,10 @@ VIDEO_COVER_WIDTH=600
VIDEO_COVER_TIMEOUT_SECONDS=15
VIDEO_COVER_FORMAT=png
# VOLC
VOLC_ACCESS_KEY_ID=AKLTYWY5Yjc5YjM3N2IwNDc3M2I3NTU2YjlmNTczYzQzMmM
VOLC_SECRET_ACCESS_KEY=TXpjM01HUTFZMlV5TUdKbE5Ea3lNRGhqTUdSak16UTFOV0ptTW1SaE5XRQ==
# VOLC_SMS
VOLC_SMS_ACCESS_KEY_ID=AKLTYWY5Yjc5YjM3N2IwNDc3M2I3NTU2YjlmNTczYzQzMmM
VOLC_SMS_SECRET_ACCESS_KEY=TXpjM01HUTFZMlV5TUdKbE5Ea3lNRGhqTUdSak16UTFOV0ptTW1SaE5XRQ==
@@ -0,0 +1,202 @@
"""rename private portrait asset limit and add library type
Revision ID: cd7688999204
Revises: e7038d02c355
Create Date: 2026-07-07 13:21:49.612465
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = "cd7688999204"
down_revision: Union[str, None] = "e7038d02c355"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# 1. users 字段改名:必须 rename,保留原有用户额度数据
op.alter_column(
"users",
"private_portrait_image_limit",
new_column_name="private_portrait_asset_limit",
existing_type=sa.Integer(),
existing_nullable=False,
existing_server_default=sa.text("5"),
comment="私域人像素材总量限制,真人/虚拟共用,图片/视频共用,0 表示关闭",
existing_comment="私域真人图片素材数量限制,0表示关闭",
)
# 2. private_portrait_asset_groups
op.add_column(
"private_portrait_asset_groups",
sa.Column(
"library_type",
sa.String(length=32),
server_default="real_person",
nullable=False,
comment="素材库类型:real_person 真人认证;aigc_virtual 虚拟人像",
),
)
op.create_index(
"idx_private_portrait_asset_groups_library_type",
"private_portrait_asset_groups",
["library_type"],
unique=False,
)
op.create_index(
"idx_private_portrait_asset_groups_user_library",
"private_portrait_asset_groups",
["user_id", "library_type"],
unique=False,
)
# 3. private_portrait_assets
op.add_column(
"private_portrait_assets",
sa.Column(
"library_type",
sa.String(length=32),
server_default="real_person",
nullable=False,
comment="素材库类型:real_person 真人认证;aigc_virtual 虚拟人像",
),
)
op.add_column(
"private_portrait_assets",
sa.Column("video_duration", sa.Float(), nullable=True, comment="视频素材时长,秒"),
)
op.add_column(
"private_portrait_assets",
sa.Column("video_cover_url", sa.Text(), nullable=True, comment="视频素材封面预览地址"),
)
op.add_column(
"private_portrait_assets",
sa.Column("file_size", sa.Integer(), nullable=True, comment="素材文件大小,字节"),
)
op.add_column(
"private_portrait_assets",
sa.Column("mime_type", sa.String(length=128), nullable=True, comment="素材 MIME 类型"),
)
# 如果 private_portrait_assets.asset_type 已经存在索引,这条要删除
op.create_index(
"idx_private_portrait_assets_asset_type",
"private_portrait_assets",
["asset_type"],
unique=False,
)
op.create_index(
"idx_private_portrait_assets_library_type",
"private_portrait_assets",
["library_type"],
unique=False,
)
op.create_index(
"idx_private_portrait_assets_user_library_status_created",
"private_portrait_assets",
["user_id", "library_type", "status", "created_at"],
unique=False,
)
# 4. private_portrait_projects
op.add_column(
"private_portrait_projects",
sa.Column(
"library_type",
sa.String(length=32),
server_default="real_person",
nullable=False,
comment="素材库类型:real_person 真人认证;aigc_virtual 虚拟人像",
),
)
op.add_column(
"private_portrait_projects",
sa.Column("image_asset_count", sa.Integer(), server_default="0", nullable=False, comment="图片素材数量"),
)
op.add_column(
"private_portrait_projects",
sa.Column("video_asset_count", sa.Integer(), server_default="0", nullable=False, comment="视频素材数量"),
)
op.add_column(
"private_portrait_projects",
sa.Column("active_image_asset_count", sa.Integer(), server_default="0", nullable=False, comment="可用图片素材数量"),
)
op.add_column(
"private_portrait_projects",
sa.Column("active_video_asset_count", sa.Integer(), server_default="0", nullable=False, comment="可用视频素材数量"),
)
op.create_index(
"idx_private_portrait_projects_library_type",
"private_portrait_projects",
["library_type"],
unique=False,
)
op.create_index(
"idx_private_portrait_projects_user_library_status_created",
"private_portrait_projects",
["user_id", "library_type", "status", "created_at"],
unique=False,
)
def downgrade() -> None:
# 1. users 字段反向改名:保留数据
op.alter_column(
"users",
"private_portrait_asset_limit",
new_column_name="private_portrait_image_limit",
existing_type=sa.Integer(),
existing_nullable=False,
existing_server_default=sa.text("5"),
comment="私域真人图片素材数量限制,0表示关闭",
existing_comment="私域人像素材总量限制,真人/虚拟共用,图片/视频共用,0 表示关闭",
)
# 2. private_portrait_projects
op.drop_index(
"idx_private_portrait_projects_user_library_status_created",
table_name="private_portrait_projects",
)
op.drop_index(
"idx_private_portrait_projects_library_type",
table_name="private_portrait_projects",
)
op.drop_column("private_portrait_projects", "active_video_asset_count")
op.drop_column("private_portrait_projects", "active_image_asset_count")
op.drop_column("private_portrait_projects", "video_asset_count")
op.drop_column("private_portrait_projects", "image_asset_count")
op.drop_column("private_portrait_projects", "library_type")
# 3. private_portrait_assets
op.drop_index(
"idx_private_portrait_assets_user_library_status_created",
table_name="private_portrait_assets",
)
op.drop_index(
"idx_private_portrait_assets_library_type",
table_name="private_portrait_assets",
)
op.drop_index(
"idx_private_portrait_assets_asset_type",
table_name="private_portrait_assets",
)
op.drop_column("private_portrait_assets", "mime_type")
op.drop_column("private_portrait_assets", "file_size")
op.drop_column("private_portrait_assets", "video_cover_url")
op.drop_column("private_portrait_assets", "video_duration")
op.drop_column("private_portrait_assets", "library_type")
# 4. private_portrait_asset_groups
op.drop_index(
"idx_private_portrait_asset_groups_user_library",
table_name="private_portrait_asset_groups",
)
op.drop_index(
"idx_private_portrait_asset_groups_library_type",
table_name="private_portrait_asset_groups",
)
op.drop_column("private_portrait_asset_groups", "library_type")
+4
View File
@@ -37,6 +37,10 @@ class Settings(BaseSettings):
SMS_TEMPLATE_CODE: str = "SMS_001"
SMS_MOCK: bool = True
# 火山引擎配置。
VOLC_ACCESS_KEY_ID: str = ""
VOLC_SECRET_ACCESS_KEY: str = ""
# 火山引擎短信配置。
# SmsAccount=消息组IDTemplateID=模板IDSign=短信签名内容。
VOLC_SMS_ACCESS_KEY_ID: str = ""
@@ -53,9 +53,9 @@ def _remote_error_http_status(code: str) -> int:
class ArkPrivateAssetClient:
"""火山 Ark 私域可信素材 Asset API Client。只做 AK/SK 鉴权调用与响应标准化。"""
def __init__(self, *, ak: str | None = None, sk: str | None = None, for_celery: bool = False):
self.ak = ak or settings.VOLC_SMS_ACCESS_KEY_ID
self.sk = sk or settings.VOLC_SMS_SECRET_ACCESS_KEY
def __init__(self, *, for_celery: bool = False):
self.ak = settings.VOLC_ACCESS_KEY_ID
self.sk = settings.VOLC_SECRET_ACCESS_KEY
self.for_celery = for_celery
if not self.ak or not self.sk:
raise ArkPrivateAssetClientError("火山 AK/SK 未配置:VOLC_SMS_ACCESS_KEY_ID / VOLC_SMS_SECRET_ACCESS_KEY")