fix issue

This commit is contained in:
EduBrain Dev
2026-04-14 21:25:41 +08:00
parent 23eabca58d
commit 4d5665d369
5 changed files with 145 additions and 137 deletions

View File

@@ -21,7 +21,7 @@ from sqlalchemy import select, func, text, delete
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database import get_db
from app.database import get_db, async_session_factory
from app.models.base import OCRImage
from app.services.ocr_service import OCRService
from app.services.llm_service import LLMService
@@ -299,15 +299,11 @@ async def search_images(
keyword: str,
limit: int = Query(5, ge=1, le=100),
sort: str = Query("time_desc"),
db: AsyncSession = Depends(get_db),
):
"""
AI 搜索图片SSE 流式返回)。
1. 从 DB 取所有图片,按指定排序
2. 每批 5 条发给 Qwen3-8B 判断
3. 并发池最多 10 个请求
4. 匹配结果实时 SSE 推送
5. 找够 limit 个或遍历完 DB 后结束
注意:不使用 Depends(get_db),因为 StreamingResponse 的生命周期
超出 request scope需要在生成器内部自行管理 session。
"""
async def event_stream():
@@ -320,6 +316,8 @@ async def search_images(
# 发送开始事件
yield f"data: {json.dumps({'type': 'start', 'keyword': keyword, 'limit': limit}, ensure_ascii=False)}\n\n"
# 在生成器内部自行管理 session避免 get_db 提前关闭
async with async_session_factory() as db:
# 先获取总记录数
count_result = await db.execute(
select(func.count(OCRImage.id)).where(OCRImage.status == "completed")

View File

@@ -8,6 +8,7 @@ from __future__ import annotations
import asyncio
import json
import logging
import os
import threading
from contextlib import asynccontextmanager
from pathlib import Path
@@ -193,7 +194,11 @@ async def health_check():
@app.get("/data/images/{image_name}", tags=["前端"])
async def serve_image(image_name: str):
"""访问 images 目录下的图片文件(用于前端预览)"""
img_path = settings.images_dir / image_name
# 防止路径遍历攻击
safe_name = os.path.basename(image_name)
if safe_name != image_name or ".." in image_name:
return JSONResponse(status_code=400, content={"detail": "非法文件名"})
img_path = settings.images_dir / safe_name
if not img_path.exists():
return JSONResponse(status_code=404, content={"detail": "图片不存在"})
return FileResponse(str(img_path))

View File

@@ -81,6 +81,7 @@ class OCRImage(Base, TimestampMixin):
String(20), default="pending", comment="状态: pending/processing/completed/failed"
)
blocks: Mapped[str | None] = mapped_column(Text, nullable=True, comment="OCR 文本块JSON")
error_message: Mapped[str | None] = mapped_column(Text, nullable=True, comment="错误信息")
@property
def blocks_list(self) -> list[dict]:

View File

@@ -286,10 +286,12 @@ class LLMService:
try:
from openai import AsyncOpenAI
http_client = httpx.AsyncClient(timeout=60.0)
try:
client = AsyncOpenAI(
api_key=settings.MINIMAX_API_KEY,
base_url=settings.MINIMAX_BASE_URL,
http_client=httpx.AsyncClient(timeout=60.0),
http_client=http_client,
)
response = await client.chat.completions.create(
model=settings.MINIMAX_CHAT_MODEL,
@@ -317,6 +319,8 @@ class LLMService:
matched_ids.append(articles[num - 1]["id"])
return matched_ids
finally:
await http_client.aclose()
except Exception as exc:
logger.warning("LLM 批量判断失败: %s", exc)
return []

View File

@@ -102,7 +102,7 @@ class PaddleOCRProvider(OCRProviderBase):
async def recognize(self, image_path: str) -> OCRResult:
"""使用 PaddleOCR 识别图片"""
self._init_ocr()
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
def _run_ocr():
result = self._ocr.ocr(image_path, cls=True)
@@ -164,7 +164,7 @@ class AliyunOCRProvider(OCRProviderBase):
import asyncio
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
def _call_api():
from alibabacloud_tea_openapi.models import Config
@@ -249,7 +249,7 @@ class TencentOCRProvider(OCRProviderBase):
import base64
import json
loop = asyncio.get_event_loop()
loop = asyncio.get_running_loop()
def _call_api():
from tencentcloud.common import credential