222 lines
8.2 KiB
Markdown
222 lines
8.2 KiB
Markdown
# 学生信息导入模板下载功能修复记录(第二版)
|
|
|
|
**日期**: 2025-07-31
|
|
**修改人**: AI Assistant
|
|
**修改原因**: 进一步优化下载模板功能,解决"Broken pipe"错误和用户体验问题
|
|
|
|
## 问题描述
|
|
|
|
在学生信息管理页面中,点击"下载模板"按钮时出现以下错误:
|
|
|
|
```
|
|
org.springframework.web.context.request.async.AsyncRequestNotUsableException: ServletOutputStream failed to write: Broken pipe
|
|
```
|
|
|
|
### 错误分析
|
|
|
|
1. **文件较大**: 模板文件504KB,传输时间较长
|
|
2. **客户端断开连接**: 用户在下载过程中可能关闭了浏览器或网络中断
|
|
3. **服务器继续写入**: 服务器仍在尝试向已断开的连接写入数据
|
|
4. **前端缺乏错误处理**: 没有适当的用户反馈和错误处理机制
|
|
|
|
## 解决方案
|
|
|
|
### 1. 前端优化 (index.vue)
|
|
|
|
#### 主要改进:
|
|
- **下载进度提示**: 显示"正在下载模板,请稍候..."的提示信息
|
|
- **超时处理**: 添加60秒下载超时机制
|
|
- **错误分类处理**: 根据不同的错误类型显示相应的错误信息
|
|
- **用户体验优化**: 使用Blob和createObjectURL进行文件下载
|
|
- **资源清理**: 确保超时定时器和URL对象正确清理
|
|
|
|
#### 关键代码改进:
|
|
```javascript
|
|
/** 下载模板操作 */
|
|
const importTemplate = () => {
|
|
// 显示下载进度提示
|
|
const loadingMessage = ElMessage({
|
|
message: '正在下载模板,请稍候...',
|
|
type: 'info',
|
|
duration: 0,
|
|
zIndex: 4000
|
|
});
|
|
|
|
// 设置下载超时时间(60秒)
|
|
const downloadTimeout = setTimeout(() => {
|
|
ElMessage.closeAll();
|
|
ElMessage.error('下载超时,请稍后重试');
|
|
}, 60000);
|
|
|
|
// 使用API方法下载模板
|
|
downloadImportTemplate()
|
|
.then((response) => {
|
|
// 清除超时定时器
|
|
clearTimeout(downloadTimeout);
|
|
|
|
// 关闭加载提示
|
|
ElMessage.closeAll();
|
|
|
|
// 创建下载链接
|
|
const blob = new Blob([response.data], {
|
|
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
|
});
|
|
const url = window.URL.createObjectURL(blob);
|
|
const link = document.createElement('a');
|
|
link.href = url;
|
|
link.download = `学生信息导入模板_${new Date().getTime()}.xlsx`;
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
window.URL.revokeObjectURL(url);
|
|
|
|
ElMessage.success('模板下载成功');
|
|
})
|
|
.catch((error) => {
|
|
// 清除超时定时器
|
|
clearTimeout(downloadTimeout);
|
|
|
|
// 关闭加载提示
|
|
ElMessage.closeAll();
|
|
|
|
// 根据错误类型显示不同的错误信息
|
|
if (error.code === 'ECONNABORTED' || error.message?.includes('timeout')) {
|
|
ElMessage.error('下载超时,请稍后重试');
|
|
} else if (error.response?.status === 404) {
|
|
ElMessage.error('模板文件不存在,请联系管理员');
|
|
} else if (error.response?.status === 500) {
|
|
ElMessage.error('服务器内部错误,请稍后重试');
|
|
} else if (error.message?.includes('Network Error')) {
|
|
ElMessage.error('网络连接错误,请检查网络后重试');
|
|
} else {
|
|
ElMessage.error('下载失败,请稍后重试');
|
|
}
|
|
|
|
console.error('下载模板失败:', error);
|
|
});
|
|
};
|
|
```
|
|
|
|
### 2. 后端优化 (XgStudentMsgController.java)
|
|
|
|
#### 主要改进:
|
|
- **文件大小获取**: 获取并记录文件大小,便于调试
|
|
- **Content-Length头**: 添加Content-Length响应头,帮助客户端了解下载进度
|
|
- **Connection头**: 添加Connection: close头,确保连接正确关闭
|
|
- **缓冲区优化**: 使用8KB缓冲区,平衡性能和内存使用
|
|
- **刷新策略优化**: 每32KB或每500ms刷新一次,提高传输效率
|
|
- **异常处理增强**: 增加Connection timed out异常处理
|
|
|
|
#### 关键代码改进:
|
|
```java
|
|
// 获取文件大小
|
|
long fileSize = resource.contentLength();
|
|
log.info("开始下载模板文件,文件大小: {} bytes", fileSize);
|
|
|
|
// 设置响应头
|
|
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
|
response.setHeader("Content-Disposition", "attachment; filename=学生信息导入模板.xlsx");
|
|
response.setHeader("Content-Length", String.valueOf(fileSize));
|
|
response.setHeader("Cache-Control", "no-cache");
|
|
response.setHeader("Pragma", "no-cache");
|
|
response.setHeader("Connection", "close");
|
|
|
|
// 将文件内容写入响应流
|
|
try (InputStream inputStream = resource.getInputStream();
|
|
OutputStream outputStream = response.getOutputStream()) {
|
|
|
|
byte[] buffer = new byte[8192]; // 使用8KB缓冲区,平衡性能和内存
|
|
int bytesRead;
|
|
long totalBytes = 0;
|
|
long lastFlushTime = System.currentTimeMillis();
|
|
|
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
|
// 检查响应是否已提交(客户端是否断开连接)
|
|
if (response.isCommitted()) {
|
|
log.warn("客户端连接已断开,停止写入数据");
|
|
break;
|
|
}
|
|
|
|
try {
|
|
outputStream.write(buffer, 0, bytesRead);
|
|
totalBytes += bytesRead;
|
|
|
|
// 每32KB或每500ms刷新一次缓冲区
|
|
long currentTime = System.currentTimeMillis();
|
|
if (totalBytes % 32768 == 0 || (currentTime - lastFlushTime) > 500) {
|
|
outputStream.flush();
|
|
lastFlushTime = currentTime;
|
|
}
|
|
} catch (IOException e) {
|
|
// 检查是否是连接断开错误
|
|
if (e.getMessage() != null &&
|
|
(e.getMessage().contains("Broken pipe") ||
|
|
e.getMessage().contains("Connection reset") ||
|
|
e.getMessage().contains("Connection refused") ||
|
|
e.getMessage().contains("Connection timed out"))) {
|
|
log.warn("客户端连接断开,停止下载: {}", e.getMessage());
|
|
break;
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 最终刷新
|
|
if (!response.isCommitted()) {
|
|
outputStream.flush();
|
|
log.info("模板文件下载完成,总传输字节数: {}", totalBytes);
|
|
}
|
|
}
|
|
```
|
|
|
|
## 技术要点
|
|
|
|
### 1. 前端技术要点
|
|
- **Blob对象**: 使用Blob处理二进制文件数据
|
|
- **createObjectURL**: 创建临时URL用于文件下载
|
|
- **资源清理**: 及时清理URL对象,避免内存泄漏
|
|
- **错误分类**: 根据HTTP状态码和错误类型提供精准的错误提示
|
|
|
|
### 2. 后端技术要点
|
|
- **流式传输**: 使用InputStream和OutputStream进行流式文件传输
|
|
- **缓冲区管理**: 合理设置缓冲区大小和刷新策略
|
|
- **连接状态检查**: 定期检查客户端连接状态
|
|
- **异常处理**: 专门处理网络连接相关的异常
|
|
|
|
### 3. 性能优化
|
|
- **缓冲区大小**: 8KB缓冲区平衡了性能和内存使用
|
|
- **刷新策略**: 基于数据量和时间的双重刷新策略
|
|
- **连接管理**: 正确的响应头设置确保连接正确关闭
|
|
|
|
## 测试验证
|
|
|
|
### 测试场景
|
|
1. **正常下载**: 验证模板文件能够正常下载
|
|
2. **网络中断**: 模拟网络中断,验证错误处理
|
|
3. **浏览器关闭**: 模拟用户关闭浏览器,验证连接断开处理
|
|
4. **超时测试**: 验证60秒超时机制
|
|
5. **大文件传输**: 验证504KB文件的传输稳定性
|
|
|
|
### 预期结果
|
|
- ✅ 模板文件能够正常下载
|
|
- ✅ 网络中断时不会产生服务器错误
|
|
- ✅ 用户能够看到清晰的错误提示
|
|
- ✅ 下载过程中有进度提示
|
|
- ✅ 超时机制正常工作
|
|
|
|
## 变更影响
|
|
|
|
### 正面影响
|
|
- **用户体验提升**: 下载过程更加透明,错误提示更加友好
|
|
- **系统稳定性**: 减少了因连接断开导致的服务器错误
|
|
- **调试便利性**: 增加了详细的日志记录,便于问题排查
|
|
|
|
### 风险评估
|
|
- **低风险**: 主要是优化和增强,不涉及核心业务逻辑变更
|
|
- **向后兼容**: 保持了原有的API接口不变
|
|
- **性能影响**: 轻微的性能提升,无负面影响
|
|
|
|
## 总结
|
|
|
|
本次修复主要解决了学生信息导入模板下载功能中的连接中断问题,通过前后端的协同优化,提供了更加稳定和用户友好的下载体验。修复后的功能能够更好地处理各种网络异常情况,减少了服务器错误日志,提升了整体系统的稳定性。 |