diff --git a/backend/app/routers/ocr.py b/backend/app/routers/ocr.py index 07097bc..8406ca5 100644 --- a/backend/app/routers/ocr.py +++ b/backend/app/routers/ocr.py @@ -1,5 +1,6 @@ # OCR 识别路由 - 专业人民币生肖纪念钞鉴定 import os +import uuid import base64 import httpx from fastapi import APIRouter, Depends, HTTPException, UploadFile, File @@ -13,6 +14,10 @@ router = APIRouter(prefix="/api/ocr", tags=["OCR 识别"]) # 阿里云 DashScope API 配置 DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "sk-9389024a37da4f7bb455ac9a6b28776f") +# 上传目录配置 +UPLOAD_DIR = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "uploads", "temp") +os.makedirs(UPLOAD_DIR, exist_ok=True) + # 专业提示词 PROFESSIONAL_PROMPT = """你是一名专业的人民币生肖纪念钞鉴定专家。请严格按照以下步骤分析这张图片,并提取准确信息。 @@ -51,11 +56,22 @@ async def recognize_image( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): - """OCR 图片识别""" + """OCR 图片识别 - 识别后自动保存图片到临时目录""" try: + # 读取图片数据 image_data = await image.read() image_base64 = base64.b64encode(image_data).decode('utf-8') + # 生成临时文件名(识别后保存到服务器) + temp_id = str(uuid.uuid4()) + ext = image.filename.split('.')[-1] if '.' in image.filename else 'jpg' + temp_filename = f"{temp_id}.{ext}" + temp_path = os.path.join(UPLOAD_DIR, temp_filename) + + # 保存图片到临时目录 + with open(temp_path, 'wb') as f: + f.write(image_data) + headers = { "Authorization": f"Bearer {DASHSCOPE_API_KEY}", "Content-Type": "application/json" @@ -90,6 +106,9 @@ async def recognize_image( ) if response.status_code != 200: + # 识别失败,删除临时文件 + if os.path.exists(temp_path): + os.remove(temp_path) raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}") ocr_result = response.json() @@ -104,11 +123,18 @@ async def recognize_image( fields = extract_fields(text_content) - # 调试日志:打印提取的字段 - import logging - logging.info(f"OCR 提取的字段:{fields}") - - return {"success": True, "text": text_content, "fields": fields} + # 返回识别结果和临时图片路径 + return { + "success": True, + "text": text_content, + "fields": fields, + "temp_image": { + "id": temp_id, + "filename": temp_filename, + "path": f"uploads/temp/{temp_filename}", + "original_name": image.filename + } + } except Exception as e: import traceback @@ -186,3 +212,80 @@ def extract_fields(text: str) -> dict: fields['version'] = f"{year_match.group(1)}{animal}" return fields + + +@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) +): + """将临时图片移动到藏品目录""" + from app.models.models import Collection, CollectionImage + import shutil + + # 验证藏品是否存在 + 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="藏品不存在") + + # 临时文件路径 + temp_filename = f"{temp_id}.jpg" + temp_path = os.path.join(UPLOAD_DIR, temp_filename) + + # 检查临时文件是否存在 + if not os.path.exists(temp_path): + # 尝试其他扩展名 + for ext in ['jpeg', 'png', 'gif']: + temp_filename = f"{temp_id}.{ext}" + temp_path = os.path.join(UPLOAD_DIR, temp_filename) + if os.path.exists(temp_path): + break + + if 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) + + # 生成新文件名 + code = collection.f01_02_code or "0000" + prefix = collection.f02_10_prefix_serial or "" + username = current_user.f01_01_name + new_filename = f"{username}-{code}-{prefix}.jpg" + new_path = os.path.join(collection_dir, new_filename) + + # 如果文件已存在,添加随机后缀 + if os.path.exists(new_path): + name, ext = os.path.splitext(new_filename) + new_filename = f"{name}-{str(uuid.uuid4())[:8]}{ext}" + new_path = os.path.join(collection_dir, new_filename) + + # 移动文件 + shutil.move(temp_path, new_path) + + # 创建图片记录 + image_record = CollectionImage( + id=str(uuid.uuid4()), + collection_id=collection.f99_90_id, + filename=new_filename, + original_name=temp_filename, + path=f"uploads/collections/{new_filename}" + ) + db.add(image_record) + db.commit() + + return { + "success": True, + "image": { + "id": image_record.id, + "filename": image_record.filename, + "path": image_record.path + } + } diff --git a/config/VERSION b/config/VERSION index 563f76e..1c6d152 100644 --- a/config/VERSION +++ b/config/VERSION @@ -2,7 +2,7 @@ # Version Configuration for Zodiac Collection Management System # 当前版本号 (语义化版本:主版本。次版本.修订版) -VERSION=1.1.4 +VERSION=1.1.5 # 版本代号 (可选) VERSION_CODENAME="新生" diff --git a/frontend/src/pages/Add.jsx b/frontend/src/pages/Add.jsx index e32812c..e9a9491 100644 --- a/frontend/src/pages/Add.jsx +++ b/frontend/src/pages/Add.jsx @@ -86,6 +86,7 @@ export default function Add() { const fileInputRef = useRef(null) const imageFileInputRef = useRef(null) const [uploadImages, setUploadImages] = useState([]) + const [tempImage, setTempImage] = useState(null) // OCR识别后的临时图片 const maxImages = 1 const handleUploadImage = (e) => { @@ -218,14 +219,16 @@ export default function Add() { if (data.fields.is_graded !== undefined) recognizedForm.isGraded = data.fields.is_graded if (data.fields.three_star !== undefined) recognizedForm.threeStar = data.fields.three_star - // 保存识别的图片到 uploadImages 数组,在手工录入界面显示预览 + // 保存识别的图片信息(包含服务器临时路径) const newImage = { file: selectedImage, preview: URL.createObjectURL(selectedImage), name: selectedImage.name, - size: selectedImage.size + size: selectedImage.size, + temp_image: data.temp_image // 保存临时图片信息 } setUploadImages([newImage]) + setTempImage(data.temp_image) // 保存临时图片信息 setForm(recognizedForm) setActiveTab('manual') @@ -295,56 +298,51 @@ export default function Add() { // 上传图片(无论是否重复都执行) let totalUploadCount = 0 - // 2. 上传识别的图片(如果有) - if (recognizedImage && collectionId) { - const imgFormData = new FormData() - imgFormData.append('file', recognizedImage) - try { - const uploadRes = await fetch(`/api/collections/upload-image?collection_id=${collectionId}`, { - method: 'POST', - headers: { 'Authorization': 'Bearer ' + token }, - body: imgFormData - }) - if (uploadRes.ok) { - totalUploadCount++ - console.log('✅ 识别图片上传成功') - } else { - console.error('识别图片上传失败:', await uploadRes.text()) - } - } catch (uploadErr) { - console.error('识别图片上传异常:', uploadErr) - } - } - - // 3. 上传手工添加的图片(如果有) + // 2. 处理识别的图片(如果有临时图片,直接认领;否则上传) if (uploadImages.length > 0 && collectionId) { - let uploadCount = 0 - console.log('开始上传手工图片,数量:', uploadImages.length) + console.log('处理图片,数量:', uploadImages.length) for (const img of uploadImages) { - const imgFormData = new FormData() - imgFormData.append('file', img.file) - try { - const uploadRes = await fetch(`/api/collections/upload-image?collection_id=${collectionId}`, { - method: 'POST', - headers: { 'Authorization': 'Bearer ' + token }, - body: imgFormData - }) - if (uploadRes.ok) { - uploadCount++ - totalUploadCount++ - console.log(`图片 ${uploadCount}/${uploadImages.length} 上传成功`) - } else { - console.error('图片上传失败:', await uploadRes.text()) + // 检查是否有临时图片(OCR识别后保存的) + if (img.temp_image && img.temp_image.id) { + // 认领临时图片 + try { + const claimRes = await fetch(`/api/ocr/claim-temp-image?temp_id=${img.temp_image.id}&collection_id=${collectionId}`, { + method: 'POST', + headers: { 'Authorization': 'Bearer ' + token } + }) + if (claimRes.ok) { + totalUploadCount++ + console.log('✅ 临时图片认领成功') + } else { + console.error('临时图片认领失败:', await claimRes.text()) + } + } catch (claimErr) { + console.error('临时图片认领异常:', claimErr) + } + } else { + // 普通上传 + const imgFormData = new FormData() + imgFormData.append('file', img.file) + try { + const uploadRes = await fetch(`/api/collections/upload-image?collection_id=${collectionId}`, { + method: 'POST', + headers: { 'Authorization': 'Bearer ' + token }, + body: imgFormData + }) + if (uploadRes.ok) { + totalUploadCount++ + console.log(`图片上传成功`) + } else { + console.error('图片上传失败:', await uploadRes.text()) + } + } catch (uploadErr) { + console.error('图片上传异常:', uploadErr) } - } catch (uploadErr) { - console.error('图片上传异常:', uploadErr) } } - if (uploadCount > 0) { - console.log(`✅ 成功上传 ${uploadCount}/${uploadImages.length} 张手工图片`) - } + console.log(`✅ 共处理 ${totalUploadCount} 张图片`) } alert('保存成功!' + (totalUploadCount > 0 ? `已上传${totalUploadCount}张图片` : '')) diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 0dca1f3..71ed9ee 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -152,7 +152,6 @@ export default function Settings() { } else { setError(data.error?.message || data.detail || '密码修改失败') } - } } catch (e) { setError('密码修改失败,请重试') } finally {