1、增加调用 AI 视频生成能力和虚拟素材库管理的对外api
2、增加后台apikkey管理 3、增加apikey单独的模型定价 4、增加apikey调用情况 5、完善所有数据的注释增加
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
from app.models.virtual_portrait_v3.api_key_quota import VpV3ApiKeyQuota
|
||||
from app.models.virtual_portrait_v3.project import VpV3Project
|
||||
from app.models.virtual_portrait_v3.asset import VpV3Asset
|
||||
|
||||
__all__ = [
|
||||
"VpV3ApiKeyQuota",
|
||||
"VpV3Project",
|
||||
"VpV3Asset",
|
||||
]
|
||||
@@ -0,0 +1,61 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, Text, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.models.base import Base, TimestampMixin, SoftDeleteMixin
|
||||
|
||||
|
||||
class VpV3ApiKeyQuota(Base, TimestampMixin):
|
||||
"""API V3 虚拟素材库配额(每个 ApiKey 一份,默认 0=不可用)。
|
||||
|
||||
配额在创建/删除项目、上传/删除素材时实时统计(直接 COUNT/SUM),
|
||||
避免缓存不准;配额字段默认 0,后台管理配置后才可用。
|
||||
"""
|
||||
|
||||
__tablename__ = "vp_v3_api_key_quotas"
|
||||
__table_args__ = (
|
||||
Index("uq_vp_v3_api_key_quotas_key_id", "api_key_id", unique=True),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
||||
api_key_id: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
ForeignKey("api_keys.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
index=True,
|
||||
comment="所属 API Key,唯一:一个 API Key 只有一份虚拟素材配额",
|
||||
)
|
||||
|
||||
# 配额上限(默认 0 = 不可使用该功能)
|
||||
project_limit: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=0, server_default="0",
|
||||
comment="虚拟项目上限,默认 0 不可创建",
|
||||
)
|
||||
asset_limit: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=0, server_default="0",
|
||||
comment="虚拟素材总数上限(图片+视频),默认 0 不可上传",
|
||||
)
|
||||
storage_mb_limit: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=0, server_default="0",
|
||||
comment="上传存储上限 MB,默认 0 不可上传文件",
|
||||
)
|
||||
|
||||
# 已使用量(冗余字段提升性能,每次增删同步,和真实 COUNT 不一致时以 COUNT 为准)
|
||||
project_used: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=0, server_default="0",
|
||||
comment="已创建项目数(未删除)",
|
||||
)
|
||||
asset_used: Mapped[int] = mapped_column(
|
||||
Integer, nullable=False, default=0, server_default="0",
|
||||
comment="已上传素材数(未删除,图片+视频)",
|
||||
)
|
||||
storage_mb_used: Mapped[float] = mapped_column(
|
||||
Integer, nullable=False, default=0, server_default="0",
|
||||
comment="已占用存储 MB(未删除文件大小合计,1MB=1024*1024)",
|
||||
)
|
||||
|
||||
remark: Mapped[str | None] = mapped_column(Text, nullable=True, comment="后台备注")
|
||||
@@ -0,0 +1,95 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, Float, ForeignKey, Index, Integer, String, Text, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.enums.private_portrait import (
|
||||
PrivatePortraitAssetStatus,
|
||||
PrivatePortraitAssetType,
|
||||
PrivatePortraitRemoteDeleteStatus,
|
||||
)
|
||||
from app.models.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
|
||||
|
||||
class VpV3Asset(Base, TimestampMixin, SoftDeleteMixin):
|
||||
"""API V3 虚拟素材(图片/视频),归属某个 Project(=火山 1 个 AssetGroup)。
|
||||
|
||||
字段语义和 private_portrait.PrivatePortraitAsset 保持一致,便于 service 层复用逻辑。
|
||||
"""
|
||||
|
||||
__tablename__ = "vp_v3_assets"
|
||||
__table_args__ = (
|
||||
Index("uq_vp_v3_assets_remote_asset_id", "remote_asset_id", unique=True),
|
||||
Index("idx_vp_v3_assets_key_status_created", "api_key_id", "status", "created_at"),
|
||||
Index("idx_vp_v3_assets_project_status_created", "project_id", "status", "created_at"),
|
||||
Index(
|
||||
"idx_vp_v3_assets_next_poll_status",
|
||||
"next_poll_at",
|
||||
"status",
|
||||
postgresql_where=text("deleted_at IS NULL AND next_poll_at IS NOT NULL"),
|
||||
),
|
||||
Index("idx_vp_v3_assets_remote_delete_status", "remote_delete_status"),
|
||||
Index("idx_vp_v3_assets_asset_type", "asset_type"),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
||||
api_key_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("api_keys.id", ondelete="CASCADE"), nullable=False, index=True,
|
||||
)
|
||||
project_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("vp_v3_projects.id", ondelete="CASCADE"), nullable=False, index=True,
|
||||
)
|
||||
|
||||
# 火山远端映射
|
||||
remote_project_name: Mapped[str] = mapped_column(String(256), nullable=False, index=True)
|
||||
remote_group_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
|
||||
remote_asset_id: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
|
||||
# 素材元信息
|
||||
asset_type: Mapped[str] = mapped_column(
|
||||
String(16), nullable=False, default=PrivatePortraitAssetType.IMAGE.value, index=True,
|
||||
comment="素材类型:Image=图片 / Video=视频",
|
||||
)
|
||||
name: Mapped[str | None] = mapped_column(String(128), nullable=True, index=True)
|
||||
|
||||
# 资源 URL
|
||||
source_url: Mapped[str] = mapped_column(Text, nullable=False, comment="本地上传后的访问 URL(UploadResource 返回的)")
|
||||
preview_url: Mapped[str | None] = mapped_column(Text, nullable=True, comment="给前端预览/显示用的 URL(签名 URL 可能过期)")
|
||||
remote_url: Mapped[str | None] = mapped_column(Text, nullable=True, comment="火山返回的资源访问 URL(可能带签名和过期)")
|
||||
remote_url_expired_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
upload_resource_id: Mapped[str | None] = mapped_column(
|
||||
String(32), nullable=True, index=True, comment="本地 UploadResource 账本 resource_id(容量释放用)",
|
||||
)
|
||||
video_duration: Mapped[float | None] = mapped_column(Float, nullable=True, comment="视频时长,秒")
|
||||
video_cover_url: Mapped[str | None] = mapped_column(Text, nullable=True, comment="视频封面预览")
|
||||
file_size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True, comment="素材文件大小,字节")
|
||||
mime_type: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||||
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(32), nullable=False,
|
||||
default=PrivatePortraitAssetStatus.CREATING.value,
|
||||
server_default=PrivatePortraitAssetStatus.CREATING.value,
|
||||
index=True,
|
||||
comment="素材状态:creating/审核中 active/可用 failed/失败 deleting/删除中",
|
||||
)
|
||||
moderation_json: Mapped[str | None] = mapped_column(Text, nullable=True, comment="火山审核结果 JSON")
|
||||
error_message: Mapped[str | None] = mapped_column(Text, nullable=True, comment="失败原因")
|
||||
raw_response_json: Mapped[str | None] = mapped_column(Text, nullable=True, comment="火山原始响应 JSON")
|
||||
|
||||
# 轮询控制(异步审核)
|
||||
last_poll_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
next_poll_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
|
||||
poll_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
|
||||
# 远端删除
|
||||
remote_delete_status: Mapped[str] = mapped_column(
|
||||
String(32), nullable=False,
|
||||
default=PrivatePortraitRemoteDeleteStatus.NONE.value,
|
||||
server_default=PrivatePortraitRemoteDeleteStatus.NONE.value,
|
||||
index=True,
|
||||
)
|
||||
remote_deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
remote_delete_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
@@ -0,0 +1,86 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, Text, text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.enums.private_portrait import (
|
||||
PrivatePortraitProjectStatus,
|
||||
PrivatePortraitRemoteDeleteStatus,
|
||||
)
|
||||
from app.models.base import Base, SoftDeleteMixin, TimestampMixin
|
||||
|
||||
|
||||
class VpV3Project(Base, TimestampMixin, SoftDeleteMixin):
|
||||
"""API V3 虚拟素材项目(按 API Key 隔离)。
|
||||
|
||||
一个 VpV3Project 对应火山远端的 1 个 AssetGroup(一对一:这里不做 nested group)。
|
||||
"""
|
||||
|
||||
__tablename__ = "vp_v3_projects"
|
||||
__table_args__ = (
|
||||
Index("idx_vp_v3_projects_key_status_created", "api_key_id", "status", "created_at"),
|
||||
Index(
|
||||
"idx_vp_v3_projects_key_deleted",
|
||||
"api_key_id",
|
||||
"deleted_at",
|
||||
postgresql_where=text("deleted_at IS NULL"),
|
||||
),
|
||||
Index("idx_vp_v3_projects_remote_project_name", "remote_project_name"),
|
||||
Index("idx_vp_v3_projects_remote_group_id", "remote_group_id"),
|
||||
)
|
||||
|
||||
id: Mapped[str] = mapped_column(String(32), primary_key=True)
|
||||
api_key_id: Mapped[str] = mapped_column(
|
||||
String(32), ForeignKey("api_keys.id", ondelete="CASCADE"), nullable=False, index=True,
|
||||
comment="所属 API Key(V3 调用方)",
|
||||
)
|
||||
|
||||
name: Mapped[str] = mapped_column(String(128), nullable=False, index=True, comment="项目展示名称")
|
||||
name_slug: Mapped[str] = mapped_column(String(128), nullable=False, index=True, comment="名称安全 slug(构建远端 GroupName 用)")
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
# 火山远端映射
|
||||
remote_project_name: Mapped[str] = mapped_column(
|
||||
String(256), nullable=False, index=True, comment="火山 ProjectName(快照)",
|
||||
)
|
||||
remote_group_id: Mapped[str] = mapped_column(
|
||||
String(128), nullable=False, index=True, comment="火山 AssetGroup Id",
|
||||
)
|
||||
remote_group_name: Mapped[str | None] = mapped_column(String(256), nullable=True, comment="火山 AssetGroup Name 快照")
|
||||
|
||||
status: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
default=PrivatePortraitProjectStatus.ACTIVE.value,
|
||||
server_default=PrivatePortraitProjectStatus.ACTIVE.value,
|
||||
index=True,
|
||||
comment="项目状态:active/creating_remote_group/create_group_failed/deleting",
|
||||
)
|
||||
|
||||
# 计数
|
||||
asset_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
active_asset_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
image_asset_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
video_asset_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
active_image_asset_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
active_video_asset_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, server_default="0")
|
||||
storage_mb_used: Mapped[float] = mapped_column(Integer, nullable=False, default=0, server_default="0",
|
||||
comment="项目占用存储 MB(未删除素材文件大小合计)")
|
||||
|
||||
last_used_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# 远端删除状态(沿用 private_portrait 枚举)
|
||||
remote_delete_status: Mapped[str] = mapped_column(
|
||||
String(32),
|
||||
nullable=False,
|
||||
default=PrivatePortraitRemoteDeleteStatus.NONE.value,
|
||||
server_default=PrivatePortraitRemoteDeleteStatus.NONE.value,
|
||||
index=True,
|
||||
)
|
||||
remote_deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
remote_delete_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
error_message: Mapped[str | None] = mapped_column(Text, nullable=True, comment="创建失败等错误信息")
|
||||
raw_response_json: Mapped[str | None] = mapped_column(Text, nullable=True, comment="火山原始响应")
|
||||
Reference in New Issue
Block a user