| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- """
- 批量处理 API
- """
- from fastapi import APIRouter, BackgroundTasks, HTTPException
- from pydantic import BaseModel, Field
- from typing import List, Optional, Dict, Any
- from pathlib import Path
- from loguru import logger
- from services.batch_service import BatchProcessor
- from models.schemas import TableStructure, ImageSize
- router = APIRouter(prefix="/api/batch", tags=["batch"])
- class FilePair(BaseModel):
- """文件对"""
- json_path: str = Field(description="JSON 文件路径")
- image_path: str = Field(description="图片文件路径")
- class BatchProcessRequest(BaseModel):
- """批量处理请求"""
- template_name: str = Field(description="要应用的模板名称")
- file_pairs: List[FilePair] = Field(description="文件对列表")
- output_dir: str = Field(description="输出目录")
- parallel: bool = Field(default=True, description="是否并行处理")
- adjust_rows: bool = Field(default=True, description="是否自适应调整行分割")
- structure_suffix: str = Field(default="_structure.json", description="结构文件后缀")
- image_suffix: str = Field(default="_with_lines.png", description="输出图片文件后缀")
- class BatchProcessResult(BaseModel):
- """单个文件处理结果"""
- success: bool
- json_path: str
- image_path: str
- structure_path: Optional[str] = None
- filename: str
- rows: Optional[int] = None
- cols: Optional[int] = None
- error: Optional[str] = None
- class BatchProcessResponse(BaseModel):
- """批量处理响应"""
- success: bool
- total: int
- processed: int
- failed: int
- results: List[Dict[str, Any]]
- message: Optional[str] = None
- class DrawBatchRequest(BaseModel):
- """批量绘图请求"""
- results: List[Dict[str, Any]] = Field(description="处理结果列表")
- line_width: int = Field(default=2, description="线条宽度")
- line_color: List[int] = Field(default=[0, 0, 0], description="线条颜色 RGB")
- class DrawBatchResponse(BaseModel):
- """批量绘图响应"""
- success: bool
- total: int
- drawn: int
- results: List[Dict[str, Any]]
- message: Optional[str] = None
- @router.post("/process", response_model=BatchProcessResponse)
- async def batch_process(request: BatchProcessRequest):
- """
- 批量处理文件
-
- 将指定模板的结构应用到多个文件:
- - 复用列边界(竖线)
- - 自适应调整行分割(横线)
- - 支持并行处理
- - 自动处理不同图片尺寸的坐标映射
- """
- try:
- # 验证输出目录
- output_path = Path(request.output_dir)
- if not output_path.parent.exists():
- raise HTTPException(
- status_code=400,
- detail=f"输出目录的父目录不存在: {output_path.parent}"
- )
-
- # 转换文件对格式
- file_pairs = [
- {
- 'json_path': pair.json_path,
- 'image_path': pair.image_path
- }
- for pair in request.file_pairs
- ]
-
- # 创建处理器
- processor = BatchProcessor(max_workers=4)
-
- # 执行批量处理
- summary = processor.process_batch_from_data_source(
- template_name=request.template_name,
- file_pairs=file_pairs,
- output_dir=request.output_dir,
- parallel=request.parallel,
- adjust_rows=request.adjust_rows,
- structure_suffix=request.structure_suffix,
- image_suffix=request.image_suffix
- )
-
- return BatchProcessResponse(
- success=True,
- total=summary['total'],
- processed=summary['success'],
- failed=summary['failed'],
- results=summary['results'],
- message=f"批量处理完成: 成功 {summary['success']}/{summary['total']}"
- )
-
- except HTTPException:
- raise
- except Exception as e:
- logger.exception(f"批量处理失败: {e}")
- raise HTTPException(status_code=500, detail=f"批量处理失败: {e}")
- @router.post("/draw", response_model=DrawBatchResponse)
- async def batch_draw(request: DrawBatchRequest):
- """
- 批量绘制表格线到图片上
-
- 根据处理结果中的结构文件,在原图上绘制表格线
- """
- try:
- processor = BatchProcessor()
-
- # 确保颜色是 RGB 三元组
- line_color = tuple(request.line_color[:3]) if len(request.line_color) >= 3 else (0, 0, 0)
-
- draw_results = processor.draw_batch_images(
- results=request.results,
- line_width=request.line_width,
- line_color=line_color # type: ignore
- )
-
- success_count = sum(1 for r in draw_results if r.get('success'))
-
- return DrawBatchResponse(
- success=True,
- total=len(request.results),
- drawn=success_count,
- results=draw_results,
- message=f"绘制完成: {success_count}/{len(request.results)} 张图片"
- )
-
- except Exception as e:
- logger.exception(f"批量绘图失败: {e}")
- raise HTTPException(status_code=500, detail=f"批量绘图失败: {e}")
- @router.get("/health")
- async def batch_health_check():
- """批量处理服务健康检查"""
- return {
- "status": "ok",
- "service": "batch-processor"
- }
|