You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Java的Web应用远程下载TXT文件出现重复条目问题排查

多服务器/负载均衡环境下TXT下载重复问题排查与调试方案

问题现象

基于Java的Web应用在远程多服务器/负载均衡环境中下载TXT文件时出现内容重复,但本地测试完全正常,且服务器上的源文件本身无重复条目:

  • 源文件内容:
Test 123
Test 456
  • 下载后文件内容:
Test 123
Test 456
Test 123
Test 456

初步怀疑是下载逻辑的JS代码在远程连接下出现异常,导致数据被莫名拼接两次。

相关代码

JS下载逻辑

jQuery('#submit-btn').on('click', function() {
    var name = jQuery(NAME_ID).val();
    var request = new XMLHttpRequest();
    request.open('POST', getUrl('search'), true); // 调用后端接口获取数据
    request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
    request.responseType = 'blob';

    request.onload = function(e) {
        if (this.status === 200) {
            var blob = this.response;
            var filename = "";
            var disposition = this.getResponseHeader('Content-Disposition');
            if (disposition && disposition.indexOf('attachment') !== -1) {
                var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
                var matches = filenameRegex.exec(disposition);
                if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
            }

            if (window.navigator.msSaveOrOpenBlob) {
                window.navigator.msSaveBlob(blob, filename);
            } else {
                var downloadLink = window.document.createElement('a');
                var contentTypeHeader = request.getResponseHeader("Content-Type");
                downloadLink.href = window.URL.createObjectURL(new Blob([blob], {
                    type: contentTypeHeader
                }));
                downloadLink.download = filename;
                document.body.appendChild(downloadLink);
                downloadLink.click();
                document.body.removeChild(downloadLink);
            }
        }
    };
    request.send("name="+name);
});

后端Java接口

@RequestMapping(value = "/search", method = RequestMethod.POST, produces = "text/plain")
public @ResponseBody String search(HttpServletRequest request,
        HttpServletResponse response) {

    String log = "";
    String fileName = "";
    String useragent = "" + request.getHeader("user-agent");

    if (request.getParameter("name") != null) {
        fileName = request.getParameter("name");
    }

    
    try {
        log = readLogContent(fileName.trim());

        StringBuilder builder = new StringBuilder(50);
        if (useragent != null && useragent.indexOf("MSIE 6") == -1) {
            builder.append("attachment; filename=");
        } else {
            builder.append("inline; filename=");
        }
        
        builder.append(fileName);           

        response.setHeader("Content-Disposition", builder.toString());
        response.setContentType(MimeTypes.getMimeType("txt"));

        OutputStream os = response.getOutputStream();
        try {
            os.write(log.getBytes(Charset.forName("UTF-8")));
            os.flush();
        } finally {
            os.close();
        }
    } catch (IOException e) {
        logger.debug(e);
    }
    return "";

}

问题排查与修复

核心问题定位:后端双重输出冲突

后端代码存在明显逻辑矛盾:

  1. 方法标注了@ResponseBody,Spring MVC会自动将方法返回值(空字符串"")写入响应体
  2. 代码中又手动通过OutputStream写入了文件内容
  3. 本地环境可能因Tomcat缓冲区或响应处理机制未显现问题,但在远程负载均衡环境下,代理服务器的响应拼接逻辑将两次输出合并,导致内容重复

修复方案:
移除@ResponseBody注解,将方法返回类型改为void,因为已经手动处理了响应输出,不需要Spring再介入:

@RequestMapping(value = "/search", method = RequestMethod.POST, produces = "text/plain")
public void search(HttpServletRequest request,
        HttpServletResponse response) {

    // 保留原有业务逻辑,删除最后的return "";语句
}

其他可能的排查方向

  • JS事件重复绑定:远程环境下页面可能被多次渲染,导致按钮点击事件被绑定多次,触发重复请求。可修改JS代码,绑定前先解除原有绑定:
    jQuery('#submit-btn').off('click').on('click', function() {
        // 原有逻辑不变
    });
    
  • 负载均衡请求重试:检查负载均衡器配置,是否存在请求超时重试机制,导致后端被重复调用,返回两次内容被拼接。

JS调试方案(查看Blob内容与请求详情)

1. 直接查看后端响应

打开浏览器开发者工具(F12)→ 切换到网络标签 → 筛选XHR请求,找到search接口:

  • 查看响应标签,直接确认后端返回的内容是否已经重复——这能快速区分是后端问题还是JS处理问题
  • 查看请求标签,确认请求参数、发送次数是否正常

2. 打印Blob内容

在JS的request.onload函数中添加代码,将Blob转换为文本并打印到控制台:

request.onload = function(e) {
    if (this.status === 200) {
        var blob = this.response;
        // 调试代码:打印Blob内容
        const reader = new FileReader();
        reader.onload = () => console.log("Blob原始内容:", reader.result);
        reader.readAsText(blob);
        
        // 原有下载逻辑继续执行
        // ...
    }
};

3. 检查事件绑定次数

在浏览器控制台执行以下代码,查看按钮的点击事件绑定情况:

// jQuery 3.x及以上版本
console.log(jQuery('#submit-btn').get(0).onclick);
// 或查看Elements标签中按钮的"事件监听器"面板,确认是否有多个click事件

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 22:45:34