Files
fund-tracer/backend/app/api/v1/images.py
2026-03-11 16:28:04 +08:00

131 lines
4.1 KiB
Python

from uuid import UUID
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, Query
from fastapi.responses import FileResponse
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.config import settings
from app.core.database import get_db
from app.models.evidence_image import EvidenceImage, SourceApp, PageType
from app.repositories.image_repo import ImageRepository
from app.repositories.case_repo import CaseRepository
from app.schemas.image import ImageOut, ImageDetailOut, OcrFieldCorrection
from app.utils.hash import sha256_file
from app.utils.file_storage import save_upload
router = APIRouter()
@router.post("/cases/{case_id}/images", response_model=list[ImageOut], status_code=201)
async def upload_images(
case_id: UUID,
files: list[UploadFile] = File(...),
db: AsyncSession = Depends(get_db),
):
case_repo = CaseRepository(db)
case = await case_repo.get(case_id)
if not case:
raise HTTPException(404, "案件不存在")
img_repo = ImageRepository(db)
results: list[EvidenceImage] = []
for f in files:
data = await f.read()
file_hash = sha256_file(data)
existing = await img_repo.find_by_hash(file_hash)
if existing:
results.append(existing)
continue
file_path, thumb_path = save_upload(data, str(case_id), f.filename or "upload.png")
image = EvidenceImage(
case_id=case_id,
file_path=file_path,
thumb_path=thumb_path,
file_hash=file_hash,
file_size=len(data),
)
image = await img_repo.create(image)
results.append(image)
case.image_count = await img_repo.count_by_case(case_id)
await db.flush()
# trigger OCR tasks (non-blocking)
from app.workers.ocr_tasks import process_image_ocr
for img in results:
if img.ocr_status.value == "pending":
try:
process_image_ocr.delay(str(img.id))
except Exception:
pass
return results
@router.get("/cases/{case_id}/images", response_model=list[ImageOut])
async def list_images(
case_id: UUID,
source_app: SourceApp | None = None,
page_type: PageType | None = None,
db: AsyncSession = Depends(get_db),
):
repo = ImageRepository(db)
return await repo.list_by_case(case_id, source_app=source_app, page_type=page_type)
@router.get("/images/{image_id}", response_model=ImageDetailOut)
async def get_image_detail(image_id: UUID, db: AsyncSession = Depends(get_db)):
repo = ImageRepository(db)
image = await repo.get(image_id)
if not image:
raise HTTPException(404, "截图不存在")
return ImageDetailOut(
id=image.id,
case_id=image.case_id,
url=f"/api/v1/images/{image.id}/file",
thumb_url=f"/api/v1/images/{image.id}/file",
source_app=image.source_app,
page_type=image.page_type,
ocr_status=image.ocr_status,
file_hash=image.file_hash,
uploaded_at=image.uploaded_at,
ocr_blocks=[
{
"id": b.id,
"content": b.content,
"bbox": b.bbox,
"seq_order": b.seq_order,
"confidence": b.confidence,
}
for b in image.ocr_blocks
],
)
@router.patch("/images/{image_id}/ocr")
async def correct_ocr(
image_id: UUID,
corrections: list[OcrFieldCorrection],
db: AsyncSession = Depends(get_db),
):
repo = ImageRepository(db)
image = await repo.get(image_id)
if not image:
raise HTTPException(404, "截图不存在")
return {"message": "修正已保存", "corrections": len(corrections)}
@router.get("/images/{image_id}/file")
async def get_image_file(image_id: UUID, db: AsyncSession = Depends(get_db)):
repo = ImageRepository(db)
image = await repo.get(image_id)
if not image:
raise HTTPException(404, "截图不存在")
full_path = settings.upload_path / image.file_path
if not full_path.exists():
raise HTTPException(404, "文件不存在")
return FileResponse(full_path)