ソースを参照

support image

Looly 6 年 前
コミット
8a5efb84da

+ 1 - 1
CHANGELOG.md

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

+ 85 - 50
hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java

@@ -5,13 +5,12 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.nio.charset.Charset;
 import java.util.Date;
-import java.util.Map;
 
 import javax.activation.DataHandler;
 import javax.activation.DataSource;
 import javax.activation.FileDataSource;
+import javax.activation.FileTypeMap;
 import javax.mail.Authenticator;
-import javax.mail.BodyPart;
 import javax.mail.MessagingException;
 import javax.mail.Multipart;
 import javax.mail.Session;
@@ -21,8 +20,11 @@ import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
 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.util.ArrayUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 
 /**
@@ -49,10 +51,8 @@ public class Mail {
 	private String content;
 	/** 是否为HTML */
 	private boolean isHtml;
-	/** 附件列表 */
-	private DataSource[] attachments;
-	/** 图片列表 */
-	private Map<String, InputStream> imageMap;
+	/** 正文、附件和图片的混合部分 */
+	private Multipart multipart = new MimeMultipart();
 	/** 是否使用全局会话,默认为false */
 	private boolean useGlobalSession = false;
 
@@ -165,7 +165,8 @@ public class Mail {
 	}
 
 	/**
-	 * 设置正文
+	 * 设置正文<br>
+	 * 正文可以是普通文本也可以是HTML(默认普通文本),可以通过调用{@link #setHtml(boolean)} 设置是否为HTML
 	 * 
 	 * @param content 正文
 	 * @return this
@@ -185,9 +186,21 @@ public class Mail {
 		this.isHtml = isHtml;
 		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 附件文件列表
 	 * @return this
@@ -205,7 +218,7 @@ public class Mail {
 	}
 
 	/**
-	 * 设置附件,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件
+	 * 增加附件或图片,附件使用{@link DataSource} 形式表示,可以使用{@link FileDataSource}包装文件表示文件附件
 	 * 
 	 * @param attachments 附件列表
 	 * @return this
@@ -213,21 +226,74 @@ public class Mail {
 	 */
 	public Mail setAttachments(DataSource... 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;
 	}
+	
+	/**
+	 * 增加图片,图片的键对应到邮件模板中的占位字符串,图片类型默认为"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) {
-		if (imageMap != null && imageMap.size() > 0) {
-			this.imageMap = imageMap;
+	public Mail addImage(String cid, File imageFile) {
+		InputStream in = null;
+		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 消息异常
 	 */
 	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));
-		mainPart.addBodyPart(body);
-
-		// 附件
-		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);
-		}
+		this.multipart.addBodyPart(body);
 
-		return mainPart;
+		return this.multipart;
 	}
 
 	/**

+ 44 - 30
hutool-extra/src/main/java/cn/hutool/extra/mail/MailUtil.java

@@ -5,8 +5,11 @@ import java.io.InputStream;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.map.MapUtil;
 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);
 	}
 
-	//------------------------------------------------------------------------------------------------------------------------------- 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) {
 		send(mailAccount, false, tos, ccs, bccs, subject, content, null, isHtml, files);
 	}
-	
+
 	/**
 	 * 使用配置文件中设置的账户发送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) {
 		send(splitAddress(to), subject, content, imageMap, isHtml, files);
 	}
-	
+
 	/**
 	 * 使用配置文件中设置的账户发送邮件,发送单个或多个收件人<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) {
 		send(splitAddress(to), splitAddress(cc), splitAddress(bcc), subject, content, imageMap, isHtml, files);
 	}
-	
+
 	/**
 	 * 使用配置文件中设置的账户发送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) {
 		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) {
 		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 isHtml 是否为HTML格式
 	 * @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) {
 		send(mailAccount, tos, null, null, subject, content, imageMap, isHtml, files);
 	}
-	
+
 	/**
 	 * 发送邮件给多人
 	 * 
@@ -313,13 +317,14 @@ public class MailUtil {
 	 * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
 	 * @param isHtml 是否为HTML格式
 	 * @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);
 	}
-	
-	//------------------------------------------------------------------------------------------------------------------------ Private method start
+
+	// ------------------------------------------------------------------------------------------------------------------------ Private method start
 	/**
 	 * 发送邮件给多人
 	 * 
@@ -330,53 +335,62 @@ public class MailUtil {
 	 * @param bccs 密送人列表,可以为null或空
 	 * @param subject 标题
 	 * @param content 正文
-	 * @param imageMap 图片与占位符,占位符格式为cid:$IMAGE_PLACEHOLDER
+	 * @param imageMap 图片与占位符,占位符格式为cid:${cid}
 	 * @param isHtml 是否为HTML格式
 	 * @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);
-		
-		//可选抄送人
-		if(CollUtil.isNotEmpty(ccs)) {
+
+		// 可选抄送人
+		if (CollUtil.isNotEmpty(ccs)) {
 			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.setTos(tos.toArray(new String[tos.size()]));
 		mail.setTitle(subject);
 		mail.setContent(content);
 		mail.setHtml(isHtml);
 		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();
 	}
-	
+
 	/**
 	 * 将多个联系人转为列表,分隔符为逗号或者分号
 	 * 
 	 * @param addresses 多个联系人,如果为空返回null
 	 * @return 联系人列表
 	 */
-	private static List<String> splitAddress(String addresses){
-		if(StrUtil.isBlank(addresses)) {
+	private static List<String> splitAddress(String addresses) {
+		if (StrUtil.isBlank(addresses)) {
 			return null;
 		}
-		
+
 		List<String> result;
-		if(StrUtil.contains(addresses, ',')) {
+		if (StrUtil.contains(addresses, ',')) {
 			result = StrUtil.splitTrim(addresses, ',');
-		}else if(StrUtil.contains(addresses, ';')) {
+		} else if (StrUtil.contains(addresses, ';')) {
 			result = StrUtil.splitTrim(addresses, ';');
-		}else {
+		} else {
 			result = CollUtil.newArrayList(addresses);
 		}
 		return result;
 	}
-	//------------------------------------------------------------------------------------------------------------------------ Private method end
+	// ------------------------------------------------------------------------------------------------------------------------ Private method end
 }

+ 13 - 2
hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java

@@ -1,5 +1,8 @@
 package cn.hutool.extra.mail;
 
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Properties;
 
 import org.junit.Assert;
@@ -17,19 +20,27 @@ public class MailTest {
 	
 	@Test
 	@Ignore
-	public void sendTest() {
+	public void sendWithFileTest() {
 		MailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true, FileUtil.file("d:/测试附件文本.txt"));
 	}
 	
 	@Test
 	@Ignore
-	public void sendTest2() {
+	public void sendWithLongNameFileTest() {
 		//附件名长度大于60时的测试
 		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
+	@Ignore
 	public void sendHtmlTest() {
 		MailUtil.send("hutool@foxmail.com", "测试", "<h1>邮件来自Hutool测试</h1>", true);
 	}