Files
hzdyjs/docs/变更管理/code-adjust-record-2025-07-31-student-template-download-fix-v2.md
T
2025-07-31 19:52:03 +08:00

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接口不变
- **性能影响**: 轻微的性能提升,无负面影响
## 总结
本次修复主要解决了学生信息导入模板下载功能中的连接中断问题,通过前后端的协同优化,提供了更加稳定和用户友好的下载体验。修复后的功能能够更好地处理各种网络异常情况,减少了服务器错误日志,提升了整体系统的稳定性。