# 学生信息导入模板下载功能修复记录 **日期**: 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. 后端优化 (XgStudentMsgController.java) #### 主要改进: - **文件存在性检查**: 在开始下载前检查模板文件是否存在 - **连接状态监控**: 在写入过程中检查客户端连接状态 - **缓冲区优化**: 使用较小的缓冲区(4KB)并定期刷新 - **异常处理增强**: 专门处理连接断开相关的异常 - **响应头优化**: 添加缓存控制头 #### 关键代码改进: ```java // 检查文件是否存在 if (!resource.exists()) { log.error("模板文件不存在: student_template.xlsx"); if (!response.isCommitted()) { response.setStatus(HttpServletResponse.SC_NOT_FOUND); response.setContentType("text/plain;charset=UTF-8"); response.getWriter().write("模板文件不存在"); } return; } // 连接状态检查 while ((bytesRead = inputStream.read(buffer)) != -1) { // 检查响应是否已提交(客户端是否断开连接) if (response.isCommitted()) { log.warn("客户端连接已断开,停止写入数据"); break; } try { outputStream.write(buffer, 0, bytesRead); totalBytes += bytesRead; // 定期刷新缓冲区,确保数据及时发送 if (totalBytes % 16384 == 0) { // 每16KB刷新一次 outputStream.flush(); } } catch (IOException e) { // 检查是否是连接断开错误 if (e.getMessage() != null && (e.getMessage().contains("Broken pipe") || e.getMessage().contains("Connection reset") || e.getMessage().contains("Connection refused"))) { log.warn("客户端连接断开,停止下载: {}", e.getMessage()); break; } else { throw e; } } } ``` ### 2. 前端优化 (index.vue) #### 主要改进: - **超时处理**: 添加60秒下载超时机制 - **错误分类**: 根据不同的错误类型显示相应的错误信息 - **用户体验**: 优化文件名和提示信息 - **资源清理**: 确保超时定时器正确清理 #### 关键代码改进: ```javascript /** 下载模板操作 */ const importTemplate = () => { // 显示下载进度提示 const loadingMessage = ElMessage({ message: '正在下载模板,请稍候...', type: 'info', duration: 0, zIndex: 4000 }); // 设置下载超时时间 const downloadTimeout = setTimeout(() => { ElMessage.closeAll(); ElMessage.error('下载超时,请稍后重试'); }, 60000); // 60秒超时 proxy ?.download('studentMsg/studentMsg/importTemplate', {}, `学生信息导入模板_${new Date().getTime()}.xlsx`) .then(() => { // 下载成功后关闭提示和超时定时器 clearTimeout(downloadTimeout); ElMessage.closeAll(); ElMessage.success('模板下载成功'); }) .catch((error) => { // 下载失败时关闭提示和超时定时器 clearTimeout(downloadTimeout); ElMessage.closeAll(); console.error('下载模板失败:', error); // 根据错误类型显示不同的错误信息 if (error.message && error.message.includes('timeout')) { ElMessage.error('下载超时,请稍后重试'); } else if (error.message && error.message.includes('403')) { ElMessage.error('权限不足,无法下载模板'); } else if (error.message && error.message.includes('404')) { ElMessage.error('模板文件不存在,请联系管理员'); } else if (error.message && error.message.includes('500')) { ElMessage.error('服务器内部错误,请稍后重试'); } else if (error.message && error.message.includes('Network Error')) { ElMessage.error('网络连接异常,请检查网络后重试'); } else { ElMessage.error('下载模板失败,请稍后重试'); } }); }; ``` ## 修改文件列表 1. **后端文件**: - `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java` - 优化 `importTemplate()` 方法 2. **前端文件**: - `web/src/views/xg/xsjcxx/studentMsg/studentMsg/index.vue` - 优化 `importTemplate()` 方法 ## 测试建议 1. **正常下载测试**: 验证模板文件能正常下载 2. **网络中断测试**: 在下载过程中断开网络连接 3. **浏览器关闭测试**: 在下载过程中关闭浏览器 4. **大文件传输测试**: 验证大文件的传输稳定性 5. **并发下载测试**: 多个用户同时下载模板 ## 预期效果 - ✅ 解决"Broken pipe"错误 - ✅ 提升下载稳定性 - ✅ 改善用户体验 - ✅ 增强错误处理能力 - ✅ 减少服务器资源浪费 ## 注意事项 1. 模板文件大小(504KB)相对较大,建议后续考虑压缩或优化 2. 定期检查模板文件是否存在和完整性 3. 监控下载失败率,及时发现问题 4. 考虑添加下载统计和监控功能