update
This commit is contained in:
parent
b1bd8c2e82
commit
64ed01f5dc
@ -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")
|
||||||
@ -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
88
app/api/v1/upload.py
Normal 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)}"
|
||||||
|
)
|
||||||
@ -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):
|
||||||
|
|||||||
@ -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
103
app/services/cos.py
Normal 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
|
||||||
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user