一、系统架构与技术选型
本方案采用微服务架构思想,利用Docker容器化部署,确保环境一致性。核心组件包括:对象存储、关系型数据库、OCR识别服务、Web后端及前端界面。技术栈具体选型如下:
- 对象存储:MinIO(高性能、S3兼容,用于存储原始档案文件及数字化后的PDF/A文件)
- 数据库:PostgreSQL 14(存储档案元数据、索引信息及用户权限)
- 消息队列:Redis(配合Celery处理耗时任务,如异步OCR识别)
- 后端服务:Python 3.9 + Flask + SQLAlchemy(轻量级,易于扩展)
- 图像处理:Tesseract-OCR + OpenCV(实现图像预处理及文字提取)
- 前端:Vue.js 3(单页应用,提供上传、检索、预览功能)
二、基础设施环境搭建
在服务器本地创建项目目录,并编写Docker Compose配置文件以一键启动所有依赖服务。请确保服务器已安装Docker及Docker Compose。
执行以下命令创建目录结构:
```bash
mkdir -p archive-digital-service/{backend,frontend,data/{minio,postgres,redis}}
cd archive-digital-service
```
在项目根目录下创建docker-compose.yml文件,写入以下完整配置。该配置定义了MinIO、PostgreSQL、Redis以及后端服务的运行环境。
```yaml
version: '3.8'
services:
PostgreSQL 数据库
db:
image: postgres:14-alpine
container_name: archive_db
environment:
POSTGRES_USER: archive_user
POSTGRES_PASSWORD: secure_password_change_me
POSTGRES_DB: archive_db
volumes:
- ./data/postgres:/var/lib/postgresql/data
ports:
- "5432:5432"
networks:
- archive_net
Redis 缓存与消息队列
redis:
image: redis:7-alpine
container_name: archive_redis
ports:
- "6379:6379"
networks:
- archive_net
MinIO 对象存储服务
minio:
image: minio/minio:latest
container_name: archive_minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
volumes:
- ./data/minio:/data
ports:
- "9000:9000"
- "9001:9001"
networks:
- archive_net
后端API服务
backend:
build: ./backend
container_name: archive_backend
command: python app.py
volumes:
- ./backend:/app
environment:
FLASK_ENV: development
DATABASE_URL: postgresql://archive_user:secure_password_change_me@db:5432/archive_db
REDIS_URL: redis://redis:6379/0
MINIO_ENDPOINT: minio:9000
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
SECRET_KEY: jwt_secret_key_change_me
depends_on:
- db
- redis
- minio
ports:
- "5000:5000"
networks:
- archive_net
Celery 异步任务处理 worker (用于OCR)
worker:
build: ./backend
container_name: archive_worker
command: celery -A tasks.celery worker --loglevel=info
volumes:
- ./backend:/app
environment:
DATABASE_URL: postgresql://archive_user:secure_password_change_me@db:5432/archive_db
REDIS_URL: redis://redis:6379/0
MINIO_ENDPOINT: minio:9000
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
depends_on:
- db
- redis
- minio
networks:
- archive_net
networks:
archive_net:
driver: bridge
```
三、后端核心服务开发
进入backend目录,编写后端核心逻辑。首先创建backend/Dockerfile,安装Python依赖及Tesseract-OCR。
```dockerfile
FROM python:3.9-slim
安装系统依赖及OCR引擎
RUN apt-get update && apt-get install -y \
tesseract-ocr \
tesseract-ocr-chi-sim \
libpq-dev \
gcc \
&& rm -rf /var/lib/apt/lists/
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
```
在backend目录下创建requirements.txt,定义Python依赖库:
```text
Flask==2.3.2
Flask-CORS==4.0.0
Flask-SQLAlchemy==3.0.5
psycopg2-binary==2.9.6
celery==5.3.1
redis==4.5.5
minio==7.1.16
Pillow==10.0.0
python-dotenv==1.0.0
pytesseract==0.3.10
```

