企业级档案数字化托管服务从零搭建实操指南

一、系统架构与技术选型

本方案采用微服务架构思想,利用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识别并归档

点击上传档案文件 (支持图片, PDF)

正在上传... {{ uploadProgress }}%

档案列表

{{ archive.filename }}
{{ archive.upload_time }}
{{ archive.status }}