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

JavaMail下载Gmail CSV附件末尾截断 内容不完整问题排查

JavaMail下载Gmail CSV附件末尾内容截断问题排查

问题表现

  • 服务配置、参数设置均验证正常,程序运行过程中无报错
  • 附件可正常触发下载流程,大部分内容可正常保存,但最终生成的CSV文件丢失最后一整行,倒数第二行存在部分内容截断,无法获取完整文件
  • 已调整代码中for循环逻辑,问题未解决
  • 已排查缓冲区溢出、缓冲区大小设置问题:待下载CSV文件体积极小,单文件最多仅包含140行数据,不存在触发该类问题的条件

问题代码

public void searchEmailByID (int emailID) {
    Properties properties = new Properties();

    //server setting
    properties.put("mail.imap.host", GmailHost);
    properties.put("mail.imap.port", GmailPort);

    //SSL setting
    properties.setProperty("mail.imap.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    properties.setProperty("mail.imap.socketFactory.fallback", "false");
    properties.setProperty("mail.imap.socketFactory.port", String.valueOf(GmailPort));

    Session session = Session.getDefaultInstance(properties);

    try {
        //connects to message store
        Store store = session.getStore("imap");
        store.connect(GmailUser, GmailPassword);

        //opens the inbox folder
        Folder folderInbox = store.getFolder("Inbox");
        folderInbox.open(Folder.READ_ONLY);

        //creates the search criteria
        SearchTerm searchCondition = new SearchTerm() {
            @Override
            public boolean match(Message message) {
                try {
                    if (message.getMessageNumber() == emailID) {
                        return true;
                    }
                } catch (NullPointerException ex) {
                    System.out.println("Null exception at line 50, may be due to variation in timing");
                    //ex.printStackTrace();
                }
                return false;
            }
        };

        //performs search through the folder
        Message[] foundMessages = folderInbox.search(searchCondition);
        setRecentlySearchMsg(foundMessages[0]);

        for (int i = 0; i < foundMessages.length; i++) {
            Message message = foundMessages[i];
            String subject = message.getSubject(); //subject in the target email
            Date date = message.getReceivedDate(); //date the email was received
            System.out.println("Found message #" + i + ": " + subject + ", Date Received: " + date.toString());
            String contentType = message.getContentType();
            System.out.println("Content type: " + contentType);
            Multipart multipart = (Multipart) message.getContent();
            int count = 0;  //count object to ensure overflows aren't triggered
            for (int j = 0; j < multipart.getCount(); j++) {
                MimeBodyPart part = (MimeBodyPart) multipart.getBodyPart(j); //using the i loop to establish connection to message
                if(part.getContentType().contains("TEXT/PLAIN")) {
                    continue;
                } else if(part.getContentType().contains("TEXT/HTML")) {
                    break;
                }
                if (Part.ATTACHMENT.equalsIgnoreCase(part.getDisposition())) {
                    String dateToday = new SimpleDateFormat("MM-dd-yyyy").format(new Date());   //Retrieving today's date
                    String path = "/file/location/here/" + dateToday + "_" + part.getFileName();
                    if(part.getFileName().contains("User")) {
                        userFileLocation = path;
                    } else if(part.getFileName().contains("Teams")) {
                        teamFileLocation = path;
                    }
                    part.saveFile(path);
                    System.out.println("File successfully saved in folder with name: " + path);
                    System.out.println("File is located at: " + path);
                    count++;
                }
                if (count == 2){
                    break;
                }

            }
        }

        //disconnect
        folderInbox.close(false);
        store.close();
    } catch (NoSuchProviderException ex) {
        System.out.println("No provider.");
        ex.printStackTrace();
    } catch (MessagingException ex) {
        System.out.println("Could not connect to the message store.");
        ex.printStackTrace();
    } catch (IOException ex) {
        System.out.println("Error parsing message attachment");
        ex.printStackTrace();
    }
}

根因定位

代码存在4处逻辑缺陷,共同导致附件内容截断:

  1. 遍历逻辑错误:遍历邮件MIME部件时,只要遇到Content-Type包含TEXT/HTML的部件就直接break终止整个遍历循环。主流邮件客户端构造的带附件邮件MIME结构顺序不固定,常出现text/plain正文 -> text/html正文 -> 附件的顺序,该逻辑会导致附件流还未完全读取就被终止。
  2. 未处理嵌套MIME结构:大部分邮件的MIME结构为多层嵌套(最外层为multipart/mixed,内层嵌套multipart/alternative存储正文,外层同级节点存储附件),代码仅遍历第一层MIME部件,遇到嵌套结构内的text/html部件就会误触发终止逻辑,无法读全附件流。
  3. IMAP配置缺失:未关闭IMAP协议的部分拉取(partialfetch)特性,Gmail IMAP服务对分块拉取的响应在旧版JavaMail中存在流截断bug,小体积文本类附件极易出现末尾字节丢失的问题。
  4. 附件保存逻辑不可靠:直接调用MimeBodyPart.saveFile()方法保存附件,该方法内部的流拷贝逻辑未显式校验流读取结束标记,且Session.getDefaultInstance()会复用JVM内已有的Session实例,可能导致自定义配置不生效,进一步加剧流读取不完整的问题。

修复方案

按以下步骤修改代码即可解决问题:

  • 删除遍历部件时遇到TEXT/HTML就break的逻辑,正文部件直接跳过即可,不要终止整个遍历循环
  • 增加嵌套Multipart递归处理逻辑,遍历所有层级的MIME部件,避免嵌套结构导致的读取中断
  • 在IMAP配置中新增参数关闭部分拉取特性,规避Gmail IMAP的分块响应bug:
    properties.put("mail.imap.partialfetch", "false");
    
  • 将Session.getDefaultInstance(properties)替换为Session.getInstance(properties),确保自定义配置全部生效
  • 替换直接调用part.saveFile()的保存逻辑,手动读取附件输入流并写入文件,显式触发缓冲区刷新和流关闭,确保所有内容完整写入磁盘:
    try (InputStream is = part.getInputStream();
         FileOutputStream fos = new FileOutputStream(path)) {
        byte[] buffer = new byte[4096];
        int readLen;
        while ((readLen = is.read(buffer)) != -1) {
            fos.write(buffer, 0, readLen);
        }
        fos.flush();
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 21:02:00