From 772748eaae0af3c0921fadc3fabc63c04addfd2e Mon Sep 17 00:00:00 2001 From: superlishunqin <852326703@qq.com> Date: Sat, 17 May 2025 00:11:20 +0800 Subject: [PATCH] =?UTF-8?q?fix=5Fbug=5Flog-export=5F=E6=A0=BC=E5=BC=8F?= =?UTF-8?q?=E5=92=8C=E4=B9=B1=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/log.py | 116 +++++++++++++++++++++++++++++++++++------ requirements.txt | 3 +- 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/app/controllers/log.py b/app/controllers/log.py index 399c722..287ffd4 100644 --- a/app/controllers/log.py +++ b/app/controllers/log.py @@ -93,16 +93,13 @@ def log_detail(log_id): @permission_required('view_logs') # 替代 @admin_required def export_logs(): """导出日志API""" - import csv - from io import StringIO - from flask import Response - data = request.get_json() user_id = data.get('user_id') action = data.get('action') target_type = data.get('target_type') start_date_str = data.get('start_date') end_date_str = data.get('end_date') + export_format = data.get('format', 'csv') # 获取导出格式参数 # 处理日期范围 start_date = None @@ -129,9 +126,35 @@ def export_logs(): logs = query.all() - # 生成CSV文件 - si = StringIO() - csv_writer = csv.writer(si) + try: + # 根据格式选择导出方法 + if export_format == 'xlsx': + return export_as_xlsx(logs) + else: + return export_as_csv(logs) + except Exception as e: + # 记录错误以便调试 + import traceback + error_details = traceback.format_exc() + current_app.logger.error(f"Export error: {str(e)}\n{error_details}") + + return jsonify({ + 'success': False, + 'message': f'导出失败: {str(e)}' + }), 500 + + +def export_as_csv(logs): + """导出为CSV格式""" + import csv + from io import StringIO + import base64 + + # 创建CSV文件 + output = StringIO() + output.write('\ufeff') # 添加BOM标记,解决Excel中文乱码 + + csv_writer = csv.writer(output) # 写入标题行 csv_writer.writerow(['ID', '用户', '操作类型', '目标类型', '目标ID', 'IP地址', '描述', '创建时间']) @@ -150,25 +173,88 @@ def export_logs(): log.created_at.strftime('%Y-%m-%d %H:%M:%S') ]) - # 设置响应头,使浏览器将其识别为下载文件 + # 获取CSV字符串并进行Base64编码 + csv_string = output.getvalue() + csv_bytes = csv_string.encode('utf-8') + b64_encoded = base64.b64encode(csv_bytes).decode('utf-8') + + # 设置文件名 filename = f"system_logs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" - output = si.getvalue() - - # 返回Base64编码的CSV数据 - import base64 - encoded_data = base64.b64encode(output.encode('utf-8')).decode('utf-8') - return jsonify({ 'success': True, 'message': f'已生成 {len(logs)} 条日志记录', 'count': len(logs), 'filename': filename, - 'filedata': encoded_data, + 'filedata': b64_encoded, 'filetype': 'text/csv' }) +def export_as_xlsx(logs): + """导出为XLSX格式""" + import base64 + from io import BytesIO + + try: + # 动态导入openpyxl,如果不存在则抛出异常 + import openpyxl + except ImportError: + raise Exception("未安装openpyxl库,无法导出Excel格式。请安装后重试: pip install openpyxl") + + # 创建工作簿和工作表 + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "系统日志" + + # 写入标题行 + headers = ['ID', '用户', '操作类型', '目标类型', '目标ID', 'IP地址', '描述', '创建时间'] + for col_idx, header in enumerate(headers, 1): + ws.cell(row=1, column=col_idx, value=header) + + # 写入数据行 + for row_idx, log in enumerate(logs, 2): + username = log.user.username if log.user else "未登录" + + ws.cell(row=row_idx, column=1, value=log.id) + ws.cell(row=row_idx, column=2, value=username) + ws.cell(row=row_idx, column=3, value=log.action) + ws.cell(row=row_idx, column=4, value=log.target_type or '') + ws.cell(row=row_idx, column=5, value=log.target_id or '') + ws.cell(row=row_idx, column=6, value=log.ip_address or '') + ws.cell(row=row_idx, column=7, value=log.description or '') + ws.cell(row=row_idx, column=8, value=log.created_at.strftime('%Y-%m-%d %H:%M:%S')) + + # 调整列宽 + for col_idx, header in enumerate(headers, 1): + column_letter = openpyxl.utils.get_column_letter(col_idx) + if header == '描述': + ws.column_dimensions[column_letter].width = 40 + else: + ws.column_dimensions[column_letter].width = 15 + + # 保存到内存 + output = BytesIO() + wb.save(output) + output.seek(0) + + # 编码为Base64 + xlsx_data = output.getvalue() + b64_encoded = base64.b64encode(xlsx_data).decode('utf-8') + + # 设置文件名 + filename = f"system_logs_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" + + return jsonify({ + 'success': True, + 'message': f'已生成 {len(logs)} 条日志记录', + 'count': len(logs), + 'filename': filename, + 'filedata': b64_encoded, + 'filetype': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + }) + + @log_bp.route('/api/clear', methods=['POST']) @login_required @permission_required('view_logs') # 替代 @admin_required diff --git a/requirements.txt b/requirements.txt index 75764d7..b7fa6c2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,5 @@ pandas flask-login openpyxl xlrd -xlsxwriter \ No newline at end of file +xlsxwriter +openpyxl \ No newline at end of file