orc-order-v2/doc/TECHNICAL_ARCHITECTURE.md
2025-11-15 18:46:03 +08:00

17 KiB
Raw Blame History

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

系统架构设计

整体架构概述

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

架构设计原则

1. 分层解耦

  • 各层之间通过明确定义的接口进行通信
  • 上层依赖下层,下层不依赖上层
  • 层与层之间保持松耦合关系

2. 模块化设计

  • 每个模块具有明确的职责边界
  • 模块内部高内聚,模块之间低耦合
  • 支持模块的独立开发和测试

3. 配置驱动

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

4. 错误处理

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

模块划分和职责

用户界面层 (UI Layer)

启动器模块 (启动器.py)

职责

  • 提供图形用户界面
  • 协调各个功能模块的调用
  • 显示处理进度和结果
  • 管理用户交互流程

主要组件

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

命令行接口 (app/cli/)

职责

  • 提供命令行操作方式
  • 支持脚本化操作
  • 实现批量处理功能

子模块

  • ocr_cli.pyOCR识别命令行接口
  • excel_cli.pyExcel处理命令行接口
  • 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识别算法流程

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

图片验证算法

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结果解析算法

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处理算法流程

商品信息提取算法

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

规格单位识别算法

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

采购单合并算法

商品汇总算法

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. 合并处理数据流

多个采购单 → 商品提取 → 去重汇总 → 冲突处理 → 合并结果 → 输出文件

数据结构设计

商品数据结构

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

处理记录数据结构

{
    'image_file': str,        # 输入图片路径
    'output_file': str,       # 输出文件路径
    'processing_time': str,   # 处理时间
    'status': str,            # 处理状态
    'error_message': str      # 错误信息(如果有)
}

OCR结果数据结构

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

关键技术实现

并发处理机制

多线程批量处理

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调用重试机制

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

内存优化策略

大文件处理

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()

配置管理实现

动态配置加载

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)

日志系统设计

结构化日志记录

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

性能监控和优化

处理时间统计

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密钥管理

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订单处理系统的技术实现细节包括系统架构设计、模块职责划分、核心算法流程、数据流设计以及关键技术实现。系统设计遵循软件工程最佳实践确保系统的可靠性、可维护性和可扩展性。