# 益选-OCR订单处理系统 - 技术架构文档

## 系统架构设计

### 整体架构概述

益选-OCR订单处理系统采用分层架构设计，遵循单一职责原则和开闭原则，确保系统的可维护性、可扩展性和可测试性。系统架构分为四个主要层次：用户界面层、业务逻辑层、核心处理层和数据访问层。

### 架构设计原则

#### 1. 分层解耦
- 各层之间通过明确定义的接口进行通信
- 上层依赖下层，下层不依赖上层
- 层与层之间保持松耦合关系

#### 2. 模块化设计
- 每个模块具有明确的职责边界
- 模块内部高内聚，模块之间低耦合
- 支持模块的独立开发和测试

#### 3. 配置驱动
- 系统行为通过配置文件控制
- 支持运行时参数调整
- 提供灵活的配置管理机制

#### 4. 错误处理
- 统一的异常处理机制
- 详细的日志记录和错误追踪
- 优雅的错误恢复机制

## 模块划分和职责

### 用户界面层 (UI Layer)

#### 启动器模块 (启动器.py)
**职责**：
- 提供图形用户界面
- 协调各个功能模块的调用
- 显示处理进度和结果
- 管理用户交互流程

**主要组件**：
- `OCR订单处理系统`类：主应用程序类
- `StatusBar`类：状态栏组件
- `LogRedirector`类：日志重定向器
- 各种对话框和预览窗口

#### 命令行接口 (app/cli/)
**职责**：
- 提供命令行操作方式
- 支持脚本化操作
- 实现批量处理功能

**子模块**：
- `ocr_cli.py`：OCR识别命令行接口
- `excel_cli.py`：Excel处理命令行接口
- `merge_cli.py`：合并功能命令行接口

### 业务逻辑层 (Service Layer)

#### OCR服务 (app/services/ocr_service.py)
**职责**：
- 协调OCR识别流程
- 管理OCR处理器的生命周期
- 提供OCR相关的业务逻辑
- 处理OCR结果的验证和转换

**核心方法**：
- `process_image()`：处理单个图片
- `process_images_batch()`：批量处理图片
- `get_unprocessed_images()`：获取待处理图片列表
- `validate_image()`：验证图片有效性

#### 订单服务 (app/services/order_service.py)
**职责**：
- 处理Excel订单文件
- 提取和标准化商品信息
- 应用条码映射规则
- 执行规格单位转换

**核心功能**：
- Excel文件读取和解析
- 商品信息提取和清洗
- 条码映射和转换
- 规格单位智能识别

#### 烟草服务 (app/services/tobacco_service.py)
**职责**：
- 专门处理烟草行业订单
- 适配烟草公司的特殊格式
- 处理烟草订单的特定规则

#### 合并服务
**职责**：
- 合并多个采购单文件
- 汇总相同商品信息
- 处理合并冲突和重复项

### 核心处理层 (Core Layer)

#### OCR处理核心 (app/core/ocr/)

##### 表格OCR处理器 (table_ocr.py)
**职责**：
- 协调OCR识别流程
- 管理处理记录
- 控制批量处理逻辑
- 处理文件I/O操作

**核心组件**：
- `OCRProcessor`类：主要的OCR处理器
- `ProcessedRecordManager`类：处理记录管理器

**处理流程**：
1. 图片验证和预处理
2. 调用百度OCR API进行识别
3. 解析OCR返回结果
4. 生成Excel文件
5. 更新处理记录

##### 百度OCR客户端 (baidu_ocr.py)
**职责**：
- 封装百度OCR API调用
- 处理API认证和授权
- 管理API请求和响应
- 实现重试和错误处理机制

**核心功能**：
- API密钥管理
- 请求签名生成
- 表格识别API调用
- 结果获取和解析

#### Excel处理核心 (app/core/excel/)

##### Excel处理器
**职责**：
- 读取和解析Excel文件
- 提取商品信息
- 数据清洗和标准化
- 生成标准采购单格式

**处理逻辑**：
1. 读取Excel文件
2. 识别商品数据区域
3. 提取商品属性（条码、名称、规格、数量、单价）
4. 应用数据清洗规则
5. 生成标准化输出

##### 单位转换器 (converter.py)
**职责**：
- 智能识别商品规格单位
- 执行单位换算
- 处理复杂的规格描述

