主要更新: - 前端改用 Ant Design 组件(Table、Modal、Select 等) - 支持三种搜索方式:星图ID、达人unique_id、达人昵称模糊匹配 - 列表页实时调用云图 API 获取 A3 数据和成本指标 - 详情弹窗显示完整 6 大类指标,支持文字复制 - 品牌 API URL 格式修复为查询参数形式 - 优化云图 API 参数格式和会话池管理 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
176 lines
4.9 KiB
Python
176 lines
4.9 KiB
Python
"""
|
||
视频分析API路由 (T-024)
|
||
|
||
GET /api/v1/videos/{item_id}/analysis - 单个视频分析
|
||
POST /api/v1/videos/search - 搜索视频列表(支持 star_id / nickname)
|
||
"""
|
||
|
||
from typing import List, Optional
|
||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||
from pydantic import BaseModel
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.database import get_db
|
||
from app.services.video_analysis import (
|
||
get_video_analysis_data,
|
||
get_video_base_info,
|
||
search_videos_by_star_id,
|
||
search_videos_by_unique_id,
|
||
search_videos_by_nickname,
|
||
get_video_list_with_a3,
|
||
)
|
||
from app.services.yuntu_api import YuntuAPIError
|
||
|
||
router = APIRouter(prefix="/videos", tags=["视频分析"])
|
||
|
||
|
||
class SearchRequest(BaseModel):
|
||
"""搜索请求"""
|
||
type: str # "star_id" | "unique_id" | "nickname"
|
||
value: str
|
||
|
||
|
||
class VideoListItem(BaseModel):
|
||
"""视频列表项"""
|
||
item_id: str
|
||
title: str
|
||
star_nickname: str
|
||
star_unique_id: str
|
||
create_date: Optional[str]
|
||
hot_type: str
|
||
total_play_cnt: int
|
||
total_new_a3_cnt: int
|
||
total_cost: float
|
||
|
||
|
||
@router.get("/{item_id}/analysis")
|
||
async def get_video_analysis(
|
||
item_id: str,
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
获取单个视频分析数据。
|
||
|
||
返回6大类指标:
|
||
- 基础信息 (8字段)
|
||
- 触达指标 (7字段)
|
||
- A3指标 (3字段)
|
||
- 搜索指标 (5字段)
|
||
- 费用指标 (3字段)
|
||
- 成本指标 (6字段,计算得出)
|
||
|
||
Args:
|
||
item_id: 视频ID
|
||
|
||
Returns:
|
||
视频分析数据
|
||
|
||
Raises:
|
||
404: 视频不存在
|
||
500: API调用失败
|
||
"""
|
||
try:
|
||
result = await get_video_analysis_data(db, item_id)
|
||
return {
|
||
"success": True,
|
||
"data": result,
|
||
}
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=404, detail=str(e))
|
||
except YuntuAPIError as e:
|
||
# API失败但有降级数据时不抛错
|
||
raise HTTPException(status_code=500, detail=f"API Error: {e.message}")
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}")
|
||
|
||
|
||
@router.post("/search")
|
||
async def search_videos(
|
||
request: SearchRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
):
|
||
"""
|
||
搜索视频列表。
|
||
|
||
支持三种搜索方式(均返回列表,点击详情查看完整数据):
|
||
- star_id: 星图ID精准匹配
|
||
- unique_id: 达人unique_id精准匹配
|
||
- nickname: 达人昵称模糊匹配
|
||
|
||
Args:
|
||
request: 搜索请求,包含 type 和 value
|
||
|
||
Returns:
|
||
视频列表(含A3数据和成本指标)
|
||
"""
|
||
try:
|
||
if request.type == "star_id":
|
||
# 星图ID查询,返回视频列表
|
||
videos = await search_videos_by_star_id(db, request.value)
|
||
if not videos:
|
||
return {
|
||
"success": True,
|
||
"type": "list",
|
||
"data": [],
|
||
"total": 0,
|
||
}
|
||
|
||
# 获取 A3 数据
|
||
result = await get_video_list_with_a3(db, videos)
|
||
return {
|
||
"success": True,
|
||
"type": "list",
|
||
"data": result,
|
||
"total": len(result),
|
||
}
|
||
|
||
elif request.type == "unique_id":
|
||
# 达人unique_id查询,返回视频列表
|
||
videos = await search_videos_by_unique_id(db, request.value)
|
||
if not videos:
|
||
return {
|
||
"success": True,
|
||
"type": "list",
|
||
"data": [],
|
||
"total": 0,
|
||
}
|
||
|
||
# 获取 A3 数据
|
||
result = await get_video_list_with_a3(db, videos)
|
||
return {
|
||
"success": True,
|
||
"type": "list",
|
||
"data": result,
|
||
"total": len(result),
|
||
}
|
||
|
||
elif request.type == "nickname":
|
||
# 昵称模糊查询,返回视频列表
|
||
videos = await search_videos_by_nickname(db, request.value)
|
||
if not videos:
|
||
return {
|
||
"success": True,
|
||
"type": "list",
|
||
"data": [],
|
||
"total": 0,
|
||
}
|
||
|
||
# 获取 A3 数据
|
||
result = await get_video_list_with_a3(db, videos)
|
||
return {
|
||
"success": True,
|
||
"type": "list",
|
||
"data": result,
|
||
"total": len(result),
|
||
}
|
||
|
||
else:
|
||
raise HTTPException(status_code=400, detail=f"Invalid search type: {request.type}")
|
||
|
||
except ValueError as e:
|
||
raise HTTPException(status_code=404, detail=str(e))
|
||
except YuntuAPIError as e:
|
||
raise HTTPException(status_code=500, detail=f"API Error: {e.message}")
|
||
except Exception as e:
|
||
raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}")
|