10

是否可以使用javax.mail和使用“现有”InputStream作为电子邮件附件内容发送电子邮件?

目前我正在构建电子邮件,如下所示:

final MimeMessage message = new MimeMessage(session);
message.setFrom(new InternetAddress(from));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(to));
message.setSubject("Subject line");

final Multipart multipartContent = new MimeMultipart();

    final MimeBodyPart textPart = new MimeBodyPart();
    textPart.setText("Message body");
    multipartContent.addBodyPart(textPart);

    final MimeBodyPart attachmentPart = new MimeBodyPart();
    final DataSource source = new InputStreamDataSource("text/plain", "test.txt", new ByteArrayInputStream("CONTENT INPUT STREAM".getBytes()));
    attachmentPart.setDataHandler(new DataHandler(source));
    attachmentPart.setFileName("text.txt");
    multipartContent.addBodyPart(attachmentPart);

message.setContent(multipartContent);

InputStreamDataSource实现如下:

public class InputStreamDataSource implements DataSource
{
    private final String contentType;
    private final String name;
    private final InputStream inputStream;

    public InputStreamDataSource(String contentType, String name, InputStream inputStream)
    {
        this.contentType = contentType;
        this.name = name;
        this.inputStream = inputStream;
    }

    public String getContentType()
    {
        return contentType;
    }

    public String getName()
    {
        return name;
    }

    public InputStream getInputStream() throws IOException
    {
        System.out.println("CALLED TWICE: InputStreamDataSource.getInputStream()");
        return new BufferedInputStream(inputStream);
        //return new ByteArrayInputStream("THIS 'NEW' INPUT STREAM WORKS BUT 'EXISTING' INPUT STREAM RESULTS IN ZERO-BYTE ATTACHMENT".getBytes());
    }

    public OutputStream getOutputStream() throws IOException
    {
        throw new UnsupportedOperationException("Not implemented");
    }
}

DataSource提供getInputStream()获取InputStream电子邮件附件内容的方法。

如果我返回一个InputStream不依赖于“现有”的“新”,InputStream那么它工作正常。但是,如果我返回一个“现有的” InputStream,那么电子邮件消息将使用零字节附件传递。

是否可以使用 发送电子邮件javax.mail,并使用“现有”InputStream作为电子邮件附件内容?

4

6 回答 6

4

编辑:

请参阅https://community.oracle.com/thread/1590625

TL;DR 使用ByteArrayDataSource


必须深入研究 Oracle 的源代码... https://java.net/projects/javamail/sources/mercurial/content/mail/src/main/java/javax/mail/internet/MimeBodyPart.java

