在企事业单位内部办公环境中,文档电子化处理是一个高频需求。特别是对于财务、档案管理等岗位,经常需要将纸质文档或扫描件转换为可编辑的电子格式。然而在封闭的局域网环境中,无法使用互联网上的各类OCR服务,这给日常工作带来了诸多不便。
传统解决方案通常面临几个痛点:
基于这些痛点,我决定开发一个能在局域网内部署的多用户文档转换工具。经过技术选型,最终选择了Umi-OCR作为核心识别引擎,配合Flask框架构建Web服务。这个方案具有以下优势:
系统采用经典的三层架构:
code复制前端Web界面 → Flask应用层 → Umi-OCR服务
前端使用Bootstrap构建响应式界面,后端采用Flask处理业务逻辑,通过HTTP API与Umi-OCR服务交互。这种架构解耦了界面、业务逻辑和OCR引擎,使得各组件可以独立升级。
用户管理模块:
文件处理模块:
任务调度模块:
格式转换模块:
Flask框架:
Umi-OCR:
Python-docx:
首先需要安装必要的Python包:
bash复制pip install flask requests python-docx
Umi-OCR需要单独下载并配置,具体步骤:
python复制@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return jsonify({'success': False, 'error': '没有文件被上传'})
file = request.files['file']
user_id = request.form.get('user_id', 'anonymous')
if file.filename == '':
return jsonify({'success': False, 'error': '没有选择文件'})
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
user_folder, _ = get_user_folder(user_id)
file_path = os.path.join(user_folder, filename)
file.save(file_path)
task_id = str(uuid.uuid4())
output_format = request.form.get('output_format', 'pdfLayered')
password = request.form.get('password', '')
future = executor.submit(
process_ocr_task,
user_id,
filename,
file_path,
task_id,
output_format,
password
)
return jsonify({
'success': True,
'task_id': task_id,
'filename': filename,
'message': '文件上传成功,任务正在处理中...'
})
return jsonify({'success': False, 'error': '不支持的文件类型'})
关键点说明:
secure_filename处理文件名,防止路径遍历攻击python复制def process_ocr_task(user_id, original_filename, temp_file_path, task_id, output_format='pdfLayered', password=''):
client = UmiOCRClient()
user_folder, output_folder = get_user_folder(user_id)
try:
update_task_log(user_id, task_id, f"开始处理文件: {original_filename}")
mission_options = {
"doc.extractionMode": "fullPage",
"password": password if password else "",
}
# 上传文件到Umi-OCR
upload_result = client.upload_document(temp_file_path, mission_options)
if upload_result.get('code') != 100:
update_task_log(user_id, task_id, f"上传失败: {upload_result.get('data', '未知错误')}")
return {'success': False, 'error': upload_result.get('data', '上传失败')}
ocr_task_id = upload_result.get('data')
active_tasks[f"{user_id}_{task_id}"] = True
# 轮询任务状态
for i in range(Config.MAX_POLL_RETRIES):
if not active_tasks.get(f"{user_id}_{task_id}", False):
update_task_log(user_id, task_id, "任务已手动停止")
return {'success': False, 'error': '任务已停止'}
time.sleep(Config.POLL_INTERVAL)
result = client.get_task_result(ocr_task_id, is_data=False)
if result.get('code') == 100:
if result.get('is_done', False):
if result.get('state') == 'success':
# 处理成功,下载结果
return handle_success_result(client, user_id, task_id, ocr_task_id,
original_filename, output_format, output_folder)
else:
error_msg = result.get('message', '任务失败')
update_task_log(user_id, task_id, f"任务失败: {error_msg}")
return {'success': False, 'error': error_msg}
else:
# 更新进度
update_progress(user_id, task_id, result)
update_task_log(user_id, task_id, "任务处理超时")
return {'success': False, 'error': '任务处理超时'}
except Exception as e:
app.logger.error(f"处理OCR任务时出错: {e}")
update_task_log(user_id, task_id, f"错误: {str(e)}")
return {'success': False, 'error': str(e)}
python复制def text_to_word(text_content, output_path):
try:
doc = Document()
doc.add_heading('OCR识别结果', 0)
# 设置中文字体
style = doc.styles['Normal']
font = style.font
font.name = '宋体'
font.size = Pt(12)
# 处理段落
paragraphs = text_content.split('\n\n')
for para in paragraphs:
if para.strip():
doc.add_paragraph(para.strip())
doc.save(output_path)
return True
except Exception as e:
app.logger.error(f"创建Word文档失败: {e}")
return False
前端使用Bootstrap构建,主要功能区域包括:
关键JavaScript代码:
javascript复制// 文件上传处理
$('#file-upload').on('change', function() {
let formData = new FormData();
formData.append('file', this.files[0]);
formData.append('user_id', userId);
formData.append('output_format', $('#output-format').val());
$.ajax({
url: '/upload',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(response) {
if(response.success) {
addTaskToList(response.task_id, response.filename);
pollTaskStatus(response.task_id);
} else {
showError(response.error);
}
}
});
});
// 轮询任务状态
function pollTaskStatus(taskId) {
let interval = setInterval(function() {
$.get(`/status/${userId}/${taskId}`, function(response) {
updateTaskStatus(taskId, response);
if(response.status === 'completed' || response.status === 'failed') {
clearInterval(interval);
}
});
}, 2000);
}
服务端部署:
pip install -r requirements.txtpython app.py客户端访问:
http://服务器IP:5000使用PyInstaller打包:
bash复制pyinstaller --onefile --add-data "templates;templates" --add-data "static;static" app.py
打包后会生成单个可执行文件,方便在没有Python环境的机器上运行。
对于大量文档处理:
MAX_WORKERS参数值内存优化:
MAX_CONTENT_LENGTH限制上传文件大小问题现象:
解决方案:
python复制mission_options = {
"doc.extractionMode": "fullPage",
"ocr.lang": "chinese",
"ocr.engine": "paddle",
"table.enable": True
}
问题现象:
解决方案:
实际测试表明,单机环境下处理A4大小文档的性能数据:
对于更高并发需求,可以考虑:
这个项目在实际部署中取得了不错的效果,特别是在政府单位和金融机构的内部办公场景中,大大提高了文档电子化处理的效率。相比商业解决方案,具有以下优势:
未来改进方向:
通过这个项目,我深刻体会到Python生态在快速开发企业级应用方面的优势。Flask的简洁性与Umi-OCR的强大功能相结合,可以在很短时间内构建出实用的业务工具。