拆镜复刻、爆款开头复刻管理后台完成
This commit is contained in:
@@ -5,7 +5,7 @@ from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy import String, cast, func, or_, select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.config import settings
|
||||
@@ -486,9 +486,16 @@ async def project_to_detail_out(db: AsyncSession, project: ModuleGenerationProje
|
||||
video_url = video_generate_output.get("result_video_url") or (video_chat.video_url if video_chat else None) or project.final_video_url
|
||||
cover_url = video_generate_output.get("result_video_cover_url") or (video_chat.video_cover_url if video_chat else None) or project.final_video_cover_url
|
||||
|
||||
user_name: str | None = None
|
||||
if project.user_id:
|
||||
user_result = await db.execute(select(User.username).where(User.id == project.user_id).limit(1))
|
||||
user_name = user_result.scalar_one_or_none()
|
||||
|
||||
return HotOpeningTaskDetailOut(
|
||||
id=project.id,
|
||||
project_id=project.id,
|
||||
user_id=project.user_id,
|
||||
user_name=user_name,
|
||||
module=project.module,
|
||||
title=project.title,
|
||||
status=project.status,
|
||||
@@ -586,40 +593,141 @@ async def create_hot_opening_project(db: AsyncSession, current_user: User, req:
|
||||
return project
|
||||
|
||||
|
||||
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 _current_step_map_by_project_ids(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
project_ids: set[str],
|
||||
step_code: str,
|
||||
) -> dict[str, ModuleGenerationStep]:
|
||||
"""一次性查询当前页项目的指定步骤,避免列表逐条查 material_input。"""
|
||||
if not project_ids:
|
||||
return {}
|
||||
result = await db.execute(
|
||||
select(ModuleGenerationStep).where(
|
||||
ModuleGenerationStep.project_id.in_(list(project_ids)),
|
||||
ModuleGenerationStep.step_code == step_code,
|
||||
ModuleGenerationStep.is_current.is_(True),
|
||||
ModuleGenerationStep.deleted_at.is_(None),
|
||||
)
|
||||
)
|
||||
return {step.project_id: step for step in result.scalars().all()}
|
||||
|
||||
|
||||
async def list_hot_opening_projects(
|
||||
db: AsyncSession,
|
||||
*,
|
||||
current_user: User,
|
||||
status: str | 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,
|
||||
page_size: int,
|
||||
) -> HotOpeningTaskListOut:
|
||||
"""
|
||||
爆款开头列表查询。
|
||||
|
||||
性能策略:
|
||||
1. 列表接口只查列表字段,不调用 project_to_detail_out,避免 steps/chat/user 多重 N+1。
|
||||
2. 主表分页查询完成后,再按当前页 project_ids 批量查 material_input。
|
||||
3. 管理员需要展示 user_name 时,按当前页 user_id 去重后一次性查 User。
|
||||
4. user_name 使用 IN (SELECT users.id ...) 子查询筛选;keyword 不查询 users 表。
|
||||
"""
|
||||
query = select(ModuleGenerationProject).where(
|
||||
ModuleGenerationProject.module == MODULE,
|
||||
ModuleGenerationProject.deleted_at.is_(None),
|
||||
)
|
||||
|
||||
if not current_user.is_admin:
|
||||
query = query.where(ModuleGenerationProject.user_id == current_user.id)
|
||||
else:
|
||||
if user_id and user_id.strip():
|
||||
query = query.where(ModuleGenerationProject.user_id == user_id.strip())
|
||||
if user_name and user_name.strip():
|
||||
query = query.where(ModuleGenerationProject.user_id.in_(_user_name_filter_subquery(user_name)))
|
||||
if created_start:
|
||||
query = query.where(ModuleGenerationProject.created_at >= created_start)
|
||||
if created_end:
|
||||
query = query.where(ModuleGenerationProject.created_at <= created_end)
|
||||
|
||||
if status:
|
||||
query = query.where(ModuleGenerationProject.status == status)
|
||||
|
||||
total = (await db.execute(select(func.count()).select_from(query.subquery()))).scalar_one()
|
||||
result = await db.execute(query.order_by(ModuleGenerationProject.created_at.desc()).offset((page - 1) * page_size).limit(page_size))
|
||||
projects = list(result.scalars().all())
|
||||
if keyword and keyword.strip():
|
||||
like = f"%{keyword.strip()}%"
|
||||
material_step_subquery = (
|
||||
select(ModuleGenerationStep.project_id).where(
|
||||
ModuleGenerationStep.step_code == HotOpeningStepCodeEnum.MATERIAL_INPUT.value,
|
||||
ModuleGenerationStep.is_current.is_(True),
|
||||
ModuleGenerationStep.deleted_at.is_(None),
|
||||
cast(ModuleGenerationStep.input_json, String).ilike(like),
|
||||
)
|
||||
)
|
||||
query = query.where(
|
||||
or_(
|
||||
ModuleGenerationProject.id.ilike(like),
|
||||
ModuleGenerationProject.title.ilike(like),
|
||||
ModuleGenerationProject.current_step_code.ilike(like),
|
||||
ModuleGenerationProject.error_message.ilike(like),
|
||||
ModuleGenerationProject.id.in_(material_step_subquery),
|
||||
)
|
||||
)
|
||||
|
||||
total = int((await db.execute(select(func.count()).select_from(query.subquery()))).scalar() or 0)
|
||||
result = await db.execute(
|
||||
query.order_by(ModuleGenerationProject.created_at.desc())
|
||||
.offset((page - 1) * page_size)
|
||||
.limit(page_size)
|
||||
)
|
||||
projects = list(result.scalars().unique().all())
|
||||
project_ids = {project.id for project in projects if project.id}
|
||||
|
||||
material_step_map = await _current_step_map_by_project_ids(
|
||||
db,
|
||||
project_ids=project_ids,
|
||||
step_code=HotOpeningStepCodeEnum.MATERIAL_INPUT.value,
|
||||
)
|
||||
user_name_map: dict[str, str | None] = {}
|
||||
if current_user.is_admin:
|
||||
user_ids = {project.user_id for project in projects if project.user_id}
|
||||
user_name_map = await _user_name_map_by_ids(db, user_ids)
|
||||
|
||||
items: list[HotOpeningTaskListItemOut] = []
|
||||
for project in projects:
|
||||
material_step = await _get_current_step_by_code(db, project.id, HotOpeningStepCodeEnum.MATERIAL_INPUT.value)
|
||||
material_step = material_step_map.get(project.id)
|
||||
material = _step_payload(material_step.input_json if material_step else None)
|
||||
items.append(
|
||||
HotOpeningTaskListItemOut(
|
||||
id=project.id,
|
||||
project_id=project.id,
|
||||
user_id=project.user_id,
|
||||
user_name=user_name_map.get(project.user_id) if current_user.is_admin and project.user_id else None,
|
||||
module=project.module,
|
||||
title=project.title,
|
||||
status=project.status,
|
||||
current_step_code=project.current_step_code,
|
||||
source_project_name=material.get("source_project_name"),
|
||||
target_project_name=material.get("target_project_name"),
|
||||
core_content_point=material.get("core_content_point"),
|
||||
final_image_url=build_resource_signed_url(project.final_image_url) if project.final_image_url else None,
|
||||
final_video_url=build_resource_signed_url(project.final_video_url) if project.final_video_url else None,
|
||||
final_video_cover_url=build_resource_signed_url(project.final_video_cover_url) if project.final_video_cover_url else None,
|
||||
|
||||
Reference in New Issue
Block a user