Skip to content

System Architecture

chiupam edited this page Apr 4, 2025 · 1 revision

系统架构 | System Architecture

本文档描述发票OCR管理系统的整体架构设计、关键模块及其交互。

总体架构 | Overall Architecture

发票OCR管理系统采用经典的Web应用三层架构:

  1. 表现层:基于Flask框架的Web界面和API接口
  2. 业务逻辑层:发票识别、数据处理、项目管理等核心业务逻辑
  3. 数据持久层:基于SQLite的数据存储和访问

系统架构图如下:

+----------------------------------+
|        Web浏览器 / 客户端          |
+----------------------------------+
               |
               | HTTP/HTTPS
               v
+----------------------------------+
|           Flask Web应用           |
|  +----------------------------+  |
|  |         路由和控制器层       |  |
|  +----------------------------+  |
|  |  +---------------------+   |  |
|  |  |    模板渲染(Jinja2)  |   |  |
|  |  +---------------------+   |  |
|  |  |    表单处理和验证     |   |  |
|  |  +---------------------+   |  |
|  +----------------------------+  |
+----------------------------------+
               |
               v
+----------------------------------+
|           业务逻辑层               |
|  +----------------------------+  |
|  |      发票OCR处理模块         |  |
|  +----------------------------+  |
|  |      项目管理模块            |  |
|  +----------------------------+  |
|  |      统计分析模块            |  |
|  +----------------------------+  |
|  |      数据导出模块            |  |
|  +----------------------------+  |
+----------------------------------+
               |
               v
+----------------------------------+
|           数据持久层               |
|  +----------------------------+  |
|  |       SQLite数据库          |  |
|  +----------------------------+  |
|  |     文件系统(图片存储)        |  |
|  +----------------------------+  |
+----------------------------------+
               |
               v
+----------------------------------+
|          外部集成服务              |
|  +----------------------------+  |
|  |       腾讯云OCR API         |  |
|  +----------------------------+  |
+----------------------------------+

模块划分 | Module Breakdown

1. 核心应用模块 (app/)

  • app/__init__.py: 应用工厂,创建和初始化Flask应用
  • app/models.py: 数据模型定义(Invoice, Project, InvoiceItem)
  • app/routes.py: Web路由和控制器
  • app/forms.py: 表单定义和验证

2. 业务逻辑模块 (core/)

  • core/ocr_process.py: OCR识别和数据提取
  • core/invoice_formatter.py: 发票数据格式化
  • core/invoice_export.py: 数据导出功能

3. 工具模块 (tools/)

  • tools/db_init.py: 数据库初始化
  • tools/db_backup.py: 数据库备份
  • tools/generate_test_data.py: 测试数据生成
  • tools/clean_temp_files.py: 清理临时文件

4. 前端资源 (app/static/)

  • app/static/js/: JavaScript文件
  • app/static/css/: 样式表
  • app/static/uploads/: 上传的发票图片

5. 模板 (app/templates/)

  • app/templates/base.html: 基础模板
  • app/templates/index.html: 主页
  • app/templates/invoice_*.html: 发票相关页面
  • app/templates/project_*.html: 项目相关页面

数据流图 | Data Flow Diagram

下面是系统的主要数据流:

发票识别流程

+-------------+     +-------------+     +-------------+
|   用户上传   | --> |   文件存储   | --> | 调用OCR API  |
|   发票图片   |     |   到本地     |     |             |
+-------------+     +-------------+     +-------------+
                                              |
                                              v
+-------------+     +-------------+     +-------------+
|   保存到     | <-- |   解析返回   | <-- |   API返回    |
|   数据库     |     |   的结果     |     |   识别结果   |
+-------------+     +-------------+     +-------------+
      |
      v
+-------------+     +-------------+
|   显示详情    | --> |   用户修正   |
|   页面       |     |   数据       |
+-------------+     +-------------+
                          |
                          v
                    +-------------+
                    |   更新到     |
                    |   数据库     |
                    +-------------+

