优化邮件发送

This commit is contained in:
zhou-hao 2021-09-28 15:57:22 +08:00
parent e2d5ffd965
commit cb11d4ce16
3 changed files with 94 additions and 42 deletions

View File

@ -5,9 +5,11 @@ import io.vavr.control.Try;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.hswebframework.web.exception.BusinessException;
import org.hswebframework.web.id.IDGenerator;
import org.hswebframework.web.utils.ExpressionUtils;
import org.hswebframework.web.utils.TemplateParser;
import org.hswebframework.web.validator.ValidatorUtils;
import org.jetlinks.community.notify.*;
import org.jetlinks.community.notify.email.EmailProvider;
@ -16,9 +18,7 @@ import org.jetlinks.core.Values;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.*;
import org.springframework.http.MediaType;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;
@ -40,6 +40,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 使用javax.mail进行邮件发送
@ -63,24 +64,30 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
@Getter
private final String notifierId;
@Setter
private boolean enableFileSystemAttachment = Boolean.getBoolean("email.attach.local-file.enabled");
public static Scheduler scheduler = Schedulers.newElastic("email-notifier");
public static Scheduler scheduler = Schedulers.elastic();
public DefaultEmailNotifier(NotifierProperties properties, TemplateManager templateManager) {
this(properties.getId(),
new JSONObject(properties.getConfiguration()).toJavaObject(DefaultEmailProperties.class),
templateManager);
}
public DefaultEmailNotifier(String id,
DefaultEmailProperties properties,
TemplateManager templateManager) {
super(templateManager);
notifierId = properties.getId();
DefaultEmailProperties emailProperties = new JSONObject(properties.getConfiguration())
.toJavaObject(DefaultEmailProperties.class);
ValidatorUtils.tryValidate(emailProperties);
ValidatorUtils.tryValidate(properties);
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(emailProperties.getHost());
mailSender.setPort(emailProperties.getPort());
mailSender.setUsername(emailProperties.getUsername());
mailSender.setPassword(emailProperties.getPassword());
mailSender.setJavaMailProperties(emailProperties.createJavaMailProperties());
this.sender = emailProperties.getSender();
mailSender.setHost(properties.getHost());
mailSender.setPort(properties.getPort());
mailSender.setUsername(properties.getUsername());
mailSender.setPassword(properties.getPassword());
mailSender.setJavaMailProperties(properties.createJavaMailProperties());
this.notifierId = id;
this.sender = properties.getSender();
this.javaMailSender = mailSender;
}
@ -88,8 +95,8 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
@Override
public Mono<Void> send(@Nonnull EmailTemplate template, @Nonnull Values context) {
return Mono.just(template)
.map(temp -> convert(temp, context.getAllValues()))
.flatMap(temp -> doSend(temp, template.getSendTo()));
.map(temp -> convert(temp, context.getAllValues()))
.flatMap(this::doSend);
}
@Nonnull
@ -110,32 +117,36 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
return EmailProvider.embedded;
}
protected Mono<Void> doSend(ParsedEmailTemplate template, List<String> sendTo) {
protected Mono<Void> doSend(ParsedEmailTemplate template) {
return Mono
.fromCallable(() -> {
MimeMessage mimeMessage = this.javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");
helper.setFrom(this.sender);
helper.setTo(sendTo.toArray(new String[0]));
helper.setTo(template.getSendTo().toArray(new String[0]));
helper.setSubject(template.getSubject());
helper.setText(new String(template.getText().getBytes(), StandardCharsets.UTF_8), true);
return Flux.fromIterable(template.getAttachments().entrySet())
return Flux
.fromIterable(template.getAttachments().entrySet())
.flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue())))
.doOnNext(tp -> Try.run(() -> helper.addAttachment(MimeUtility.encodeText(tp.getT1()), tp.getT2())).get())
.then(
Flux.fromIterable(template.getImages().entrySet())
.flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue())))
.doOnNext(tp -> Try.run(() -> helper.addInline(tp.getT1(), tp.getT2(), MediaType.APPLICATION_OCTET_STREAM_VALUE)).get())
.then()
.doOnNext(tp -> Try
.run(() -> helper.addAttachment(MimeUtility.encodeText(tp.getT1()), tp.getT2())).get())
.then(Flux
.fromIterable(template.getImages().entrySet())
.flatMap(entry -> Mono.zip(Mono.just(entry.getKey()), convertResource(entry.getValue())))
.doOnNext(tp -> Try
.run(() -> helper.addInline(tp.getT1(), tp.getT2(), MediaType.APPLICATION_OCTET_STREAM_VALUE))
.get())
.then()
).thenReturn(mimeMessage)
;
})
.publishOn(scheduler)
.flatMap(Function.identity())
.doOnNext(message -> this.javaMailSender.send(message))
.subscribeOn(scheduler)
.then()
;
}
@ -143,46 +154,65 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
protected Mono<InputStreamSource> convertResource(String resource) {
if (resource.startsWith("http")) {
return WebClient.create()
return WebClient
.create()
.get()
.uri(resource)
.accept(MediaType.APPLICATION_OCTET_STREAM)
.exchange()
.flatMap(rep -> rep.bodyToMono(Resource.class));
} else if (resource.startsWith("data:") && resource.contains(";base64,")) {
String base64 = resource.substring(resource.indexOf(";base64,") + 8);
return Mono.just(
new ByteArrayResource(Base64.decodeBase64(base64))
);
} else if (enableFileSystemAttachment) {
return Mono.just(
new FileSystemResource(resource)
);
} else {
try {
return Mono.just(new InputStreamResource(new FileInputStream(resource)));
} catch (FileNotFoundException e) {
return Mono.error(e);
}
throw new UnsupportedOperationException("不支持的文件地址:" + resource);
}
}
protected ParsedEmailTemplate convert(EmailTemplate template, Map<String, Object> context) {
public static ParsedEmailTemplate convert(EmailTemplate template, Map<String, Object> context) {
List<String> sendTo = template.getSendTo()
.stream()
.map(s -> render(s, context))
.collect(Collectors.toList());
String subject = template.getSubject();
String text = template.getText();
if (StringUtils.isEmpty(subject) || StringUtils.isEmpty(text)) {
throw new BusinessException("模板内容错误text 或者 subject 不能为空.");
}
String sendText = render(text, context);
String sendText = render(text, context, true);
List<EmailTemplate.Attachment> tempAttachments = template.getAttachments();
Map<String, String> attachments = new HashMap<>();
if (tempAttachments != null) {
for (EmailTemplate.Attachment tempAttachment : tempAttachments) {
List<EmailTemplate.Attachment> distinctAttachment = tempAttachments
.stream()
.distinct()
.collect(Collectors.toList());
for (EmailTemplate.Attachment tempAttachment : distinctAttachment) {
attachments.put(tempAttachment.getName(), render(tempAttachment.getLocation(), context));
}
}
return ParsedEmailTemplate.builder()
return ParsedEmailTemplate
.builder()
.attachments(attachments)
.images(extractSendTextImage(sendText))
.text(sendText)
.subject(render(subject, context))
.sendTo(sendTo)
.build();
}
private Map<String, String> extractSendTextImage(String sendText) {
private static Map<String, String> extractSendTextImage(String sendText) {
Map<String, String> images = new HashMap<>();
Document doc = Jsoup.parse(sendText);
for (Element src : doc.getElementsByTag("img")) {
@ -197,7 +227,21 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
return images;
}
private String render(String str, Map<String, Object> context) {
private static String render(String str, Map<String, Object> context) {
return render(str, context, false);
}
private static String render(String str, Map<String, Object> context, boolean html) {
if (StringUtils.isEmpty(str)) {
return "";
}
if (html) {
return TemplateParser.parse(str, expr ->
ExpressionUtils.analytical("${" + Jsoup
.parse(expr)
.text() + "}", context, "spel"));
}
return ExpressionUtils.analytical(str, context, "spel");
}

View File

@ -1,7 +1,6 @@
package org.jetlinks.community.notify.email.embedded;
import lombok.Getter;
import lombok.Setter;
import lombok.*;
import org.jetlinks.community.notify.template.Template;
import java.util.List;
@ -20,6 +19,9 @@ public class EmailTemplate implements Template {
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode(of = "name")
public static class Attachment {
private String name;

View File

@ -5,6 +5,7 @@ import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map;
/**
@ -23,7 +24,12 @@ public class ParsedEmailTemplate {
//图片 key:text中图片占位符 value:图片uri
private Map<String, String> images;
//邮件主题
private String subject;
//邮件内容
private String text;
//发送人集合
private List<String> sendTo;
}