"""默认模板兜底共享逻辑(P0 #1922). 提供 get_or_create_default_template_id(db, user_id) 共享函数, 供 templates.py 列表查询、clips_standalone.py 独立端点、dependencies.py resolve_draft_plan_id 三处复用,避免三处各写一套兜底逻辑产生分叉。 根因:PR#1918 清理模板管理 API 时误删了 GET /templates 自动创建默认模板 兜底,前端 PR#1913 去掉空 tid 拦截后首次进入生成页拼出 /templates//editor/clips/from-assets(双斜杠)→ FastAPI 404,阻断新用户首次 生成。 """ from __future__ import annotations import logging from typing import Optional from sqlalchemy.orm import Session logger = logging.getLogger(__name__) def get_or_create_default_template_id(db: Session, user_id: str) -> Optional[str]: """获取或自动创建默认配音模板的 id。 判定逻辑(不做异常降级,只有确实创建失败时才回滚重查): 1. 查用户名下 is_active=True 且有 TemplateClipConfig 的模板 → 返回其 id; 2. 无则调用 CreateTemplateUseCase 创建一条默认 voice_over 模板; 3. 创建异常时 rollback 再重查一次(防并发唯一键冲突),重查仍无返回 None。 """ from packages.adapters.sqlalchemy_impl.models import ( TemplateClipConfigModel, TemplateModel, ) from packages.adapters.sqlalchemy_impl.template_repository import ( SQLAlchemyTemplateRepository, ) from packages.application.template.commands import ( CreateTemplateCommand, SegmentCommand, ) from packages.application.template.use_cases import CreateTemplateUseCase existing = ( db.query(TemplateModel) .filter(TemplateModel.user_id == user_id, TemplateModel.is_active.is_(True)) .order_by(TemplateModel.created_at.asc()) .first() ) if existing is not None: has_seg = ( db.query(TemplateClipConfigModel.id).filter(TemplateClipConfigModel.template_id == existing.id).first() ) if has_seg: return existing.id try: repo = SQLAlchemyTemplateRepository(db) cmd = CreateTemplateCommand( user_id=user_id, name="默认配音模板", mode="voice_over", category="default", tags=[], title_config={}, subtitle_config={}, bgm_config={}, estimated_duration=0.0, segments=[SegmentCommand(segment_order=0, duration_min=1.0, duration_max=30.0)], ) tpl = CreateTemplateUseCase(repo).execute(cmd) db.commit() logger.info("auto-created default voice_over template: id=%s user=%s", tpl.id, user_id) return tpl.id except Exception: db.rollback() # 重查:可能并发请求已建好 existing = ( db.query(TemplateModel) .filter(TemplateModel.user_id == user_id, TemplateModel.is_active.is_(True)) .order_by(TemplateModel.created_at.asc()) .first() ) if existing is not None: has_seg = ( db.query(TemplateClipConfigModel.id).filter(TemplateClipConfigModel.template_id == existing.id).first() ) if has_seg: return existing.id logger.exception("failed to auto-create default template user=%s", user_id) return None