Explorar o código

优化异常邮件处理逻辑

woody hai 3 meses
pai
achega
422b33be9a

+ 55 - 46
framework-email/src/main/java/com/chelvc/framework/email/LogbackEmailAppender.java

@@ -4,25 +4,24 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import javax.mail.Session;
 
-import ch.qos.logback.classic.Level;
 import ch.qos.logback.classic.filter.ThresholdFilter;
 import ch.qos.logback.classic.spi.ILoggingEvent;
 import ch.qos.logback.classic.spi.IThrowableProxy;
 import ch.qos.logback.core.AppenderBase;
 import ch.qos.logback.core.Layout;
 import ch.qos.logback.core.spi.FilterReply;
-import com.chelvc.framework.base.context.ApplicationContextHolder;
 import com.chelvc.framework.base.util.SpringUtils;
 import com.chelvc.framework.common.util.StringUtils;
+import com.chelvc.framework.email.config.EmailProperties;
+import com.chelvc.framework.email.context.EmailContextHolder;
 import com.google.common.collect.Maps;
-import lombok.AccessLevel;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
 import lombok.Setter;
+import lombok.ToString;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.BeansException;
-import org.springframework.context.ApplicationContext;
-import org.springframework.context.ApplicationContextAware;
-import org.springframework.stereotype.Component;
 
 /**
  * Logback日志邮件发送器
@@ -31,23 +30,19 @@ import org.springframework.stereotype.Component;
  * @date 2024/5/20
  */
 @Slf4j
