support image

This commit is contained in:
Looly
2019-08-20 17:13:52 +08:00
parent b791a75dd6
commit 8a5efb84da
4 changed files with 143 additions and 83 deletions

View File

@@ -7,7 +7,7 @@
### 新特性 ### 新特性
* 【core】 改进CollUtil.zip逻辑减少内存复制issue#I10T01@Gitee * 【core】 改进CollUtil.zip逻辑减少内存复制issue#I10T01@Gitee
* 【extra】 邮件支持图片pr#495@Github * 【extra】 邮件增加图片支持pr#495@Github
### Bug修复 ### Bug修复
* 【http】 修复HttpRquest中body方法长度计算问题issue#I10UPG@Gitee * 【http】 修复HttpRquest中body方法长度计算问题issue#I10UPG@Gitee

View File

@@ -5,13 +5,12 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Date; import java.util.Date;
import java.util.Map;
import javax.activation.DataHandler; import javax.activation.DataHandler;
import javax.activation.DataSource; import javax.activation.DataSource;
import javax.activation.FileDataSource; import javax.activation.FileDataSource;
import javax.activation.FileTypeMap;
import javax.mail.Authenticator; import javax.mail.Authenticator;
import javax.mail.BodyPart;
import javax.mail.MessagingException; import javax.mail.MessagingException;
import javax.mail.Multipart; import javax.mail.Multipart;
import javax.mail.Session; import javax.mail.Session;
@@ -21,8 +20,11 @@ import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart; import javax.mail.internet.MimeMultipart;
import javax.mail.util.ByteArrayDataSource; import javax.mail.util.ByteArrayDataSource;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
/** /**
@@ -49,10 +51,8 @@ public class Mail {
private String content; private String content;
/** 是否为HTML */ /** 是否为HTML */
private boolean isHtml; private boolean isHtml;
/** 附件列表 */ /** 正文、附件和图片的混合部分 */
private DataSource[] attachments; private Multipart multipart = new MimeMultipart();
/** 图片列表 */
private Map<String, InputStream> imageMap;
/** 是否使用全局会话默认为false */ /** 是否使用全局会话默认为false */
private boolean useGlobalSession = false; private boolean useGlobalSession = false;
@@ -165,7 +165,8 @@ public class Mail {
} }
/** /**
* 设置正文 * 设置正文<br>
* 正文可以是普通文本也可以是HTML默认普通文本可以通过调用{@link #setHtml(boolean)} 设置是否为HTML
* *
* @param content 正文 * @param content 正文
* @return this * @return this
@@ -185,9 +186,21 @@ public class Mail {
this.isHtml = isHtml; this.isHtml = isHtml;
return this; return this;
} }
/**
* 设置正文
*
* @param content 正文内容
* @param isHtml 是否为HTML
* @return this
*/
public Mail setContent(String content, boolean isHtml) {
setContent(content);
return setHtml(isHtml);
}
/** /**
* 设置文件类型附件 * 设置文件类型附件文件可以是图片文件此时自动设置cid正文中引用图片默认cid为文件名
* *
* @param files 附件文件列表 * @param files 附件文件列表
* @return this * @return this
@@ -205,7 +218,7 @@ public class Mail {
} }
/** /**
* 设置附件,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件 * 增加附件或图片,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件
* *
* @param attachments 附件列表 * @param attachments 附件列表
* @return this * @return this
@@ -213,21 +226,74 @@ public class Mail {
*/ */
public Mail setAttachments(DataSource... attachments) { public Mail setAttachments(DataSource... attachments) {
if (ArrayUtil.isNotEmpty(attachments)) { if (ArrayUtil.isNotEmpty(attachments)) {
this.attachments = attachments; final Charset charset = this.mailAccount.getCharset();
MimeBodyPart bodyPart;
String nameEncoded;
try {
for (DataSource attachment : attachments) {
bodyPart = new MimeBodyPart();
bodyPart.setDataHandler(new DataHandler(attachment));
nameEncoded = InternalMailUtil.encodeText(attachment.getName(), charset);
// 普通附件文件名
bodyPart.setFileName(nameEncoded);
if(StrUtil.startWith(attachment.getContentType(), "image/")) {
// 图片附件,用于正文中引用图片
bodyPart.setContentID(nameEncoded);
}
this.multipart.addBodyPart(bodyPart);
}
} catch (MessagingException e) {
throw new MailException(e);
}
} }
return this; return this;
} }
/**
* 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"image/jpeg"
*
* @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageStream 图片文件
* @since 4.6.3
*/
public Mail addImage(String cid, InputStream imageStream) {
return addImage(cid, imageStream, null);
}
/**
* 增加图片,图片的键对应到邮件模板中的占位字符串
*
* @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageStream 图片流,不关闭
* @param contentType 图片类型null赋值默认的"image/jpeg"
* @since 4.6.3
*/
public Mail addImage(String cid, InputStream imageStream, String contentType) {
ByteArrayDataSource imgSource;
try {
imgSource = new ByteArrayDataSource(imageStream, ObjectUtil.defaultIfNull(contentType, "image/jpeg"));
} catch (IOException e) {
throw new IORuntimeException(e);
}
imgSource.setName(cid);
return setAttachments(imgSource);
}
/** /**
* 设置图片,图片的键对应到邮件模板中的占位字符串 * 增加图片,图片的键对应到邮件模板中的占位字符串
* *
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER * @param cid 图片与占位符占位符格式为cid:${cid}
* @param imageFile 图片文件
* @since 4.6.3
*/ */
public Mail setImageMap(Map<String, InputStream> imageMap) { public Mail addImage(String cid, File imageFile) {
if (imageMap != null && imageMap.size() > 0) { InputStream in = null;
this.imageMap = imageMap; try{
in = FileUtil.getInputStream(imageFile);
return addImage(cid, in, FileTypeMap.getDefaultFileTypeMap().getContentType(imageFile));
} finally {
IoUtil.close(in);
} }
return this;
} }
/** /**
@@ -330,43 +396,12 @@ public class Mail {
* @throws MessagingException 消息异常 * @throws MessagingException 消息异常
*/ */
private Multipart buildContent(Charset charset) throws MessagingException { private Multipart buildContent(Charset charset) throws MessagingException {
final Multipart mainPart = new MimeMultipart();
// 正文 // 正文
final BodyPart body = new MimeBodyPart(); final MimeBodyPart body = new MimeBodyPart();
body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charset)); body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charset));
mainPart.addBodyPart(body); this.multipart.addBodyPart(body);
// 附件 return this.multipart;
if (ArrayUtil.isNotEmpty(this.attachments)) {
BodyPart bodyPart;
for (DataSource attachment : attachments) {
bodyPart = new MimeBodyPart();
bodyPart.setDataHandler(new DataHandler(attachment));
bodyPart.setFileName(InternalMailUtil.encodeText(attachment.getName(), charset));
mainPart.addBodyPart(bodyPart);
}
}
// 图片
for (Map.Entry<String, InputStream> entry : imageMap.entrySet()) {
MimeBodyPart imgBodyPart = new MimeBodyPart();
DataSource ds;
try {
ds = new ByteArrayDataSource(entry.getValue(), "image/jpeg");
IoUtil.close(entry.getValue());
} catch (IOException e) {
throw new MailException(e);
}
imgBodyPart.setDataHandler(new DataHandler(ds));
// imgBodyPart.setHeader("Content-ID", String.format("<%s>", entry.getKey()));
imgBodyPart.setContentID(entry.getKey());
// add it
mainPart.addBodyPart(imgBodyPart);
}
return mainPart;
} }
/** /**

View File

@@ -5,8 +5,11 @@ import java.io.InputStream;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
/** /**
@@ -129,7 +132,7 @@ public class MailUtil {
send(GlobalMailAccount.INSTANCE.getAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files); send(GlobalMailAccount.INSTANCE.getAccount(), true, tos, ccs, bccs, subject, content, null, isHtml, files);
} }
//------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
@@ -175,7 +178,7 @@ public class MailUtil {
public static void send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) { public static void send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, boolean isHtml, File... files) {
send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files); send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
} }
/** /**
* 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br> * 使用配置文件中设置的账户发送HTML邮件发送给单个或多个收件人<br>
* 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔 * 多个收件人可以使用逗号“,”分隔,也可以通过分号“;”分隔
@@ -205,7 +208,7 @@ public class MailUtil {
public static void send(String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) { public static void send(String to, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
send(splitAddress(to), subject, content, imageMap, isHtml, files); send(splitAddress(to), subject, content, imageMap, isHtml, files);
} }
/** /**
* 使用配置文件中设置的账户发送邮件,发送单个或多个收件人<br> * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人<br>
* 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔 * 多个收件人、抄送人、密送人可以使用逗号“,”分隔,也可以通过分号“;”分隔
@@ -223,7 +226,7 @@ public class MailUtil {
public static void send(String to, String cc, String bcc, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) { public static void send(String to, String cc, String bcc, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files); send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
} }
/** /**
* 使用配置文件中设置的账户发送HTML邮件发送给多人 * 使用配置文件中设置的账户发送HTML邮件发送给多人
* *
@@ -251,7 +254,7 @@ public class MailUtil {
public static void send(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) { public static void send(Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
send(tos, null, null, subject, content, imageMap, isHtml, files); send(tos, null, null, subject, content, imageMap, isHtml, files);
} }
/** /**
* 使用配置文件中设置的账户发送邮件,发送给多人 * 使用配置文件中设置的账户发送邮件,发送给多人
* *
@@ -268,8 +271,8 @@ public class MailUtil {
public static void send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) { public static void send(Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
send(GlobalMailAccount.INSTANCE.getAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files); send(GlobalMailAccount.INSTANCE.getAccount(), true, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
} }
//------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount // ------------------------------------------------------------------------------------------------------------------------------- Custom MailAccount
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
@@ -296,11 +299,12 @@ public class MailUtil {
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER * @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式 * @param isHtml 是否为HTML格式
* @param files 附件列表 * @param files 附件列表
* @since 4.6.3
*/ */
public static void send(MailAccount mailAccount, Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) { public static void send(MailAccount mailAccount, Collection<String> tos, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) {
send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files); send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
} }
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
@@ -313,13 +317,14 @@ public class MailUtil {
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER * @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER
* @param isHtml 是否为HTML格式 * @param isHtml 是否为HTML格式
* @param files 附件列表 * @param files 附件列表
* @since 4.0.3 * @since 4.6.3
*/ */
public static void send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) { public static void send(MailAccount mailAccount, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap,
boolean isHtml, File... files) {
send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files); send(mailAccount, false, tos, ccs, bccs, subject, content, imageMap, isHtml, files);
} }
//------------------------------------------------------------------------------------------------------------------------ Private method start // ------------------------------------------------------------------------------------------------------------------------ Private method start
/** /**
* 发送邮件给多人 * 发送邮件给多人
* *
@@ -330,53 +335,62 @@ public class MailUtil {
* @param bccs 密送人列表可以为null或空 * @param bccs 密送人列表可以为null或空
* @param subject 标题 * @param subject 标题
* @param content 正文 * @param content 正文
* @param imageMap 图片与占位符占位符格式为cid:$IMAGE_PLACEHOLDER * @param imageMap 图片与占位符占位符格式为cid:${cid}
* @param isHtml 是否为HTML格式 * @param isHtml 是否为HTML格式
* @param files 附件列表 * @param files 附件列表
* @since 4.0.3 * @since 4.6.3
*/ */
private static void send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content, Map<String, InputStream> imageMap, boolean isHtml, File... files) { private static void send(MailAccount mailAccount, boolean useGlobalSession, Collection<String> tos, Collection<String> ccs, Collection<String> bccs, String subject, String content,
Map<String, InputStream> imageMap, boolean isHtml, File... files) {
final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession); final Mail mail = Mail.create(mailAccount).setUseGlobalSession(useGlobalSession);
//可选抄送人 // 可选抄送人
if(CollUtil.isNotEmpty(ccs)) { if (CollUtil.isNotEmpty(ccs)) {
mail.setCcs(ccs.toArray(new String[ccs.size()])); mail.setCcs(ccs.toArray(new String[ccs.size()]));
} }
//可选密送人 // 可选密送人
if(CollUtil.isNotEmpty(bccs)) { if (CollUtil.isNotEmpty(bccs)) {
mail.setBccs(bccs.toArray(new String[bccs.size()])); mail.setBccs(bccs.toArray(new String[bccs.size()]));
} }
mail.setTos(tos.toArray(new String[tos.size()])); mail.setTos(tos.toArray(new String[tos.size()]));
mail.setTitle(subject); mail.setTitle(subject);
mail.setContent(content); mail.setContent(content);
mail.setHtml(isHtml); mail.setHtml(isHtml);
mail.setFiles(files); mail.setFiles(files);
mail.setImageMap(imageMap);
// 图片
if(MapUtil.isNotEmpty(imageMap)) {
for (Entry<String, InputStream> entry : imageMap.entrySet()) {
mail.addImage(entry.getKey(), entry.getValue());
// 关闭流
IoUtil.close(entry.getValue());
}
}
mail.send(); mail.send();
} }
/** /**
* 将多个联系人转为列表,分隔符为逗号或者分号 * 将多个联系人转为列表,分隔符为逗号或者分号
* *
* @param addresses 多个联系人如果为空返回null * @param addresses 多个联系人如果为空返回null
* @return 联系人列表 * @return 联系人列表
*/ */
private static List<String> splitAddress(String addresses){ private static List<String> splitAddress(String addresses) {
if(StrUtil.isBlank(addresses)) { if (StrUtil.isBlank(addresses)) {
return null; return null;
} }
List<String> result; List<String> result;
if(StrUtil.contains(addresses, ',')) { if (StrUtil.contains(addresses, ',')) {
result = StrUtil.splitTrim(addresses, ','); result = StrUtil.splitTrim(addresses, ',');
}else if(StrUtil.contains(addresses, ';')) { } else if (StrUtil.contains(addresses, ';')) {
result = StrUtil.splitTrim(addresses, ';'); result = StrUtil.splitTrim(addresses, ';');
}else { } else {
result = CollUtil.newArrayList(addresses); result = CollUtil.newArrayList(addresses);
} }
return result; return result;
} }
//------------------------------------------------------------------------------------------------------------------------ Private method end // ------------------------------------------------------------------------------------------------------------------------ Private method end
} }

View File

@@ -1,5 +1,8 @@
package cn.hutool.extra.mail; package cn.hutool.extra.mail;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties; import java.util.Properties;
import org.junit.Assert; import org.junit.Assert;
@@ -17,17 +20,25 @@ public class MailTest {
@Test @Test
@Ignore @Ignore
public void sendTest() { public void sendWithFileTest() {
MailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/测试附件文本.txt")); MailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/测试附件文本.txt"));
} }
@Test @Test
@Ignore @Ignore
public void sendTest2() { public void sendWithLongNameFileTest() {
//附件名长度大于60时的测试 //附件名长度大于60时的测试
MailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/6-LongLong一阶段平台建设周报2018.3.12-3.16.xlsx")); MailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/6-LongLong一阶段平台建设周报2018.3.12-3.16.xlsx"));
} }
@Test
@Ignore
public void sendWithImageTest() {
Map<String, InputStream> map = new HashMap<>();
map.put("testImage", FileUtil.getInputStream("f:/test/me.png"));
MailUtil.sendHtml("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1><img src=\"cid:testImage\" />", map);
}
@Test @Test
@Ignore @Ignore
public void sendHtmlTest() { public void sendHtmlTest() {