2026-03-16 11:39:39 +08:00
|
|
|
|
# OCR 识别路由 - 专业人民币生肖纪念钞鉴定
|
|
|
|
|
|
import os
|
2026-03-18 13:51:24 +08:00
|
|
|
|
import uuid
|
2026-03-16 11:39:39 +08:00
|
|
|
|
import base64
|
|
|
|
|
|
import httpx
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
|
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
from app.core.database import get_db
|
|
|
|
|
|
from app.core.auth import get_current_user
|
|
|
|
|
|
from app.models.models import User
|
|
|
|
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api/ocr", tags=["OCR 识别"])
|
|
|
|
|
|
|
|
|
|
|
|
# 阿里云 DashScope API 配置
|
|
|
|
|
|
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "sk-9389024a37da4f7bb455ac9a6b28776f")
|
|
|
|
|
|
|
2026-03-18 14:01:02 +08:00
|
|
|
|
# 阿里云 OSS 配置
|
|
|
|
|
|
OSS_CONFIG = {
|
|
|
|
|
|
"access_key_id": os.getenv("OSS_ACCESS_KEY_ID", "LTAI5t6HUnpFBLEK9194kPVG"),
|
|
|
|
|
|
"access_key_secret": os.getenv("OSS_ACCESS_KEY_SECRET", "LEr4Q8yRxb8D5b24cKfCwlt4MMoke1"),
|
|
|
|
|
|
"bucket_name": "jiachenlong-oss",
|
|
|
|
|
|
"endpoint": "oss-cn-hangzhou.aliyuncs.com",
|
|
|
|
|
|
"public_url": "https://jiachenlong-oss.oss-cn-hangzhou.aliyuncs.com"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 14:14:46 +08:00
|
|
|
|
# 临时上传目录(用于OCR识别本地备选)
|
2026-03-18 13:51:24 +08:00
|
|
|
|
UPLOAD_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "temp")
|
|
|
|
|
|
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
|
|
|
|
|
|
2026-03-18 14:01:02 +08:00
|
|
|
|
|
2026-03-18 14:14:46 +08:00
|
|
|
|
def get_oss_path(file_type: str, user_id: str = None, collection_id: str = None, filename: str = None):
|
|
|
|
|
|
"""生成OSS路径 - 按年/月/日分类"""
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
now = datetime.now()
|
|
|
|
|
|
year = now.strftime("%Y")
|
|
|
|
|
|
month = now.strftime("%m")
|
|
|
|
|
|
day = now.strftime("%d")
|
|
|
|
|
|
|
|
|
|
|
|
if file_type == "temp":
|
|
|
|
|
|
# 临时文件: temp/{year}/{month}/{day}/{uuid}.{ext}
|
|
|
|
|
|
import uuid
|
|
|
|
|
|
unique_id = str(uuid.uuid4())
|
|
|
|
|
|
ext = filename.split('.')[-1] if filename and '.' in filename else 'jpg'
|
|
|
|
|
|
return f"temp/{year}/{month}/{day}/{unique_id}.{ext}", unique_id
|
|
|
|
|
|
|
|
|
|
|
|
elif file_type == "collection":
|
|
|
|
|
|
# 藏品文件: collections/{user_id}/{year}/{collection_id}/{filename}
|
|
|
|
|
|
if not user_id or not collection_id:
|
|
|
|
|
|
raise ValueError("user_id and collection_id required for collection")
|
|
|
|
|
|
return f"collections/{user_id}/{year}/{collection_id}/{filename}"
|
|
|
|
|
|
|
|
|
|
|
|
elif file_type == "avatar":
|
|
|
|
|
|
# 头像: avatars/{user_id}/avatar.{ext}
|
|
|
|
|
|
if not user_id:
|
|
|
|
|
|
raise ValueError("user_id required for avatar")
|
|
|
|
|
|
ext = filename.split('.')[-1] if filename and '.' in filename else 'jpg'
|
|
|
|
|
|
return f"avatars/{user_id}/avatar.{ext}"
|
|
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-03-18 19:16:20 +08:00
|
|
|
|
# 上传图片到OSS - 使用服务层(带压缩)
|
|
|
|
|
|
from app.services.oss import upload_to_oss as oss_upload
|
|
|
|
|
|
|
2026-03-18 14:14:46 +08:00
|
|
|
|
def upload_to_oss(file_data, oss_key):
|
2026-03-18 19:16:20 +08:00
|
|
|
|
"""上传文件到阿里云OSS(带自动压缩)"""
|
|
|
|
|
|
return oss_upload(file_data, oss_key)
|
2026-03-18 14:01:02 +08:00
|
|
|
|
|
2026-03-16 11:39:39 +08:00
|
|
|
|
# 专业提示词
|
|
|
|
|
|
PROFESSIONAL_PROMPT = """你是一名专业的人民币生肖纪念钞鉴定专家。请严格按照以下步骤分析这张图片,并提取准确信息。
|
|
|
|
|
|
|
|
|
|
|
|
【识别流程】
|
|
|
|
|
|
1. 判断类型是否评级钞:首先确认是否为裸钞还是评级钞(有封装盒和标签)
|
|
|
|
|
|
2. 验证纪念钞特征:对照生肖纪念钞特征进行确认
|
|
|
|
|
|
3. 验证评级类型:有'标十'字眼的为标十,有'百连'字眼的为标百,其他为单张
|
|
|
|
|
|
4. 提取信息:仔细阅读标签上的所有文字内容
|
|
|
|
|
|
|
|
|
|
|
|
【版别格式要求】
|
|
|
|
|
|
只需要:年份 + 属相,例如:
|
|
|
|
|
|
- 2024 龙
|
|
|
|
|
|
- 2025 蛇
|
|
|
|
|
|
- 2026 马
|
|
|
|
|
|
|
|
|
|
|
|
【输出要求】
|
|
|
|
|
|
严格按照以下格式输出,每个字段必须填写具体值:
|
|
|
|
|
|
✅ 1 发行机构:中国人民银行
|
|
|
|
|
|
✅ 2 发行版别:2024 龙
|
|
|
|
|
|
✅ 3 面额:贰拾圆
|
|
|
|
|
|
✅ 4 是否评级:是/否
|
|
|
|
|
|
✅ 5 封装类型:裸钞/单张/标十/标百
|
|
|
|
|
|
✅ 6 冠字序号:J0xxxxxxxx
|
|
|
|
|
|
✅ 7 评级机构:ACG/PCGS/PMG
|
|
|
|
|
|
✅ 8 评级分数:67/68/69
|
|
|
|
|
|
✅ 9 是否三星:是/否
|
|
|
|
|
|
✅ 10 特殊标识:金山标/天马标/红绳版等
|
|
|
|
|
|
✅ 11 号码特征:金山号 2 张,天马号 3 张等
|
|
|
|
|
|
|
|
|
|
|
|
现在请仔细分析提供的图片,按上述格式输出结果。"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/recognize")
|
|
|
|
|
|
async def recognize_image(
|
|
|
|
|
|
image: UploadFile = File(...),
|
|
|
|
|
|
current_user: User = Depends(get_current_user),
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
2026-03-18 14:14:46 +08:00
|
|
|
|
"""OCR 图片识别 - 识别后自动保存图片到OSS临时目录"""
|
2026-03-16 11:39:39 +08:00
|
|
|
|
try:
|
2026-03-18 13:51:24 +08:00
|
|
|
|
# 读取图片数据
|
2026-03-16 11:39:39 +08:00
|
|
|
|
image_data = await image.read()
|
|
|
|
|
|
image_base64 = base64.b64encode(image_data).decode('utf-8')
|
|
|
|
|
|
|
2026-03-18 14:14:46 +08:00
|
|
|
|
# 生成OSS临时路径: temp/{year}/{month}/{day}/{uuid}.{ext}
|
|
|
|
|
|
oss_key, temp_id = get_oss_path("temp", filename=image.filename)
|
2026-03-18 13:51:24 +08:00
|
|
|
|
|
2026-03-18 14:01:02 +08:00
|
|
|
|
# 上传到OSS
|
|
|
|
|
|
try:
|
|
|
|
|
|
image_url = upload_to_oss(image_data, oss_key)
|
|
|
|
|
|
except Exception as oss_err:
|
|
|
|
|
|
# OSS失败时保存到本地作为备选
|
2026-03-18 14:14:46 +08:00
|
|
|
|
temp_path = os.path.join(UPLOAD_DIR, oss_key.split('/')[-1])
|
|
|
|
|
|
os.makedirs(os.path.dirname(temp_path), exist_ok=True)
|
2026-03-18 14:01:02 +08:00
|
|
|
|
with open(temp_path, 'wb') as f:
|
|
|
|
|
|
f.write(image_data)
|
2026-03-18 14:14:46 +08:00
|
|
|
|
image_url = f"/uploads/temp/{oss_key.split('/')[-1]}"
|
2026-03-18 13:51:24 +08:00
|
|
|
|
|
2026-03-16 11:39:39 +08:00
|
|
|
|
headers = {
|
|
|
|
|
|
"Authorization": f"Bearer {DASHSCOPE_API_KEY}",
|
|
|
|
|
|
"Content-Type": "application/json"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-18 12:17:41 +08:00
|
|
|
|
# 阿里云 DashScope API 格式 (qwen-vl-plus 视觉模型)
|
2026-03-16 11:39:39 +08:00
|
|
|
|
payload = {
|
2026-03-18 12:17:41 +08:00
|
|
|
|
"model": "qwen-vl-plus",
|
|
|
|
|
|
"input": {
|
|
|
|
|
|
"messages": [{
|
|
|
|
|
|
"role": "user",
|
|
|
|
|
|
"content": [
|
|
|
|
|
|
{
|
|
|
|
|
|
"image": f"data:{image.content_type};base64,{image_base64}"
|
|
|
|
|
|
},
|
|
|
|
|
|
{
|
|
|
|
|
|
"text": PROFESSIONAL_PROMPT
|
2026-03-16 11:39:39 +08:00
|
|
|
|
}
|
2026-03-18 12:17:41 +08:00
|
|
|
|
]
|
|
|
|
|
|
}]
|
|
|
|
|
|
},
|
|
|
|
|
|
"parameters": {
|
|
|
|
|
|
"max_tokens": 1000
|
|
|
|
|
|
}
|
2026-03-16 11:39:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async with httpx.AsyncClient(timeout=60.0) as client:
|
|
|
|
|
|
response = await client.post(
|
2026-03-18 12:17:41 +08:00
|
|
|
|
"https://dashscope.aliyuncs.com/api/v1/services/aigc/multimodal-generation/generation",
|
2026-03-16 11:39:39 +08:00
|
|
|
|
json=payload,
|
|
|
|
|
|
headers=headers
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if response.status_code != 200:
|
2026-03-18 13:51:24 +08:00
|
|
|
|
# 识别失败,删除临时文件
|
|
|
|
|
|
if os.path.exists(temp_path):
|
|
|
|
|
|
os.remove(temp_path)
|
2026-03-16 11:39:39 +08:00
|
|
|
|
raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}")
|
|
|
|
|
|
|
|
|
|
|
|
ocr_result = response.json()
|
|
|
|
|
|
text_content = ""
|
2026-03-18 12:17:41 +08:00
|
|
|
|
# 新版API返回格式
|
|
|
|
|
|
if "output" in ocr_result and "choices" in ocr_result["output"]:
|
|
|
|
|
|
choices = ocr_result["output"]["choices"]
|
|
|
|
|
|
if choices and len(choices) > 0:
|
|
|
|
|
|
content = choices[0].get("message", {}).get("content", [])
|
|
|
|
|
|
if content and len(content) > 0:
|
|
|
|
|
|
text_content = content[0].get("text", "")
|
2026-03-16 11:39:39 +08:00
|
|
|
|
|
|
|
|
|
|
fields = extract_fields(text_content)
|
|
|
|
|
|
|
2026-03-18 13:51:24 +08:00
|
|
|
|
# 返回识别结果和临时图片路径
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"text": text_content,
|
|
|
|
|
|
"fields": fields,
|
|
|
|
|
|
"temp_image": {
|
|
|
|
|
|
"id": temp_id,
|
2026-03-18 14:01:02 +08:00
|
|
|
|
"filename": oss_key.split('/')[-1],
|
|
|
|
|
|
"path": image_url,
|
|
|
|
|
|
"original_name": image.filename,
|
|
|
|
|
|
"is_oss": image_url.startswith("https://")
|
2026-03-18 13:51:24 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-16 11:39:39 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-03-18 12:17:41 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
error_detail = f"识别失败:{str(e)}\n{traceback.format_exc()}"
|
|
|
|
|
|
raise HTTPException(status_code=500, detail=error_detail)
|
2026-03-16 11:39:39 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def extract_fields(text: str) -> dict:
|
|
|
|
|
|
"""从 OCR 文本中提取字段 - 直接返回 AI 识别结果"""
|
|
|
|
|
|
import re
|
|
|
|
|
|
fields = {}
|
|
|
|
|
|
|
|
|
|
|
|
# 解析结构化输出
|
|
|
|
|
|
patterns = {
|
|
|
|
|
|
'issuer': r'✅.*?1.*?发行机构.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'version': r'✅.*?2.*?发行版别.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'denomination': r'✅.*?3.*?面额.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'is_graded_text': r'✅.*?4.*?是否评级.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'packaging': r'✅.*?5.*?封装类型.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'prefix_serial': r'✅.*?6.*?冠字序号.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'grading_company': r'✅.*?7.*?评级机构.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'grading_score': r'✅.*?8.*?评级分数.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'three_star_text': r'✅.*?9.*?是否三星.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'special_mark': r'✅.*?10.*?特殊标识.*?[::]\s*(.+?)(?:\n|$)',
|
|
|
|
|
|
'serial_feature': r'✅.*?11.*?号码特征.*?[::]\s*(.+?)(?:\n|$)'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for field, pattern in patterns.items():
|
|
|
|
|
|
match = re.search(pattern, text, re.IGNORECASE | re.DOTALL)
|
|
|
|
|
|
if match:
|
|
|
|
|
|
value = match.group(1).strip()
|
|
|
|
|
|
# 保留所有值,包括"无"和"未识别",让前端处理
|
|
|
|
|
|
fields[field] = value
|
|
|
|
|
|
|
|
|
|
|
|
# 处理是否评级
|
|
|
|
|
|
if 'is_graded_text' in fields:
|
|
|
|
|
|
fields['is_graded'] = '是' in fields.pop('is_graded_text')
|
|
|
|
|
|
|
|
|
|
|
|
# 处理是否三星
|
|
|
|
|
|
if 'three_star_text' in fields:
|
|
|
|
|
|
fields['three_star'] = '是' in fields.pop('three_star_text')
|
|
|
|
|
|
|
|
|
|
|
|
# 简化版别字段(2024 龙年贺岁纪念钞(标十) → 2024 龙)
|
|
|
|
|
|
if 'version' in fields:
|
|
|
|
|
|
version = fields['version']
|
|
|
|
|
|
# 提取年份和生肖
|
|
|
|
|
|
year_match = re.search(r'(20\d{2})', version)
|
|
|
|
|
|
animal = ''
|
|
|
|
|
|
if '龙' in version:
|
|
|
|
|
|
animal = '龙'
|
|
|
|
|
|
elif '蛇' in version:
|
|
|
|
|
|
animal = '蛇'
|
|
|
|
|
|
elif '马' in version:
|
|
|
|
|
|
animal = '马'
|
|
|
|
|
|
elif '羊' in version:
|
|
|
|
|
|
animal = '羊'
|
|
|
|
|
|
elif '猴' in version:
|
|
|
|
|
|
animal = '猴'
|
|
|
|
|
|
elif '鸡' in version:
|
|
|
|
|
|
animal = '鸡'
|
|
|
|
|
|
elif '狗' in version:
|
|
|
|
|
|
animal = '狗'
|
|
|
|
|
|
elif '猪' in version:
|
|
|
|
|
|
animal = '猪'
|
|
|
|
|
|
elif '鼠' in version:
|
|
|
|
|
|
animal = '鼠'
|
|
|
|
|
|
elif '牛' in version:
|
|
|
|
|
|
animal = '牛'
|
|
|
|
|
|
elif '虎' in version:
|
|
|
|
|
|
animal = '虎'
|
|
|
|
|
|
elif '兔' in version:
|
|
|
|
|
|
animal = '兔'
|
|
|
|
|
|
|
|
|
|
|
|
if year_match and animal:
|
|
|
|
|
|
fields['version'] = f"{year_match.group(1)}{animal}"
|
|
|
|
|
|
|
|
|
|
|
|
return fields
|
2026-03-18 13:51:24 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/claim-temp-image")
|
|
|
|
|
|
async def claim_temp_image(
|
|
|
|
|
|
temp_id: str,
|
|
|
|
|
|
collection_id: str,
|
|
|
|
|
|
current_user: User = Depends(get_current_user),
|
|
|
|
|
|
db: Session = Depends(get_db)
|
|
|
|
|
|
):
|
2026-03-18 14:14:46 +08:00
|
|
|
|
"""将临时图片移动到正式藏品目录 - 按用户/年/藏品ID分类"""
|
2026-03-18 13:51:24 +08:00
|
|
|
|
from app.models.models import Collection, CollectionImage
|
2026-03-18 14:14:46 +08:00
|
|
|
|
from datetime import datetime
|
2026-03-18 13:51:24 +08:00
|
|
|
|
|
|
|
|
|
|
# 验证藏品是否存在
|
|
|
|
|
|
collection = db.query(Collection).filter(
|
|
|
|
|
|
Collection.f99_90_id == collection_id,
|
|
|
|
|
|
Collection.f99_91_user_id == current_user.f99_90_id
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if not collection:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="藏品不存在")
|
|
|
|
|
|
|
2026-03-18 14:14:46 +08:00
|
|
|
|
# 生成正式文件名和OSS路径: collections/{user_id}/{year}/{collection_id}/{filename}
|
2026-03-18 13:51:24 +08:00
|
|
|
|
code = collection.f01_02_code or "0000"
|
|
|
|
|
|
prefix = collection.f02_10_prefix_serial or ""
|
|
|
|
|
|
username = current_user.f01_01_name
|
2026-03-18 14:01:02 +08:00
|
|
|
|
import time
|
|
|
|
|
|
final_filename = f"{username}-{code}-{prefix}-{int(time.time())}.jpg"
|
2026-03-18 14:14:46 +08:00
|
|
|
|
oss_key = get_oss_path("collection", user_id=current_user.f99_90_id, collection_id=collection_id, filename=final_filename)
|
2026-03-18 13:51:24 +08:00
|
|
|
|
|
2026-03-18 14:14:46 +08:00
|
|
|
|
# 尝试从OSS获取临时图片 - 尝试多种扩展名和日期路径
|
2026-03-20 00:25:53 +08:00
|
|
|
|
temp_extensions = ['jpg', 'jpeg', 'png', 'gif', 'JPG', 'JPEG', 'PNG', 'GIF']
|
2026-03-18 14:01:02 +08:00
|
|
|
|
temp_content = None
|
2026-03-18 14:14:46 +08:00
|
|
|
|
found_key = None
|
2026-03-18 13:51:24 +08:00
|
|
|
|
|
2026-03-18 14:14:46 +08:00
|
|
|
|
# 尝试最近7天的路径
|
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
|
for i in range(7):
|
|
|
|
|
|
date = datetime.now() - timedelta(days=i)
|
|
|
|
|
|
year = date.strftime("%Y")
|
|
|
|
|
|
month = date.strftime("%m")
|
|
|
|
|
|
day = date.strftime("%d")
|
|
|
|
|
|
|
|
|
|
|
|
for ext in temp_extensions:
|
|
|
|
|
|
try:
|
|
|
|
|
|
temp_oss_key = f"temp/{year}/{month}/{day}/{temp_id}.{ext}"
|
|
|
|
|
|
import oss2
|
|
|
|
|
|
auth = oss2.Auth(OSS_CONFIG["access_key_id"], OSS_CONFIG["access_key_secret"])
|
|
|
|
|
|
bucket = oss2.Bucket(auth, OSS_CONFIG["endpoint"], OSS_CONFIG["bucket_name"])
|
|
|
|
|
|
temp_content = bucket.get_object(temp_oss_key).read()
|
|
|
|
|
|
found_key = temp_oss_key
|
|
|
|
|
|
break
|
|
|
|
|
|
except:
|
|
|
|
|
|
continue
|
|
|
|
|
|
if temp_content:
|
2026-03-18 14:01:02 +08:00
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
if temp_content:
|
|
|
|
|
|
# 上传到正式目录
|
|
|
|
|
|
bucket.put_object(oss_key, temp_content)
|
|
|
|
|
|
|
|
|
|
|
|
# 删除临时图片
|
|
|
|
|
|
try:
|
2026-03-18 14:14:46 +08:00
|
|
|
|
bucket.delete_object(found_key)
|
2026-03-18 14:01:02 +08:00
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# OSS URL
|
|
|
|
|
|
image_path = f"{OSS_CONFIG['public_url']}/{oss_key}"
|
|
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
|
# OSS失败,使用本地文件
|
|
|
|
|
|
temp_path = None
|
|
|
|
|
|
for ext in temp_extensions:
|
|
|
|
|
|
temp_path = os.path.join(UPLOAD_DIR, f"{temp_id}.{ext}")
|
|
|
|
|
|
if os.path.exists(temp_path):
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
if not temp_path or not os.path.exists(temp_path):
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="临时图片不存在或已过期")
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到本地
|
|
|
|
|
|
collection_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "collections")
|
|
|
|
|
|
os.makedirs(collection_dir, exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
new_path = os.path.join(collection_dir, final_filename)
|
|
|
|
|
|
import shutil
|
|
|
|
|
|
shutil.move(temp_path, new_path)
|
|
|
|
|
|
image_path = f"uploads/collections/{final_filename}"
|
2026-03-18 13:51:24 +08:00
|
|
|
|
|
|
|
|
|
|
# 创建图片记录
|
|
|
|
|
|
image_record = CollectionImage(
|
|
|
|
|
|
id=str(uuid.uuid4()),
|
|
|
|
|
|
collection_id=collection.f99_90_id,
|
2026-03-18 14:01:02 +08:00
|
|
|
|
filename=final_filename,
|
|
|
|
|
|
original_name=temp_id,
|
|
|
|
|
|
path=image_path
|
2026-03-18 13:51:24 +08:00
|
|
|
|
)
|
|
|
|
|
|
db.add(image_record)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"image": {
|
|
|
|
|
|
"id": image_record.id,
|
|
|
|
|
|
"filename": image_record.filename,
|
|
|
|
|
|
"path": image_record.path
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|