当前的 java 邮件实现是输入流的 2 倍:

  1. 首先确定是否应该将标头“Content-Transfer-Encoding”设置为 7 位或 8 位(请参阅Content Transfer Encoding 7bit 或 8 位
  2. 然后第二次实际写入消息

...哪种糟透了,因为整个流(可能是通过慢速连接的数百 MB)将被读取两次 ...并且对于一旦读取就“消耗”的流会导致这个问题。


我尝试的第一个“解决方法”是自己指定标题:

attachmentPart.setDataHandler(new DataHandler(source));
attachmentPart.setHeader("Content-Transfer-Encoding", "8bit");
attachmentPart.setHeader("Content-Type", ds.getContentType() + "; " + ds.getName());

...并按此顺序,而不是相反...因为由于某种原因在setDataHandler内部调用了另一个再次invalidateContentHeaders清除"Content-Transfer-Encoding"标题的方法(wtf?!)

听起来不错,邮件已发送,万岁!!!:D ... :( 见下


附件发送...但损坏

我的邮件服务器中收到的文件已损坏。嗯。为什么?!. 经过长时间的搜索并再次深入研究这个糟糕的 java 邮件代码,我找到了它,他们通过管道将其InputStream插入到 aLineOutputStream中,它会更改二进制数据的行尾。嗯。java邮件实现真的是一团糟。:/

于 2017-04-21T16:08:05.567 回答
3

我重写了您的 InputStreamDataSource 类,它对我有用。

class InputStreamDataSource implements DataSource {
    String contentType;
    String name;

    byte[] fileData;

    public InputStreamDataSource(String contentType, String name, InputStream inputStream) throws IOException {
        this.contentType = contentType;
        this.name = name;
        /**
         * It seems DataSource will close inputStream and reopen it.
         * I converted inputStream to a byte array, so it won't be closed again.
         */
        fileData = IOUtils.toByteArray(inputStream);
    }

    public String getContentType() {
        return contentType;
    }

    public String getName() {
        return name;
    }

    public InputStream getInputStream() throws IOException {
        /**
         * Convert byte array back to inputStream.
         */
        return new ByteArrayInputStream(fileData);
    }

    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Not implemented");
    }
}
于 2018-10-18T07:35:37.797 回答
2

如果InputStream包含 mime 标头,则使用javax.mail.internet.MimeBodyPart(InputStream)构造函数。您不需要使用自定义DataSource类。

否则,如果InputStream只是没有标题的正文,则将流转换为字节数组并使用javax.mail.internet.MimeBodyPart(InternetHeaders, byte[])构造函数来提供标题。

于 2015-12-01T15:28:42.673 回答
1

我解决了将其转换InputStream为字节数组并将其转换为 Base64 格式的问题。

//get file name
String fileName = ...;
//get content type
String fileContentType = ...;
//get file content
InputStream fileStream = ...;

//convert to byte array
byte[] fileByteArray = IOUtils.toByteArray(fileStream);
//and convert to Base64
byte[] fileBase64ByteArray = java.util.Base64.getEncoder().encode(fileByteArray);

//manually define headers
InternetHeaders fileHeaders = new InternetHeaders();
fileHeaders.setHeader("Content-Type", fileContentType + "; name=\"" + fileName + "\"");
fileHeaders.setHeader("Content-Transfer-Encoding", "base64");
fileHeaders.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");

//build MIME body part
MimeBodyPart mbp = new MimeBodyPart(fileHeaders, fileBase64ByteArray);
mbp.setFileName(fileName);

//add it to the multipart
multipart.addBodyPart(mbp);
于 2017-12-13T15:26:04.890 回答
0

当前的 java 邮件实现对输入流进行了两次检查:第一次检测数据的编码,第二次发送数据。

如果您使用 EncodingAware 接口指定编码,则可以防止第一次通过。提供的 DataSource 应实现此接口。这是一个例子:

public class AttachementDataSource implements javax.activation.DataSource, javax.mail.EncodingAware {

    private final InputStreamSource inputStreamSource; 
    
    public AttachementDataSource(InputStreamSource inputStreamSource) {
        this.inputStreamSource = inputStreamSource;
    }
    
    @Override
    public InputStream getInputStream() throws IOException {
        return inputStreamSource.getInputStream();
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        throw new UnsupportedOperationException("Read-only javax.activation.DataSource");
    }

    @Override
    public String getContentType() {
        return "application/octet-stream";
    }

    @Override
    public String getName() {
        return "inline";
    }

    @Override
    public String getEncoding() {
        return "base64";
    }
}
于 2021-12-20T12:47:52.400 回答
0

I use this code for sending email with web downloaded attachment. You can easily edit it for your purpose. In mimeType use mime type of your attachment. Happy coding.

try {

        Message message = new MimeMessage(session);
        message.setFrom(new InternetAddress(
                "sender@gmail.com"));
        message.setRecipients(Message.RecipientType.TO,
                InternetAddress.parse("reciever@gmail.com"));
        message.setSubject("subject");

        Multipart multipart = new MimeMultipart();

        URL url = new URL(url);

        InputStream is = url.openStream();
        MimeBodyPart bodyPart = new MimeBodyPart(is);

        multipart.addBodyPart(bodyPart);

        message.setContent(multipart);
        message.addHeader("Content-Type", mimeType);
        Transport.send(message);
        logger.info("SENT to" + message.getRecipients(RecipientType.TO));

    } catch (MessagingException e) {
        //some implementation
    }
于 2017-12-21T11:35:15.357 回答