Selaa lähdekoodia

优化邮件通知

zhouhao 2 vuotta sitten
vanhempi
commit
4c1de801ac

+ 63 - 53
jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/DefaultEmailNotifier.java

@@ -1,32 +1,36 @@
 package org.jetlinks.community.notify.email.embedded;
 
-import com.alibaba.fastjson.JSONObject;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufUtil;
+import io.netty.buffer.Unpooled;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.codec.binary.Base64;
+import org.hswebframework.web.bean.FastBeanCopier;
 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;
-import org.jetlinks.core.Values;
 import org.jetlinks.community.io.file.FileManager;
 import org.jetlinks.community.notify.*;
+import org.jetlinks.community.notify.email.EmailProvider;
 import org.jetlinks.community.notify.template.TemplateManager;
+import org.jetlinks.core.Values;
 import org.jsoup.Jsoup;
 import org.jsoup.nodes.Document;
 import org.jsoup.nodes.Element;
-import org.springframework.core.io.*;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.InputStreamSource;
+import org.springframework.core.io.Resource;
 import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.core.io.buffer.NettyDataBuffer;
 import org.springframework.http.MediaType;
 import org.springframework.mail.javamail.JavaMailSender;
 import org.springframework.mail.javamail.JavaMailSenderImpl;
 import org.springframework.mail.javamail.MimeMessageHelper;
 import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
+import org.springframework.util.ObjectUtils;
 import org.springframework.web.reactive.function.client.WebClient;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
@@ -38,10 +42,10 @@ import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeUtility;
 import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Function;
