This commit is contained in:
beabigegg
2025-09-02 13:11:48 +08:00
parent a60d965317
commit b11a8272c4
76 changed files with 15321 additions and 200 deletions

View File

@@ -9,6 +9,8 @@ Modified: 2024-01-28
"""
import json
import zipfile
import tempfile
from pathlib import Path
from flask import Blueprint, request, jsonify, send_file, current_app, g
from werkzeug.utils import secure_filename
@@ -122,9 +124,36 @@ def upload_file():
logger.info(f"File uploaded successfully: {job.job_uuid} - {file_info['filename']}")
# 觸發翻譯任務(這裡會在實作 Celery 時加入)
# from app.tasks.translation import process_translation_job
# process_translation_job.delay(job.id)
# 觸發翻譯任務
try:
from app.tasks.translation import process_translation_job
# 嘗試使用 Celery 異步處理
try:
task = process_translation_job.delay(job.id)
logger.info(f"Translation task queued with Celery: {task.id} for job {job.job_uuid}")
except Exception as celery_error:
logger.warning(f"Celery not available, falling back to synchronous processing: {str(celery_error)}")
# Celery 不可用時,使用同步處理
try:
from app.services.translation_service import TranslationService
service = TranslationService()
# 在後台執行翻譯(同步處理)
logger.info(f"Starting synchronous translation for job {job.job_uuid}")
result = service.translate_document(job.job_uuid)
logger.info(f"Synchronous translation completed for job {job.job_uuid}: {result}")
except Exception as sync_error:
logger.error(f"Synchronous translation failed for job {job.job_uuid}: {str(sync_error)}")
job.update_status('FAILED', error_message=f"翻譯處理失敗: {str(sync_error)}")
db.session.commit()
except Exception as e:
logger.error(f"Failed to process translation for job {job.job_uuid}: {str(e)}")
job.update_status('FAILED', error_message=f"任務處理失敗: {str(e)}")
db.session.commit()
return jsonify(create_response(
success=True,
@@ -440,4 +469,141 @@ def get_supported_languages():
success=False,
error='SYSTEM_ERROR',
message='取得支援語言失敗'
)), 500
@files_bp.route('/<job_uuid>/download/batch', methods=['GET'])
@jwt_login_required
def download_batch_files(job_uuid):
"""批量下載所有翻譯檔案為 ZIP"""
try:
# 驗證 UUID 格式
validate_job_uuid(job_uuid)
# 取得任務
job = TranslationJob.query.filter_by(job_uuid=job_uuid).first()
if not job:
return jsonify(create_response(
success=False,
error='JOB_NOT_FOUND',
message='任務不存在'
)), 404
# 檢查權限
if job.user_id != g.current_user_id and not g.is_admin:
return jsonify(create_response(
success=False,
error='PERMISSION_DENIED',
message='無權限存取此檔案'
)), 403
# 檢查任務狀態
if job.status != 'COMPLETED':
return jsonify(create_response(
success=False,
error='JOB_NOT_COMPLETED',
message='任務尚未完成'
)), 400
# 收集所有翻譯檔案
translated_files = job.get_translated_files()
if not translated_files:
return jsonify(create_response(
success=False,
error='NO_TRANSLATED_FILES',
message='沒有找到翻譯檔案'
)), 404
# 建立臨時 ZIP 檔案
temp_dir = tempfile.gettempdir()
zip_filename = f"{job.original_filename.split('.')[0]}_translations_{job.job_uuid[:8]}.zip"
zip_path = Path(temp_dir) / zip_filename
try:
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zip_file:
files_added = 0
# 添加原始檔案
original_file = job.get_original_file()
if original_file and Path(original_file.file_path).exists():
zip_file.write(
original_file.file_path,
f"original/{original_file.filename}"
)
files_added += 1
# 添加所有翻譯檔案(避免重複)
added_files = set() # 追蹤已添加的檔案,避免重複
for tf in translated_files:
file_path = Path(tf.file_path)
if file_path.exists():
# 按語言建立資料夾結構
archive_name = f"{tf.language_code}/{tf.filename}"
# 檢查是否已經添加過這個檔案
if archive_name not in added_files:
zip_file.write(str(file_path), archive_name)
added_files.add(archive_name)
files_added += 1
else:
logger.warning(f"Translation file not found: {tf.file_path}")
if files_added == 0:
return jsonify(create_response(
success=False,
error='NO_FILES_TO_ZIP',
message='沒有可用的檔案進行壓縮'
)), 404
# 檢查 ZIP 檔案是否建立成功
if not zip_path.exists():
return jsonify(create_response(
success=False,
error='ZIP_CREATION_FAILED',
message='ZIP 檔案建立失敗'
)), 500
# 記錄下載日誌
SystemLog.info(
'files.download_batch',
f'Batch files downloaded: {zip_filename}',
user_id=g.current_user_id,
job_id=job.id,
extra_data={
'zip_filename': zip_filename,
'files_count': files_added,
'job_uuid': job_uuid
}
)
logger.info(f"Batch files downloaded: {job.job_uuid} - {files_added} files in ZIP")
# 發送 ZIP 檔案
return send_file(
str(zip_path),
as_attachment=True,
download_name=zip_filename,
mimetype='application/zip'
)
finally:
# 清理臨時檔案(在發送後會自動清理)
pass
except ValidationError as e:
return jsonify(create_response(
success=False,
error=e.error_code,
message=str(e)
)), 400
except Exception as e:
logger.error(f"Batch download error: {str(e)}")
return jsonify(create_response(
success=False,
error='SYSTEM_ERROR',
message='批量下載失敗'
)), 500

View File

@@ -440,4 +440,95 @@ def cancel_job(job_uuid):
success=False,
error='SYSTEM_ERROR',
message='取消任務失敗'
)), 500
@jobs_bp.route('/<job_uuid>', methods=['DELETE'])
@jwt_login_required
def delete_job(job_uuid):
"""刪除任務"""
try:
# 驗證 UUID 格式
validate_job_uuid(job_uuid)
# 取得任務
job = TranslationJob.query.filter_by(job_uuid=job_uuid).first()
if not job:
return jsonify(create_response(
success=False,
error='JOB_NOT_FOUND',
message='任務不存在'
)), 404
# 檢查權限
if job.user_id != g.current_user_id and not g.is_admin:
return jsonify(create_response(
success=False,
error='PERMISSION_DENIED',
message='無權限操作此任務'
)), 403
# 檢查任務狀態 - 不能刪除正在處理中的任務
if job.status == 'PROCESSING':
return jsonify(create_response(
success=False,
error='CANNOT_DELETE',
message='無法刪除正在處理中的任務'
)), 400
# 刪除任務相關檔案
import os
import shutil
from pathlib import Path
try:
if job.file_path and os.path.exists(job.file_path):
# 取得任務目錄(通常是 uploads/job_uuid
job_dir = Path(job.file_path).parent
if job_dir.exists() and job_dir.name == job.job_uuid:
shutil.rmtree(job_dir)
logger.info(f"Deleted job directory: {job_dir}")
except Exception as file_error:
logger.warning(f"Failed to delete job files: {str(file_error)}")
# 記錄刪除日誌
SystemLog.info(
'jobs.delete',
f'Job deleted by user: {job_uuid}',
user_id=g.current_user_id,
job_id=job.id,
extra_data={
'filename': job.original_filename,
'status': job.status
}
)
from app import db
# 刪除資料庫記錄
db.session.delete(job)
db.session.commit()
logger.info(f"Job deleted by user: {job_uuid}")
return jsonify(create_response(
success=True,
message='任務已刪除'
))
except ValidationError as e:
return jsonify(create_response(
success=False,
error=e.error_code,
message=str(e)
)), 400
except Exception as e:
logger.error(f"Delete job error: {str(e)}")
return jsonify(create_response(
success=False,
error='SYSTEM_ERROR',
message='刪除任務失敗'
)), 500