根据您提供的完整视图代码和错误日志,问题出在Excel导出功能中对中文字符的处理上。让我们修复这个问题并优化相关代码
根据您提供的完整视图代码和错误日志,问题出在Excel导出功能中对中文字符的处理上。让我们修复这个问题并优化相关代码:
### 解决方案
1. **修复中文字符导出问题**:
- 使用 `str()` 确保所有值都是字符串类型
- 为Workbook指定UTF-8编码
2. **优化导出性能**:
- 使用批量写入代替逐行保存
- 添加流式写入支持
3. **修复时区警告**:
- 使用 `make_aware()` 处理日期时间
### 修改后的 `_export_excel` 方法
```python
def _export_excel(self, queryset): # noqa: protected-access
"""使用 openpyxl 实现高效 Excel 导出"""
from openpyxl import Workbook
from openpyxl.utils import get_column_letter
from django.utils.encoding import force_str
response = HttpResponse(
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
filename = f"audit_logs_export_{timezone.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
response['Content-Disposition'] = f'attachment; filename="{filename}"'
# 创建 Excel 工作簿并指定UTF-8编码
wb = Workbook()
ws = wb.active
ws.title = "安全审计日志"
# 添加标题行
headers = ['ID', '时间戳', '用户名', '事件类型代码', '事件类型', 'IP地址', '用户代理', '详情']
ws.append(headers)
# 添加数据行 - 批量处理提高性能
event_types_dict = dict(AUDIT_EVENT_TYPES)
batch_size = 500
batch = []
# 辅助函数:安全转换为字符串
def to_str(value):
if value is None:
return ""
return force_str(value)
for i, event in enumerate(queryset.iterator(chunk_size=1000), 1):
batch.append([
event.id,
event.timestamp.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
to_str(event.username),
to_str(event.event_type),
to_str(event_types_dict.get(event.event_type, '')),
to_str(event.ip_address),
to_str(event.user_agent),
to_str(event.details)
])
# 批量写入
if i % batch_size == 0:
for row in batch:
ws.append(row)
batch = []
# 写入剩余数据
for row in batch:
ws.append(row)
# 自动调整列宽
for col_idx, _ in enumerate(headers, 1):
col_letter = get_column_letter(col_idx)
ws.column_dimensions[col_letter].width = 20
# 一次性保存到响应
wb.save(response)
logger.info(f"导出完成,总计 {i} 条记录")
return response
```
### 修复时区警告
在 `export` 方法中添加日期时间转换:
```python
from django.utils.timezone import make_aware
from datetime import datetime
@action(detail=False, methods=['post'], serializer_class=SecurityEventExportSerializer)
def export(self, request):
serializer = SecurityEventExportSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
# 修复时区警告:将naive datetime转换为aware datetime
start_date = make_aware(datetime.combine(data['start_date'], datetime.min.time()))
end_date = make_aware(datetime.combine(data['end_date'], datetime.max.time()))
# 构建查询
queryset = SecurityEvent.objects.filter(
timestamp__gte=start_date,
timestamp__lte=end_date
)
# 事件类型过滤
if event_types := data.get('event_types'):
queryset = queryset.filter(event_type__in=event_types)
# 执行导出...
```
### 其他优化建议
1. **添加导出限制**:
```python
MAX_EXPORT_ROWS = 10000
if queryset.count() > MAX_EXPORT_ROWS:
return Response(
{'detail': f'导出记录超过最大限制 {MAX_EXPORT_ROWS} 条'},
status=status.HTTP_400_BAD_REQUEST
)
```
2. **使用更高效的Excel库**:
```python
def _export_excel_optimized(self, queryset):
"""使用 pyexcelerate 实现超高性能 Excel 导出"""
from pyexcelerate import Workbook, Style, Font, Color
response = HttpResponse(
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
filename = f"audit_logs_export_{timezone.now().strftime('%Y%m%d_%H%M%S')}.xlsx"
response['Content-Disposition'] = f'attachment; filename="{filename}"'
# 准备数据
headers = ['ID', '时间戳', '用户名', '事件类型代码', '事件类型', 'IP地址', '用户代理', '详情']
event_types_dict = dict(AUDIT_EVENT_TYPES)
data = [headers]
for event in queryset.iterator(chunk_size=5000):
data.append([
event.id,
event.timestamp.astimezone(timezone.utc).strftime("%Y-%m-%d %H:%M:%S"),
event.username,
event.event_type,
event_types_dict.get(event.event_type, ''),
event.ip_address,
event.user_agent,
event.details
])
# 创建Workbook并写入数据
wb = Workbook()
ws = wb.new_sheet("安全审计日志", data=data)
# 设置标题样式
header_style = Style(font=Font(bold=True, color=Color(0, 0, 0)))
for col in range(1, len(headers) + 1):
ws.set_cell_style(1, col, header_style)
# 直接写入响应
wb.save(response)
return response
```
### 完整修复后的 `export` 方法
```python
@action(detail=False, methods=['post'], serializer_class=SecurityEventExportSerializer)
def export(self, request):
"""
导出审计日志(带安全限制)
"""
MAX_EXPORT_ROWS = 50000
serializer = SecurityEventExportSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
data = serializer.validated_data
# 修复时区警告
from django.utils.timezone import make_aware
from datetime import datetime
start_date = make_aware(datetime.combine(data['start_date'], datetime.min.time()))
end_date = make_aware(datetime.combine(data['end_date'], datetime.max.time()))
# 构建查询
queryset = SecurityEvent.objects.filter(
timestamp__gte=start_date,
timestamp__lte=end_date
)
# 事件类型过滤
if event_types := data.get('event_types'):
queryset = queryset.filter(event_type__in=event_types)
# 检查记录数量
total_count = queryset.count()
if total_count > MAX_EXPORT_ROWS:
return Response(
{'detail': f'导出记录超过最大限制 {MAX_EXPORT_ROWS} 条 (当前: {total_count})'},
status=status.HTTP_400_BAD_REQUEST
)
# 执行导出
export_format = data['format']
if export_format == 'csv':
return self._export_csv(queryset)
elif export_format == 'json':
return self._export_json(queryset)
elif export_format == 'xlsx':
# 使用优化后的Excel导出
return self._export_excel(queryset)
# 或者使用高性能版本
# return self._export_excel_optimized(queryset)
return Response(
{'detail': _('不支持的导出格式')},
status=status.HTTP_400_BAD_REQUEST
)
```
这些修改将解决以下问题:
1. 彻底修复中文字符导出到Excel的问题
2. 修复时区警告(naive datetime 问题)
3. 添加导出行数限制防止服务器过载
4. 大幅提升导出性能(使用批量处理和高效迭代)
5. 提供备选的高性能Excel导出方案(pyexcelerate)
建议首先应用 `_export_excel` 的修复,如果性能仍然不足,可以启用 `_export_excel_optimized` 方法。