# Vision-OCR 项目深度分析与优化报告 > 分析日期: 2026-01-08 > 分析范围: 全项目代码审查 --- ## 目录 - [一、架构设计问题](#一架构设计问题) - [二、性能优化点](#二性能优化点) - [三、代码质量问题](#三代码质量问题) - [四、安全性问题](#四安全性问题) - [五、配置管理问题](#五配置管理问题) - [六、测试覆盖问题](#六测试覆盖问题) - [七、功能增强建议](#七功能增强建议) - [八、优化优先级总结](#八优化优先级总结) --- ## 一、架构设计问题 ### 1.1 API 请求参数未生效 (严重 🔴) **问题描述** `lang`、`use_gpu`、`drop_score` 等请求参数虽然被接收,但在实际 OCR 处理中 **完全未被使用**。 **问题位置**: `api/routes/ocr.py` 第 78-95 行 ```python def _process_ocr( image_bytes: bytes, pipeline: OCRPipeline, roi: Optional[ROIParams] = None, return_annotated_image: bool = False, ) -> tuple[OCRResult, Optional[str]]: # lang, use_gpu, drop_score 参数未传入也未使用! ``` **影响** - 用户设置的语言、GPU 加速、置信度阈值完全无效 - API 文档描述与实际行为不一致 - 用户误以为参数生效,导致困惑 **解决方案** 方案一:在 `_process_ocr` 中动态创建或更新 OCRConfig ```python def _process_ocr( image_bytes: bytes, pipeline: OCRPipeline, params: OCRRequestParams, # 新增参数 roi: Optional[ROIParams] = None, return_annotated_image: bool = False, ) -> tuple[OCRResult, Optional[str]]: # 创建临时配置 ocr_config = OCRConfig( lang=params.lang, use_gpu=params.use_gpu, drop_score=params.drop_score, ) # 使用新配置处理... ``` 方案二:修改 `OCRPipeline.process()` 方法接受运行时参数 ```python def process( self, image: np.ndarray, image_path: Optional[str] = None, drop_score: Optional[float] = None, # 运行时覆盖 ) -> OCRResult: effective_drop_score = drop_score or self._ocr_config.drop_score # ... ``` --- ### 1.2 Pipeline 配置临时替换 - 线程安全问题 (严重 🔴) **问题描述** `api/routes/ocr.py` 中直接修改共享的 pipeline 配置对象,在并发场景下会产生竞态条件。 **问题位置**: `api/routes/ocr.py` 第 102-113 行 ```python # 临时更新管道配置 original_config = pipeline._pipeline_config pipeline._pipeline_config = pipeline_config # ⚠️ 非线程安全! try: result = pipeline.process(image) finally: pipeline._pipeline_config = original_config # ⚠️ 并发时可能恢复错误的配置 ``` **影响** - 多用户并发请求时,配置会相互干扰 - 用户 A 的 ROI 设置可能被应用到用户 B 的请求 - 产生不可预期且难以复现的 Bug **解决方案** 方案一:将配置作为 `process()` 方法的参数传入(推荐) ```python def process( self, image: np.ndarray, image_path: Optional[str] = None, pipeline_config: Optional[PipelineConfig] = None, # 新增 ) -> OCRResult: config = pipeline_config or self._pipeline_config # 使用传入的配置... ``` 方案二:使用 `contextvars` 实现请求级别隔离 ```python from contextvars import ContextVar _request_config: ContextVar[PipelineConfig] = ContextVar('request_config') # 在请求处理开始时设置 _request_config.set(pipeline_config) # 在 process 中读取 config = _request_config.get(self._pipeline_config) ``` 方案三:使用线程锁(性能较差,不推荐) ```python import threading class OCRPipeline: _lock = threading.Lock() def process_with_config(self, image, config): with self._lock: original = self._pipeline_config self._pipeline_config = config try: return self.process(image) finally: self._pipeline_config = original ``` --- ### 1.3 缺少日志系统 (中等 🟡) **问题描述** 全项目使用 `print()` 输出信息,无法控制日志级别、格式、输出目标。 **问题位置**: 分布在多个文件 ```python # main.py print("[INFO] 正在初始化 OCR 系统...") # input/loader.py print(f"[ERROR] 文件不存在: {path}") # api/main.py print("[INFO] 正在加载 OCR 模型...") ``` **影响** - 无法按级别过滤日志(开发/生产环境) - 无法将日志输出到文件或日志服务 - 缺少时间戳、调用位置等上下文信息 - 无法进行日志聚合和分析 **解决方案** 引入 Python 标准 `logging` 模块: ```python # utils/logger.py import logging import sys def setup_logger(name: str, level: int = logging.INFO) -> logging.Logger: logger = logging.getLogger(name) logger.setLevel(level) handler = logging.StreamHandler(sys.stdout) handler.setFormatter(logging.Formatter( '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )) logger.addHandler(handler) return logger # 使用 logger = setup_logger(__name__) logger.info("正在初始化 OCR 系统...") logger.error(f"文件不存在: {path}") ``` 或使用 `loguru`(更简洁): ```python from loguru import logger logger.info("正在初始化 OCR 系统...") logger.error(f"文件不存在: {path}") ``` --- ## 二、性能优化点 ### 2.1 缺少批处理 API (中等 🟡) **现状** API 只支持单张图片处理,需要批量识别时必须多次调用。 **影响** - 网络往返开销大 - 无法充分利用 GPU 批处理能力 - 客户端实现复杂 **解决方案** 添加批处理端点: ```python @router.post("/recognize/batch") async def recognize_batch( files: List[UploadFile] = File(..., max_length=10), params: OCRRequestParams = Depends(parse_multipart_params), pipeline: OCRPipeline = Depends(get_ocr_pipeline), ) -> BatchOCRResponse: results = [] for file in files: image_bytes = await parse_multipart_image(file) result, _ = _process_ocr(image_bytes, pipeline, params.get_roi(), False) results.append(_convert_ocr_result_to_response(result)) return BatchOCRResponse(success=True, data=results) ``` --- ### 2.2 可视化器重复创建 (低 🟢) **问题描述** `api/routes/ocr.py` 中每次请求都创建新的 `OCRVisualizer`。 **问题位置**: `api/routes/ocr.py` 第 117-120 行 ```python if return_annotated_image and result.text_count > 0: visualizer = OCRVisualizer(VisualizeConfig()) # 每次请求都创建 annotated = visualizer.draw_result(image, result) ``` **影响** - 每次都重新加载中文字体文件 - PIL/OpenCV 初始化开销 **解决方案** 将 visualizer 作为应用级单例: ```python # api/dependencies.py _visualizer: Optional[OCRVisualizer] = None def get_visualizer() -> OCRVisualizer: global _visualizer if _visualizer is None: _visualizer = OCRVisualizer(VisualizeConfig()) return _visualizer ``` --- ### 2.3 图片编码格式固定 (低 🟢) **现状** 返回标注图片时固定使用 JPEG 格式。 **问题位置**: `api/dependencies.py` 第 119 行 ```python def encode_image_base64(image: np.ndarray, format: str = ".jpg") -> str: ``` **建议** 允许用户指定输出格式,PNG 适合需要透明度或无损压缩的场景: ```python def encode_image_base64( image: np.ndarray, format: str = ".jpg", quality: int = 95, # JPEG 质量 ) -> str: params = [] if format == ".jpg": params = [cv2.IMWRITE_JPEG_QUALITY, quality] elif format == ".png": params = [cv2.IMWRITE_PNG_COMPRESSION, 3] success, encoded = cv2.imencode(format, image, params) # ... ``` --- ## 三、代码质量问题 ### 3.1 OCR 路由重复代码 (中等 🟡) **问题描述** `express_multipart` 和 `express_base64` 中解析快递单的逻辑完全重复,约 30+ 行。 **问题位置**: `api/routes/ocr.py` 第 204-242 行 和 第 322-360 行 ```python # express_multipart 中 express_info = result.parse_express() merged_text = result.merge_text() return ExpressResponse( success=True, data=ExpressResultData( processing_time_ms=result.processing_time_ms, express_info=ExpressInfoData( tracking_number=express_info.tracking_number, sender=ExpressPersonData( name=express_info.sender_name, phone=express_info.sender_phone, address=express_info.sender_address, ), receiver=ExpressPersonData( name=express_info.receiver_name, phone=express_info.receiver_phone, address=express_info.receiver_address, ), courier_company=express_info.courier_company, confidence=express_info.confidence, extra_fields=express_info.extra_fields, raw_text=express_info.raw_text, ), merged_text=merged_text, annotated_image_base64=annotated_base64, ), ) ``` **影响** - 修改一处逻辑需要同步修改另一处 - 容易遗漏导致行为不一致 **解决方案** 提取公共辅助函数: ```python def _convert_express_result_to_response( result: OCRResult, annotated_base64: Optional[str] = None, ) -> ExpressResultData: """将 OCRResult 转换为快递单响应数据""" express_info = result.parse_express() merged_text = result.merge_text() return ExpressResultData( processing_time_ms=result.processing_time_ms, express_info=ExpressInfoData( tracking_number=express_info.tracking_number, sender=ExpressPersonData( name=express_info.sender_name, phone=express_info.sender_phone, address=express_info.sender_address, ), receiver=ExpressPersonData( name=express_info.receiver_name, phone=express_info.receiver_phone, address=express_info.receiver_address, ), courier_company=express_info.courier_company, confidence=express_info.confidence, extra_fields=express_info.extra_fields, raw_text=express_info.raw_text, ), merged_text=merged_text, annotated_image_base64=annotated_base64, ) ``` --- ### 3.2 异常处理过于宽泛 (中等 🟡) **问题描述** 多处使用裸 `except Exception`,吞掉所有异常。 **问题位置**: `api/routes/ocr.py` 第 165-172 行 等多处 ```python except Exception as e: return OCRResponse( success=False, error=ErrorDetail( code=type(e).__name__, message=str(e), ), ) ``` **影响** - 隐藏了真正的错误信息 - 难以定位问题根源 - 可能掩盖严重的系统错误 **解决方案** 明确捕获预期异常,让未预期异常传播: ```python from api.exceptions import OCRAPIException, InvalidImageError, OCRProcessingError try: # ... except OCRAPIException as e: # 业务异常,返回友好信息 return OCRResponse( success=False, error=ErrorDetail(code=type(e).__name__, message=e.message), ) except Exception as e: # 未预期异常,记录日志并返回通用错误 logger.exception(f"OCR 处理发生未知错误: {e}") raise # 让全局异常处理器处理 ``` --- ### 3.3 类型注解不完整 (低 🟢) **问题描述** 部分函数返回值和参数缺少完整的类型注解。 **问题位置**: `ocr/pipeline.py` 第 185 行 ```python def _apply_roi(self, image: np.ndarray) -> tuple: # 应该是: -> Tuple[np.ndarray, Tuple[int, int], Optional[Tuple[int, int, int, int]]] ``` **建议** 补充完整的类型注解: ```python from typing import Tuple, Optional def _apply_roi( self, image: np.ndarray ) -> Tuple[np.ndarray, Tuple[int, int], Optional[Tuple[int, int, int, int]]]: """ Returns: (裁剪后的图像, ROI 偏移量, ROI 矩形) """ ``` --- ## 四、安全性问题 ### 4.1 缺少速率限制 (Rate Limiting) (严重 🔴) **问题描述** API 无任何请求频率限制,易受 DDoS 攻击或滥用。 **影响** - 恶意用户可无限制发送请求 - OCR 处理是 CPU/GPU 密集型操作,易导致服务过载 - 可能产生高额的计算成本 **解决方案** 使用 `slowapi` 实现速率限制: ```python # requirements.txt 添加 slowapi>=0.1.9 # api/main.py from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) # api/routes/ocr.py from slowapi import Limiter @router.post("/recognize") @limiter.limit("10/minute") # 每分钟最多 10 次请求 async def recognize_multipart(...): ``` --- ### 4.2 CORS 配置过于宽松 (中等 🟡) **问题描述** 生产环境不应允许所有来源访问。 **问题位置**: `api/main.py` 第 106-112 行 ```python app.add_middleware( CORSMiddleware, allow_origins=["*"], # ⚠️ 危险!允许任何域名 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) ``` **影响** - 任何网站都可以调用你的 API - 可能被用于 CSRF 攻击 - 敏感数据可能泄露给第三方 **解决方案** 通过环境变量配置允许的域名: ```python import os ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "http://localhost:3000").split(",") app.add_middleware( CORSMiddleware, allow_origins=ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["GET", "POST"], allow_headers=["*"], ) ``` --- ### 4.3 Base64 解码后缺少图片尺寸验证 (中等 🟡) **问题描述** 恶意用户可构造压缩率极高的图片(如 zip bomb),解码后占用大量内存。 **问题位置**: `api/dependencies.py` 第 94-116 行 **影响** - 单个请求可能消耗数 GB 内存 - 导致服务崩溃或 OOM **解决方案** 在图片解码后添加尺寸检查: ```python def decode_image_bytes(content: bytes, max_dimension: int = 10000) -> np.ndarray: """ 将图片字节解码为 numpy 数组 Args: content: 图片字节数据 max_dimension: 最大允许的图片尺寸(宽或高) """ try: nparr = np.frombuffer(content, np.uint8) image = cv2.imdecode(nparr, cv2.IMREAD_COLOR) if image is None: raise InvalidImageError("图片解码失败") # 新增: 检查图片尺寸 height, width = image.shape[:2] if width > max_dimension or height > max_dimension: raise InvalidImageError( f"图片尺寸过大 ({width}x{height}),最大允许 {max_dimension}x{max_dimension}" ) return image except InvalidImageError: raise except Exception as e: raise InvalidImageError(f"图片解码失败: {str(e)}") ``` --- ## 五、配置管理问题 ### 5.1 硬编码配置 (中等 🟡) **问题描述** 多处配置硬编码在代码中,无法通过环境变量调整。 **问题位置** ```python # api/security.py:19 MAX_FILE_SIZE = 10 * 1024 * 1024 # 硬编码 # api/main.py:48-56 return OCRConfig( lang="ch", # 硬编码默认值 use_angle_cls=True, use_gpu=False, drop_score=0.5, ) ``` **影响** - 不同环境(开发/测试/生产)无法使用不同配置 - 修改配置需要改代码并重新部署 **解决方案** 使用 `pydantic-settings` 统一管理: ```python # utils/settings.py from pydantic_settings import BaseSettings from typing import List class Settings(BaseSettings): # 文件上传限制 max_file_size: int = 10 * 1024 * 1024 max_image_dimension: int = 10000 # OCR 默认配置 ocr_default_lang: str = "ch" ocr_use_gpu: bool = False ocr_drop_score: float = 0.5 # API 配置 api_rate_limit: str = "10/minute" cors_origins: List[str] = ["http://localhost:3000"] # 日志配置 log_level: str = "INFO" class Config: env_prefix = "VISION_OCR_" env_file = ".env" settings = Settings() ``` 使用示例: ```python from utils.settings import settings MAX_FILE_SIZE = settings.max_file_size ocr_config = OCRConfig( lang=settings.ocr_default_lang, use_gpu=settings.ocr_use_gpu, drop_score=settings.ocr_drop_score, ) ``` --- ### 5.2 API 版本号分散 (低 🟢) **问题描述** 版本号在多处定义,可能不一致。 **问题位置** ```python # api/main.py:98 version="1.0.0", # api/routes/health.py:14 API_VERSION = "1.0.0" ``` **解决方案** 从单一来源读取版本号: ```python # api/__init__.py __version__ = "1.0.0" # 其他文件使用 from api import __version__ ``` 或从 `pyproject.toml` 动态读取: ```python from importlib.metadata import version __version__ = version("vision-ocr") ``` --- ## 六、测试覆盖问题 ### 6.1 核心模块缺少单元测试 (中等 🟡) **现状** 只有 API 集成测试,核心业务逻辑无单元测试。 **缺失的测试** | 模块 | 测试覆盖 | 风险 | |------|----------|------| | `ocr/engine.py` | ❌ 无 | 高 - OCR 核心逻辑 | | `ocr/express_parser.py` | ❌ 无 | 高 - 正则匹配复杂 | | `ocr/pipeline.py` | ❌ 无 | 高 - 处理流程 | | `input/loader.py` | ❌ 无 | 中 - 文件加载 | | `visualize/draw.py` | ❌ 无 | 低 - 可视化 | | `utils/config.py` | ❌ 无 | 低 - 配置类 | **建议** 为 `ExpressParser` 添加单元测试(最高优先级): ```python # tests/test_express_parser.py import pytest from ocr.express_parser import ExpressParser from ocr.engine import TextBlock class TestExpressParser: @pytest.fixture def parser(self): return ExpressParser() def test_extract_tracking_number(self, parser): text_blocks = [ TextBlock( text="运单号:SF1234567890", confidence=0.95, bbox=[[0, 0], [100, 0], [100, 20], [0, 20]], ) ] result = parser.parse(text_blocks) assert result.tracking_number == "SF1234567890" def test_extract_phone_number(self, parser): text_blocks = [ TextBlock( text="收件人:张三 13800138000", confidence=0.95, bbox=[[0, 0], [200, 0], [200, 20], [0, 20]], ) ] result = parser.parse(text_blocks) assert result.receiver_phone == "13800138000" def test_detect_courier_company(self, parser): text_blocks = [ TextBlock( text="顺丰速运", confidence=0.95, bbox=[[0, 0], [100, 0], [100, 20], [0, 20]], ) ] result = parser.parse(text_blocks) assert result.courier_company == "顺丰速运" ``` --- ### 6.2 测试使用 Mock 导致假阳性 (中等 🟡) **问题描述** 测试全程使用 Mock Pipeline,无法验证真实 OCR 行为。 **问题位置**: `tests/conftest.py` 第 25-67 行 ```python @pytest.fixture(scope="session") def mock_ocr_pipeline(): mock_pipeline = MagicMock() mock_pipeline.process.return_value = mock_result # 永远返回固定结果 ``` **影响** - 无法发现 OCR 引擎的问题 - 接口变更可能导致测试仍然通过 - 端到端流程未被验证 **解决方案** 添加集成测试(可选择性运行): ```python # tests/test_integration.py import pytest import os # 通过环境变量控制是否运行集成测试 SKIP_INTEGRATION = os.getenv("SKIP_INTEGRATION_TESTS", "true").lower() == "true" @pytest.mark.skipif(SKIP_INTEGRATION, reason="跳过集成测试") class TestOCRIntegration: @pytest.fixture(scope="class") def real_pipeline(self): """使用真实的 OCR Pipeline""" from ocr.pipeline import OCRPipeline from utils.config import OCRConfig, PipelineConfig pipeline = OCRPipeline(OCRConfig(), PipelineConfig()) pipeline.initialize() return pipeline def test_real_ocr_recognition(self, real_pipeline): """测试真实 OCR 识别""" import cv2 import numpy as np # 创建包含文字的测试图片 image = np.ones((100, 300, 3), dtype=np.uint8) * 255 cv2.putText(image, "Hello OCR", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 0), 2) result = real_pipeline.process(image) assert result is not None assert result.text_count >= 0 # 可能识别到也可能没有 ``` --- ## 七、功能增强建议 ### 7.1 支持语言动态切换 **现状** `lang` 参数即使生效,切换语言也需要重新初始化 OCR 引擎,耗时较长。 **建议** 预加载多语言模型,或实现模型池: ```python class OCREnginePool: """OCR 引擎池,支持多语言""" def __init__(self): self._engines: Dict[str, OCREngine] = {} def get_engine(self, lang: str) -> OCREngine: if lang not in self._engines: config = OCRConfig(lang=lang) engine = OCREngine(config) engine.initialize() self._engines[lang] = engine return self._engines[lang] ``` --- ### 7.2 添加结果缓存 **场景** 相同图片重复识别时可直接返回缓存结果,节省计算资源。 **建议** 基于图片哈希实现缓存: ```python import hashlib from functools import lru_cache def get_image_hash(image_bytes: bytes) -> str: return hashlib.md5(image_bytes).hexdigest() # 使用 Redis 或内存缓存 _cache: Dict[str, OCRResult] = {} def process_with_cache(image_bytes: bytes, pipeline: OCRPipeline) -> OCRResult: cache_key = get_image_hash(image_bytes) if cache_key in _cache: return _cache[cache_key] image = decode_image_bytes(image_bytes) result = pipeline.process(image) _cache[cache_key] = result return result ``` --- ### 7.3 支持异步处理 **场景** 大批量图片处理时,同步等待耗时过长。 **建议** 提供任务队列 + Webhook 回调模式: ```python @router.post("/recognize/async") async def recognize_async( file: UploadFile, callback_url: str = Form(..., description="处理完成后的回调 URL"), ) -> dict: # 1. 保存图片到临时存储 task_id = str(uuid.uuid4()) save_to_storage(task_id, await file.read()) # 2. 提交任务到队列 queue.enqueue(process_ocr_task, task_id, callback_url) # 3. 立即返回任务 ID return {"task_id": task_id, "status": "pending"} @router.get("/task/{task_id}") async def get_task_status(task_id: str) -> dict: # 查询任务状态 return {"task_id": task_id, "status": get_status(task_id)} ``` --- ### 7.4 增强快递单解析能力 **现状** 正则匹配覆盖有限,部分快递公司格式无法识别。 **建议** 1. **扩展正则模式库**:收集更多快递单样本,补充正则规则 2. **引入 NER 模型**:使用命名实体识别提取人名、地址等 3. **添加置信度评估**:对解析结果的可靠性给出评分 ```python class ExpressParser: def parse(self, text_blocks: List[TextBlock]) -> ExpressInfo: info = self._extract_by_regex(text_blocks) # 如果正则效果不好,尝试 NER if not info.is_valid: info = self._extract_by_ner(text_blocks) # 评估解析结果的置信度 info.parse_confidence = self._evaluate_confidence(info) return info ``` --- ## 八、优化优先级总结 ### 按紧急程度分类 #### P0 - 必须立即修复 🔴 | 问题 | 影响 | 工作量 | |------|------|--------| | API 参数未生效 | 功能完全失效,用户设置的参数无意义 | 中 | | Pipeline 线程安全 | 并发请求数据错乱,生产事故风险 | 中 | | 缺少速率限制 | 服务可被 DDoS 攻击,稳定性风险 | 低 | #### P1 - 近期需要处理 🟡 | 问题 | 影响 | 工作量 | |------|------|--------| | 缺少日志系统 | 无法排查线上问题 | 低 | | CORS 过于宽松 | 安全风险 | 低 | | 图片尺寸验证缺失 | 内存攻击风险 | 低 | | 代码重复 | 维护成本增加 | 低 | | 测试覆盖不足 | 回归风险 | 中 | #### P2 - 可以规划 🟢 | 问题 | 影响 | 工作量 | |------|------|--------| | 配置硬编码 | 部署灵活性差 | 中 | | 异常处理宽泛 | 问题定位困难 | 低 | | 类型注解不完整 | 代码可读性 | 低 | | 可视化器重复创建 | 性能损耗(轻微) | 低 | #### P3 - 长期优化 | 问题 | 影响 | 工作量 | |------|------|--------| | 批处理 API | 用户体验 | 中 | | 结果缓存 | 性能优化 | 中 | | 异步处理 | 大批量场景支持 | 高 | | 快递单解析增强 | 产品竞争力 | 高 | --- ### 建议的修复顺序 ``` 1. [P0] 修复 API 参数传递问题 2. [P0] 解决 Pipeline 线程安全问题 3. [P0] 添加速率限制 4. [P1] 引入日志框架 5. [P1] 修复 CORS 配置 6. [P1] 添加图片尺寸验证 7. [P1] 提取重复代码 8. [P2] 配置外部化 9. [P2] 补充单元测试 ``` --- ## 附录:快速修复代码片段 ### A. 修复 API 参数传递 ```python # api/routes/ocr.py def _process_ocr( image_bytes: bytes, pipeline: OCRPipeline, params: OCRRequestParams, # 新增 roi: Optional[ROIParams] = None, return_annotated_image: bool = False, ) -> tuple[OCRResult, Optional[str]]: image = decode_image_bytes(image_bytes) pipeline_config = build_pipeline_config(roi) # 关键:将参数传递给 process 方法 result = pipeline.process( image, pipeline_config=pipeline_config, drop_score=params.drop_score, ) # ... ``` ### B. 修复线程安全问题 ```python # ocr/pipeline.py def process( self, image: np.ndarray, image_path: Optional[str] = None, pipeline_config: Optional[PipelineConfig] = None, # 新增 drop_score: Optional[float] = None, # 新增 ) -> OCRResult: config = pipeline_config or self._pipeline_config effective_drop_score = drop_score or self._ocr_config.drop_score # 使用传入的配置,而不是修改实例属性 cropped_image, roi_offset, roi_rect = self._apply_roi(image, config.roi) # ... ``` ### C. 添加速率限制 ```python # requirements.txt slowapi>=0.1.9 # api/main.py from slowapi import Limiter from slowapi.util import get_remote_address limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter # api/routes/ocr.py from fastapi import Request from api.main import limiter @router.post("/recognize") @limiter.limit("10/minute") async def recognize_multipart(request: Request, ...): # ... ``` ---