创建backend/models.py,定义数据库模型。该模型用于存储档案的基本信息及OCR识别后的文本内容。
```python
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
class Archive(db.Model):
__tablename__ = 'archives'
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String(255), nullable=False)
file_type = db.Column(db.String(50))
upload_time = db.Column(db.DateTime, default=datetime.utcnow)
status = db.Column(db.String(50), default='uploaded') uploaded, processing, completed, failed
ocr_text = db.Column(db.Text, default='')
s3_bucket = db.Column(db.String(100))
s3_key = db.Column(db.String(255))
def to_dict(self):
return {
'id': self.id,
'filename': self.filename,
'status': self.status,
'upload_time': self.upload_time.isoformat(),
'ocr_text': self.ocr_text[:200] + '...' if len(self.ocr_text) > 200 else self.ocr_text
}
```
创建backend/tasks.py,配置Celery异步任务,实现文件下载、OCR识别及回写的逻辑。
```python
from celery import Celery
import os
import pytesseract
from minio import Minio
from models import db, Archive
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
环境变量配置
MINIO_ENDPOINT = os.getenv('MINIO_ENDPOINT', 'minio:9000')
MINIO_ACCESS_KEY = os.getenv('MINIO_ACCESS_KEY', 'minioadmin')
MINIO_SECRET_KEY = os.getenv('MINIO_SECRET_KEY', 'minioadmin')
DATABASE_URL = os.getenv('DATABASE_URL')
初始化Celery
celery = Celery('tasks', broker=os.getenv('REDIS_URL', 'redis://redis:6379/0'))
celery.conf.update(result_backend=os.getenv('REDIS_URL', 'redis://redis:6379/0'))
初始化MinIO客户端
minio_client = Minio(
MINIO_ENDPOINT,
access_key=MINIO_ACCESS_KEY,
secret_key=MINIO_SECRET_KEY,
secure=False
)
def get_db_session():
engine = create_engine(DATABASE_URL)
Session = sessionmaker(bind=engine)
return Session()
@celery.task(bind=True)
def process_ocr_task(self, archive_id):
session = get_db_session()
try:
archive = session.query(Archive).get(archive_id)
if not archive:
return
更新状态为处理中
archive.status = 'processing'
session.commit()
从MinIO下载文件
temp_file_path = f"/tmp/{archive.filename}"
minio_client.fget_object(archive.s3_bucket, archive.s3_key, temp_file_path)
执行OCR识别 (支持中英文)
text = pytesseract.image_to_string(temp_file_path, lang='chi_sim+eng')
更新数据库
archive.ocr_text = text
archive.status = 'completed'
session.commit()
清理临时文件
os.remove(temp_file_path)
return {'status': 'success', 'archive_id': archive_id}
except Exception as e:
archive.status = 'failed'
session.commit()
return {'status': 'failed', 'error': str(e)}
finally:
session.close()
```
创建backend/app.py,实现Flask API接口,包括文件上传、列表查询及初始化数据库。
```python
import os
import uuid
from flask import Flask, request, jsonify
from flask_cors import CORS
from minio import Minio
from models import db, Archive
from tasks import process_ocr_task
app = Flask(__name__)
CORS(app)
配置
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
MinIO 客户端
minio_client = Minio(
os.getenv('MINIO_ENDPOINT', 'minio:9000'),
access_key=os.getenv('MINIO_ACCESS_KEY', 'minioadmin'),
secret_key=os.getenv('MINIO_SECRET_KEY', 'minioadmin'),
secure=False
)
BUCKET_NAME = "archive-files"
@app.before_first_request
def create_tables():
db.create_all()
确保MinIO Bucket存在
if not minio_client.bucket_exists(BUCKET_NAME):
minio_client.make_bucket(BUCKET_NAME)
@app.route('/api/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'error': 'No file part'}), 400
file = request.files['file']
if file.filename == '':
return jsonify({'error': 'No selected file'}), 400
生成唯一文件名
file_ext = file.filename.split('.')[-1]
unique_filename = f"{uuid.uuid4().hex}.{file_ext}"
保存到MinIO
try:
minio_client.put_object(
BUCKET_NAME,
unique_filename,
file,
length=-1,
part_size=1010241024
)
写入数据库记录
new_archive = Archive(
filename=file.filename,
file_type=file_ext,
s3_bucket=BUCKET_NAME,
s3_key=unique_filename
)
db.session.add(new_archive)
db.session.commit()
触发异步OCR任务
process_ocr_task.delay(new_archive.id)
return jsonify({'message': 'File uploaded successfully', 'id': new_archive.id}), 201
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/archives', methods=['GET'])
def list_archives():
archives = Archive.query.order_by(Archive.upload_time.desc()).all()
return jsonify([a.to_dict() for a in archives])
@app.route('/api/archives/
', methods=['GET'])
def get_archive_detail(id):
archive = Archive.query.get(id)
if not archive:
return jsonify({'error': 'Not found'}), 404
return jsonify({
'id': archive.id,
'filename': archive.filename,
'status': archive.status,
'ocr_text': archive.ocr_text
})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
```
四、前端交互页面实现
在frontend目录下创建index.html。为了实现零门槛快速验证,我们直接使用Vue 3的CDN版本构建单页应用,无需Node.js构建步骤。
```html
档案数字化托管服务
档案数字化托管系统
上传档案图片,自动OCR识别并归档
档案列表
{{ archive.filename }}
{{ archive.upload_time }}
{{ archive.status }}
{{ selectedArchive.filename }}
识别内容:
{{ selectedArchive.ocr_text }}