拆镜复刻开发完成
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query
|
||||
from sqlalchemy import inspect as sa_inspect
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dependencies import get_current_user, get_db
|
||||
from app.models.user import User
|
||||
from app.enums.hot_opening_replicate import ModuleCodeEnum
|
||||
from app.schemas.hot_opening_replicate import (
|
||||
HotOpeningActionOut,
|
||||
HotOpeningDeleteOut,
|
||||
@@ -35,14 +39,94 @@ from app.services.hot_opening_replicate_service import (
|
||||
update_hot_opening_material_input,
|
||||
update_hot_opening_video_prompt_schema,
|
||||
)
|
||||
from app.services.module_generation_log_service import log_module_error
|
||||
from app.tasks.celery_app import celery_app
|
||||
|
||||
MODULE = ModuleCodeEnum.HOT_OPENING_REPLICATE.value
|
||||
|
||||
|
||||
|
||||
def _safe_user_id(user: object | None) -> str | None:
|
||||
"""从 ORM 对象中安全取用户ID,避免 rollback/commit 后访问过期属性触发 MissingGreenlet。"""
|
||||
if user is None:
|
||||
return None
|
||||
try:
|
||||
value = getattr(user, "__dict__", {}).get("id")
|
||||
if value is not None:
|
||||
return str(value)
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
identity = sa_inspect(user).identity
|
||||
if identity:
|
||||
return str(identity[0])
|
||||
except Exception:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def _safe_user_is_admin(user: object | None) -> bool:
|
||||
"""安全判断管理员身份;如果对象属性已过期,保守按普通用户处理。"""
|
||||
if user is None:
|
||||
return False
|
||||
try:
|
||||
data = getattr(user, "__dict__", {})
|
||||
if "is_admin" in data:
|
||||
return bool(data.get("is_admin"))
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def _user_context(user: object | None) -> SimpleNamespace:
|
||||
return SimpleNamespace(id=_safe_user_id(user), is_admin=_safe_user_is_admin(user))
|
||||
|
||||
router = APIRouter(
|
||||
prefix="/hot-opening-replications",
|
||||
tags=["hot-opening-replications"],
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
def _log_api_error(
|
||||
*,
|
||||
event_type: str,
|
||||
current_user: User | None = None,
|
||||
project_id: str | None = None,
|
||||
step_id: str | None = None,
|
||||
message: str | None = None,
|
||||
exc: BaseException | None = None,
|
||||
detail: dict | None = None,
|
||||
) -> None:
|
||||
log_module_error(
|
||||
module=MODULE,
|
||||
event_type=event_type,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
user_id=_safe_user_id(current_user),
|
||||
message=message,
|
||||
detail=detail,
|
||||
exc=exc,
|
||||
)
|
||||
|
||||
|
||||
def _log_api_exception_from_locals(exc: BaseException, local_values: dict, message: str) -> None:
|
||||
current_user = local_values.get("current_user")
|
||||
project_id = local_values.get("project_id_value") or local_values.get("project_id")
|
||||
step_id = local_values.get("step_id_value") or local_values.get("step_id")
|
||||
req = local_values.get("req")
|
||||
detail = {"request": req.model_dump() if hasattr(req, "model_dump") else str(req) if req is not None else None}
|
||||
_log_api_error(
|
||||
event_type="API_REQUEST_FAILED",
|
||||
current_user=current_user if isinstance(current_user, User) else None,
|
||||
project_id=str(project_id) if project_id else None,
|
||||
step_id=str(step_id) if step_id else None,
|
||||
message=message,
|
||||
detail=detail,
|
||||
exc=exc,
|
||||
)
|
||||
|
||||
async def _reload_project_detail(
|
||||
db: AsyncSession,
|
||||
current_user: User,
|
||||
@@ -52,7 +136,7 @@ async def _reload_project_detail(
|
||||
project = await _get_project_for_user(
|
||||
db,
|
||||
project_id=project_id,
|
||||
user=current_user,
|
||||
user=_user_context(current_user),
|
||||
for_update=False,
|
||||
populate_existing=True,
|
||||
)
|
||||
@@ -72,14 +156,33 @@ async def _mark_dispatch_failed_and_raise(
|
||||
try:
|
||||
await mark_hot_opening_step_dispatch_failed(
|
||||
db,
|
||||
current_user=current_user,
|
||||
current_user=_user_context(current_user),
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
error_message=message,
|
||||
)
|
||||
await db.commit()
|
||||
except Exception:
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
_log_api_error(
|
||||
event_type="CELERY_DISPATCH_MARK_FAILED",
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
message="Celery 投递失败后标记步骤失败也失败",
|
||||
detail={"dispatch_error": message},
|
||||
exc=exc,
|
||||
)
|
||||
log_module_error(
|
||||
module=MODULE,
|
||||
event_type="CELERY_DISPATCH_FAILED",
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
user_id=_safe_user_id(current_user),
|
||||
message=message,
|
||||
detail={"reason": "celery_dispatch_failed"},
|
||||
error=message,
|
||||
)
|
||||
raise HTTPException(status_code=503, detail=message)
|
||||
|
||||
|
||||
@@ -118,6 +221,7 @@ async def create_task(
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
_log_api_exception_from_locals(exc, locals(), f"创建爆款开头复刻项目失败: {exc}")
|
||||
raise HTTPException(status_code=500, detail=f"创建爆款开头复刻项目失败: {exc}")
|
||||
|
||||
return await _reload_project_detail(db, current_user, project_id_value)
|
||||
@@ -185,6 +289,7 @@ async def update_material(
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
_log_api_exception_from_locals(exc, locals(), f"修改素材输入失败: {exc}")
|
||||
raise HTTPException(status_code=500, detail=f"修改素材输入失败: {exc}")
|
||||
|
||||
return HotOpeningActionOut(
|
||||
@@ -228,6 +333,7 @@ async def update_image_prompt(
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
_log_api_exception_from_locals(exc, locals(), f"修改图片 AI 提词失败: {exc}")
|
||||
raise HTTPException(status_code=500, detail=f"修改图片 AI 提词失败: {exc}")
|
||||
|
||||
return HotOpeningActionOut(
|
||||
@@ -300,6 +406,14 @@ async def generate_image_prompt(
|
||||
):
|
||||
_ = req
|
||||
if celery_app is None:
|
||||
_log_api_error(
|
||||
event_type="CELERY_DISABLED",
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
message="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker",
|
||||
detail={"api": "hot_opening_replicate"},
|
||||
)
|
||||
raise HTTPException(status_code=503, detail="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker")
|
||||
|
||||
try:
|
||||
@@ -312,6 +426,7 @@ async def generate_image_prompt(
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
_log_api_exception_from_locals(exc, locals(), f"图片提词任务创建失败: {exc}")
|
||||
raise HTTPException(status_code=500, detail=f"图片提词任务创建失败: {exc}")
|
||||
|
||||
from app.tasks.hot_opening_replicate_tasks import start_image_prompt_optimize
|
||||
@@ -354,6 +469,14 @@ async def generate_image(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if celery_app is None:
|
||||
_log_api_error(
|
||||
event_type="CELERY_DISABLED",
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
message="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker",
|
||||
detail={"api": "hot_opening_replicate"},
|
||||
)
|
||||
raise HTTPException(status_code=503, detail="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker")
|
||||
|
||||
try:
|
||||
@@ -369,6 +492,7 @@ async def generate_image(
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
_log_api_exception_from_locals(exc, locals(), f"图片生成任务创建失败: {exc}")
|
||||
raise HTTPException(status_code=500, detail=f"图片生成任务创建失败: {exc}")
|
||||
|
||||
from app.tasks.generation_create_tasks import chatapi_create_generation_task
|
||||
@@ -411,6 +535,14 @@ async def generate_video_prompt(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if celery_app is None:
|
||||
_log_api_error(
|
||||
event_type="CELERY_DISABLED",
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
message="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker",
|
||||
detail={"api": "hot_opening_replicate"},
|
||||
)
|
||||
raise HTTPException(status_code=503, detail="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker")
|
||||
|
||||
try:
|
||||
@@ -423,6 +555,7 @@ async def generate_video_prompt(
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
_log_api_exception_from_locals(exc, locals(), f"视频提词任务创建失败: {exc}")
|
||||
raise HTTPException(status_code=500, detail=f"视频提词任务创建失败: {exc}")
|
||||
|
||||
from app.tasks.hot_opening_replicate_tasks import start_video_prompt_optimize
|
||||
@@ -466,6 +599,14 @@ async def generate_video(
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
if celery_app is None:
|
||||
_log_api_error(
|
||||
event_type="CELERY_DISABLED",
|
||||
current_user=current_user,
|
||||
project_id=project_id,
|
||||
step_id=step_id,
|
||||
message="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker",
|
||||
detail={"api": "hot_opening_replicate"},
|
||||
)
|
||||
raise HTTPException(status_code=503, detail="Celery未启用:请配置 REDIS_URL 或 CELERY_BROKER_URL 后启动 worker")
|
||||
|
||||
try:
|
||||
@@ -481,6 +622,7 @@ async def generate_video(
|
||||
raise
|
||||
except Exception as exc:
|
||||
await db.rollback()
|
||||
_log_api_exception_from_locals(exc, locals(), f"视频生成任务创建失败: {exc}")
|
||||
raise HTTPException(status_code=500, detail=f"视频生成任务创建失败: {exc}")
|
||||
|
||||
from app.tasks.generation_create_tasks import chatapi_create_generation_task
|
||||
|
||||
Reference in New Issue
Block a user