- 将默认AI生图模型升级为flux-dev及seedream-5.0版本 - SiliconFlow模型由FLUX.1-dev切换为Kolors,优化调用参数和返回值 - 火山引擎Seedream升级至5.0 lite版本,支持多视角参考图传入 - 设计图片字段由字符串改为Text扩展URL长度限制 - 设计图下载支持远程URL重定向和本地文件兼容 - 生成AI图片时多视角保持风格一致,SiliconFlow复用seed,Seedream传参考图 - 后台配置界面更改模型名称及价格显示,新增API Key状态检测 - 前端照片下载从链接改为按钮,远程文件新窗口打开 - 设计相关接口支持较长请求超时,下载走API路径无/api前缀 - 前端页面兼容驼峰与下划线格式URL参数识别 - 用户中心设计图下载支持本地文件Token授权下载 - 初始化数据库新增完整表结构与约束,适配新版设计业务逻辑
222 lines
6.3 KiB
Python
222 lines
6.3 KiB
Python
"""
|
||
设计相关路由
|
||
提供设计生成、查询、删除、下载接口
|
||
"""
|
||
import os
|
||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||
from fastapi.responses import FileResponse, RedirectResponse
|
||
from sqlalchemy.orm import Session
|
||
|
||
from ..database import get_db
|
||
from ..models import User, Design
|
||
from ..schemas import DesignCreate, DesignResponse, DesignListResponse, DesignImageResponse
|
||
from ..utils.deps import get_current_user
|
||
from ..services import design_service
|
||
|
||
router = APIRouter(prefix="/api/designs", tags=["设计"])
|
||
|
||
|
||
def design_to_response(design: Design) -> DesignResponse:
|
||
"""将 Design 模型转换为响应格式"""
|
||
# 构建多视角图片列表
|
||
images = []
|
||
if hasattr(design, 'images') and design.images:
|
||
images = [
|
||
DesignImageResponse(
|
||
id=img.id,
|
||
view_name=img.view_name,
|
||
image_url=img.image_url,
|
||
model_used=img.model_used,
|
||
prompt_used=img.prompt_used,
|
||
sort_order=img.sort_order,
|
||
)
|
||
for img in design.images
|
||
]
|
||
|
||
return DesignResponse(
|
||
id=design.id,
|
||
user_id=design.user_id,
|
||
category={
|
||
"id": design.category.id,
|
||
"name": design.category.name,
|
||
"icon": design.category.icon,
|
||
"sort_order": design.category.sort_order,
|
||
"flow_type": design.category.flow_type
|
||
},
|
||
sub_type={
|
||
"id": design.sub_type.id,
|
||
"category_id": design.sub_type.category_id,
|
||
"name": design.sub_type.name,
|
||
"description": design.sub_type.description,
|
||
"preview_image": design.sub_type.preview_image,
|
||
"sort_order": design.sub_type.sort_order
|
||
} if design.sub_type else None,
|
||
color={
|
||
"id": design.color.id,
|
||
"category_id": design.color.category_id,
|
||
"name": design.color.name,
|
||
"hex_code": design.color.hex_code,
|
||
"sort_order": design.color.sort_order
|
||
} if design.color else None,
|
||
prompt=design.prompt,
|
||
carving_technique=design.carving_technique,
|
||
design_style=design.design_style,
|
||
motif=design.motif,
|
||
size_spec=design.size_spec,
|
||
surface_finish=design.surface_finish,
|
||
usage_scene=design.usage_scene,
|
||
image_url=design.image_url,
|
||
images=images,
|
||
status=design.status,
|
||
created_at=design.created_at,
|
||
updated_at=design.updated_at
|
||
)
|
||
|
||
|
||
@router.post("/generate", response_model=DesignResponse)
|
||
async def generate_design(
|
||
design_data: DesignCreate,
|
||
db: Session = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
提交设计生成请求(异步,支持 AI 多视角生图)
|
||
需要认证
|
||
"""
|
||
try:
|
||
design = await design_service.create_design_async(
|
||
db=db,
|
||
user_id=current_user.id,
|
||
design_data=design_data
|
||
)
|
||
return design_to_response(design)
|
||
except ValueError as e:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_400_BAD_REQUEST,
|
||
detail=str(e)
|
||
)
|
||
|
||
|
||
@router.get("", response_model=DesignListResponse)
|
||
def get_designs(
|
||
page: int = Query(1, ge=1, description="页码"),
|
||
page_size: int = Query(20, ge=1, le=100, description="每页数量"),
|
||
db: Session = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
获取当前用户的设计历史列表(分页)
|
||
需要认证
|
||
"""
|
||
designs, total = design_service.get_user_designs(
|
||
db=db,
|
||
user_id=current_user.id,
|
||
page=page,
|
||
page_size=page_size
|
||
)
|
||
|
||
return DesignListResponse(
|
||
items=[design_to_response(d) for d in designs],
|
||
total=total,
|
||
page=page,
|
||
page_size=page_size
|
||
)
|
||
|
||
|
||
@router.get("/{design_id}", response_model=DesignResponse)
|
||
def get_design(
|
||
design_id: int,
|
||
db: Session = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
获取设计详情
|
||
只能查看自己的设计,非本人设计返回 404
|
||
"""
|
||
design = design_service.get_design_by_id(
|
||
db=db,
|
||
design_id=design_id,
|
||
user_id=current_user.id
|
||
)
|
||
|
||
if not design:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="设计不存在"
|
||
)
|
||
|
||
return design_to_response(design)
|
||
|
||
|
||
@router.delete("/{design_id}")
|
||
def delete_design(
|
||
design_id: int,
|
||
db: Session = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
删除设计
|
||
只能删除自己的设计,非本人设计返回 404
|
||
"""
|
||
success = design_service.delete_design(
|
||
db=db,
|
||
design_id=design_id,
|
||
user_id=current_user.id
|
||
)
|
||
|
||
if not success:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="设计不存在"
|
||
)
|
||
|
||
return {"message": "删除成功"}
|
||
|
||
|
||
@router.get("/{design_id}/download")
|
||
def download_design(
|
||
design_id: int,
|
||
db: Session = Depends(get_db),
|
||
current_user: User = Depends(get_current_user)
|
||
):
|
||
"""
|
||
下载设计图
|
||
只能下载自己的设计,非本人设计返回 404
|
||
支持远程 URL(重定向)和本地文件(兼容历史数据)
|
||
"""
|
||
design = design_service.get_design_by_id(
|
||
db=db,
|
||
design_id=design_id,
|
||
user_id=current_user.id
|
||
)
|
||
|
||
if not design:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="设计不存在"
|
||
)
|
||
|
||
if not design.image_url:
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="设计图片不存在"
|
||
)
|
||
|
||
# 远程 URL 直接重定向
|
||
if design.image_url.startswith("http"):
|
||
return RedirectResponse(url=design.image_url)
|
||
|
||
# 本地文件(兼容历史数据)
|
||
file_path = design.image_url.lstrip("/")
|
||
if not os.path.exists(file_path):
|
||
raise HTTPException(
|
||
status_code=status.HTTP_404_NOT_FOUND,
|
||
detail="设计图片文件不存在"
|
||
)
|
||
|
||
return FileResponse(
|
||
path=file_path,
|
||
filename=f"design_{design_id}.png",
|
||
media_type="image/png"
|
||
)
|