**支持的单位**：
- 数量单位：个、只、条、包、箱、件等
- 重量单位：克、千克、斤、公斤等
- 体积单位：毫升、升、立方米等

#### 工具模块 (app/core/utils/)

##### 文件工具 (file_utils.py)
**职责**：
- 文件系统操作封装
- 路径处理和验证
- 文件类型检查
- 批量文件操作

##### 日志工具 (log_utils.py)
**职责**：
- 日志配置和管理
- 日志级别控制
- 日志文件轮转
- 错误追踪和记录

##### 对话框工具 (dialog_utils.py)
**职责**：
- 自定义对话框实现
- 用户交互界面组件
- 配置界面管理

### 数据访问层 (Data Access Layer)

#### 配置管理 (app/config/)

##### 配置管理器 (settings.py)
**职责**：
- 配置文件加载和解析
- 配置项访问和修改
- 配置验证和默认值处理
- 配置持久化

##### 默认配置 (defaults.py)
**职责**：
- 定义系统默认配置
- 提供配置模板
- 确保配置完整性

#### 数据存储

##### 文件系统接口
- **输入目录**：`data/input/` - 存放待处理的图片文件
- **输出目录**：`data/output/` - 存放处理结果和生成的Excel文件
- **临时目录**：`data/temp/` - 存放临时文件
- **模板目录**：`templates/` - 存放Excel模板文件
- **配置目录**：`config/` - 存放配置文件和映射规则

##### 处理记录管理
- **JSON记录文件**：`data/output/processed_files.json`
- **记录内容**：已处理文件的映射关系
- **更新机制**：处理完成后自动更新记录

## 核心算法和流程

### OCR识别算法流程

```mermaid
graph TD
    A[开始] --> B[图片验证]
    B --> C{图片有效?}
    C -->|是| D[检查是否已处理]
    C -->|否| E[返回错误]
    D --> F{已处理?}
    F -->|是| G[返回现有结果]
    F -->|否| H[调用百度OCR API]
    H --> I[解析OCR结果]
    I --> J[生成Excel文件]
    J --> K[更新处理记录]
    K --> L[返回成功]
    E --> M[结束]
    G --> M
    L --> M
```

#### 图片验证算法
```python
def validate_image(image_path: str) -> bool:
    # 1. 文件存在性检查
    if not os.path.exists(image_path):
        return False
    
    # 2. 文件扩展名验证
    ext = get_file_extension(image_path)
    if ext not in ALLOWED_EXTENSIONS:
        return False
    
    # 3. 文件大小检查
    if not is_file_size_valid(image_path, MAX_SIZE_MB):
        return False
    
    # 4. 图片格式验证（可选）
    try:
        with Image.open(image_path) as img:
            img.verify()
    except:
        return False
    
    return True
```

#### OCR结果解析算法
```python
def parse_ocr_result(ocr_response: dict) -> dict:
    result = {
        'tables': [],
        'text': '',
        'excel_data': None
    }
    
    # 1. 提取表格数据
    if 'tables_result' in ocr_response:
        for table in ocr_response['tables_result']:
            table_data = extract_table_data(table)
            result['tables'].append(table_data)
    
    # 2. 提取文本内容
    if 'words_result' in ocr_response:
        result['text'] = extract_text_content(ocr_response['words_result'])
    
    # 3. 提取Excel数据
    excel_base64 = find_excel_data(ocr_response)
    if excel_base64:
        result['excel_data'] = base64.b64decode(excel_base64)
    
    return result
```

### Excel处理算法流程

#### 商品信息提取算法
```python
def extract_product_info(excel_data: pd.DataFrame) -> List[Dict]:
    products = []
    
    # 1. 识别表头行
    header_row = identify_header_row(excel_data)
    
    # 2. 确定列映射
    column_mapping = map_columns(excel_data.iloc[header_row])
    
    # 3. 提取商品数据
    for row_idx in range(header_row + 1, len(excel_data)):
        row_data = excel_data.iloc[row_idx]
        
        product = {
            'barcode': extract_barcode(row_data, column_mapping),
            'name': extract_product_name(row_data, column_mapping),
            'specification': extract_specification(row_data, column_mapping),
            'quantity': extract_quantity(row_data, column_mapping),
            'unit_price': extract_unit_price(row_data, column_mapping),
            'total_price': extract_total_price(row_data, column_mapping)
        }
        
        # 4. 数据验证和清洗
        if validate_product(product):
            cleaned_product = clean_product_data(product)
            products.append(cleaned_product)
    
    return products
```

