-
Notifications
You must be signed in to change notification settings - Fork 7
System Architecture
chiupam edited this page Apr 4, 2025
·
1 revision
本文档描述发票OCR管理系统的整体架构设计、关键模块及其交互。
发票OCR管理系统采用经典的Web应用三层架构:
- 表现层:基于Flask框架的Web界面和API接口
- 业务逻辑层:发票识别、数据处理、项目管理等核心业务逻辑
- 数据持久层:基于SQLite的数据存储和访问
系统架构图如下:
+----------------------------------+
| Web浏览器 / 客户端 |
+----------------------------------+
|
| HTTP/HTTPS
v
+----------------------------------+
| Flask Web应用 |
| +----------------------------+ |
| | 路由和控制器层 | |
| +----------------------------+ |
| | +---------------------+ | |
| | | 模板渲染(Jinja2) | | |
| | +---------------------+ | |
| | | 表单处理和验证 | | |
| | +---------------------+ | |
| +----------------------------+ |
+----------------------------------+
|
v
+----------------------------------+
| 业务逻辑层 |
| +----------------------------+ |
| | 发票OCR处理模块 | |
| +----------------------------+ |
| | 项目管理模块 | |
| +----------------------------+ |
| | 统计分析模块 | |
| +----------------------------+ |
| | 数据导出模块 | |
| +----------------------------+ |
+----------------------------------+
|
v
+----------------------------------+
| 数据持久层 |
| +----------------------------+ |
| | SQLite数据库 | |
| +----------------------------+ |
| | 文件系统(图片存储) | |
| +----------------------------+ |
+----------------------------------+
|
v
+----------------------------------+
| 外部集成服务 |
| +----------------------------+ |
| | 腾讯云OCR API | |
| +----------------------------+ |
+----------------------------------+
-
app/__init__.py: 应用工厂,创建和初始化Flask应用 -
app/models.py: 数据模型定义(Invoice, Project, InvoiceItem) -
app/routes.py: Web路由和控制器 -
app/forms.py: 表单定义和验证
-
core/ocr_process.py: OCR识别和数据提取 -
core/invoice_formatter.py: 发票数据格式化 -
core/invoice_export.py: 数据导出功能
-
tools/db_init.py: 数据库初始化 -
tools/db_backup.py: 数据库备份 -
tools/generate_test_data.py: 测试数据生成 -
tools/clean_temp_files.py: 清理临时文件
-
app/static/js/: JavaScript文件 -
app/static/css/: 样式表 -
app/static/uploads/: 上传的发票图片
-
app/templates/base.html: 基础模板 -
app/templates/index.html: 主页 -
app/templates/invoice_*.html: 发票相关页面 -
app/templates/project_*.html: 项目相关页面
下面是系统的主要数据流:
+-------------+ +-------------+ +-------------+
| 用户上传 | --> | 文件存储 | --> | 调用OCR API |
| 发票图片 | | 到本地 | | |
+-------------+ +-------------+ +-------------+
|
v
+-------------+ +-------------+ +-------------+
| 保存到 | <-- | 解析返回 | <-- | API返回 |
| 数据库 | | 的结果 | | 识别结果 |
+-------------+ +-------------+ +-------------+
|
v
+-------------+ +-------------+
| 显示详情 | --> | 用户修正 |
| 页面 | | 数据 |
+-------------+ +-------------+
|
v
+-------------+
| 更新到 |
| 数据库 |
+-------------+
+-------------+ +-------------+ +-------------+
| 创建项目 | --> | 保存项目 | --> | 显示项目 |
| | | 到数据库 | | 列表 |
+-------------+ +-------------+ +-------------+
|
v
+-------------+ +-------------+ +-------------+
| 更新项目 | <-- | 编辑项目 | <-- | 选择项目 |
| 数据 | | 信息 | | |
+-------------+ +-------------+ +-------------+
+-------------+ +-------------+ +-------------+
| 查看未分类 | --> | 选择发票 | --> | 选择目标 |
| 发票 | | | | 项目 |
+-------------+ +-------------+ +-------------+
|
v
+-------------+ +-------------+ +-------------+
| 更新项目 | <-- | 更新发票 | <-- | 确认分类 |
| 统计 | | 归属 | | |
+-------------+ +-------------+ +-------------+
系统使用SQLAlchemy ORM管理以下数据模型:
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) # 发票明细项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) # 关联的发票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) # 税率系统使用腾讯云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系统使用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
}
}
}
}
});
}系统支持多种格式数据导出,关键实现在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系统配置通过.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系统设计考虑了以下扩展点:
-
支持更多发票类型:通过扩展
core/ocr_process.py中的识别函数,可以支持更多类型的发票 -
支持不同的OCR服务提供商:系统采用适配器模式,可以方便地替换OCR服务提供商
-
数据库扩展:可以从SQLite迁移到MySQL/PostgreSQL等更强大的数据库系统,只需修改配置
-
身份认证系统:预留了用户认证相关的扩展点,可以实现多用户支持