-import java.util.stream.Collectors;
 
 /**
  * 使用javax.mail进行邮件发送
@@ -77,9 +81,9 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
                                 TemplateManager templateManager,
                                 FileManager fileManager) {
         this(properties.getId(),
-             new JSONObject(properties.getConfiguration()).toJavaObject(DefaultEmailProperties.class),
-             templateManager,
-             fileManager);
+            FastBeanCopier.copy(properties.getConfiguration(), new DefaultEmailProperties()),
+            templateManager,
+            fileManager);
 
     }
 
@@ -105,8 +109,8 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
     @Override
     public Mono<Void> send(@Nonnull EmailTemplate template, @Nonnull Values context) {
         return Mono.just(template)
-                   .flatMap(temp -> convert(temp, context.getAllValues()))
-                   .flatMap(this::doSend);
+            .flatMap(temp -> convert(temp, context.getAllValues()))
+            .flatMap(this::doSend);
     }
 
     @Nonnull
@@ -176,7 +180,7 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
                 .get()
                 .uri(resource)
                 .accept(MediaType.APPLICATION_OCTET_STREAM)
-                .exchangeToMono(res->res.bodyToMono(Resource.class));
+                .exchangeToMono(res -> res.bodyToMono(Resource.class));
         } else if (resource.startsWith("data:") && resource.contains(";base64,")) {
             String base64 = resource.substring(resource.indexOf(";base64,") + 8);
             return Mono.just(
@@ -190,43 +194,61 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
             return fileManager
                 .read(resource)
                 .as(DataBufferUtils::join)
-                .map(dataBuffer -> new ByteArrayResource(dataBuffer.asByteBuffer().array()))
-                .onErrorResume(e-> Mono.error(()-> new UnsupportedOperationException("不支持的文件地址:" + resource)))
-                .switchIfEmpty(Mono.error(()-> new UnsupportedOperationException("不支持的文件地址:" + resource)));
+                .map(dataBuffer -> {
+                    try {
+                        ByteBuf buf = dataBuffer instanceof NettyDataBuffer
+                            ? ((NettyDataBuffer) dataBuffer).getNativeBuffer()
+                            : Unpooled.wrappedBuffer(dataBuffer.asByteBuffer());
+                        return new ByteArrayResource(ByteBufUtil.getBytes(buf));
+                    } finally {
+                        DataBufferUtils.release(dataBuffer);
+                    }
+                })
+                .onErrorMap(error -> new UnsupportedOperationException("不支持的文件地址:" + resource, error))
+                .switchIfEmpty(Mono.error(() -> new UnsupportedOperationException("不支持的文件地址:" + resource)));
         }
     }
 
 
     public static Mono<ParsedEmailTemplate> convert(EmailTemplate template, Map<String, Object> context) {
-        return template.getSendTo(context)
-            .map(sendTo -> {
+        return template
+            .getSendTo(context)
+            .flatMapMany(Flux::fromIterable)
+            .map(receiver -> template.render(receiver, context))
+            .collectList()
+            .map(sendToList -> {
                 String subject = template.getSubject();
                 String text = template.getText();
-                if (CollectionUtils.isEmpty(sendTo) || StringUtils.isEmpty(subject) || StringUtils.isEmpty(text)) {
+                if (CollectionUtils.isEmpty(sendToList) || ObjectUtils.isEmpty(subject) || ObjectUtils.isEmpty(text)) {
                     throw new BusinessException("模板内容错误,sendTo, text 或者 subject 不能为空.");
                 }
-                List<String> sendToList = sendTo
-                    .stream()
-                    .map(s -> template.render(s, context))
-                    .collect(Collectors.toList());
+
                 String sendText = template.render(text, context);
                 List<EmailTemplate.Attachment> tempAttachments = template.getAttachments();
-                Map<String, String> attachments = new HashMap<>();
+
+                Map<String, String> attachments = new LinkedHashMap<>();
 
                 if (tempAttachments != null) {
-                    List<EmailTemplate.Attachment> distinctAttachment = tempAttachments
-                        .stream()
-                        .distinct()
-                        .collect(Collectors.toList());
-                    for (EmailTemplate.Attachment tempAttachment : distinctAttachment) {
-                        attachments.put(tempAttachment.getName(), template.get(tempAttachment.getLocation(), EmailTemplate.Attachment.LOCATION_KEY, context));
+                    int index = 0;
+                    for (EmailTemplate.Attachment tempAttachment : tempAttachments) {
+                        index++;
+
+                        String name = template.render(tempAttachment.getName(), context);
+
+                        String location = template.get(tempAttachment.getLocation(), EmailTemplate.Attachment.locationKey(index), context);
+
+                        attachments.put(name, location);
                     }
                 }
 
+                Map<String, String> images = new HashMap<>();
+
+                sendText = extractSendTextImage(sendText, images);
+
                 return ParsedEmailTemplate
                     .builder()
                     .attachments(attachments)
-                    .images(extractSendTextImage(sendText))
+                    .images(images)
                     .text(sendText)
                     .subject(template.render(subject, context))
                     .sendTo(sendToList)
@@ -235,37 +257,25 @@ public class DefaultEmailNotifier extends AbstractNotifier<EmailTemplate> {
     }
 
 
-    private static Map<String, String> extractSendTextImage(String sendText) {
-        Map<String, String> images = new HashMap<>();
+    private static String extractSendTextImage(String sendText, Map<String, String> images) {
+        if (!sendText.contains("<")) {
+            return sendText;
+        }
+        boolean anyImage = false;
+
         Document doc = Jsoup.parse(sendText);
         for (Element src : doc.getElementsByTag("img")) {
             String s = src.attr("src");
             if (s.startsWith("http")) {
                 continue;
             }
+            anyImage = true;
             String tempKey = IDGenerator.MD5.generate();
             src.attr("src", "cid:".concat(tempKey));
             images.put(tempKey, s);
         }
-        return images;
-    }
-
-    private static String render(String str, Map<String, Object> context) {
-        return render(str, context, false);
+        return anyImage ? doc.html() : sendText;
     }
 
-    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");
-    }
 
 }

+ 32 - 24
jetlinks-components/notify-component/notify-email/src/main/java/org/jetlinks/community/notify/email/embedded/EmailTemplate.java

@@ -6,6 +6,7 @@ import org.apache.commons.lang3.StringUtils;
 import org.jetlinks.community.notify.NotifyVariableBusinessConstant;
 import org.jetlinks.community.notify.template.AbstractTemplate;
 import org.jetlinks.community.notify.template.VariableDefinition;
+import org.jetlinks.community.utils.ConverterUtils;
 import org.jetlinks.core.metadata.types.ArrayType;
 import org.jetlinks.core.metadata.types.FileType;
 import org.jetlinks.community.notify.template.Variable;
@@ -18,9 +19,11 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 import java.util.stream.Collectors;
 
 import static org.jetlinks.community.notify.email.embedded.EmailTemplate.Attachment.LOCATION_KEY;
+import static org.jetlinks.community.notify.email.embedded.EmailTemplate.Attachment.locationKey;
 
 @Getter
 @Setter
@@ -37,14 +40,11 @@ public class EmailTemplate extends AbstractTemplate<EmailTemplate> {
     private List<String> sendTo;
 
     public Mono<List<String>> getSendTo(Map<String, Object> context) {
-        return VariableSource.resolveValue(SEND_TO_KEY, context, RelationConstants.UserProperty.email)
-            .map(String::valueOf)
-            .collect(Collectors.joining(","))
-            .filter(StringUtils::isNoneBlank)
-            .map(value -> Arrays.stream(value
-                .split(","))
-                .collect(Collectors.toList()))
-            .switchIfEmpty(Mono.just(sendTo == null ? new ArrayList<>() : sendTo));
+        return VariableSource
+            .resolveValue(SEND_TO_KEY, context, RelationConstants.UserProperty.email)
+            .flatMapIterable(e -> ConverterUtils.convertToList(e, String::valueOf))
+            .concatWith(Mono.justOrEmpty(sendTo).flatMapIterable(Function.identity()))
+            .collectList();
     }
 
     @Getter
@@ -59,6 +59,10 @@ public class EmailTemplate extends AbstractTemplate<EmailTemplate> {
         private String name;
 
         private String location;
+
+        public static String locationKey(int index) {
+            return "_attach_location_" + index;
+        }
     }
 
 
@@ -73,7 +77,7 @@ public class EmailTemplate extends AbstractTemplate<EmailTemplate> {
                     .id(SEND_TO_KEY)
                     .name("收件人")
                     .expand(NotifyVariableBusinessConstant.businessId,
-                            NotifyVariableBusinessConstant.NotifyVariableBusinessTypes.userType)
+                        NotifyVariableBusinessConstant.NotifyVariableBusinessTypes.userType)
                     .required(true)
                     .type(ArrayType.ID)
                     .build()
@@ -82,21 +86,25 @@ public class EmailTemplate extends AbstractTemplate<EmailTemplate> {
 
         List<Attachment> attachments = getAttachments();
         if (!CollectionUtils.isEmpty(attachments)) {
-            variables.addAll(attachments
-                .stream()
-                //附件名称不为空,附件数据为空
-                .filter(attachment -> StringUtils.isNotEmpty(attachment.getName())
-                    && StringUtils.isEmpty(attachment.getLocation()))
-                .map(attachment -> VariableDefinition
-                    .builder()
-                    .id(LOCATION_KEY)
-                    .name(attachment.getName())
-                    .type(FileType.ID)
-                    .description(attachment.getName())
-                    .format(Variable.FileFormat.any)
-                    .required(true)
-                    .build())
-                .collect(Collectors.toList()));
+            int index = 0;
+            for (Attachment attachment : attachments) {
+                index++;
+                if (StringUtils.isNotEmpty(attachment.getName())
+                    && StringUtils.isEmpty(attachment.getLocation())) {
+                    continue;
+                }
+                variables.add(
+                    VariableDefinition
+                        .builder()
+                        .id(locationKey(index))
+                        .name(attachment.getName())
+                        .type(FileType.ID)
+                        .description(attachment.getName())
+                        .format(Variable.FileFormat.any)
+                        .required(true)
+                        .build()
+                );
+            }
         }
         return variables;
     }