#### 规格单位识别算法
```python
def parse_specification(spec_text: str) -> Dict:
    result = {
        'original': spec_text,
        'quantity': 1,
        'unit': '个',
        'parsed': False
    }
    
    # 1. 预定义单位模式
    unit_patterns = {
        r'(\d+)\s*个': ('个', 1),
        r'(\d+)\s*只': ('只', 1),
        r'(\d+)\s*条': ('条', 1),
        r'(\d+)\s*包': ('包', 1),
        r'(\d+)\s*箱': ('箱', 1),
        r'(\d+)\s*件': ('件', 1),
        r'(\d+)\s*克': ('克', 1),
        r'(\d+)\s*千克': ('千克', 1),
        r'(\d+)\s*斤': ('斤', 1),
        r'(\d+)\s*公斤': ('公斤', 1)
    }
    
    # 2. 模式匹配
    for pattern, (unit, multiplier) in unit_patterns.items():
        match = re.search(pattern, spec_text, re.IGNORECASE)
        if match:
            result['quantity'] = int(match.group(1))
            result['unit'] = unit
            result['parsed'] = True
            break
    
    # 3. 复杂规格处理
    if not result['parsed']:
        result = parse_complex_specification(spec_text)
    
    return result
```

### 采购单合并算法

#### 商品汇总算法
```python
def merge_products(products_list: List[List[Dict]]) -> List[Dict]:
    merged_products = {}
    
    # 1. 收集所有商品
    for products in products_list:
        for product in products:
            key = generate_product_key(product)
            
            if key in merged_products:
                # 2. 合并相同商品
                merged_products[key]['quantity'] += product['quantity']
                merged_products[key]['total_price'] += product['total_price']
                merged_products[key]['source_files'].append(product.get('source_file', ''))
            else:
                # 3. 添加新商品
                merged_products[key] = product.copy()
                merged_products[key]['source_files'] = [product.get('source_file', '')]
    
    # 4. 转换回列表格式
    result = list(merged_products.values())
    
    # 5. 排序（按条码或名称）
    result.sort(key=lambda x: x.get('barcode', x.get('name', '')))
    
    return result
```

## 数据流设计

### 主要数据流

#### 1. OCR识别数据流
```
输入图片 → 图片验证 → OCR API调用 → 结果解析 → Excel生成 → 输出文件
```

#### 2. Excel处理数据流
```
Excel文件 → 数据读取 → 商品提取 → 数据清洗 → 格式转换 → 标准采购单
```

#### 3. 合并处理数据流
```
多个采购单 → 商品提取 → 去重汇总 → 冲突处理 → 合并结果 → 输出文件
```

### 数据结构设计

#### 商品数据结构
```python
{
    'barcode': str,           # 商品条码
    'name': str,              # 商品名称
    'specification': str,     # 商品规格
    'quantity': int,          # 数量
    'unit': str,              # 单位
    'unit_price': float,      # 单价
    'total_price': float,     # 总价
    'source_file': str,       # 来源文件
    'category': str,          # 商品类别
    'brand': str              # 品牌
}
```

#### 处理记录数据结构
```python
{
    'image_file': str,        # 输入图片路径
    'output_file': str,       # 输出文件路径
    'processing_time': str,   # 处理时间
    'status': str,            # 处理状态
    'error_message': str      # 错误信息（如果有）
}
```

#### OCR结果数据结构
```python
{
    'tables': List[Dict],     # 表格数据
    'text': str,              # 文本内容
    'excel_data': bytes,      # Excel文件数据
    'confidence': float,      # 识别置信度
    'processing_time': float   # 处理耗时
}
```

## 关键技术实现

### 并发处理机制

