This commit is contained in:
aaron 2025-04-10 11:38:15 +08:00
parent b1bd8c2e82
commit 64ed01f5dc
7 changed files with 237 additions and 2 deletions

View File

@ -5,6 +5,7 @@ from app.api.v1.auth import router as auth_router
from app.api.v1.person_images import router as person_images_router from app.api.v1.person_images import router as person_images_router
from app.api.v1.clothing import router as clothing_router from app.api.v1.clothing import router as clothing_router
from app.api.v1.tryon import router as tryon_router from app.api.v1.tryon import router as tryon_router
from app.api.v1.upload import router as upload_router
api_router = APIRouter() api_router = APIRouter()
api_router.include_router(endpoints_router, prefix="") api_router.include_router(endpoints_router, prefix="")
@ -12,4 +13,5 @@ api_router.include_router(users_router, prefix="/users")
api_router.include_router(auth_router, prefix="/auth") api_router.include_router(auth_router, prefix="/auth")
api_router.include_router(person_images_router, prefix="/person-images") api_router.include_router(person_images_router, prefix="/person-images")
api_router.include_router(clothing_router, prefix="/clothing") api_router.include_router(clothing_router, prefix="/clothing")
api_router.include_router(tryon_router, prefix="/tryon") api_router.include_router(tryon_router, prefix="/tryon")
api_router.include_router(upload_router, prefix="/upload")

View File

@ -13,6 +13,8 @@ from app.api.deps import get_current_user
from app.services.dashscope_service import DashScopeService from app.services.dashscope_service import DashScopeService
from app.schemas.response import StandardResponse from app.schemas.response import StandardResponse
from app.models.tryon import TryonHistory from app.models.tryon import TryonHistory
from app.schemas.tryon import TryonHistoryModel
from sqlalchemy import select
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
@ -72,3 +74,16 @@ async def tryon(
return StandardResponse(code=200, message="试穿任务已提交", data=tryon_history.id) return StandardResponse(code=200, message="试穿任务已提交", data=tryon_history.id)
else: else:
return StandardResponse(code=500, message="试穿任务提交失败") return StandardResponse(code=500, message="试穿任务提交失败")
@router.get("/tryon/histories", tags=["tryon"])
async def get_tryon_histories(
db: AsyncSession = Depends(deps.get_db),
current_user: User = Depends(get_current_user)
):
"""
获取试穿历史
"""
histories = await db.execute(select(TryonHistory).where(TryonHistory.user_id == current_user.id))
tryon_histories = histories.scalars().all()
return StandardResponse(code=200, message="试穿历史获取成功", data=[TryonHistoryModel.model_validate(history) for history in tryon_histories])

88
app/api/v1/upload.py Normal file
View File

@ -0,0 +1,88 @@
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, status, Request
from fastapi.responses import JSONResponse
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List, Optional
from app.api import deps
from app.services import cos as cos_service
from app.schemas.response import StandardResponse
import logging
router = APIRouter()
logger = logging.getLogger(__name__)
@router.post("", response_model=StandardResponse, tags=["upload"])
async def upload_file(
file: UploadFile = File(...),
directory: str = "uploads"
):
"""
上传文件到腾讯云COS
- 支持的文件类型: 图片(jpg, jpeg, png, gif, webp), 文档(pdf, doc, docx)
- 返回文件的访问URL
"""
try:
content_type = file.content_type or ""
# 检查文件类型
allowed_types = [
"image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp",
"application/pdf", "application/msword",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
]
if content_type not in allowed_types:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="不支持的文件类型"
)
# 获取文件内容
file_content = await file.read()
# 获取文件扩展名
filename = file.filename or ""
file_extension = "." + filename.split(".")[-1] if "." in filename else ""
# 上传到腾讯云COS
url = await cos_service.upload_file(
file_content=file_content,
file_extension=file_extension,
directory=directory
)
return StandardResponse(code=200, data={"url": url})
except Exception as e:
logger.error(f"文件上传失败: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"文件上传失败: {str(e)}"
)
@router.post("/from-url", response_model=StandardResponse, tags=["upload"])
async def upload_from_url(
url: str,
directory: str = "uploads"
):
"""
从URL上传文件到腾讯云COS
- 支持的文件类型: 图片(jpg, jpeg, png, gif, webp)
- 返回文件的访问URL
"""
try:
# 从URL上传文件
cos_url = await cos_service.upload_file_from_url(
url=url,
directory=directory
)
return StandardResponse(code=200, data={"url": cos_url})
except Exception as e:
logger.error(f"从URL上传文件失败: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"从URL上传文件失败: {str(e)}"
)

View File

