You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

使用Apache Commons CSV生成下载CSV文件遇延迟问题求助

解决CSV文件生成延迟导致的首次下载404问题

这个问题我之前也碰到过,核心原因就是文件写入磁盘的IO操作存在延迟——你代码里虽然调用了flush()close(),但操作系统层面可能还没把数据完全持久化到磁盘,这时候前端触发下载请求,服务器自然找不到文件(就出现了首次404);第二次点击时,上一次的文件已经写完,所以下载到的是旧文件。而且这种生成固定文件名临时文件的方式,还会有多个用户操作时文件被覆盖的隐患。

下面给你两个解决方案,优先推荐第一个,彻底绕开文件IO的问题:


方案1:直接在Servlet中输出CSV(最优解)

不需要生成本地文件,直接把CSV内容写入HTTP响应流,让浏览器直接触发下载。这种方式完全避免了文件IO延迟的问题,还不用操心临时文件的清理。

修改Servlet代码:

try {
    // 设置响应头,告诉浏览器这是CSV文件,触发下载行为
    response.setContentType("text/csv");
    response.setHeader("Content-Disposition", "attachment; filename=\"file.csv\"");
    
    // 直接用响应输出流创建CSVPrinter,不需要写入本地文件
    CSVPrinter csvPrinter = new CSVPrinter(response.getWriter(), CSVFormat.DEFAULT.withHeader("Name"));
    for(String name: names){
        csvPrinter.printRecord(name);
    }
    csvPrinter.flush();
    csvPrinter.close();
} catch (Exception e) {
    e.printStackTrace();
    // 发生错误时返回500状态码
    response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}

修改JSP前端代码:

不需要AJAX,直接让按钮跳转到生成CSV的Servlet即可,浏览器会自动处理下载:

<button id="download" type="button">Download</button>
<script type="text/javascript">
$(function(){
    $('#download').on('click', function(){
        // 替换成你的Servlet实际路径
        window.location.href = '<%= request.getContextPath() %>/your-csv-generator-servlet';
    });
});
</script>

如果需要先做前置验证(比如检查用户权限),可以先通过AJAX完成验证,成功后再跳转下载:

<button id="download" type="button">Download</button>
<script type="text/javascript">
$(function(){
    $('#download').on('click', function(){
        $.ajax({
            url: '<%= request.getContextPath() %>/your-validation-servlet',
            // 这里可以传验证需要的参数
            success: function(response){
                var resp = JSON.parse(response);
                if(resp.result == "succ"){
                    // 验证通过,跳转下载
                    window.location.href = '<%= request.getContextPath() %>/your-csv-generator-servlet';
                }
            },
            error: function(xhr, err){
                alert('验证失败,请稍后重试');
            }
        });
    });
});
</script>

方案2:必须生成本地文件时的兼容处理(不推荐)

如果因为业务限制必须生成文件到磁盘,需要解决两个问题:确保文件完全写入后再返回响应,以及避免多用户覆盖文件。

修改Servlet代码:

// 生成唯一文件名,避免多用户操作时覆盖文件
String uniqueFileName = "csv-file-" + System.currentTimeMillis() + ".csv";
String filePath = request.getServletContext().getRealPath("assets/" + uniqueFileName);

try{
    BufferedWriter bufferedWriter = Files.newBufferedWriter(Paths.get(filePath));
    CSVPrinter csvPrinter = new CSVPrinter(bufferedWriter, CSVFormat.DEFAULT.withHeader("Name"));
    for(String name: names){
        csvPrinter.printRecord(name);
    }
    csvPrinter.flush();
    csvPrinter.close();
    
    // 强制刷新文件系统,确保数据完全写入磁盘
    Files.flush(Paths.get(filePath));
    
    // 返回成功状态和唯一文件的URL
    jsonObject.addProperty("result", "succ");
    String fileUrl = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + "/assets/" + uniqueFileName;
    jsonObject.addProperty("fileUrl", fileUrl);
}catch (Exception e) {
    e.printStackTrace();
    jsonObject.addProperty("result", "fail");
}

修改JSP前端代码:

<button id="download" type="button">Download</button>
<script type="text/javascript">
$(function(){
    $('#download').on('click', function(){
        $.ajax({
            url: '<%= request.getContextPath() %>/your-file-generator-servlet',
            success: function(response){
                var resp = JSON.parse(response);
                if(resp.result == "succ"){
                    // 创建临时a标签触发下载
                    var tempLink = document.createElement('a');
                    tempLink.href = resp.fileUrl;
                    tempLink.target = '_blank';
                    document.body.appendChild(tempLink);
                    tempLink.click();
                    document.body.removeChild(tempLink);
                    
                    // 可选:添加临时文件清理逻辑,比如下载后请求服务器删除文件
                    setTimeout(function(){
                        $.get('<%= request.getContextPath() %>/file-cleanup-servlet?fileName=' + resp.fileUrl.split('/').pop());
                    }, 5000);
                }
            },
            error: function(xhr, err){
                alert('文件生成失败,请稍后重试');
            }
        });
    });
});
</script>

注意:方案2需要额外实现临时文件的清理逻辑(比如定时任务或者用户下载后删除),否则服务器磁盘会被大量无用文件占用,所以优先推荐方案1。

内容的提问来源于stack exchange,提问作者daemon

火山引擎 最新活动