重构: 切换存储至SQLite,启用INI配置与API Key校验
This commit is contained in:
@ -7,6 +7,39 @@ from pathlib import Path
|
||||
from flask import Blueprint, send_file, current_app
|
||||
|
||||
from ..shared import _format_response, log_performance, logger
|
||||
from ..auth import require_api_key
|
||||
|
||||
|
||||
def _mark_task_downloaded(task_id):
|
||||
"""Mark task as downloaded and schedule deletion in database."""
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
# Use independent database connection (not from flask.g which may be closed)
|
||||
from ..db import get_db_path as get_config_db_path
|
||||
db_path = get_config_db_path(current_app)
|
||||
|
||||
try:
|
||||
conn = sqlite3.connect(str(db_path), check_same_thread=False)
|
||||
conn.execute("PRAGMA foreign_keys=ON")
|
||||
conn.execute("PRAGMA busy_timeout=3000")
|
||||
|
||||
# Update downloaded timestamp and set deletion time (10 minutes later)
|
||||
conn.execute("""
|
||||
UPDATE tasks
|
||||
SET downloaded_at = datetime('now', '+8 hours'),
|
||||
delete_after_at = datetime('now', '+8 hours', '+10 minutes')
|
||||
WHERE task_id = ?
|
||||
""", (task_id,))
|
||||
|
||||
conn.commit()
|
||||
logger.info(f"Task {task_id} marked as downloaded, scheduled for deletion in 10 minutes")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to mark task {task_id} as downloaded: {str(e)}", exc_info=True)
|
||||
finally:
|
||||
if 'conn' in locals():
|
||||
conn.close()
|
||||
|
||||
# Create blueprint
|
||||
download_bp = Blueprint('download', __name__, url_prefix='/download')
|
||||
@ -20,6 +53,32 @@ def download_file(filename):
|
||||
|
||||
logger.info(f"Download request for file: {filename} from IP {request.remote_addr}")
|
||||
|
||||
# Check API key from header or query parameter
|
||||
provided_key = request.headers.get('X-API-Key') or request.args.get('api_key')
|
||||
if not provided_key:
|
||||
logger.warning(f"API key missing from request: POST /download/{filename}")
|
||||
return _format_response(401, "API key required")
|
||||
|
||||
# Validate API key
|
||||
from ..auth import verify_api_key, get_db
|
||||
db = get_db()
|
||||
row = db.execute(
|
||||
"SELECT key_hash, salt, revoked FROM api_keys WHERE key_id = ?",
|
||||
(provided_key,)
|
||||
).fetchone()
|
||||
|
||||
if not row:
|
||||
logger.warning(f"Unknown API key used: POST /download/{filename}")
|
||||
return _format_response(401, "Invalid API key")
|
||||
|
||||
if row['revoked']:
|
||||
logger.warning(f"Revoked API key used: POST /download/{filename}")
|
||||
return _format_response(401, "API key revoked")
|
||||
|
||||
if not verify_api_key(provided_key, row['key_hash'], row['salt']):
|
||||
logger.warning(f"Invalid API key hash: POST /download/{filename}")
|
||||
return _format_response(401, "Invalid API key")
|
||||
|
||||
try:
|
||||
# 支持两种路径格式:
|
||||
# 1. 绝对路径(以 / 开头,如 /full/path/to/file)
|
||||
@ -27,6 +86,7 @@ def download_file(filename):
|
||||
if filename.startswith('/'):
|
||||
# 绝对路径 - 直接使用
|
||||
file_path = Path(filename)
|
||||
task_id = None # Can't determine task_id from absolute path
|
||||
else:
|
||||
# 相对路径 - 相对于 OUTPUT_FOLDER
|
||||
output_folder = Path(current_app.config.get('OUTPUT_FOLDER') or '')
|
||||
@ -61,7 +121,15 @@ def download_file(filename):
|
||||
file_size = file_path.stat().st_size
|
||||
logger.info(f"Serving file: {filename} ({file_size} bytes)")
|
||||
|
||||
return send_file(file_path)
|
||||
# Mark download immediately before sending file
|
||||
if task_id:
|
||||
try:
|
||||
_mark_task_downloaded(task_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to mark download for task {task_id}: {str(e)}")
|
||||
|
||||
response = send_file(file_path)
|
||||
return response
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error serving file {filename}: {str(e)}", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user