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

5.6 KiB

学生信息导入模板下载功能修复记录

日期: 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)并定期刷新
  • 异常处理增强: 专门处理连接断开相关的异常
  • 响应头优化: 添加缓存控制头

关键代码改进:

// 检查文件是否存在
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秒下载超时机制
  • 错误分类: 根据不同的错误类型显示相应的错误信息
  • 用户体验: 优化文件名和提示信息
  • 资源清理: 确保超时定时器正确清理

关键代码改进:

/** 下载模板操作 */
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. 考虑添加下载统计和监控功能