#### 多线程批量处理
```python
class BatchProcessor:
    def __init__(self, max_workers: int = 4):
        self.max_workers = max_workers
        self.executor = ThreadPoolExecutor(max_workers=max_workers)
    
    def process_batch(self, items: List[Any], processor_func) -> List[Any]:
        # 使用线程池并发处理
        futures = [self.executor.submit(processor_func, item) for item in items]
        
        # 收集处理结果
        results = []
        for future in as_completed(futures):
            try:
                result = future.result()
                results.append(result)
            except Exception as e:
                logger.error(f"处理失败: {e}")
                results.append(None)
        
        return results
```

### 错误处理和重试机制

#### API调用重试机制
```python
def call_with_retry(func, max_retries=3, retry_delay=2):
    for attempt in range(max_retries):
        try:
            result = func()
            return result
        except Exception as e:
            logger.warning(f"第{attempt + 1}次尝试失败: {e}")
            
            if attempt < max_retries - 1:
                time.sleep(retry_delay)
            else:
                logger.error(f"所有重试尝试都失败")
                raise
```

### 内存优化策略

#### 大文件处理
```python
def process_large_file(file_path: str, chunk_size: int = 1000):
    # 使用生成器避免一次性加载大文件
    def read_in_chunks():
        with pd.read_excel(file_path, chunksize=chunk_size) as reader:
            for chunk in reader:
                yield chunk
    
    # 逐块处理
    for chunk in read_in_chunks():
        process_chunk(chunk)
        # 及时清理内存
        del chunk
        gc.collect()
```

### 配置管理实现

#### 动态配置加载
```python
class ConfigManager:
    def __init__(self, config_file: str):
        self.config_file = config_file
        self.config = configparser.ConfigParser()
        self.load_config()
    
    def load_config(self):
        if os.path.exists(self.config_file):
            self.config.read(self.config_file, encoding='utf-8')
        else:
            self.create_default_config()
    
    def get(self, section: str, option: str, fallback: Any = None) -> Any:
        return self.config.get(section, option, fallback=fallback)
    
    def getint(self, section: str, option: str, fallback: int = 0) -> int:
        return self.config.getint(section, option, fallback=fallback)
    
    def getboolean(self, section: str, option: str, fallback: bool = False) -> bool:
        return self.config.getboolean(section, option, fallback=fallback)
```

### 日志系统设计

#### 结构化日志记录
```python
import logging
from logging.handlers import RotatingFileHandler

def setup_logging(log_file: str = 'logs/app.log'):
    # 创建logger
    logger = logging.getLogger(__name__)
    logger.setLevel(logging.DEBUG)
    
    # 创建文件处理器（带轮转）
    file_handler = RotatingFileHandler(
        log_file, maxBytes=10*1024*1024, backupCount=5
    )
    file_handler.setLevel(logging.DEBUG)
    
    # 创建控制台处理器
    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    
    # 创建格式化器
    formatter = logging.Formatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    # 添加格式化器到处理器
    file_handler.setFormatter(formatter)
    console_handler.setFormatter(formatter)
    
    # 添加处理器到logger
    logger.addHandler(file_handler)
    logger.addHandler(console_handler)
    
    return logger
```

### 性能监控和优化

#### 处理时间统计
```python
import time
from functools import wraps

def timing_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        
        processing_time = end_time - start_time
        logger.info(f"{func.__name__} 执行耗时: {processing_time:.2f}秒")
        
        return result
    
    return wrapper
```

### 安全性考虑

#### API密钥管理
```python
import os
from cryptography.fernet import Fernet

class SecureConfig:
    def __init__(self, encryption_key: str = None):
        self.cipher = Fernet(encryption_key or self._get_or_create_key())
    
    def _get_or_create_key(self) -> str:
        key_file = 'config/.key'
        if os.path.exists(key_file):
            with open(key_file, 'rb') as f:
                return f.read()
        else:
            key = Fernet.generate_key()
            with open(key_file, 'wb') as f:
                f.write(key)
            return key
    
    def encrypt(self, data: str) -> str:
        return self.cipher.encrypt(data.encode()).decode()
    
    def decrypt(self, encrypted_data: str) -> str:
        return self.cipher.decrypt(encrypted_data.encode()).decode()
```

这个技术架构文档详细描述了益选-OCR订单处理系统的技术实现细节，包括系统架构设计、模块职责划分、核心算法流程、数据流设计以及关键技术实现。系统设计遵循软件工程最佳实践，确保系统的可靠性、可维护性和可扩展性。
