fix: harden uploads downloads and deployment config

This commit is contained in:
2026-03-08 19:40:22 +08:00
parent aa2e6bbe95
commit c23c4ac1e3
7 changed files with 133 additions and 46 deletions

View File

@@ -12,6 +12,26 @@ from app.schemas.order import OrderCreate, OrderResponse, PaymentResponse
router = APIRouter(prefix="/orders", tags=["订单"])
def _generate_order_no(db: Session) -> str:
"""生成带随机后缀的唯一订单号,避免同秒冲突。"""
now = datetime.now()
six_months_ago = now - timedelta(days=180)
date_part = six_months_ago.strftime('%Y%m%d')
time_part = now.strftime('%H%M%S')
for _ in range(5):
suffix = secrets.token_hex(3).upper()
order_no = f"ORD{date_part}{time_part}{suffix}"
exists = db.query(Order.id).filter(Order.order_no == order_no).first()
if not exists:
return order_no
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="生成订单号失败,请稍后重试",
)
def get_current_user(authorization: str = Header(None), db: Session = Depends(get_db)):
"""获取当前登录用户"""
if not authorization or not authorization.startswith("Bearer "):
@@ -66,12 +86,8 @@ def create_order(
detail="您已购买过此作品"
)
# 生成订单号:前缀 + (当前时间-6个月)的年月日 + 当前时间的时分秒
now = datetime.now()
six_months_ago = now - timedelta(days=180) # 半年前
date_part = six_months_ago.strftime('%Y%m%d') # 半年前的年月日
time_part = now.strftime('%H%M%S') # 当前时间的时分秒
order_no = f"ORD{date_part}{time_part}"
# 生成唯一订单号:半年前日期 + 当前时分秒 + 随机后缀
order_no = _generate_order_no(db)
# 创建订单
new_order = Order(

View File

@@ -1,13 +1,12 @@
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form, Header
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File, Form, Header, Query
from sqlalchemy.orm import Session
from sqlalchemy import desc, or_
from typing import Optional
import os
import uuid
import shutil
from datetime import datetime
from PIL import Image
import json
from app.core.database import get_db
from app.models.work import Work
from app.models.user import User
@@ -51,6 +50,15 @@ def get_current_user(authorization: str = Header(None), db: Session = Depends(ge
return user
def _user_designer_aliases(user: User) -> list[str]:
aliases = []
for value in (getattr(user, "nickname", None), getattr(user, "phone", None)):
cleaned = str(value or "").strip()
if cleaned and cleaned not in aliases:
aliases.append(cleaned)
return aliases
def generate_thumbnail(image_path: str, thumb_path: str, size=(400, 400)):
"""生成缩略图 - 修复透明 PNG 问题"""
with Image.open(image_path) as img:
@@ -177,7 +185,7 @@ async def upload_work(
# 解析标签 - 修复 Bug #3: tags 转字符串
if tags:
tags_list = [tag.strip() for tag in tags.split(",")]
tags_list = [tag.strip() for tag in tags.split(",") if tag.strip()]
tags_str = ",".join(tags_list) # 转成逗号分隔的字符串
else:
tags_str = None
@@ -227,20 +235,22 @@ async def upload_work(
@router.get("/my", summary="我的上传")
def get_my_uploads(
page: int = Form(1, ge=1),
page_size: int = Form(20, ge=1, le=100),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取当前用户的上传记录"""
from sqlalchemy import desc
aliases = _user_designer_aliases(current_user)
offset = (page - 1) * page_size
works = db.query(Work).filter(
Work.designer == current_user.phone
).order_by(desc(Work.created_at)).offset(offset).limit(page_size).all()
total = db.query(Work).filter(Work.designer == current_user.phone).count()
query = db.query(Work)
if aliases:
query = query.filter(or_(*[Work.designer == alias for alias in aliases]))
else:
query = query.filter(Work.id == -1)
works = query.order_by(desc(Work.created_at)).offset(offset).limit(page_size).all()
total = query.count()
return {
"total": total,

View File

@@ -4,11 +4,13 @@ from sqlalchemy.orm import Session
from sqlalchemy import desc
from typing import List
import os
import mimetypes
from app.core.database import get_db
from app.models.work import Work
from app.models.order import Order, OrderStatus
from app.models.user import User
from app.core.security import decode_access_token
from app.core.config import settings
from app.schemas.work import WorkResponse, WorkListResponse
router = APIRouter(prefix="/works", tags=["作品"])
@@ -137,9 +139,10 @@ def download_work(
)
# 构建原图文件路径
# 假设原图存储在 uploads/original/ 目录下
file_path = work.original_image.lstrip('/')
full_path = os.path.join(os.getcwd(), file_path)
relative_path = work.original_image.lstrip("/")
if relative_path.startswith("uploads/"):
relative_path = relative_path[len("uploads/"):]
full_path = os.path.join(settings.UPLOAD_DIR, relative_path)
# 检查文件是否存在
if not os.path.exists(full_path):
@@ -150,8 +153,9 @@ def download_work(
# 返回文件
filename = os.path.basename(full_path)
media_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
return FileResponse(
path=full_path,
filename=filename,
media_type='application/octet-stream'
media_type=media_type
)