@ -34,6 +34,12 @@ class Settings(BaseSettings):
# 微信设置 # 微信设置
WECHAT_APP_ID: str = os.getenv("WECHAT_APP_ID", "") WECHAT_APP_ID: str = os.getenv("WECHAT_APP_ID", "")
WECHAT_APP_SECRET: str = os.getenv("WECHAT_APP_SECRET", "") WECHAT_APP_SECRET: str = os.getenv("WECHAT_APP_SECRET", "")
# 腾讯云COS配置
COS_SECRET_ID: str = os.getenv("COS_SECRET_ID", "")
COS_SECRET_KEY: str = os.getenv("COS_SECRET_KEY", "")
COS_REGION: str = os.getenv("COS_REGION", "ap-guangzhou")
COS_BUCKET: str = os.getenv("COS_BUCKET", "")
@property @property
def cors_origins(self): def cors_origins(self):

View File

@ -1,5 +1,7 @@
from pydantic import BaseModel from pydantic import BaseModel
from typing import Optional from typing import Optional
from app.models.tryon import TryonStatus
from datetime import datetime
class TryonRequest(BaseModel): class TryonRequest(BaseModel):
@ -7,3 +9,18 @@ class TryonRequest(BaseModel):
bottom_clothing_id: Optional[int] = None bottom_clothing_id: Optional[int] = None
top_clothing_url: Optional[str] = None top_clothing_url: Optional[str] = None
bottom_clothing_url: Optional[str] = None bottom_clothing_url: Optional[str] = None
class TryonHistoryModel(BaseModel):
id: int
person_image_id: int
top_clothing_id: int
bottom_clothing_id: int
top_clothing_url: str
bottom_clothing_url: str
status: TryonStatus
create_time: datetime
update_time: datetime
class Config:
from_attributes = True

103
app/services/cos.py Normal file
View File

@ -0,0 +1,103 @@
import logging
import os
import uuid
from datetime import datetime
from qcloud_cos import CosConfig, CosS3Client
from app.core.config import settings
logger = logging.getLogger(__name__)
# 腾讯云COS配置
config = CosConfig(
Region=settings.COS_REGION,
SecretId=settings.COS_SECRET_ID,
SecretKey=settings.COS_SECRET_KEY
)
# 创建客户端
cos_client = CosS3Client(config)
def generate_file_path(directory: str, file_extension: str) -> str:
"""生成文件路径"""
today = datetime.now().strftime("%Y%m%d")
filename = f"{uuid.uuid4().hex}{file_extension}"
return f"{directory}/{today}/{filename}"
async def upload_file(file_content: bytes, file_extension: str, directory: str = "uploads") -> str:
"""上传文件到腾讯云COS"""
try:
# 生成唯一文件路径
file_path = generate_file_path(directory, file_extension)
# 上传到腾讯云COS
cos_client.put_object(
Bucket=settings.COS_BUCKET,
Body=file_content,
Key=file_path
)
# 返回可访问的URL
url = f"https://{settings.COS_BUCKET}.cos.{settings.COS_REGION}.myqcloud.com/{file_path}"
logger.info(f"文件上传成功: {url}")
return url
except Exception as e:
logger.error(f"文件上传失败: {str(e)}")
raise
async def upload_file_from_url(url: str, directory: str = "uploads") -> str:
"""从URL下载文件并上传到腾讯云COS"""
try:
import requests
# 下载文件
response = requests.get(url, timeout=10)
response.raise_for_status()
# 获取文件扩展名
file_extension = os.path.splitext(url)[1]
if not file_extension:
# 如果URL没有扩展名根据内容类型判断
content_type = response.headers.get("Content-Type", "")
if "jpeg" in content_type or "jpg" in content_type:
file_extension = ".jpg"
elif "png" in content_type:
file_extension = ".png"
elif "gif" in content_type:
file_extension = ".gif"
else:
file_extension = ".bin" # 默认二进制文件
# 上传到腾讯云COS
return await upload_file(response.content, file_extension, directory)
except Exception as e:
logger.error(f"从URL上传文件失败: {str(e)}")
raise
async def get_presigned_url(key: str, expires: int = 3600) -> str:
"""获取预签名URL"""
try:
# 生成预签名URL
url = cos_client.get_presigned_url(
Method='GET',
Bucket=settings.COS_BUCKET,
Key=key,
Expired=expires
)
return url
except Exception as e:
logger.error(f"获取预签名URL失败: {str(e)}")
raise
async def delete_file(key: str) -> bool:
"""删除文件"""
try:
# 删除文件
cos_client.delete_object(
Bucket=settings.COS_BUCKET,
Key=key
)
logger.info(f"文件删除成功: {key}")
return True
except Exception as e:
logger.error(f"文件删除失败: {str(e)}")
return False

View File

@ -10,4 +10,8 @@ python-jose[cryptography]==3.3.0
passlib==1.7.4 passlib==1.7.4
httpx==0.24.1 httpx==0.24.1
dashscope==1.10.0 dashscope==1.10.0
itsdangerous==2.2.0 itsdangerous==2.2.0
# 腾讯云COS SDK
cos-python-sdk-v5==1.9.25
requests>=2.28.1
python-multipart>=0.0.10