项目管理流程

+-------------+     +-------------+     +-------------+
|   创建项目   | --> |   保存项目    | --> |   显示项目  |
|             |     |   到数据库    |     |   列表      |
+-------------+     +-------------+     +-------------+
                                              |
                                              v
+-------------+     +-------------+     +-------------+
|   更新项目   | <-- |   编辑项目    | <-- |   选择项目   |
|   数据       |    |   信息       |     |             |
+-------------+     +-------------+     +-------------+

发票分类流程

+-------------+     +-------------+     +-------------+
|  查看未分类   | --> |   选择发票   | --> |   选择目标   |
|   发票       |     |             |     |   项目      |
+-------------+     +-------------+     +-------------+
                                              |
                                              v
+-------------+     +-------------+     +-------------+
|   更新项目   | <-- |   更新发票    | <-- |   确认分类   |
|   统计       |     |   归属       |     |             |
+-------------+     +-------------+     +-------------+

数据模型 | Data Models

系统使用SQLAlchemy ORM管理以下数据模型:

Invoice (发票)

class Invoice(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    invoice_code = db.Column(db.String(20))  # 发票代码
    invoice_number = db.Column(db.String(20))  # 发票号码
    invoice_date = db.Column(db.Date)  # 开票日期
    total_amount = db.Column(db.Float)  # 合计金额
    tax_amount = db.Column(db.Float)  # 税额
    seller_name = db.Column(db.String(100))  # 销售方名称
    seller_tax_number = db.Column(db.String(20))  # 销售方税号
    buyer_name = db.Column(db.String(100))  # 购买方名称
    buyer_tax_number = db.Column(db.String(20))  # 购买方税号
    image_path = db.Column(db.String(200))  # 图片路径
    project_id = db.Column(db.Integer, db.ForeignKey('project.id'))  # 项目ID
    created_at = db.Column(db.DateTime, default=datetime.utcnow)  # 创建时间
    items = db.relationship('InvoiceItem', backref='invoice', lazy=True)  # 发票明细项

Project (项目)

class Project(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)  # 项目名称
    description = db.Column(db.Text)  # 项目描述
    created_at = db.Column(db.DateTime, default=datetime.utcnow)  # 创建时间
    invoices = db.relationship('Invoice', backref='project', lazy=True)  # 关联的发票

InvoiceItem (发票明细项)

class InvoiceItem(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    invoice_id = db.Column(db.Integer, db.ForeignKey('invoice.id'), nullable=False)  # 所属发票ID
    name = db.Column(db.String(100))  # 商品名称
    specification = db.Column(db.String(100))  # 规格型号
    unit = db.Column(db.String(20))  # 单位
    quantity = db.Column(db.Float)  # 数量
    unit_price = db.Column(db.Float)  # 单价
    amount = db.Column(db.Float)  # 金额
    tax_rate = db.Column(db.Float)  # 税率

关键技术说明 | Key Technologies

1. OCR识别

系统使用腾讯云OCR API进行增值税发票自动识别,关键实现在core/ocr_process.py

def recognize_vat_invoice(image_path):
    """
    识别增值税发票
    :param image_path: 发票图片路径
    :return: 识别结果字典
    """
    try:
        # 初始化腾讯云OCR客户端
        cred = credential.Credential(os.environ.get("TENCENTCLOUD_SECRET_ID"), 
                                    os.environ.get("TENCENTCLOUD_SECRET_KEY"))
        client = ocr_client.OcrClient(cred, "ap-guangzhou")
        
        # 读取图片文件并编码
        with open(image_path, 'rb') as f:
            image_data = base64.b64encode(f.read()).decode('utf-8')
        
        # 调用API
        req = models.VatInvoiceOCRRequest()
        req.ImageBase64 = image_data
        resp = client.VatInvoiceOCR(req)
        
        # 解析API返回结果
        result = json.loads(resp.to_json_string())
        
        # 格式化处理识别结果
        invoice_data = format_vat_invoice_data(result)
        return invoice_data
    except Exception as e:
        app.logger.error(f"OCR识别失败: {str(e)}")
        return None

2. 数据处理与展示

系统使用Chart.js进行数据可视化,关键实现在app/static/js/index.js

function renderStatisticsChart(data) {
    const ctx = document.getElementById('statisticsChart').getContext('2d');
    
    new Chart(ctx, {
        type: 'bar',
        data: {
            labels: data.labels,
            datasets: [{
                label: '发票总额(元)',
                data: data.amounts,
                backgroundColor: 'rgba(54, 162, 235, 0.5)',
                borderColor: 'rgba(54, 162, 235, 1)',
                borderWidth: 1
            }, {
                label: '发票数量',
                data: data.counts,
                backgroundColor: 'rgba(255, 99, 132, 0.5)',
                borderColor: 'rgba(255, 99, 132, 1)',
                borderWidth: 1,
                yAxisID: 'y1'
            }]
        },
        options: {
            responsive: true,
            scales: {
                y: {
                    beginAtZero: true,
                    title: {
                        display: true,
                        text: '金额(元)'
                    }
                },
                y1: {
                    beginAtZero: true,
                    position: 'right',
                    title: {
                        display: true,
                        text: '数量'
                    },
                    grid: {
                        drawOnChartArea: false
                    }
                }
            }
        }
    });
}

3. 数据导出

系统支持多种格式数据导出,关键实现在core/invoice_export.py

def export_to_excel(invoices):
    """
    将发票数据导出为Excel文件
    :param invoices: 发票数据列表
    :return: Excel文件路径
    """
    wb = openpyxl.Workbook()
    ws = wb.active
    ws.title = "发票数据"
    
    # 添加表头
    headers = ['发票代码', '发票号码', '开票日期', '销售方', '购买方', '金额', '税额', '合计', '项目']
    ws.append(headers)
    
    # 添加数据行
    for invoice in invoices:
        project_name = invoice.project.name if invoice.project else '未分类'
        row = [
            invoice.invoice_code,
            invoice.invoice_number,
            invoice.invoice_date.strftime('%Y-%m-%d') if invoice.invoice_date else '',
            invoice.seller_name,
            invoice.buyer_name,
            invoice.total_amount - invoice.tax_amount if invoice.total_amount and invoice.tax_amount else 0,
            invoice.tax_amount or 0,
            invoice.total_amount or 0,
            project_name
        ]
        ws.append(row)
    
    # 保存文件
    filename = f"invoice_export_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
    file_path = os.path.join(current_app.config['TEMP_FOLDER'], filename)
    wb.save(file_path)
    
    return file_path

系统配置 | System Configuration

系统配置通过.env文件和应用配置类实现:

class Config:
    """基础配置类"""
    SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard-to-guess-string'
    SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
        'sqlite:///' + os.path.join(basedir, 'data', 'invoices.db')
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    UPLOAD_FOLDER = os.path.join(basedir, 'app', 'static', 'uploads')
    TEMP_FOLDER = os.path.join(basedir, 'app', 'static', 'temp')
    ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'pdf'}
    MAX_CONTENT_LENGTH = 16 * 1024 * 1024  # 16MB

class DevelopmentConfig(Config):
    """开发环境配置"""
    DEBUG = True
    
class ProductionConfig(Config):
    """生产环境配置"""
    DEBUG = False

扩展性设计 | Extensibility Design

系统设计考虑了以下扩展点:

  1. 支持更多发票类型:通过扩展core/ocr_process.py中的识别函数,可以支持更多类型的发票

  2. 支持不同的OCR服务提供商:系统采用适配器模式,可以方便地替换OCR服务提供商

  3. 数据库扩展:可以从SQLite迁移到MySQL/PostgreSQL等更强大的数据库系统,只需修改配置

  4. 身份认证系统:预留了用户认证相关的扩展点,可以实现多用户支持