拆镜复刻、爆款开头复刻管理后台完成
This commit is contained in:
@@ -7,7 +7,7 @@ from typing import Any
|
||||
from fastapi import HTTPException
|
||||
|
||||
from app.config import settings
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy import String, cast, func, or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.enums.shot_replicate import (
|
||||
@@ -19,6 +19,7 @@ from app.enums.shot_replicate import (
|
||||
ShotSplitStatusEnum,
|
||||
ShotTaskSetStatusEnum,
|
||||
)
|
||||
from app.models.module_generation_project import ModuleGenerationProject
|
||||
from app.models.shot_replicate_segment import ShotReplicateSegment
|
||||
from app.models.shot_replicate_task_set import ShotReplicateTaskSet
|
||||
from app.models.user import User
|
||||
@@ -85,26 +86,37 @@ def _normalize_suggestions(value: Any) -> list[dict[str, Any]]:
|
||||
return normalized
|
||||
|
||||
|
||||
def _task_set_to_out(task_set: ShotReplicateTaskSet) -> ShotTaskSetOut:
|
||||
return ShotTaskSetOut.model_validate(task_set)
|
||||
def _task_set_to_out(task_set: ShotReplicateTaskSet, user_name: str | None = None) -> ShotTaskSetOut:
|
||||
data = ShotTaskSetOut.model_validate(task_set)
|
||||
data.user_name = user_name
|
||||
return data
|
||||
|
||||
|
||||
def _task_set_to_detail_out(task_set: ShotReplicateTaskSet) -> ShotTaskSetDetailOut:
|
||||
def _task_set_to_detail_out(task_set: ShotReplicateTaskSet, user_name: str | None = None) -> ShotTaskSetDetailOut:
|
||||
suggestions = [ShotAISuggestionOut(**{k: v for k, v in item.items() if k != "raw"}) for item in _normalize_suggestions(task_set.ai_suggestion_json)]
|
||||
base = ShotTaskSetDetailOut.model_validate(task_set)
|
||||
base.user_name = user_name
|
||||
base.ai_suggestions = suggestions
|
||||
return base
|
||||
|
||||
|
||||
def _segment_to_out(segment: ShotReplicateSegment) -> ShotSegmentOut:
|
||||
def _segment_to_out(segment: ShotReplicateSegment, project: ModuleGenerationProject | None = None) -> ShotSegmentOut:
|
||||
data = ShotSegmentOut.model_validate(segment)
|
||||
data.segment_name = f"片段{segment.segment_index}"
|
||||
if project:
|
||||
data.module_project_title = project.title
|
||||
data.module_project_status = project.status
|
||||
data.module_project_current_step_code = project.current_step_code
|
||||
return data
|
||||
|
||||
|
||||
def _segment_to_detail_out(segment: ShotReplicateSegment) -> ShotSegmentDetailOut:
|
||||
def _segment_to_detail_out(segment: ShotReplicateSegment, project: ModuleGenerationProject | None = None) -> ShotSegmentDetailOut:
|
||||
data = ShotSegmentDetailOut.model_validate(segment)
|
||||
data.segment_name = f"片段{segment.segment_index}"
|
||||
if project:
|
||||
data.module_project_title = project.title
|
||||
data.module_project_status = project.status
|
||||
data.module_project_current_step_code = project.current_step_code
|
||||
return data
|
||||
|
||||
|
||||
@@ -201,6 +213,23 @@ async def create_task_set(db: AsyncSession, *, current_user: User, req: ShotTask
|
||||
return task_set
|
||||
|
||||
|
||||
def _user_name_filter_subquery(value: str):
|
||||
"""后台按用户名筛选时使用的子查询。
|
||||
|
||||
只在传入 user_name 时查询 users 表;keyword 不再关联 users,避免后台关键词搜索扩大查询范围。
|
||||
"""
|
||||
like = f"%{value.strip()}%"
|
||||
return select(User.id).where(User.username.ilike(like))
|
||||
|
||||
|
||||
async def _user_name_map_by_ids(db: AsyncSession, user_ids: set[str]) -> dict[str, str | None]:
|
||||
"""一次性查询当前页涉及的用户,避免列表逐条查询用户表。"""
|
||||
if not user_ids:
|
||||
return {}
|
||||
result = await db.execute(select(User.id, User.username).where(User.id.in_(list(user_ids))))
|
||||
return {user_id: username for user_id, username in result.all()}
|
||||
|
||||
|
||||
async def list_task_sets(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
@@ -209,39 +238,85 @@ async def list_task_sets(
|
||||
analysis_status: str | None = None,
|
||||
split_status: str | None = None,
|
||||
keyword: str | None = None,
|
||||
user_id: str | None = None,
|
||||
user_name: str | None = None,
|
||||
created_start: datetime | None = None,
|
||||
created_end: datetime | None = None,
|
||||
page: int = 1,
|
||||
page_size: int = 20,
|
||||
) -> ShotTaskSetListOut:
|
||||
"""
|
||||
拆镜总任务集列表查询。
|
||||
|
||||
性能策略:
|
||||
1. 主列表不 join User,避免 count/list 复杂化。
|
||||
2. 管理员按 user_name 搜索时使用 IN (SELECT users.id ...) 子查询;keyword 不查询 users 表。
|
||||
3. 当前页数据取出后,再按 user_id 去重批量查 user_name,用于后台渲染。
|
||||
"""
|
||||
query = select(ShotReplicateTaskSet).where(ShotReplicateTaskSet.deleted_at.is_(None))
|
||||
|
||||
if not current_user.is_admin:
|
||||
query = query.where(ShotReplicateTaskSet.user_id == current_user.id)
|
||||
else:
|
||||
if user_id and user_id.strip():
|
||||
query = query.where(ShotReplicateTaskSet.user_id == user_id.strip())
|
||||
if user_name and user_name.strip():
|
||||
query = query.where(ShotReplicateTaskSet.user_id.in_(_user_name_filter_subquery(user_name)))
|
||||
if created_start:
|
||||
query = query.where(ShotReplicateTaskSet.created_at >= created_start)
|
||||
if created_end:
|
||||
query = query.where(ShotReplicateTaskSet.created_at <= created_end)
|
||||
|
||||
if status:
|
||||
query = query.where(ShotReplicateTaskSet.status == status)
|
||||
if analysis_status:
|
||||
query = query.where(ShotReplicateTaskSet.analysis_status == analysis_status)
|
||||
if split_status:
|
||||
query = query.where(ShotReplicateTaskSet.split_status == split_status)
|
||||
if keyword:
|
||||
if keyword and keyword.strip():
|
||||
like = f"%{keyword.strip()}%"
|
||||
query = query.where(
|
||||
(ShotReplicateTaskSet.title.ilike(like))
|
||||
| (ShotReplicateTaskSet.original_video_content.ilike(like))
|
||||
| (ShotReplicateTaskSet.original_video_category.ilike(like))
|
||||
)
|
||||
conditions = [
|
||||
ShotReplicateTaskSet.id.ilike(like),
|
||||
ShotReplicateTaskSet.title.ilike(like),
|
||||
ShotReplicateTaskSet.original_video_content.ilike(like),
|
||||
ShotReplicateTaskSet.original_video_category.ilike(like),
|
||||
ShotReplicateTaskSet.original_video_audience.ilike(like),
|
||||
cast(ShotReplicateTaskSet.ai_suggestion_json, String).ilike(like),
|
||||
]
|
||||
query = query.where(or_(*conditions))
|
||||
|
||||
total_result = await db.execute(select(func.count()).select_from(query.subquery()))
|
||||
total = int(total_result.scalar() or 0)
|
||||
rows = await db.execute(
|
||||
result = await db.execute(
|
||||
query.order_by(ShotReplicateTaskSet.created_at.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
)
|
||||
return ShotTaskSetListOut(total=total, page=page, page_size=page_size, items=[_task_set_to_out(item) for item in rows.scalars().all()])
|
||||
task_sets = list(result.scalars().unique().all())
|
||||
|
||||
user_name_map: dict[str, str | None] = {}
|
||||
if current_user.is_admin:
|
||||
user_ids = {task_set.user_id for task_set in task_sets if task_set.user_id}
|
||||
user_name_map = await _user_name_map_by_ids(db, user_ids)
|
||||
|
||||
return ShotTaskSetListOut(
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
items=[
|
||||
_task_set_to_out(
|
||||
task_set,
|
||||
user_name_map.get(task_set.user_id) if current_user.is_admin and task_set.user_id else None,
|
||||
)
|
||||
for task_set in task_sets
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def task_set_detail(db: AsyncSession, *, current_user: User, task_set_id: str) -> ShotTaskSetDetailOut:
|
||||
task_set = await get_task_set_for_user(db, task_set_id=task_set_id, user=current_user)
|
||||
return _task_set_to_detail_out(task_set)
|
||||
user_result = await db.execute(select(User.username).where(User.id == task_set.user_id).limit(1))
|
||||
return _task_set_to_detail_out(task_set, user_result.scalar_one_or_none())
|
||||
|
||||
|
||||
async def _next_segment_index(db: AsyncSession, task_set_id: str) -> int:
|
||||
@@ -499,9 +574,13 @@ async def list_segments(
|
||||
page_size: int = 20,
|
||||
) -> ShotSegmentListOut:
|
||||
await get_task_set_for_user(db, task_set_id=task_set_id, user=current_user)
|
||||
query = select(ShotReplicateSegment).where(
|
||||
ShotReplicateSegment.task_set_id == task_set_id,
|
||||
ShotReplicateSegment.deleted_at.is_(None),
|
||||
query = (
|
||||
select(ShotReplicateSegment, ModuleGenerationProject)
|
||||
.outerjoin(ModuleGenerationProject, ModuleGenerationProject.id == ShotReplicateSegment.module_project_id)
|
||||
.where(
|
||||
ShotReplicateSegment.task_set_id == task_set_id,
|
||||
ShotReplicateSegment.deleted_at.is_(None),
|
||||
)
|
||||
)
|
||||
if not current_user.is_admin:
|
||||
query = query.where(ShotReplicateSegment.user_id == current_user.id)
|
||||
@@ -521,9 +600,18 @@ async def list_segments(
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
)
|
||||
return ShotSegmentListOut(total=total, page=page, page_size=page_size, items=[_segment_to_out(item) for item in rows.scalars().all()])
|
||||
return ShotSegmentListOut(
|
||||
total=total,
|
||||
page=page,
|
||||
page_size=page_size,
|
||||
items=[_segment_to_out(segment, project) for segment, project in rows.all()],
|
||||
)
|
||||
|
||||
|
||||
async def segment_detail(db: AsyncSession, *, current_user: User, segment_id: str) -> ShotSegmentDetailOut:
|
||||
segment = await get_segment_for_user(db, segment_id=segment_id, user=current_user)
|
||||
return _segment_to_detail_out(segment)
|
||||
project: ModuleGenerationProject | None = None
|
||||
if segment.module_project_id:
|
||||
project_result = await db.execute(select(ModuleGenerationProject).where(ModuleGenerationProject.id == segment.module_project_id).limit(1))
|
||||
project = project_result.scalar_one_or_none()
|
||||
return _segment_to_detail_out(segment, project)
|
||||
|
||||
Reference in New Issue
Block a user