更改总计逻辑
This commit is contained in:
@@ -2,39 +2,203 @@
|
||||
文件下载 API
|
||||
"""
|
||||
import os
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from flask import request, jsonify, send_from_directory
|
||||
from lib.db import execute_query
|
||||
from lib.logger import log_info, log_error
|
||||
from . import download_bp
|
||||
|
||||
|
||||
def get_reports_dir():
|
||||
"""获取报表文件目录"""
|
||||
return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'public', 'reports')
|
||||
|
||||
|
||||
def get_temp_dir():
|
||||
"""获取临时zip文件目录"""
|
||||
temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'public', 'temp_zip')
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
return temp_dir
|
||||
|
||||
|
||||
def cleanup_old_temp_zips():
|
||||
"""清理1小时前的临时zip文件"""
|
||||
try:
|
||||
temp_dir = get_temp_dir()
|
||||
now = datetime.now().timestamp()
|
||||
count = 0
|
||||
for filename in os.listdir(temp_dir):
|
||||
if filename.endswith('.zip'):
|
||||
filepath = os.path.join(temp_dir, filename)
|
||||
if now - os.path.getmtime(filepath) > 3600: # 1小时前的
|
||||
os.remove(filepath)
|
||||
count += 1
|
||||
if count > 0:
|
||||
log_info(f'[下载API] 清理了 {count} 个过期临时zip文件', 'download')
|
||||
except Exception as e:
|
||||
log_error(f'[下载API] 清理临时文件失败: {e}', 'download')
|
||||
|
||||
|
||||
@download_bp.route('/download', methods=['GET'])
|
||||
def download_file():
|
||||
"""下载文件"""
|
||||
"""下载文件
|
||||
支持两种方式:
|
||||
1. path: 直接传文件路径
|
||||
2. id: 传历史记录ID,从数据库查询文件路径
|
||||
"""
|
||||
try:
|
||||
file_path = request.args.get('path')
|
||||
print(f"[下载] 请求路径:{file_path}")
|
||||
history_id = request.args.get('id')
|
||||
|
||||
log_info(f'[下载API] 请求,path={file_path}, id={history_id}', 'download')
|
||||
|
||||
# 如果传了id,从历史记录表查询文件路径
|
||||
if history_id:
|
||||
result = execute_query(
|
||||
'SELECT file_path, status FROM t_daily_report_history WHERE id = %s LIMIT 1',
|
||||
(history_id,)
|
||||
)
|
||||
if not result:
|
||||
log_error(f'[下载API] 历史记录不存在,id={history_id}', 'download')
|
||||
return jsonify({'success': False, 'message': '历史记录不存在'}), 404
|
||||
|
||||
history = result[0]
|
||||
if history.get('status') != 1:
|
||||
log_error(f'[下载API] 生成失败的记录无法下载,id={history_id}', 'download')
|
||||
return jsonify({'success': False, 'message': '该记录生成失败,无法下载'}), 400
|
||||
|
||||
file_path = history.get('file_path')
|
||||
if not file_path:
|
||||
log_error(f'[下载API] 文件路径为空,id={history_id}', 'download')
|
||||
return jsonify({'success': False, 'message': '文件路径不能为空'}), 400
|
||||
|
||||
log_info(f'[下载API] 从历史记录查询到文件路径: {file_path}', 'download')
|
||||
|
||||
if not file_path:
|
||||
return jsonify({'success': False, 'message': '文件路径不能为空'}), 400
|
||||
|
||||
# 安全检查:确保路径在 public 目录下
|
||||
if not file_path.startswith('/reports/'):
|
||||
# 安全检查:确保路径在 public/reports 目录下
|
||||
if not file_path.startswith('/reports/') and not file_path.startswith('/public/reports/'):
|
||||
return jsonify({'success': False, 'message': '无效的文件路径'}), 400
|
||||
|
||||
filename = file_path.replace('/reports/', '')
|
||||
# 使用绝对路径
|
||||
reports_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', 'public', 'reports')
|
||||
# 提取文件名
|
||||
if file_path.startswith('/public/reports/'):
|
||||
filename = file_path.replace('/public/reports/', '')
|
||||
else:
|
||||
filename = file_path.replace('/reports/', '')
|
||||
|
||||
print(f"[下载] 文件目录:{reports_dir}")
|
||||
print(f"[下载] 文件名:{filename}")
|
||||
print(f"[下载] 文件是否存在:{os.path.exists(os.path.join(reports_dir, filename))}")
|
||||
|
||||
# 列出目录中的所有文件
|
||||
if os.path.exists(reports_dir):
|
||||
print(f"[下载] 目录中的文件:{os.listdir(reports_dir)}")
|
||||
reports_dir = get_reports_dir()
|
||||
full_path = os.path.join(reports_dir, filename)
|
||||
if not os.path.exists(full_path):
|
||||
log_error(f'[下载API] 文件不存在: {full_path}', 'download')
|
||||
return jsonify({'success': False, 'message': '文件不存在'}), 404
|
||||
|
||||
log_info(f'[下载API] 下载文件: {filename}', 'download')
|
||||
return send_from_directory(reports_dir, filename, as_attachment=True)
|
||||
except Exception as e:
|
||||
print(f"[下载] 错误:{str(e)}")
|
||||
log_error(f'[下载API] 错误: {e}', 'download')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
|
||||
@download_bp.route('/download/batch', methods=['POST'])
|
||||
def batch_download():
|
||||
"""批量打包下载
|
||||
接收 ids 数组,将对应的报表文件打包成zip后下载
|
||||
"""
|
||||
try:
|
||||
data = request.get_json(silent=True) or {}
|
||||
ids = data.get('ids', [])
|
||||
|
||||
log_info(f'[下载API] 批量下载,数量: {len(ids)}', 'download')
|
||||
|
||||
if not ids or len(ids) == 0:
|
||||
return jsonify({'success': False, 'message': '请选择要下载的记录'}), 400
|
||||
|
||||
# 清理旧的临时文件
|
||||
cleanup_old_temp_zips()
|
||||
|
||||
# 查询所有成功的历史记录
|
||||
placeholders = ', '.join(['%s'] * len(ids))
|
||||
result = execute_query(
|
||||
f'SELECT id, file_path, config_name, report_date, status FROM t_daily_report_history WHERE id IN ({placeholders})',
|
||||
tuple(ids)
|
||||
)
|
||||
|
||||
if not result:
|
||||
return jsonify({'success': False, 'message': '未找到对应的历史记录'}), 404
|
||||
|
||||
reports_dir = get_reports_dir()
|
||||
temp_dir = get_temp_dir()
|
||||
|
||||
# 收集要打包的文件
|
||||
files_to_zip = []
|
||||
failed_names = []
|
||||
|
||||
for item in result:
|
||||
if item.get('status') != 1:
|
||||
failed_names.append(f"{item.get('config_name', '未知')} (生成失败)")
|
||||
continue
|
||||
|
||||
file_path = item.get('file_path')
|
||||
if not file_path:
|
||||
failed_names.append(f"{item.get('config_name', '未知')} (无文件)")
|
||||
continue
|
||||
|
||||
# 提取文件名
|
||||
if file_path.startswith('/public/reports/'):
|
||||
filename = file_path.replace('/public/reports/', '')
|
||||
elif file_path.startswith('/reports/'):
|
||||
filename = file_path.replace('/reports/', '')
|
||||
else:
|
||||
filename = os.path.basename(file_path)
|
||||
|
||||
full_path = os.path.join(reports_dir, filename)
|
||||
if not os.path.exists(full_path):
|
||||
failed_names.append(f"{item.get('config_name', '未知')} (文件不存在)")
|
||||
continue
|
||||
|
||||
# 使用配置名作为zip内的文件名,避免重名
|
||||
config_name = item.get('config_name', '未知')
|
||||
report_date = item.get('report_date', '')
|
||||
ext = os.path.splitext(filename)[1]
|
||||
if report_date:
|
||||
zip_inner_name = f"{config_name}_{report_date}{ext}"
|
||||
else:
|
||||
zip_inner_name = f"{config_name}{ext}"
|
||||
|
||||
files_to_zip.append({
|
||||
'full_path': full_path,
|
||||
'inner_name': zip_inner_name
|
||||
})
|
||||
|
||||
if len(files_to_zip) == 0:
|
||||
return jsonify({'success': False, 'message': '没有可下载的文件'}), 400
|
||||
|
||||
# 生成zip文件名
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
zip_filename = f'批量下载_{timestamp}.zip'
|
||||
zip_path = os.path.join(temp_dir, zip_filename)
|
||||
|
||||
# 打包
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for file_info in files_to_zip:
|
||||
zf.write(file_info['full_path'], file_info['inner_name'])
|
||||
|
||||
log_info(f'[下载API] 批量打包完成,共 {len(files_to_zip)} 个文件,失败 {len(failed_names)} 个', 'download')
|
||||
|
||||
# 返回zip文件
|
||||
from flask import send_file
|
||||
return send_file(
|
||||
zip_path,
|
||||
as_attachment=True,
|
||||
download_name=zip_filename,
|
||||
mimetype='application/zip'
|
||||
)
|
||||
except Exception as e:
|
||||
log_error(f'[下载API] 批量下载错误: {e}', 'download')
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({'success': False, 'message': str(e)}), 500
|
||||
|
||||
@@ -20,7 +20,7 @@ from . import sum_data_bp
|
||||
def collect_sum_data():
|
||||
"""从历史记录采集求和数据"""
|
||||
try:
|
||||
data = request.json or {}
|
||||
data = request.get_json(silent=True) or {}
|
||||
history_id = data.get('history_id')
|
||||
log_info(f'[求和数据API] 采集求和数据,历史记录ID: {history_id or "全部"}', 'api')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user