feat: OCR识别后自动保存图片,保存藏品时无需重新上传

This commit is contained in:
菜鸟生产 2026-03-18 13:51:24 +08:00
parent c175d9e842
commit 74efe2d543
4 changed files with 153 additions and 53 deletions

View File

@ -1,5 +1,6 @@
# OCR 识别路由 - 专业人民币生肖纪念钞鉴定 # OCR 识别路由 - 专业人民币生肖纪念钞鉴定
import os import os
import uuid
import base64 import base64
import httpx import httpx
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File from fastapi import APIRouter, Depends, HTTPException, UploadFile, File
@ -13,6 +14,10 @@ router = APIRouter(prefix="/api/ocr", tags=["OCR 识别"])
# 阿里云 DashScope API 配置 # 阿里云 DashScope API 配置
DASHSCOPE_API_KEY = os.getenv("DASHSCOPE_API_KEY", "sk-9389024a37da4f7bb455ac9a6b28776f") 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 = """你是一名专业的人民币生肖纪念钞鉴定专家。请严格按照以下步骤分析这张图片,并提取准确信息。 PROFESSIONAL_PROMPT = """你是一名专业的人民币生肖纪念钞鉴定专家。请严格按照以下步骤分析这张图片,并提取准确信息。
@ -51,11 +56,22 @@ async def recognize_image(
current_user: User = Depends(get_current_user), current_user: User = Depends(get_current_user),
db: Session = Depends(get_db) db: Session = Depends(get_db)
): ):
"""OCR 图片识别""" """OCR 图片识别 - 识别后自动保存图片到临时目录"""
try: try:
# 读取图片数据
image_data = await image.read() image_data = await image.read()
image_base64 = base64.b64encode(image_data).decode('utf-8') 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 = { headers = {
"Authorization": f"Bearer {DASHSCOPE_API_KEY}", "Authorization": f"Bearer {DASHSCOPE_API_KEY}",
"Content-Type": "application/json" "Content-Type": "application/json"
@ -90,6 +106,9 @@ async def recognize_image(
) )
if response.status_code != 200: 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]}") raise HTTPException(status_code=500, detail=f"OCR API 调用失败:{response.text[:200]}")
ocr_result = response.json() ocr_result = response.json()
@ -104,11 +123,18 @@ async def recognize_image(
fields = extract_fields(text_content) fields = extract_fields(text_content)
# 调试日志:打印提取的字段 # 返回识别结果和临时图片路径
import logging return {
logging.info(f"OCR 提取的字段:{fields}") "success": True,
"text": text_content,
return {"success": True, "text": text_content, "fields": fields} "fields": fields,
"temp_image": {
"id": temp_id,
"filename": temp_filename,
"path": f"uploads/temp/{temp_filename}",
"original_name": image.filename
}
}
except Exception as e: except Exception as e:
import traceback import traceback
@ -186,3 +212,80 @@ def extract_fields(text: str) -> dict:
fields['version'] = f"{year_match.group(1)}{animal}" fields['version'] = f"{year_match.group(1)}{animal}"
return fields 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
}
}

View File

@ -2,7 +2,7 @@
# Version Configuration for Zodiac Collection Management System # Version Configuration for Zodiac Collection Management System
# 当前版本号 (语义化版本:主版本。次版本.修订版) # 当前版本号 (语义化版本:主版本。次版本.修订版)
VERSION=1.1.4 VERSION=1.1.5
# 版本代号 (可选) # 版本代号 (可选)
VERSION_CODENAME="新生" VERSION_CODENAME="新生"

View File

@ -86,6 +86,7 @@ export default function Add() {
const fileInputRef = useRef(null) const fileInputRef = useRef(null)
const imageFileInputRef = useRef(null) const imageFileInputRef = useRef(null)
const [uploadImages, setUploadImages] = useState([]) const [uploadImages, setUploadImages] = useState([])
const [tempImage, setTempImage] = useState(null) // OCR
const maxImages = 1 const maxImages = 1
const handleUploadImage = (e) => { 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.is_graded !== undefined) recognizedForm.isGraded = data.fields.is_graded
if (data.fields.three_star !== undefined) recognizedForm.threeStar = data.fields.three_star if (data.fields.three_star !== undefined) recognizedForm.threeStar = data.fields.three_star
// uploadImages //
const newImage = { const newImage = {
file: selectedImage, file: selectedImage,
preview: URL.createObjectURL(selectedImage), preview: URL.createObjectURL(selectedImage),
name: selectedImage.name, name: selectedImage.name,
size: selectedImage.size size: selectedImage.size,
temp_image: data.temp_image //
} }
setUploadImages([newImage]) setUploadImages([newImage])
setTempImage(data.temp_image) //
setForm(recognizedForm) setForm(recognizedForm)
setActiveTab('manual') setActiveTab('manual')
@ -295,56 +298,51 @@ export default function Add() {
// //
let totalUploadCount = 0 let totalUploadCount = 0
// 2. // 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.
if (uploadImages.length > 0 && collectionId) { if (uploadImages.length > 0 && collectionId) {
let uploadCount = 0 console.log('处理图片,数量:', uploadImages.length)
console.log('开始上传手工图片,数量:', uploadImages.length)
for (const img of uploadImages) { for (const img of uploadImages) {
const imgFormData = new FormData() // OCR
imgFormData.append('file', img.file) if (img.temp_image && img.temp_image.id) {
try { //
const uploadRes = await fetch(`/api/collections/upload-image?collection_id=${collectionId}`, { try {
method: 'POST', const claimRes = await fetch(`/api/ocr/claim-temp-image?temp_id=${img.temp_image.id}&collection_id=${collectionId}`, {
headers: { 'Authorization': 'Bearer ' + token }, method: 'POST',
body: imgFormData headers: { 'Authorization': 'Bearer ' + token }
}) })
if (uploadRes.ok) { if (claimRes.ok) {
uploadCount++ totalUploadCount++
totalUploadCount++ console.log('✅ 临时图片认领成功')
console.log(`图片 ${uploadCount}/${uploadImages.length} 上传成功`) } else {
} else { console.error('临时图片认领失败:', await claimRes.text())
console.error('图片上传失败:', await uploadRes.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(`✅ 共处理 ${totalUploadCount} 张图片`)
console.log(`✅ 成功上传 ${uploadCount}/${uploadImages.length} 张手工图片`)
}
} }
alert('保存成功!' + (totalUploadCount > 0 ? `已上传${totalUploadCount}张图片` : '')) alert('保存成功!' + (totalUploadCount > 0 ? `已上传${totalUploadCount}张图片` : ''))

View File

@ -152,7 +152,6 @@ export default function Settings() {
} else { } else {
setError(data.error?.message || data.detail || '密码修改失败') setError(data.error?.message || data.detail || '密码修改失败')
} }
}
} catch (e) { } catch (e) {
setError('密码修改失败,请重试') setError('密码修改失败,请重试')
} finally { } finally {