根据《中华人民共和国档案法》第二十四条,电子档案管理系统必须满足以下技术要求:
《档案法》第三十七条要求电子档案与传统载体档案具有同等效力,这要求系统必须实现四性检测:真实性、完整性、可用性、安全性。
操作系统:CentOS 7.9 或 Ubuntu 20.04 LTS
数据库:MySQL 8.0 或 PostgreSQL 13
文件存储:MinIO(兼容S3协议)或本地存储
编程语言:Python 3.8+ 或 Java 11+
Python环境部署命令:
``` 安装系统依赖 sudo apt-get update sudo apt-get install -y python3-pip python3-dev libmysqlclient-dev 创建虚拟环境 python3 -m venv /opt/archives_env source /opt/archives_env/bin/activate 安装核心包 pip install django==4.2 django-cors-headers djangorestframework pip install mysqlclient pillow cryptography pip install minio python-magic ```创建档案主表:
``` CREATE TABLE archives ( id CHAR(32) PRIMARY KEY COMMENT '档案ID(UUID)', archive_code VARCHAR(100) NOT NULL COMMENT '档号', title VARCHAR(500) NOT NULL COMMENT '题名', fonds_code VARCHAR(50) NOT NULL COMMENT '全宗号', catalog_code VARCHAR(50) NOT NULL COMMENT '目录号', volume_code VARCHAR(50) NOT NULL COMMENT '案卷号', piece_code VARCHAR(50) COMMENT '件号', security_level ENUM('公开','内部','秘密','机密','绝密') DEFAULT '内部', retention_period INT COMMENT '保管期限(年)', create_time DATETIME NOT NULL COMMENT '形成时间', archive_time DATETIME NOT NULL COMMENT '归档时间', format_type VARCHAR(20) COMMENT 'OFD/PDF/XML等', file_size BIGINT COMMENT '文件大小(字节)', checksum VARCHAR(64) NOT NULL COMMENT 'SHA256校验值', status ENUM('在库','借阅','销毁','移交') DEFAULT '在库', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_fonds (fonds_code), INDEX idx_status (status), UNIQUE KEY uk_archive_code (archive_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='档案主表'; ```《档案法》要求元数据齐全完整,必须包含以下核心元数据:
``` CREATE TABLE archive_metadata ( id BIGINT PRIMARY KEY AUTO_INCREMENT, archive_id CHAR(32) NOT NULL COMMENT '档案ID', meta_key VARCHAR(100) NOT NULL COMMENT '元数据键', meta_value TEXT NOT NULL COMMENT '元数据值', meta_type ENUM('系统','业务','管理') DEFAULT '业务', is_required BOOLEAN DEFAULT TRUE COMMENT '是否必填', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (archive_id) REFERENCES archives(id) ON DELETE CASCADE, INDEX idx_archive (archive_id), INDEX idx_key (meta_key) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='档案元数据表'; ```
实现数字签名验证:
``` import hashlib import hmac from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.serialization import load_pem_public_key def verify_signature(file_path, signature, public_key_path): """验证文件数字签名""" 计算文件哈希 sha256_hash = hashlib.sha256() with open(file_path, "rb") as f: for byte_block in iter(lambda: f.read(4096), b""): sha256_hash.update(byte_block) file_digest = sha256_hash.digest() 加载公钥 with open(public_key_path, "rb") as key_file: public_key = load_pem_public_key(key_file.read()) 验证签名 try: public_key.verify( signature, file_digest, padding.PSS( mgf=padding.MGF1(hashes.SHA256()), salt_length=padding.PSS.MAX_LENGTH ), hashes.SHA256() ) return True except Exception: return False ```实现区块链存证:
``` import json import time import hashlib class ArchiveBlockchain: def __init__(self): self.chain = [] self.create_block(proof=1, previous_hash='0') def create_block(self, proof, previous_hash, archive_data=None): block = { 'index': len(self.chain) + 1, 'timestamp': time.time(), 'proof': proof, 'previous_hash': previous_hash, 'archive_data': archive_data } self.chain.append(block) return block def add_archive_record(self, archive_id, checksum, operation): """添加档案操作记录到区块链""" archive_data = { 'archive_id': archive_id, 'checksum': checksum, 'operation': operation, 上传/修改/借阅/归还/销毁 'timestamp': time.time(), 'operator': 'system_user' } previous_block = self.get_previous_block() previous_hash = self.hash(previous_block) proof = self.proof_of_work(previous_proof=previous_block['proof']) return self.create_block(proof, previous_hash, archive_data) def verify_chain(self): """验证区块链完整性""" previous_block = self.chain[0] block_index = 1 while block_index < len(self.chain): block = self.chain[block_index] if block['previous_hash'] != self.hash(previous_block): return False previous_proof = previous_block['proof'] proof = block['proof'] hash_operation = hashlib.sha256( f'{proof2 - previous_proof2}'.encode() ).hexdigest() if hash_operation[:4] != '0000': return False previous_block = block block_index += 1 return True ```安装转换工具:
``` 安装LibreOffice(支持OFD输出) sudo apt-get install -y libreoffice Python转换脚本 import subprocess import os def convert_to_ofd(input_path, output_dir): """将文档转换为OFD格式""" filename = os.path.basename(input_path) output_path = os.path.join(output_dir, os.path.splitext(filename)[0] + '.ofd') 使用LibreOffice进行转换 cmd = [ 'libreoffice', '--headless', '--convert-to', 'ofd', '--outdir', output_dir, input_path ] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) if result.returncode == 0: return output_path else: raise Exception(f"转换失败: {result.stderr}") except subprocess.TimeoutExpired: raise Exception("转换超时") ```实现符合《档案法》要求的文件存储:
``` class ArchiveStorage: def __init__(self, minio_endpoint, access_key, secret_key): from minio import Minio self.client = Minio( minio_endpoint, access_key=access_key, secret_key=secret_key, secure=False ) def store_archive(self, file_path, archive_id, bucket_name='archives'): """存储档案文件""" 创建bucket(如果不存在) if not self.client.bucket_exists(bucket_name): self.client.make_bucket(bucket_name) 计算校验和 with open(file_path, 'rb') as file_data: file_hash = hashlib.sha256() chunk_size = 8192 while chunk := file_data.read(chunk_size): file_hash.update(chunk) checksum = file_hash.hexdigest() 分块上传(支持大文件) object_name = f"{archive_id}.ofd" self.client.fput_object( bucket_name, object_name, file_path, metadata={ 'x-amz-meta-archive-id': archive_id, 'x-amz-meta-checksum': checksum, 'x-amz-meta-format': 'OFD' } ) return { 'object_name': object_name, 'checksum': checksum, 'bucket': bucket_name } ```实现借阅状态机:
``` from enum import Enum from django.db import models class BorrowStatus(Enum): APPLIED = '已申请' APPROVED = '已批准' REJECTED = '已拒绝' BORROWED = '已借出' RETURNED = '已归还' OVERDUE = '已超期' class BorrowRecord(models.Model): archive = models.ForeignKey('Archive', on_delete=models.CASCADE) borrower = models.ForeignKey('User', on_delete=models.CASCADE) borrow_reason = models.TextField() borrow_date = models.DateTimeField() expected_return = models.DateTimeField() actual_return = models.DateTimeField(null=True) status = models.CharField( max_length=20, choices=[(tag.value, tag.name) for tag in BorrowStatus], default=BorrowStatus.APPLIED.value ) approver = models.ForeignKey('User', on_delete=models.SET_NULL, null=True, related_name='approved_records') approval_comment = models.TextField(blank=True) def can_approve(self, user): """检查用户是否有审批权限""" 审批人不能是自己 if user == self.borrower: return False 根据密级判断审批权限 archive_level = self.archive.security_level user_level = user.security_level 权限映射:公开<内部<秘密<机密<绝密 level_map = {'公开': 1, '内部': 2, '秘密': 3, '机密': 4, '绝密': 5} return level_map[user_level] >= level_map[archive_level] ```PDF文件添加水印:
``` from reportlab.lib.pagesizes import A4 from reportlab.pdfgen import canvas from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont import PyPDF2 def add_watermark(input_pdf, output_pdf, watermark_text, user_info): """为借阅文件添加水印""" 注册中文字体 pdfmetrics.registerFont(TTFont('SimSun', '/usr/share/fonts/truetype/simsun.ttc')) 创建水印PDF watermark = canvas.Canvas("watermark.pdf", pagesize=A4) watermark.setFont("SimSun", 10) watermark.setFillColorRGB(0.8, 0.8, 0.8) 灰色 斜向重复水印 for i in range(0, 600, 50): for j in range(0, 850, 50): watermark.saveState() watermark.translate(i, j) watermark.rotate(45) watermark.drawString(0, 0, f"{watermark_text} | {user_info}") watermark.restoreState() watermark.save() 合并水印 input_file = PyPDF2.PdfReader(open(input_pdf, "rb")) watermark_file = PyPDF2.PdfReader(open("watermark.pdf", "rb")) output = PyPDF2.PdfWriter() for page_num in range(len(input_file.pages)): page = input_file.pages[page_num] watermark_page = watermark_file.pages[0] page.merge_page(watermark_page) output.add_page(page) with open(output_pdf, "wb") as output_file: output.write(output_file) ```实现双重确认销毁机制:
``` class DestructionManager: def __init__(self, db_connection): self.db = db_connection def request_destruction(self, archive_ids, reason, requester_id): """申请销毁档案""" 记录销毁申请 cursor = self.db.cursor() for archive_id in archive_ids: cursor.execute(""" INSERT INTO destruction_requests (archive_id, requester_id, reason, status) VALUES (%s, %s, %s, 'pending') """, (archive_id, requester_id, reason)) 需要两级审批:档案管理员+部门负责人 cursor.execute(""" INSERT INTO approval_chain (request_id, approver_role, required_level) VALUES (LAST_INSERT_ID(), 'archivist', 2), (LAST_INSERT_ID(), 'department_head', 3) """) self.db.commit() return True def execute_destruction(self, request_id, executor_id): """执行销毁操作""" cursor = self.db.cursor() 验证审批完成 cursor.execute(""" SELECT COUNT() as pending FROM approval_chain WHERE request_id = %s AND status != 'approved' """, (request_id,)) if cursor.fetchone()['pending'] > 0: raise Exception("审批流程未完成") 记录销毁日志(不可删除) cursor.execute(""" INSERT INTO destruction_logs (archive_id, request_id, executor_id, destruction_time) SELECT archive_id, %s, %s, NOW() FROM destruction_requests WHERE id = %s """, (request_id, executor_id, request_id)) 更新档案状态(不物理删除) cursor.execute(""" UPDATE archives SET status = 'destroyed' WHERE id IN ( SELECT archive_id FROM destruction_requests WHERE id = %s ) """, (request_id,)) self.db.commit() ```实现档案移交接口:
``` class TransferManager: def create_transfer_plan(self, archive_ids, target_unit, transfer_reason): """创建移交计划""" transfer_id = self.generate_transfer_id() 生成移交清册 inventory = [] for archive_id in archive_ids: archive_info = self.get_archive_info(archive_id) inventory.append({ 'archive_id': archive_id, 'archive_code': archive_info['archive_code'], 'title': archive_info['title'], 'checksum': archive_info['checksum'] }) 生成OFD格式移交清册文件 inventory_file = self.generate_inventory_ofd(inventory, transfer_id) 计算清册哈希值 with open(inventory_file, 'rb') as f: inventory_hash = hashlib.sha256(f.read()).hexdigest() return { 'transfer_id': transfer_id, 'inventory_file': inventory_file, 'inventory_hash': inventory_hash, 'archive_count': len(archive_ids), 'target_unit': target_unit } def confirm_transfer(self, transfer_id, receiving_unit_signature): """确认移交完成""" 验证接收单位签名 if not self.verify_signature(receiving_unit_signature): raise Exception("接收单位签名验证失败") 更新档案状态 self.update_archive_status(transfer_id, 'transferred') 记录移交完成 self.log_transfer_completion(transfer_id, receiving_unit_signature) ```实现完整的操作审计:
``` CREATE TABLE operation_logs ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id VARCHAR(50) NOT NULL COMMENT '操作人ID', operation_type ENUM('查询','借阅','归还','上传','修改','删除','销毁','移交'), target_type ENUM('档案','用户','系统','权限'), target_id VARCHAR(100) COMMENT '操作目标ID', operation_detail JSON COMMENT '操作详情', ip_address VARCHAR(45) COMMENT '操作IP', user_agent TEXT COMMENT '用户代理', operation_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP, result ENUM('成功','失败','异常') DEFAULT '成功', error_message TEXT