feat: OCR识别后自动保存图片,保存藏品时无需重新上传
This commit is contained in:
parent
c175d9e842
commit
74efe2d543
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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="新生"
|
||||||
|
|
|
||||||
|
|
@ -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}张图片` : ''))
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue