131 lines
4.9 KiB
Python
131 lines
4.9 KiB
Python
import os
|
|
import tempfile
|
|
import subprocess
|
|
import shutil
|
|
import glob
|
|
from fastapi import FastAPI, HTTPException, UploadFile, File
|
|
from fastapi.responses import FileResponse
|
|
from pydantic import BaseModel
|
|
from starlette.concurrency import run_in_threadpool
|
|
from starlette.background import BackgroundTask
|
|
|
|
app = FastAPI()
|
|
|
|
|
|
@app.get('/health')
|
|
async def health():
|
|
return {'status': 'ok'}
|
|
|
|
|
|
@app.get('/diagnose')
|
|
async def diagnose():
|
|
info = {}
|
|
try:
|
|
info['soffice_path'] = shutil.which('soffice')
|
|
except Exception as e:
|
|
info['soffice_path_error'] = str(e)
|
|
|
|
try:
|
|
proc = subprocess.run(['soffice', '--version'], capture_output=True, text=True, timeout=5)
|
|
info['soffice_version'] = proc.stdout.strip() or proc.stderr.strip()
|
|
except Exception as e:
|
|
info['soffice_version_error'] = str(e)
|
|
|
|
try:
|
|
info['libreoffice_program_list'] = os.listdir('/usr/lib/libreoffice/program')[:50]
|
|
except Exception as e:
|
|
info['libreoffice_program_list_error'] = str(e)
|
|
|
|
return info
|
|
|
|
|
|
@app.post('/convert')
|
|
async def convert(file: UploadFile = File(...)):
|
|
"""Accept a file upload, convert it to .docx via soffice, and return the resulting file."""
|
|
# Save upload to a temp file
|
|
tmpdir = tempfile.mkdtemp(prefix='soffice_upload_')
|
|
# print("Created temp dir", tmpdir)
|
|
try:
|
|
original_name = file.filename or 'uploaded'
|
|
# print(original_name)
|
|
_, ext = os.path.splitext(original_name)
|
|
in_path = os.path.join(tmpdir, 'input' + (ext or ''))
|
|
with open(in_path, 'wb') as f:
|
|
content = await file.read()
|
|
f.write(content)
|
|
# print("Saved uploaded file to", in_path)
|
|
|
|
# ========== 修改点 1: 添加文件存在检查 ==========
|
|
if not os.path.exists(in_path):
|
|
raise HTTPException(status_code=500, detail="Uploaded file was not saved properly")
|
|
|
|
# If already .docx, return it directly
|
|
if in_path.lower().endswith('.docx'):
|
|
# ========== 修改点 2: 为 .docx 文件添加背景清理任务 ==========
|
|
return FileResponse(
|
|
in_path,
|
|
media_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
filename=os.path.basename(in_path),
|
|
background=BackgroundTask(lambda: cleanup_temp_dir(tmpdir)) # 使用 BackgroundTask
|
|
)
|
|
|
|
def run_conv():
|
|
return subprocess.run(
|
|
['soffice', '--headless', '--convert-to', 'docx', '--outdir', tmpdir, in_path],
|
|
check=True, capture_output=True, text=True, timeout=120
|
|
)
|
|
|
|
try:
|
|
await run_in_threadpool(run_conv)
|
|
except subprocess.CalledProcessError as e:
|
|
stderr = (e.stderr or '').strip()
|
|
raise HTTPException(status_code=500, detail=f'soffice conversion failed: {stderr}')
|
|
except subprocess.TimeoutExpired:
|
|
raise HTTPException(status_code=504, detail='conversion timeout')
|
|
|
|
candidates = glob.glob(os.path.join(tmpdir, '*.docx'))
|
|
# print("Conversion produced candidates:", candidates)
|
|
if not candidates:
|
|
raise HTTPException(status_code=500, detail='soffice produced no .docx')
|
|
out_file = candidates[0]
|
|
|
|
# ========== 修改点 3: 添加输出文件存在检查 ==========
|
|
if not os.path.exists(out_file):
|
|
raise HTTPException(status_code=500, detail="Converted file was not created properly")
|
|
|
|
out_fname = os.path.splitext(original_name)[0] + '.docx'
|
|
# print("Returning converted file", out_file, "as", out_fname)
|
|
|
|
# ========== 修改点 4: 主要修改 - 使用 BackgroundTask 延迟清理 ==========
|
|
return FileResponse(
|
|
out_file,
|
|
media_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
filename=out_fname,
|
|
background=BackgroundTask(lambda: cleanup_temp_dir(tmpdir)) # 使用 BackgroundTask
|
|
)
|
|
|
|
# ========== 修改点 5: 移除 finally 块中的立即清理,改为只在异常时清理 ==========
|
|
except Exception as e:
|
|
# 只有在发生异常时才立即清理
|
|
cleanup_temp_dir(tmpdir)
|
|
# 重新抛出异常
|
|
if isinstance(e, HTTPException):
|
|
raise e
|
|
else:
|
|
raise HTTPException(status_code=500, detail=f"Conversion error: {str(e)}")
|
|
|
|
# ========== 修改点 6: 添加清理函数 ==========
|
|
def cleanup_temp_dir(tmpdir: str):
|
|
"""清理临时目录,忽略错误"""
|
|
try:
|
|
if tmpdir and os.path.exists(tmpdir):
|
|
shutil.rmtree(tmpdir, ignore_errors=True)
|
|
print(f"Cleaned up temp dir: {tmpdir}")
|
|
except Exception as e:
|
|
print(f"Warning: Failed to cleanup temp dir {tmpdir}: {e}")
|
|
|
|
if __name__ == '__main__':
|
|
import uvicorn
|
|
port = int(os.environ.get('SOFFICE_PORT') or 8003)
|
|
uvicorn.run('app:app', host='0.0.0.0', port=port)
|