API接口格式更新

This commit is contained in:
shuohigh@gmail.com 2025-06-16 16:02:21 +08:00
parent f1ed7f0da9
commit 2c042eece1
4 changed files with 57 additions and 42 deletions

View File

@ -1,4 +1,4 @@
MODAL_ENVIRONMENT=prod MODAL_ENVIRONMENT=test
modal_app_name=bowong-ai-video modal_app_name=bowong-ai-video
S3_mount_dir=/mntS3 S3_mount_dir=/mntS3
S3_bucket_name=modal-media-cache S3_bucket_name=modal-media-cache

View File

@ -154,9 +154,14 @@ class MediaSource(BaseModel):
@property @property
def local_available(self) -> bool: def local_available(self) -> bool:
if self.status == MediaCacheStatus.ready: if self.status == MediaCacheStatus.ready:
return os.path.exists(self.local_mount_path) return self.local_exists
return False return False
@computed_field(description="是否存在本地文件")
@property
def local_exists(self) -> bool:
return os.path.exists(self.local_mount_path)
@computed_field(description="本地挂载地址") @computed_field(description="本地挂载地址")
@property @property
def local_mount_path(self) -> str: def local_mount_path(self) -> str:

View File

@ -56,6 +56,15 @@ class ModalTaskResponse(BaseModel):
taskId: str = Field(description="任务Id") taskId: str = Field(description="任务Id")
class CacheDeleteTaskResponse(BaseModel):
success: bool = Field(description="运行成功")
keys: List[str] = Field(description="成功从KV和S3删除掉的URN")
non_kv_keys: List[str] = Field(alias="nonKVKeys", serialization_alias="nonKVKeys",
description="成功从S3删除的URN, 不存在于KV中")
not_found_keys: List[str] = Field(alias="notFoundKeys", serialization_alias="notFoundKeys",
description="不存在的URN")
class RecordingTaskResponse(BaseModel): class RecordingTaskResponse(BaseModel):
success: bool = Field(description="任务接受成功") success: bool = Field(description="任务接受成功")
taskId: str = Field(description="任务Id") taskId: str = Field(description="任务Id")

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
import datetime import datetime
import os import os
from typing import Annotated, Optional from typing import Annotated, Optional, List, Tuple
import modal import modal
from loguru import logger from loguru import logger
@ -22,10 +22,10 @@ from ..models.media_model import (MediaSources,
DownloadResult, DownloadResult,
UploadResultResponse, UploadResultResponse,
UploadBase64Request, UploadPresignRequest, UploadPresignResponse, UploadBase64Request, UploadPresignRequest, UploadPresignResponse,
UploadMultipartPresignRequest, UploadMultipartPresignResponse UploadMultipartPresignRequest, UploadMultipartPresignResponse, MediaProtocol
) )
from ..models.web_model import SentryTransactionInfo, MonitorLiveRoomProductRequest, ModalTaskResponse, \ from ..models.web_model import SentryTransactionInfo, MonitorLiveRoomProductRequest, ModalTaskResponse, \
LiveRoomProductCachesResponse LiveRoomProductCachesResponse, CacheDeleteTaskResponse
from ..utils.KVCache import MediaSourceKVCache, LiveProductKVCache from ..utils.KVCache import MediaSourceKVCache, LiveProductKVCache
from ..utils.SentryUtils import SentryUtils from ..utils.SentryUtils import SentryUtils
@ -127,29 +127,55 @@ async def cache(medias: MediaSources) -> CacheResult:
summary="清除指定的所有缓存", summary="清除指定的所有缓存",
description="清除指定的所有缓存(包括KV记录和S3存储文件)", description="清除指定的所有缓存(包括KV记录和S3存储文件)",
dependencies=[Depends(verify_token)]) dependencies=[Depends(verify_token)])
async def purge_media_kv_file(medias: MediaSources): async def purge_media_kv_file(medias: MediaSources) -> CacheDeleteTaskResponse:
fn_id = current_function_call_id() fn_id = current_function_call_id()
fn = modal.Function.from_name(config.modal_app_name, "cache_delete", environment_name=config.modal_environment) fn = modal.Function.from_name(config.modal_app_name, "cache_delete", environment_name=config.modal_environment)
@SentryUtils.sentry_tracker(name="清除媒体源缓存", op="cache.purge", fn_id=fn_id, @SentryUtils.sentry_tracker(name="清除媒体源缓存", op="cache.purge", fn_id=fn_id,
sentry_trace_id=None, sentry_baggage=None) sentry_trace_id=None, sentry_baggage=None)
async def purge_handle(media: MediaSource): async def purge_handle(media: MediaSource) -> Tuple[Optional[str], int]:
cache_media = modal_kv_cache.pop(media.urn) try:
if cache_media: cache_media = modal_kv_cache.pop(media.urn)
deleted_cache: MediaSource = await fn.remote.aio(cache_media) if cache_media:
return deleted_cache.urn deleted_cache: MediaSource = await fn.remote.aio(cache_media)
return None return deleted_cache.urn, 1
except KeyError as e:
logger.exception(e)
if media.local_exists:
deleted_cache: MediaSource = await fn.remote.aio(media)
if deleted_cache.status == MediaCacheStatus.missing:
logger.warning(f"不存在s3挂载文件 {deleted_cache.urn}")
return None, 0
else:
logger.warning(f"s3挂载文件 {deleted_cache.urn} 已删除")
return deleted_cache.urn, 0
return media.urn, -1
async with asyncio.TaskGroup() as group: async with asyncio.TaskGroup() as group:
tasks = [group.create_task(purge_handle(media)) for media in medias.inputs] tasks = [group.create_task(purge_handle(media)) for media in medias.inputs]
keys = [task.result() for task in tasks] keys: List[str] = []
non_kv_keys: List[str] = []
error_keys: List[str] = []
for task in tasks:
urn, task_status = task.result()
if urn:
if task_status == 1: # 成功从kv和s3删除
keys.append(urn)
elif task_status == 2: # 只从s3删除
non_kv_keys.append(urn)
else:
error_keys.append(urn)
# keys = [task.result() for task in tasks]
modal_kv_cache.batch_remove_cloudflare_kv(keys) modal_kv_cache.batch_remove_cloudflare_kv(keys)
return JSONResponse(content={"success": True, "keys": keys}) return CacheDeleteTaskResponse(success=True, keys=keys, nonKVKeys=non_kv_keys, notFoundKeys=error_keys)
# return JSONResponse(content={"success": True, "keys": keys, "nonKVKeys": non_kv_keys, "errorKeys": error_keys})
@router.post("/download", @router.post("/download",
summary="批量获取下载地址", summary="批量获取下载地址, 返回的MediaSource类自带CDN访问URL, 不需要另外请求获取",
deprecated=True,
description="获取已缓存的视频下载地址", description="获取已缓存的视频下载地址",
dependencies=[Depends(verify_token)]) dependencies=[Depends(verify_token)])
@sentry_sdk.trace @sentry_sdk.trace
@ -163,7 +189,8 @@ async def download_caches(medias: MediaSources) -> DownloadResult:
@router.get("/download", @router.get("/download",
summary="下载已缓存的视频", summary="下载已缓存的视频",
description="通过CDN下载已缓存的视频文件") deprecated=True,
description="通过CDN下载已缓存的视频文件, 不在提供通过此接口下载请使用CDN URL直接下载文件")
@sentry_sdk.trace @sentry_sdk.trace
async def download_cache(media: str) -> RedirectResponse: async def download_cache(media: str) -> RedirectResponse:
cdn_endpoint = config.S3_cdn_endpoint cdn_endpoint = config.S3_cdn_endpoint
@ -199,32 +226,6 @@ async def purge_kv(medias: MediaSources):
return JSONResponse(content={"success": False, "error": str(e)}) return JSONResponse(content={"success": False, "error": str(e)})
@router.post("/media",
summary="清除指定的所有缓存",
description="清除指定的所有缓存(包括KV记录和S3存储文件), 将要被淘汰使用DELETE /cache/替代",
deprecated=True,
dependencies=[Depends(verify_token)])
async def purge_media(medias: MediaSources):
fn_id = current_function_call_id()
fn = modal.Function.from_name(config.modal_app_name, "cache_delete", environment_name=config.modal_environment)
@SentryUtils.sentry_tracker(name="清除媒体源缓存", op="cache.purge", fn_id=fn_id,
sentry_trace_id=None, sentry_baggage=None)
async def purge_handle(media: MediaSource):
cache_media = modal_kv_cache.pop(media.urn)
if cache_media:
deleted_cache: MediaSource = await fn.remote.aio(cache_media)
return deleted_cache.urn
return None
async with asyncio.TaskGroup() as group:
tasks = [group.create_task(purge_handle(media)) for media in medias.inputs]
keys = [task.result() for task in tasks]
modal_kv_cache.batch_remove_cloudflare_kv(keys)
return JSONResponse(content={"success": True, "keys": keys})
@router.post("/upload-s3", @router.post("/upload-s3",
summary="上传文件到S3", summary="上传文件到S3",
description="上传文件到S3的文件必须小于200M", description="上传文件到S3的文件必须小于200M",