Django多文件上传:LibreOffice批量转xlsx偶发失败求助
问题描述
- 开发的Django应用仅支持处理xlsx格式Excel文件,上传xls/ods文件时会通过
libreoffice --convert-to xlsx命令转换格式 - 应用支持多文件上传,文件存入数据库后状态设为Created,通过post_save信号触发后台线程处理,处理完成后状态更新为Processed或Error,同时支持重新处理功能
- 故障现象:批量上传非xlsx文件时,部分文件无规律转换失败;单个上传或重新处理这些失败文件均正常;批量上传xlsx文件则全部成功
- 失败时LibreOffice返回码为1,无stdout和stderr输出,后续加载转换后的文件触发FileNotFoundError
故障原因分析
1. LibreOffice多进程并发冲突
LibreOffice本质是单实例架构的应用,批量处理时多个后台线程同时调用libreoffice命令,会触发进程间资源竞争,导致无规律的转换失败。单个处理时无并发冲突,因此能正常执行。
2. 临时文件/输出目录竞争
批量转换时,多个线程可能同时使用系统临时目录或同一自定义输出目录,容易出现文件命名冲突、文件被提前覆盖/删除、写入权限争夺等问题,最终导致转换后的文件不存在,触发FileNotFoundError。
3. 系统资源限制
批量处理短时间内占用大量CPU、内存资源,导致LibreOffice进程无法正常启动或完成转换流程,直接返回错误码1且无任何输出。
4. 文件写入未完成就触发转换
post_save信号在批量保存时,可能在文件还未完全写入磁盘/数据库时就触发了后台线程,此时线程读取的文件不完整,导致LibreOffice转换失败。
相关代码片段
models.py
from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver from .functions import process_excel_file import threading class ExcelFile(models.Model): FILE_STATUS = ( ('Created', 'Created'), ('Processed', 'Processed'), ('Error', 'Error'), ) file = models.FileField(upload_to='excel_files/') status = models.CharField(max_length=20, choices=FILE_STATUS, default='Created') converted_file = models.FileField(upload_to='converted/', blank=True, null=True) @receiver(post_save, sender=ExcelFile) def trigger_processing(sender, instance, created, **kwargs): if created and instance.status == 'Created': threading.Thread(target=process_excel_file, args=(instance.id,)).start()
functions.py
import subprocess import os from django.conf import settings from .models import ExcelFile def process_excel_file(file_id): instance = ExcelFile.objects.get(id=file_id) file_path = instance.file.path filename, ext = os.path.splitext(os.path.basename(file_path)) if ext.lower() not in ['.xlsx']: # 转换格式 convert_cmd = [ 'libreoffice', '--headless', '--convert-to', 'xlsx', '--outdir', settings.MEDIA_ROOT + '/converted/', file_path ] result = subprocess.run(convert_cmd, capture_output=True, text=True) if result.returncode != 0: instance.status = 'Error' instance.save() return converted_path = os.path.join(settings.MEDIA_ROOT + '/converted/', f"{filename}.xlsx") if not os.path.exists(converted_path): instance.status = 'Error' instance.save() return instance.converted_file.name = f"converted/{filename}.xlsx" # 后续处理逻辑 instance.status = 'Processed' instance.save()
执行输出示例
成功转换输出
Return code: 0 Stdout: convert /path/to/file.ods -> /path/to/converted/file.xlsx using filter Calc MS Excel 2007 XML Stderr:
失败转换输出
Return code: 1 Stdout: Stderr:
内容的提问来源于stack exchange,提问作者AndresJ
相关产品推荐
相关产品推荐