-@Setter
-@Component
-public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements ApplicationContextAware {
-    /**
-     * 邮件处理器实例
-     */
-    private static EmailHandler EMAIL_HANDLER;
+public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> {
+    @Setter
+    private EmailConfig email;
 
-    private String to;
-    private String subject;
-    private long idle = 60000;
+    @Setter
     private Layout<ILoggingEvent> layout;
 
+    private final long idle = 60000;
     private final Lock lock = new ReentrantLock();
+    private final String name = this.getClass().getName();
     private final Map<Integer, Long> caches = Maps.newConcurrentMap();
 
-    @Setter(AccessLevel.NONE)
+    private volatile EmailHandler handler;
     private volatile long cleaning = System.currentTimeMillis();
 
     /**
@@ -55,8 +50,8 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
      *
      * @return true/false
      */
-    protected boolean isReady() {
-        return this.layout != null && EMAIL_HANDLER != null && StringUtils.notEmpty(this.to);
+    private boolean isReady() {
+        return this.email != null && this.layout != null;
     }
 
     /**
@@ -64,8 +59,16 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
      *
      * @return 邮件处理器
      */
-    protected EmailHandler getEmailHandler() {
-        return Objects.requireNonNull(EMAIL_HANDLER);
+    private EmailHandler getEmailHandler() {
+        if (this.handler == null) {
+            synchronized (this) {
+                if (this.handler == null) {
+                    Session session = EmailContextHolder.session(this.email);
+                    this.handler = new DefaultEmailHandler(session, this.email);
+                }
+            }
+        }
+        return this.handler;
     }
 
     /**
@@ -74,7 +77,7 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
      * @param event 日志事件对象实例
      * @return hash值
      */
-    protected int hash(ILoggingEvent event) {
+    private int hash(ILoggingEvent event) {
         IThrowableProxy throwable = event.getThrowableProxy();
         int hash = throwable == null ? 0 : Objects.hash((Object[]) throwable.getStackTraceElementProxyArray());
         return 31 * Math.max(hash, 1) + Objects.hashCode(event.getLoggerName());
@@ -86,10 +89,7 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
      * @param event 日志事件对象实例
      * @return true/false
      */
-    protected boolean isDuplicated(ILoggingEvent event) {
-        if (this.idle < 1) {
-            return false;
-        }
+    private boolean isDuplicated(ILoggingEvent event) {
         int hash = this.hash(event);
         long now = System.currentTimeMillis();
         Long timestamp = this.caches.put(hash, now);
@@ -101,23 +101,16 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
      *
      * @param event 日志事件对象实例
      */
-    protected void send(ILoggingEvent event) {
+    private void send(ILoggingEvent event) {
         String type = this.layout.getContentType();
         String content = this.layout.doLayout(event);
-        Body body = Body.builder().to(this.to).type(type).subject(this.subject).content(content).build();
+        Body body = Body.builder().to(this.email.to).type(type).subject(this.email.subject).content(content).build();
         this.getEmailHandler().send(body);
     }
 
-    @Override
-    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
-        EMAIL_HANDLER = applicationContext.getBean(EmailHandler.class);
-    }
-
     @Override
     protected void append(ILoggingEvent event) {
-        boolean enabled = ApplicationContextHolder.getProperty("logback.email.enabled", boolean.class, true);
-        if (!enabled || !this.isReady() || LogbackEmailAppender.class.getName().equals(event.getLoggerName())
-                || (event.getLevel() == Level.ERROR && this.isDuplicated(event))) {
+        if (!this.isReady() || this.name.equals(event.getLoggerName()) || this.isDuplicated(event)) {
             return;
         }
 
@@ -129,21 +122,37 @@ public class LogbackEmailAppender extends AppenderBase<ILoggingEvent> implements
                 log.warn("Email send failed: {}", e.getMessage());
             } finally {
                 // 清理日志缓存
-                if (this.idle > 0) {
-                    long now = System.currentTimeMillis();
-                    if (now - this.cleaning > this.idle && this.lock.tryLock()) {
-                        try {
-                            this.caches.entrySet().removeIf(entry -> now - entry.getValue() > this.idle);
-                            this.cleaning = now;
-                        } finally {
-                            this.lock.unlock();
-                        }
+                long now = System.currentTimeMillis();
+                if (now - this.cleaning > this.idle && this.lock.tryLock()) {
+                    try {
+                        this.caches.entrySet().removeIf(entry -> now - entry.getValue() > this.idle);
+                        this.cleaning = now;
+                    } finally {
+                        this.lock.unlock();
                     }
                 }
             }
         });
     }
 
+    /**
+     * 日志邮件配置
+     */
+    @Data
+    @ToString(callSuper = true)
+    @EqualsAndHashCode(callSuper = true)
+    public static class EmailConfig extends EmailProperties {
+        /**
+         * 收件人
+         */
+        private String to;
+
+        /**
+         * 邮件主题
+         */
+        private String subject;
+    }
+
     /**
      * 日志邮件过滤器实现
      */

+ 4 - 24
framework-email/src/main/java/com/chelvc/framework/email/config/EmailConfigurer.java

@@ -1,13 +1,11 @@
 package com.chelvc.framework.email.config;
 
-import java.util.Properties;
-import javax.mail.Authenticator;
-import javax.mail.PasswordAuthentication;
 import javax.mail.Session;
 
-import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.email.DefaultEmailHandler;
 import com.chelvc.framework.email.EmailHandler;
+import com.chelvc.framework.email.context.EmailContextHolder;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -19,30 +17,12 @@ import org.springframework.context.annotation.Configuration;
  * @date 2024/1/30
  */
 @Configuration
+@ConditionalOnBean(EmailProperties.class)
 public class EmailConfigurer {
     @Bean
     @ConditionalOnMissingBean(Session.class)
     public Session session(EmailProperties properties) {
-        Properties config = new Properties();
-        config.setProperty("mail.transport.protocol", properties.getProtocol());
-        config.setProperty(String.format("mail.%s.port", properties.getProtocol()),
-                String.valueOf(properties.getPort()));
-        config.setProperty(String.format("mail.%s.host", properties.getProtocol()), properties.getHost());
-        config.setProperty(String.format("mail.%s.auth", properties.getProtocol()), StringUtils.TRUE);
-        if (properties.isEncrypted()) {
-            config.setProperty(String.format("mail.%s.ssl.enable", properties.getProtocol()), StringUtils.TRUE);
-            config.setProperty(String.format("mail.%s.ssl.protocols", properties.getProtocol()), "TLSv1.2");
-            config.setProperty(String.format("mail.%s.socketFactory.port", properties.getProtocol()),
-                    String.valueOf(properties.getPort()));
-            config.setProperty(String.format("mail.%s.socketFactory.class", properties.getProtocol()),
-                    "javax.net.ssl.SSLSocketFactory");
-        }
-        return Session.getInstance(config, new Authenticator() {
-            @Override
-            protected PasswordAuthentication getPasswordAuthentication() {
-                return new PasswordAuthentication(properties.getUsername(), properties.getPassword());
-            }
-        });
+        return EmailContextHolder.session(properties);
     }
 
     @Bean

+ 2 - 0
framework-email/src/main/java/com/chelvc/framework/email/config/EmailProperties.java

@@ -1,6 +1,7 @@
 package com.chelvc.framework.email.config;
 
 import lombok.Data;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Configuration;
 
@@ -13,6 +14,7 @@ import org.springframework.context.annotation.Configuration;
 @Data
 @Configuration
 @ConfigurationProperties("email")
+@ConditionalOnProperty(prefix = "email", name = {"host", "username", "password"})
 public class EmailProperties {
     /**
      * 邮件服务器地址

+ 34 - 0
framework-email/src/main/java/com/chelvc/framework/email/context/EmailContextHolder.java

@@ -9,13 +9,17 @@ import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Properties;
 import java.util.regex.Matcher;
 import javax.activation.DataHandler;
 import javax.activation.DataSource;
 import javax.activation.FileDataSource;
 import javax.activation.FileTypeMap;
+import javax.mail.Authenticator;
 import javax.mail.MessagingException;
 import javax.mail.Multipart;
+import javax.mail.PasswordAuthentication;
+import javax.mail.Session;
 import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMultipart;
 import javax.mail.internet.MimeUtility;
@@ -25,6 +29,7 @@ import ch.qos.logback.core.util.ContentTypeUtil;
 import com.chelvc.framework.common.util.ObjectUtils;
 import com.chelvc.framework.common.util.StringUtils;
 import com.chelvc.framework.email.Body;
+import com.chelvc.framework.email.config.EmailProperties;
 import lombok.NonNull;
 
 /**
@@ -46,6 +51,35 @@ public final class EmailContextHolder {
     private EmailContextHolder() {
     }
 
+    /**
+     * 构建邮件会话
+     *
+     * @param properties 配置属性
+     * @return 邮件会话实例
+     */
+    public static Session session(@NonNull EmailProperties properties) {
+        Properties config = new Properties();
+        config.setProperty("mail.transport.protocol", properties.getProtocol());
+        config.setProperty(String.format("mail.%s.port", properties.getProtocol()),
+                String.valueOf(properties.getPort()));
+        config.setProperty(String.format("mail.%s.host", properties.getProtocol()), properties.getHost());
+        config.setProperty(String.format("mail.%s.auth", properties.getProtocol()), StringUtils.TRUE);
+        if (properties.isEncrypted()) {
+            config.setProperty(String.format("mail.%s.ssl.enable", properties.getProtocol()), StringUtils.TRUE);
+            config.setProperty(String.format("mail.%s.ssl.protocols", properties.getProtocol()), "TLSv1.2");
+            config.setProperty(String.format("mail.%s.socketFactory.port", properties.getProtocol()),
+                    String.valueOf(properties.getPort()));
+            config.setProperty(String.format("mail.%s.socketFactory.class", properties.getProtocol()),
+                    "javax.net.ssl.SSLSocketFactory");
+        }
+        return Session.getInstance(config, new Authenticator() {
+            @Override
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(properties.getUsername(), properties.getPassword());
+            }
+        });
+    }
+
     /**
      * 将内容转换成邮件内容体
      *