24282 2 anni fa
parent
commit
2bb5e88544
23 ha cambiato i file con 966 aggiunte e 682 eliminazioni
  1. 3 3
      pom.xml
  2. 0 42
      src/main/java/com/fjhx/email/config/TaskPoolConfig.java
  3. 2 2
      src/main/java/com/fjhx/email/controller/MailController.java
  4. 0 5
      src/main/java/com/fjhx/email/entity/EnterpriseMessage.java
  5. 0 5
      src/main/java/com/fjhx/email/entity/PersonalMessage.java
  6. 0 5
      src/main/java/com/fjhx/email/entity/dto/MailFolderInfo.java
  7. 1 6
      src/main/java/com/fjhx/email/entity/dto/MailInfo.java
  8. 0 5
      src/main/java/com/fjhx/email/entity/dto/MailboxInfo.java
  9. 10 2
      src/main/java/com/fjhx/email/entity/vo/MessageDetailVo.java
  10. 1 8
      src/main/java/com/fjhx/email/mapper/xml/MailMapper.xml
  11. 87 121
      src/main/java/com/fjhx/email/service/impl/CoreServiceImpl.java
  12. 16 10
      src/main/java/com/fjhx/email/service/impl/EnterpriseMailboxServiceImpl.java
  13. 169 185
      src/main/java/com/fjhx/email/service/impl/MailServiceImpl.java
  14. 17 4
      src/main/java/com/fjhx/email/service/impl/PersonalMailboxServiceImpl.java
  15. 0 278
      src/main/java/com/fjhx/email/utils/EmailUtil.java
  16. 19 0
      src/main/java/com/fjhx/email/utils/email/AddressTypeEnum.java
  17. 289 0
      src/main/java/com/fjhx/email/utils/email/ImapUtil.java
  18. 1 1
      src/main/java/com/fjhx/email/utils/email/MailFlagEnum.java
  19. 25 0
      src/main/java/com/fjhx/email/utils/email/MessageAddress.java
  20. 37 0
      src/main/java/com/fjhx/email/utils/email/MessageInfo.java
  21. 136 0
      src/main/java/com/fjhx/email/utils/email/SmtpUtil.java
  22. 22 0
      src/main/java/com/fjhx/email/utils/email/TextAndAttachment.java
  23. 131 0
      src/main/resources/logback-spring.xml

+ 3 - 3
pom.xml

@@ -42,8 +42,8 @@
 
         <!-- mail -->
         <dependency>
-            <groupId>org.springframework.boot</groupId>
-            <artifactId>spring-boot-starter-mail</artifactId>
+            <groupId>com.sun.mail</groupId>
+            <artifactId>jakarta.mail</artifactId>
         </dependency>
 
         <!-- mybatis-plus -->
@@ -57,7 +57,7 @@
         <dependency>
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
-            <version>5.8.15</version>
+            <version>5.8.18</version>
         </dependency>
 
         <dependency>

+ 0 - 42
src/main/java/com/fjhx/email/config/TaskPoolConfig.java

@@ -1,42 +0,0 @@
-package com.fjhx.email.config;
-
-import cn.hutool.core.thread.ThreadUtil;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.scheduling.annotation.EnableAsync;
-
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * 线程池配置
- */
-@EnableAsync
-@Configuration
-public class TaskPoolConfig {
-
-    public static final String emailInfoTaskExecutor = "emailInfoTaskExecutor";
-
-    /**
-     * 获取邮件基本信息线程池
-     */
-    @Bean(name = emailInfoTaskExecutor)
-    public ThreadPoolExecutor emailInfoTaskExecutor() {
-        return new ThreadPoolExecutor(
-                10,
-                20,
-                60,
-                TimeUnit.SECONDS,
-                new ArrayBlockingQueue<>(20),
-                (runnable, executor) -> {
-                    if (!executor.isShutdown()) {
-                        // 等待1秒之后重新入线程池
-                        ThreadUtil.sleep(1000);
-                        executor.execute(runnable);
-                    }
-                }
-        );
-    }
-
-}

+ 2 - 2
src/main/java/com/fjhx/email/controller/MailController.java

@@ -15,9 +15,9 @@ import com.fjhx.email.entity.vo.MessageVo;
 import com.fjhx.email.service.IEnterpriseMailboxService;
 import com.fjhx.email.service.IMailService;
 import com.fjhx.email.service.IPersonalMailboxService;
-import com.fjhx.email.utils.EmailUtil;
 import com.fjhx.email.utils.HxHttpUtil;
 import com.fjhx.email.utils.PageWrapper;
+import com.fjhx.email.utils.email.SmtpUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Scheduled;
@@ -131,7 +131,7 @@ public class MailController {
     public R sendMail(@PathVariable("host") String host, @PathVariable("user") String user,
                       @PathVariable("password") String password, @RequestBody SendDto sendDto) {
         try {
-            EmailUtil.sendMail(host, user, password, sendDto);
+            SmtpUtil.sendMail(host, user, password, sendDto);
         } catch (Exception e) {
             log.error("发送邮件失败:", e);
             throw new ServiceException("发送邮件失败");

+ 0 - 5
src/main/java/com/fjhx/email/entity/EnterpriseMessage.java

@@ -56,11 +56,6 @@ public class EnterpriseMessage extends BaseEntity {
     private String fromPersonalName;
 
     /**
-     * 发件类型
-     */
-    private String fromType;
-
-    /**
      * 发件时间
      */
     private Date sendDate;

+ 0 - 5
src/main/java/com/fjhx/email/entity/PersonalMessage.java

@@ -57,11 +57,6 @@ public class PersonalMessage extends BaseEntity {
     private String fromPersonalName;
 
     /**
-     * 发件类型
-     */
-    private String fromType;
-
-    /**
      * 发件时间
      */
     private Date sendDate;

+ 0 - 5
src/main/java/com/fjhx/email/entity/dto/MailFolderInfo.java

@@ -20,11 +20,6 @@ public class MailFolderInfo {
     private String name;
 
     /**
-     * 是否跳过
-     */
-    private Boolean skip;
-
-    /**
      * 上次同步邮件uid
      */
     private Long lastUid;

+ 1 - 6
src/main/java/com/fjhx/email/entity/dto/MailInfo.java

@@ -30,17 +30,12 @@ public class MailInfo {
     private String fromPersonalName;
 
     /**
-     * 发件类型
-     */
-    private String fromType;
-
-    /**
      * 邮件发送时间
      */
     private Date sendDate;
 
     /**
-     * 邮件接收时间
+     * 服务器接收时间
      */
     private Date receivedDate;
 

+ 0 - 5
src/main/java/com/fjhx/email/entity/dto/MailboxInfo.java

@@ -17,11 +17,6 @@ public class MailboxInfo {
     private Integer type;
 
     /**
-     * 是否跳过
-     */
-    private Boolean skip;
-
-    /**
      * id
      */
     private Long id;

+ 10 - 2
src/main/java/com/fjhx/email/entity/vo/MessageDetailVo.java

@@ -10,17 +10,25 @@ import java.util.List;
 public class MessageDetailVo {
 
     /**
+     * 邮件id
+     */
+    private Long messageId;
+
+    /**
      * 邮件关联地址
      */
-    List<MessageAddress> messageAddressList;
+    private List<MessageAddress> messageAddressList;
+
     /**
      * 附件
      */
-    List<MessageAttachment> messageAttachmentList;
+    private List<MessageAttachment> messageAttachmentList;
+
     /**
      * 内容
      */
     private String content;
+
     /**
      * 类型
      */

+ 1 - 8
src/main/java/com/fjhx/email/mapper/xml/MailMapper.xml

@@ -8,7 +8,6 @@
 
         <id column="id" property="id"/>
         <result column="type" property="type"/>
-        <result column="skip" property="skip"/>
         <result column="mail_user" property="mailUser"/>
         <result column="mail_password" property="mailPassword"/>
         <result column="receive_host" property="receiveHost"/>
@@ -22,7 +21,6 @@
 
             <id column="folder_id" property="id"/>
             <result column="folder_name" property="name"/>
-            <result column="skip" property="skip"/>
             <result column="last_uid" property="lastUid"/>
             <result column="last_received_date" property="lastReceivedDate"/>
 
@@ -33,7 +31,6 @@
     <select id="getMailboxInfoListByUserId" resultMap="getMailboxInfoListByUserIdResultMap">
         select
         type,
-        skip,
         id,
         mail_user,
         mail_password,
@@ -46,7 +43,6 @@
         last_received_date
         from (
         (select 1 type,
-        0 skip,
         pm.id,
         pm.mail_user,
         pm.mail_password,
@@ -72,7 +68,6 @@
         )
         union all
         (select 2 type,
-        0 skip,
         em.id,
         CONCAT(em.mail_user_prefix,'@',ed.domain_name) mail_user,
         em.mail_password,
@@ -99,9 +94,7 @@
         and ef.del_flag = 0
         )
         ) t
-        ORDER BY
-        t.id ASC,
-        t.folder_id DESC
+        ORDER BY t.id, t.folder_id DESC
     </select>
 
 </mapper>

+ 87 - 121
src/main/java/com/fjhx/email/service/impl/CoreServiceImpl.java

@@ -5,7 +5,6 @@ import cn.hutool.core.io.IORuntimeException;
 import cn.hutool.core.thread.ThreadUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson2.JSON;
-import com.fjhx.email.config.TaskPoolConfig;
 import com.fjhx.email.config.exception.ServiceException;
 import com.fjhx.email.entity.EnterpriseFolder;
 import com.fjhx.email.entity.EnterpriseMessage;
@@ -16,15 +15,14 @@ import com.fjhx.email.entity.dto.MailInfo;
 import com.fjhx.email.entity.dto.MailSyncInfo;
 import com.fjhx.email.entity.dto.MailboxInfo;
 import com.fjhx.email.service.*;
-import com.fjhx.email.utils.EmailUtil;
 import com.fjhx.email.utils.HxHttpUtil;
+import com.fjhx.email.utils.email.ImapUtil;
 import com.sun.mail.imap.IMAPFolder;
 import com.sun.mail.imap.IMAPMessage;
 import com.sun.mail.imap.IMAPStore;
 import com.sun.mail.util.MailConnectException;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Qualifier;
 import org.springframework.boot.ApplicationArguments;
 import org.springframework.boot.ApplicationRunner;
 import org.springframework.stereotype.Service;
@@ -35,10 +33,7 @@ import org.springframework.transaction.TransactionStatus;
 import javax.mail.*;
 import javax.mail.internet.InternetAddress;
 import java.util.*;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.*;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
@@ -47,22 +42,38 @@ import java.util.stream.Collectors;
 public class CoreServiceImpl implements ApplicationRunner {
 
     /**
-     * 缓存错误次数
-     */
-    private static final Map<Long, Integer> map = new ConcurrentHashMap<>();
-    /**
      * 正在同步的邮箱集合
      */
     private static final List<Long> syncMailList = Collections.synchronizedList(new ArrayList<>());
+
     /**
      * 累计同步次数
      */
     private static final AtomicInteger numCount = new AtomicInteger();
+
     /**
      * 单次同步邮件总次数
      */
     private static final AtomicInteger mailCount = new AtomicInteger();
 
+    /**
+     * 抓取邮件线程池
+     */
+    private static final Executor mailExecutor = new ThreadPoolExecutor(
+            12,
+            36,
+            60,
+            TimeUnit.SECONDS,
+            new ArrayBlockingQueue<>(36),
+            (runnable, executor) -> {
+                if (!executor.isShutdown()) {
+                    // 等待0.5秒之后重新入线程池
+                    ThreadUtil.sleep(500);
+                    executor.execute(runnable);
+                }
+            }
+    );
+
     @Autowired
     private PlatformTransactionManager platformTransactionManager;
 
@@ -70,8 +81,7 @@ public class CoreServiceImpl implements ApplicationRunner {
     private TransactionDefinition transactionDefinition;
 
     @Autowired
-    @Qualifier(TaskPoolConfig.emailInfoTaskExecutor)
-    private Executor emailTaskExecutor;
+    private IMailService mailService;
 
     @Autowired
     private IPersonalFolderService personalFolderService;
@@ -85,9 +95,6 @@ public class CoreServiceImpl implements ApplicationRunner {
     @Autowired
     private IEnterpriseMessageService enterpriseMessageService;
 
-    @Autowired
-    private IMailService mailService;
-
     @Override
     public void run(ApplicationArguments args) {
 
@@ -157,24 +164,17 @@ public class CoreServiceImpl implements ApplicationRunner {
 
         // 开启抓取邮件任务
         for (MailboxInfo mailbox : mailboxInfoList) {
-
             // 开启异步操作
-            emailTaskExecutor.execute(() -> {
-                try {
-                    synchronousMail(mailbox);
-                } catch (Exception e) {
-                    log.error("同步邮件发生未知异常", e);
-                } finally {
-                    countDownLatch.countDown();
-                }
+            mailExecutor.execute(() -> {
+                synchronousMail(mailbox);
+                countDownLatch.countDown();
             });
-
         }
 
         try {
             boolean awaitFlag = countDownLatch.await(MailSyncInfo.awaitTimeout, TimeUnit.MINUTES);
             if (!awaitFlag) {
-                log.error("超时执行");
+                log.error("邮件同步超时执行");
             }
         } catch (InterruptedException e) {
             log.error("countDownLatch.await() 发生异常", e);
@@ -203,10 +203,6 @@ public class CoreServiceImpl implements ApplicationRunner {
 
         Long id = mailboxInfo.getId();
 
-        if (mailboxInfo.getSkip()) {
-            return;
-        }
-
         if (syncMailList.contains(id)) {
             log.error("邮箱 {} 同步超时:{}", mailboxInfo.getMailUser(), JSON.toJSONString(mailboxInfo));
             return;
@@ -220,7 +216,10 @@ public class CoreServiceImpl implements ApplicationRunner {
         try {
 
             // 获取邮件连接
-            store = EmailUtil.getIMAPStore(mailboxInfo);
+            store = ImapUtil.getStore(
+                    mailboxInfo.getReceiveHost(),
+                    mailboxInfo.getMailUser(),
+                    mailboxInfo.getMailPassword());
 
             // 邮件文件夹
             List<MailFolderInfo> mailFolderInfoList = mailboxInfo.getMailFolderInfoList();
@@ -228,11 +227,6 @@ public class CoreServiceImpl implements ApplicationRunner {
             // 分别拉取每个文件夹邮件
             for (MailFolderInfo mailFolder : mailFolderInfoList) {
 
-                // 是否跳过文件夹抓取
-                if (mailFolder.getSkip()) {
-                    continue;
-                }
-
                 // 获取文件夹
                 IMAPFolder folder = (IMAPFolder) store.getFolder(mailFolder.getName());
 
@@ -240,74 +234,52 @@ public class CoreServiceImpl implements ApplicationRunner {
                 try {
                     folder.open(Folder.READ_ONLY);
                 } catch (FolderNotFoundException folderNotFoundException) {
-                    // 文件夹不存在跳过邮件同步
-                    mailFolder.setSkip(true);
                     continue;
                 }
 
                 try {
                     // 获取文件夹邮件
                     Message[] messages = folder.getMessages();
-                    if (messages.length == 0) {
+                    if (messages == null || messages.length == 0) {
                         continue;
                     }
 
                     // 拉取邮件
                     List<MailInfo> mailInfoList = getMailInfoList(folder, mailFolder, messages);
 
+                    if (ObjectUtil.isEmpty(mailInfoList)) {
+                        continue;
+                    }
+
                     // 保存邮件数据,更新文件夹最后同步邮件时间
                     saveMailInfo(mailboxInfo, mailFolder, mailInfoList);
 
-                    // 更新更新最后一封邮件uid以及接收时间
-                    if (ObjectUtil.isNotEmpty(mailInfoList)) {
-                        MailInfo mailInfo = mailInfoList.get(mailInfoList.size() - 1);
-                        mailFolder.setLastUid(mailInfo.getUid());
-                        mailFolder.setLastReceivedDate(mailInfo.getReceivedDate());
-                    }
+                    // 最后一次同步邮件信息
+                    MailInfo mailInfo = mailInfoList.get(mailInfoList.size() - 1);
+                    mailFolder.setLastUid(mailInfo.getUid());
+                    mailFolder.setLastReceivedDate(mailInfo.getReceivedDate());
 
                 } finally {
-                    if (folder.isOpen()) {
-                        folder.close();
-                    }
+                    ImapUtil.closeFolder(folder);
                 }
             }
 
-            // 删除错误次数
-            map.remove(id);
-
-        } catch (AuthenticationFailedException | MailConnectException exception) {
-
-            log.error("连接异常,邮箱信息:{}", JSON.toJSONString(mailboxInfo), exception);
-
-            // 添加异常次数
-            map.merge(id, 1, Integer::sum);
-
-            // 异常次数连续超过n次,不再同步邮件
-            if (map.get(id) >= MailSyncInfo.errorNumber) {
-                mailboxInfo.setSkip(true);
-                map.remove(id);
-            }
-
+        } catch (AuthenticationFailedException e) {
+            log.error("邮箱认证失败:{}", JSON.toJSONString(mailboxInfo), e);
+        } catch (MailConnectException e) {
+            log.error("邮件连接失败:{}", JSON.toJSONString(mailboxInfo), e);
         } catch (Exception e) {
-
             log.error("邮件同步出错,邮箱信息:{}", JSON.toJSONString(mailboxInfo), e);
-
         } finally {
-
             // 同步完成,删除id
             syncMailList.remove(id);
-
-            if (store != null) {
-                try {
-                    if (store.isConnected()) {
-                        store.close();
-                    }
-                } catch (MessagingException e) {
-                    log.error("store关闭失败", e);
-                }
+            try {
+                ImapUtil.closeStore(store);
+            } catch (MessagingException e) {
+                log.error("email链接关闭失败:{}", mailboxInfo);
             }
-
         }
+
     }
 
     /**
@@ -358,19 +330,10 @@ public class CoreServiceImpl implements ApplicationRunner {
             mailInfo.setReceivedDate(receivedDate);
 
             // 保存发件人信息
-            Address[] addresses = message.getFrom();
-            if (addresses != null && addresses.length > 0) {
-                InternetAddress from = (InternetAddress) addresses[0];
-                mailInfo.setFromEmail(from.getAddress());
-                mailInfo.setFromPersonalName(from.getPersonal());
-                mailInfo.setFromType("from");
-            } else {
-                InternetAddress sender = (InternetAddress) message.getSender();
-                if (sender != null) {
-                    mailInfo.setFromEmail(sender.getAddress());
-                    mailInfo.setFromPersonalName(sender.getPersonal());
-                    mailInfo.setFromType("sender");
-                }
+            InternetAddress sender = (InternetAddress) message.getSender();
+            if (sender != null) {
+                mailInfo.setFromEmail(sender.getAddress());
+                mailInfo.setFromPersonalName(sender.getPersonal());
             }
 
             // 添加到邮件列表
@@ -389,11 +352,6 @@ public class CoreServiceImpl implements ApplicationRunner {
      */
     private void saveMailInfo(MailboxInfo mailboxInfo, MailFolderInfo mailFolder, List<MailInfo> mailInfoList) {
 
-        // 未抓取邮件直接跳过保存
-        if (mailInfoList.size() == 0) {
-            return;
-        }
-
         // 获取抓取的最后一封邮件
         MailInfo lastMailInfo = mailInfoList.get(mailInfoList.size() - 1);
 
@@ -401,8 +359,10 @@ public class CoreServiceImpl implements ApplicationRunner {
         TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
 
         try {
+
             // 个人邮箱
             if (mailboxInfo.getType() == 1) {
+
                 PersonalFolder personalFolder = new PersonalFolder();
                 personalFolder.setId(mailFolder.getId());
                 personalFolder.setLastUid(lastMailInfo.getUid());
@@ -418,7 +378,6 @@ public class CoreServiceImpl implements ApplicationRunner {
                     personalMessage.setSubject(mailInfo.getSubject());
                     personalMessage.setFromEmail(mailInfo.getFromEmail());
                     personalMessage.setFromPersonalName(mailInfo.getFromPersonalName());
-                    personalMessage.setFromType(mailInfo.getFromType());
                     personalMessage.setSendDate(mailInfo.getSendDate());
                     personalMessage.setReceivedDate(mailInfo.getReceivedDate());
                     personalMessage.setSyncStatus(0);
@@ -426,41 +385,48 @@ public class CoreServiceImpl implements ApplicationRunner {
                     return personalMessage;
                 }).collect(Collectors.toList());
                 personalMessageService.saveBatch(personalMessageList);
+
             }
             // 企业邮箱
             else {
-                    EnterpriseFolder enterpriseFolder = new EnterpriseFolder();
-                    enterpriseFolder.setId(mailFolder.getId());
-                    enterpriseFolder.setLastUid(lastMailInfo.getUid());
-                    enterpriseFolder.setLastReceivedDate(lastMailInfo.getReceivedDate());
-                    enterpriseFolderService.updateById(enterpriseFolder);
-
-                    List<EnterpriseMessage> enterpriseMessageList = mailInfoList.stream().map(mailInfo -> {
-                        EnterpriseMessage enterpriseMessage = new EnterpriseMessage();
-                        enterpriseMessage.setUid(mailInfo.getUid());
-                        enterpriseMessage.setMailboxId(mailboxInfo.getId());
-                        enterpriseMessage.setFolderId(mailFolder.getId());
-                        enterpriseMessage.setFolderName(mailFolder.getName());
-                        enterpriseMessage.setSubject(mailInfo.getSubject());
-                        enterpriseMessage.setFromEmail(mailInfo.getFromEmail());
-                        enterpriseMessage.setFromPersonalName(mailInfo.getFromPersonalName());
-                        enterpriseMessage.setFromType(mailInfo.getFromType());
-                        enterpriseMessage.setSendDate(mailInfo.getSendDate());
-                        enterpriseMessage.setReceivedDate(mailInfo.getReceivedDate());
-                        enterpriseMessage.setSyncStatus(0);
-                        enterpriseMessage.setDelFlag(0);
-                        return enterpriseMessage;
-                    }).collect(Collectors.toList());
-                    enterpriseMessageService.saveBatch(enterpriseMessageList);
+
+                EnterpriseFolder enterpriseFolder = new EnterpriseFolder();
+                enterpriseFolder.setId(mailFolder.getId());
+                enterpriseFolder.setLastUid(lastMailInfo.getUid());
+                enterpriseFolder.setLastReceivedDate(lastMailInfo.getReceivedDate());
+                enterpriseFolderService.updateById(enterpriseFolder);
+
+                List<EnterpriseMessage> enterpriseMessageList = mailInfoList.stream().map(mailInfo -> {
+                    EnterpriseMessage enterpriseMessage = new EnterpriseMessage();
+                    enterpriseMessage.setUid(mailInfo.getUid());
+                    enterpriseMessage.setMailboxId(mailboxInfo.getId());
+                    enterpriseMessage.setFolderId(mailFolder.getId());
+                    enterpriseMessage.setFolderName(mailFolder.getName());
+                    enterpriseMessage.setSubject(mailInfo.getSubject());
+                    enterpriseMessage.setFromEmail(mailInfo.getFromEmail());
+                    enterpriseMessage.setFromPersonalName(mailInfo.getFromPersonalName());
+                    enterpriseMessage.setSendDate(mailInfo.getSendDate());
+                    enterpriseMessage.setReceivedDate(mailInfo.getReceivedDate());
+                    enterpriseMessage.setSyncStatus(0);
+                    enterpriseMessage.setDelFlag(0);
+                    return enterpriseMessage;
+                }).collect(Collectors.toList());
+                enterpriseMessageService.saveBatch(enterpriseMessageList);
+
             }
 
             // 提交事务
             platformTransactionManager.commit(transactionStatus);
+
         } catch (Exception e) {
+
             // 回滚事务
             platformTransactionManager.rollback(transactionStatus);
-            throw new RuntimeException("保存邮件失败");
+
+            throw new RuntimeException("保存邮件失败", e);
+
         }
+
     }
 
 }

+ 16 - 10
src/main/java/com/fjhx/email/service/impl/EnterpriseMailboxServiceImpl.java

@@ -6,15 +6,15 @@ import com.fjhx.email.config.exception.ServiceException;
 import com.fjhx.email.entity.EnterpriseDomain;
 import com.fjhx.email.entity.EnterpriseFolder;
 import com.fjhx.email.entity.EnterpriseMailbox;
-import com.fjhx.email.entity.dto.MailboxInfo;
 import com.fjhx.email.mapper.EnterpriseMailboxMapper;
 import com.fjhx.email.service.IEnterpriseDomainService;
 import com.fjhx.email.service.IEnterpriseFolderService;
 import com.fjhx.email.service.IEnterpriseMailboxService;
-import com.fjhx.email.utils.EmailUtil;
+import com.fjhx.email.utils.email.ImapUtil;
 import com.sun.mail.imap.IMAPStore;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.mail.MessagingException;
 import java.util.List;
@@ -37,6 +37,7 @@ public class EnterpriseMailboxServiceImpl extends ServiceImpl<EnterpriseMailboxM
     @Autowired
     private IEnterpriseDomainService enterpriseDomainService;
 
+    @Transactional
     @Override
     public void add(EnterpriseMailbox enterpriseMailbox) {
         Long enterpriseMailboxId = IdWorker.getId();
@@ -47,17 +48,16 @@ public class EnterpriseMailboxServiceImpl extends ServiceImpl<EnterpriseMailboxM
         if (enterpriseDomain == null) {
             throw new ServiceException("未找到企业域名");
         }
-
+        IMAPStore store = null;
         try {
 
-            MailboxInfo mailboxInfo = new MailboxInfo();
-            mailboxInfo.setReceiveHost(enterpriseDomain.getReceiveHost());
-            mailboxInfo.setMailUser(enterpriseMailbox.getMailUserPrefix() + "@" + enterpriseDomain.getDomainName());
-            mailboxInfo.setReceivePort(enterpriseDomain.getReceivePort());
-            mailboxInfo.setMailPassword(enterpriseMailbox.getMailPassword());
-            IMAPStore imapStore = EmailUtil.getIMAPStore(mailboxInfo);
+            store = ImapUtil.getStore(
+                    enterpriseDomain.getReceiveHost(),
+                    enterpriseMailbox.getMailUserPrefix() + "@" + enterpriseDomain.getDomainName(),
+                    enterpriseMailbox.getMailPassword());
+
+            List<String> folderNameList = ImapUtil.getFolderNameList(store);
 
-            List<String> folderNameList = EmailUtil.getFolderNameList(imapStore);
             List<EnterpriseFolder> personalFolderList = folderNameList.stream().map(item -> {
                 EnterpriseFolder enterpriseFolder = new EnterpriseFolder();
                 enterpriseFolder.setMailboxId(enterpriseMailboxId);
@@ -72,6 +72,12 @@ public class EnterpriseMailboxServiceImpl extends ServiceImpl<EnterpriseMailboxM
         } catch (MessagingException e) {
             log.error("连接邮箱异常", e);
             throw new ServiceException("连接邮箱异常:" + e.getMessage());
+        } finally {
+            try {
+                ImapUtil.closeStore(store);
+            } catch (MessagingException e) {
+                log.error("连接关闭失败");
+            }
         }
 
         enterpriseMailbox.setId(enterpriseMailboxId);

+ 169 - 185
src/main/java/com/fjhx/email/service/impl/MailServiceImpl.java

@@ -3,7 +3,6 @@ package com.fjhx.email.service.impl;
 import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.util.ObjectUtil;
 import com.alibaba.fastjson2.JSONObject;
-import com.baomidou.mybatisplus.core.toolkit.StringPool;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.fjhx.email.config.base.BaseEntity;
 import com.fjhx.email.config.exception.ServiceException;
@@ -16,9 +15,11 @@ import com.fjhx.email.entity.vo.MessageDetailVo;
 import com.fjhx.email.entity.vo.MessageVo;
 import com.fjhx.email.mapper.MailMapper;
 import com.fjhx.email.service.*;
-import com.fjhx.email.utils.EmailUtil;
 import com.fjhx.email.utils.ObsFileUtil;
 import com.fjhx.email.utils.PageWrapper;
+import com.fjhx.email.utils.email.ImapUtil;
+import com.fjhx.email.utils.email.MessageAddress;
+import com.fjhx.email.utils.email.TextAndAttachment;
 import com.sun.mail.imap.IMAPFolder;
 import com.sun.mail.imap.IMAPMessage;
 import com.sun.mail.imap.IMAPStore;
@@ -30,11 +31,12 @@ import org.springframework.transaction.PlatformTransactionManager;
 import org.springframework.transaction.TransactionDefinition;
 import org.springframework.transaction.TransactionStatus;
 
-import javax.mail.*;
-import javax.mail.internet.MimeUtility;
+import javax.mail.Folder;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Part;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
@@ -49,12 +51,12 @@ public class MailServiceImpl implements IMailService {
     /**
      * 创建一个线程池
      */
-    private static final ExecutorService executorService = new ThreadPoolExecutor(
-            20,
-            50,
-            5,
-            TimeUnit.MINUTES,
-            new ArrayBlockingQueue<>(50),
+    private static final Executor executor = new ThreadPoolExecutor(
+            12,
+            36,
+            60,
+            TimeUnit.SECONDS,
+            new ArrayBlockingQueue<>(36),
             new ThreadPoolExecutor.CallerRunsPolicy()
     );
 
@@ -104,17 +106,21 @@ public class MailServiceImpl implements IMailService {
     @Override
     public PageWrapper<MessageVo> getMessagePage(GetMessagePageDto dto) {
 
-        PageWrapper<MessageVo> wrapper;
+        PageWrapper<MessageVo> pageWrapper;
+        IMAPStore imapStore;
         IMAPFolder folder;
 
+        // 个人邮箱
         if (dto.getType().equals(1)) {
             Page<PersonalMessage> page = personalMessageService.page(dto, q -> q
                     .eq(PersonalMessage::getFolderId, dto.getFolderId())
                     .orderByDesc(PersonalMessage::getSendDate)
             );
-            wrapper = new PageWrapper<>(page, MessageVo.class);
+
+            pageWrapper = new PageWrapper<>(page, MessageVo.class);
+
             if (page.getSize() == 0) {
-                return wrapper;
+                return pageWrapper;
             }
 
             PersonalFolder personalFolder = personalFolderService.getById(dto.getFolderId());
@@ -123,16 +129,25 @@ public class MailServiceImpl implements IMailService {
             }
 
             MailboxInfo mailboxInfo = getMailboxInfo(1, personalFolder.getMailboxId());
-            IMAPStore imapStore = EmailUtil.getIMAPStore(mailboxInfo);
+            imapStore = ImapUtil.getStore(
+                    mailboxInfo.getReceiveHost(),
+                    mailboxInfo.getMailUser(),
+                    mailboxInfo.getMailPassword()
+            );
             folder = (IMAPFolder) imapStore.getFolder(personalFolder.getName());
-        } else {
+        }
+
+        // 企业邮箱
+        else {
             Page<EnterpriseMessage> page = enterpriseMessageService.page(dto, q -> q
                     .eq(EnterpriseMessage::getFolderId, dto.getFolderId())
                     .orderByDesc(EnterpriseMessage::getSendDate)
             );
-            wrapper = new PageWrapper<>(page, MessageVo.class);
+
+            pageWrapper = new PageWrapper<>(page, MessageVo.class);
+
             if (page.getSize() == 0) {
-                return wrapper;
+                return pageWrapper;
             }
 
             EnterpriseFolder enterpriseFolder = enterpriseFolderService.getById(dto.getFolderId());
@@ -141,20 +156,37 @@ public class MailServiceImpl implements IMailService {
             }
 
             MailboxInfo mailboxInfo = getMailboxInfo(2, enterpriseFolder.getMailboxId());
-            IMAPStore imapStore = EmailUtil.getIMAPStore(mailboxInfo);
+            imapStore = ImapUtil.getStore(
+                    mailboxInfo.getReceiveHost(),
+                    mailboxInfo.getMailUser(),
+                    mailboxInfo.getMailPassword()
+            );
             folder = (IMAPFolder) imapStore.getFolder(enterpriseFolder.getName());
         }
 
         folder.open(Folder.READ_ONLY);
 
-        for (MessageVo messageVo : wrapper.getRows()) {
-            Long uid = messageVo.getUid();
-            Message message = folder.getMessageByUID(uid);
-            String flags = EmailUtil.getFlags(message.getFlags());
-            messageVo.setFlags(flags);
+        ArrayList<CompletableFuture<Void>> futureList = new ArrayList<>();
+        for (MessageVo messageVo : pageWrapper.getRows()) {
+            CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
+                Long uid = messageVo.getUid();
+                try {
+                    Message message = folder.getMessageByUID(uid);
+                    String flags = ImapUtil.getFlags(message.getFlags());
+                    messageVo.setFlags(flags);
+                } catch (MessagingException e) {
+                    throw new ServiceException("获取邮件是否已读失败");
+                }
+            }, executor);
+            futureList.add(completableFuture);
         }
 
-        return wrapper;
+        futureList.forEach(CompletableFuture::join);
+
+        ImapUtil.closeFolder(folder);
+        ImapUtil.closeStore(imapStore);
+
+        return pageWrapper;
     }
 
     @SneakyThrows
@@ -163,9 +195,9 @@ public class MailServiceImpl implements IMailService {
         Long messageId = dto.getMessageId();
         Integer type = dto.getType();
 
-        String folderName;
         Long mailboxId;
         Integer syncStatus;
+        String folderName;
         Long uid;
 
         if (type.equals(1)) {
@@ -174,9 +206,9 @@ public class MailServiceImpl implements IMailService {
                 throw new ServiceException("没有找到邮箱");
             }
 
-            folderName = personalMessage.getFolderName();
-            syncStatus = personalMessage.getSyncStatus();
             mailboxId = personalMessage.getMailboxId();
+            syncStatus = personalMessage.getSyncStatus();
+            folderName = personalMessage.getFolderName();
             uid = personalMessage.getUid();
         } else {
             EnterpriseMessage enterpriseMessage = enterpriseMessageService.getById(messageId);
@@ -184,29 +216,39 @@ public class MailServiceImpl implements IMailService {
                 throw new ServiceException("没有找到邮箱");
             }
 
-            folderName = enterpriseMessage.getFolderName();
-            syncStatus = enterpriseMessage.getSyncStatus();
             mailboxId = enterpriseMessage.getMailboxId();
+            syncStatus = enterpriseMessage.getSyncStatus();
+            folderName = enterpriseMessage.getFolderName();
             uid = enterpriseMessage.getUid();
         }
 
         MessageDetailVo messageDetailVo = new MessageDetailVo();
+        messageDetailVo.setMessageId(messageId);
 
         // 已同步
         if (syncStatus.equals(1)) {
+
             if (type.equals(1)) {
                 setPersonalMessageDetailVo(messageDetailVo, messageId);
             } else {
                 setEnterpriseMessageDetailVo(messageDetailVo, messageId);
             }
+
         }
+
         // 未同步
         else {
 
             MailboxInfo mailboxInfo = getMailboxInfo(type, mailboxId);
 
-            IMAPStore imapStore = EmailUtil.getIMAPStore(mailboxInfo);
-            IMAPFolder folder = (IMAPFolder) imapStore.getFolder(folderName);
+            IMAPStore store = ImapUtil.getStore(
+                    mailboxInfo.getReceiveHost(),
+                    mailboxInfo.getMailUser(),
+                    mailboxInfo.getMailPassword()
+            );
+
+            IMAPFolder folder = (IMAPFolder) store.getFolder(folderName);
+
             folder.open(Folder.READ_WRITE);
 
             IMAPMessage message = (IMAPMessage) folder.getMessageByUID(uid);
@@ -215,35 +257,34 @@ public class MailServiceImpl implements IMailService {
                 throw new ServiceException("没有找到邮件详情,此邮件可能已被删除");
             }
 
-            Flags flags = message.getFlags();
-            if (!flags.contains(Flags.Flag.SEEN)) {
-                flags.add(Flags.Flag.SEEN);
-            }
-            message.setFlags(flags, true);
-
-            List<CompletableFuture<MessageDetailVo.MessageAttachment>> list = new ArrayList<>();
-
-            CompletableFuture<Void> attachmentFuture = CompletableFuture.runAsync(() -> {
-
-                getMessageAndAttachment(messageDetailVo, message, messageId, list);
-
-                List<MessageDetailVo.MessageAttachment> messageAttachmentList =
-                        list.stream().map(CompletableFuture::join).collect(Collectors.toList());
+            // 赋值邮件 正文、附件
+            CompletableFuture<Void> setTextAndAttachmentFuture =
+                    CompletableFuture.runAsync(() -> setTextAndAttachment(message, messageDetailVo), executor);
 
-                messageDetailVo.setMessageAttachmentList(messageAttachmentList);
+            // 赋值邮件 收件人、抄送人、密送人、回复人
+            CompletableFuture<Void> setAddressFuture =
+                    CompletableFuture.runAsync(() -> setAddress(message, messageDetailVo), executor);
 
-            }, executorService);
-
-            CompletableFuture<Void> addressFuture = CompletableFuture.runAsync(() ->
-                    messageDetailVo.setMessageAddressList(EmailUtil.mailAddressList(message)), executorService);
+            // 设置已读
+            CompletableFuture<Void> setSeenFuture = CompletableFuture.runAsync(() -> {
+                try {
+                    ImapUtil.setSeen(message);
+                } catch (MessagingException e) {
+                    throw new ServiceException("设置邮件已读错误");
+                }
+            }, executor);
 
-            CompletableFuture.allOf(attachmentFuture, addressFuture).join();
+            // 保存数据
+            setSeenFuture.join();
+            setAddressFuture.join();
+            setTextAndAttachmentFuture.join();
 
-            new Thread(() -> saveMessageDetail(messageId, messageDetailVo, type)).start();
+            // 保存内容
+            executor.execute(() -> saveMessageDetail(messageId, messageDetailVo, type));
 
-            if (folder.isOpen()) {
-                folder.close();
-            }
+            // 关闭连接
+            ImapUtil.closeFolder(folder);
+            ImapUtil.closeStore(store);
         }
 
         // copy一份数据,防止多线程下把文件路径前缀保存到数据库中
@@ -262,22 +303,28 @@ public class MailServiceImpl implements IMailService {
 
         for (MailboxInfo mailboxInfo : mailboxInfoList) {
 
-            IMAPStore imapStore = EmailUtil.getIMAPStore(mailboxInfo);
+            IMAPStore store = ImapUtil.getStore(
+                    mailboxInfo.getReceiveHost(),
+                    mailboxInfo.getMailUser(),
+                    mailboxInfo.getMailPassword()
+            );
 
             for (MailFolderInfo mailFolderInfo : mailboxInfo.getMailFolderInfoList()) {
-                IMAPFolder folder = (IMAPFolder) imapStore.getFolder(mailFolderInfo.getName());
+                IMAPFolder folder = (IMAPFolder) store.getFolder(mailFolderInfo.getName());
                 folder.open(Folder.READ_WRITE);
                 int unreadMessageCount = folder.getUnreadMessageCount();
                 mailFolderInfo.setUnreadMessageCount(unreadMessageCount);
-                if (folder.isOpen()) {
-                    folder.close();
-                }
+                ImapUtil.closeFolder(folder);
             }
 
+            ImapUtil.closeStore(store);
         }
 
     }
 
+    /**
+     * 保存内容
+     */
     private void saveMessageDetail(Long messageId, MessageDetailVo messageDetailVo, Integer type) {
 
         TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
@@ -386,6 +433,9 @@ public class MailServiceImpl implements IMailService {
         return mailboxInfo;
     }
 
+    /**
+     * 保存个人邮件明细
+     */
     private void setPersonalMessageDetailVo(MessageDetailVo vo, Long messageId) {
         List<PersonalMessageAddress> addressList = personalMessageAddressService.list(
                 q -> q.eq(PersonalMessageAddress::getMessageId, messageId));
@@ -400,6 +450,9 @@ public class MailServiceImpl implements IMailService {
         vo.setContent(content.getContent());
     }
 
+    /**
+     * 保存企业邮件明细
+     */
     private void setEnterpriseMessageDetailVo(MessageDetailVo vo, Long messageId) {
         List<EnterpriseMessageAddress> addressList = enterpriseMessageAddressService.list(
                 q -> q.eq(EnterpriseMessageAddress::getMessageId, messageId));
@@ -415,151 +468,82 @@ public class MailServiceImpl implements IMailService {
     }
 
     /**
-     * 保存附件和正文
+     * 获取邮件 收件人、抄送人、密送人、回复人
      */
     @SneakyThrows
-    private void getMessageAndAttachment(MessageDetailVo messageDetailVo,
-                                         Part part,
-                                         Long messageId,
-                                         List<CompletableFuture<MessageDetailVo.MessageAttachment>> completableFutureList) {
-
-        // 正文:文本格式
-        if (part.isMimeType("text/plain")) {
-            addPlain(part, messageDetailVo);
-        }
-
-        // 正文:html格式
-        else if (part.isMimeType("text/html")) {
-            addHtml(part, messageDetailVo);
-        }
-
-        // 正文:html格式
-        else if (part.isMimeType("text/html; charset=UTF-8")) {
-            addHtmlUtf8(part, messageDetailVo);
-        }
-
-        // 大对象
-        else if (part.isMimeType("multipart/*")) {
-
-            // 复杂体邮件
-            Multipart multipart = (Multipart) part.getContent();
-
-            // 复杂体邮件包含多个邮件体
-            int partCount = multipart.getCount();
-
-            for (int i = 0; i < partCount; i++) {
-
-                // 获得复杂体邮件中其中一个邮件体
-                BodyPart bodyPart = multipart.getBodyPart(i);
-
-                // 某一个邮件体也有可能是由多个邮件体组成的复杂体
-                String disposition = bodyPart.getDisposition();
-
-                // 正文:html格式
-                if (bodyPart.isMimeType("text/html")) {
-                    addHtml(bodyPart, messageDetailVo);
-                }
-
-                // 正文:文本格式
-                if (bodyPart.isMimeType("text/plain")) {
-                    addPlain(bodyPart, messageDetailVo);
-                }
+    private void setAddress(IMAPMessage message, MessageDetailVo messageDetailVo) {
+
+        List<MessageAddress> messageAddressList = ImapUtil.getMessageAddressList(message);
+
+        List<MessageDetailVo.MessageAddress> list = messageAddressList
+                .stream()
+                .map(item -> {
+                    MessageDetailVo.MessageAddress messageAddress = new MessageDetailVo.MessageAddress();
+                    messageAddress.setType(item.getType());
+                    messageAddress.setEmail(item.getEmail());
+                    messageAddress.setPersonalName(item.getName());
+                    return messageAddress;
+                })
+                .collect(Collectors.toList());
+
+        messageDetailVo.setMessageAddressList(list);
+    }
 
-                // 正文:html格式
-                else if (part.isMimeType("text/html; charset=UTF-8")) {
-                    addHtmlUtf8(part, messageDetailVo);
-                }
+    /**
+     * 获取邮件正文以及附件
+     */
+    @SneakyThrows
+    private void setTextAndAttachment(IMAPMessage message, MessageDetailVo vo) {
 
-                // 附件
-                else if (disposition != null && (disposition.equalsIgnoreCase(Part.ATTACHMENT) || disposition.equalsIgnoreCase(Part.INLINE))) {
-                    completableFutureList.add(CompletableFuture.supplyAsync(() -> getMessageAttachment(bodyPart), executorService));
-                }
+        List<CompletableFuture<Void>> futureList = new ArrayList<>();
 
-                // 大对象
-                else if (bodyPart.isMimeType("multipart/*")) {
-                    getMessageAndAttachment(messageDetailVo, bodyPart, messageId, completableFutureList);
-                }
+        List<MessageDetailVo.MessageAttachment> attachmentList = new ArrayList<>();
 
-                // 其他类型
-                else {
-                    String contentType = bodyPart.getContentType();
-                    if (contentType.contains("name") || contentType.contains("application")) {
-                        completableFutureList.add(CompletableFuture.supplyAsync(() -> getMessageAttachment(bodyPart), executorService));
+        ImapUtil.setTextAndAttachment(message, new TextAndAttachment() {
 
-                    } else {
-                        if (!contentType.equals("text/html; charset=UTF-8")) {
-                            log.error("未知文件格式:{} ,邮件id:{},待解析", contentType, messageId);
-                        }
+            @Override
+            public void setPlain(String plain) {
+                if (plain != null) {
+                    if (ObjectUtil.notEqual(vo.getMimeType(), "text/html")) {
+                        vo.setContent(plain);
+                        vo.setMimeType("text/plain");
                     }
                 }
+            }
 
+            @Override
+            public void setHtml(String html) {
+                vo.setContent(html);
+                vo.setMimeType("text/html");
             }
-        }
 
-        // rfc822
-        else if (part.isMimeType("message/rfc822")) {
-            getMessageAndAttachment(messageDetailVo, (Part) part.getContent(), messageId, completableFutureList);
-        }
+            @Override
+            public void setAttachment(String fileName, InputStream is) {
 
-        // 未知格式
-        else {
-            log.error("未知文件格式:{} ,邮件id:{},待解析", part.getContentType(), messageId);
-        }
+                CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
 
-    }
+                    String url = ObsFileUtil.uploadFile(fileName, is);
+                    MessageDetailVo.MessageAttachment messageAttachment = new MessageDetailVo.MessageAttachment();
+                    messageAttachment.setUrl(url);
+                    messageAttachment.setName(fileName);
+                    attachmentList.add(messageAttachment);
 
-    @SneakyThrows
-    private MessageDetailVo.MessageAttachment getMessageAttachment(Part part) {
-        InputStream is = part.getInputStream();
-        String fileName = decodeText(part.getFileName());
+                }, executor);
 
-        String url = ObsFileUtil.uploadFile(fileName, is);
+                futureList.add(future);
 
-        MessageDetailVo.MessageAttachment messageAttachment = new MessageDetailVo.MessageAttachment();
-        messageAttachment.setUrl(url);
-        messageAttachment.setName(fileName);
-        return messageAttachment;
-    }
+            }
 
-    private void addHtml(Part part, MessageDetailVo messageDetailVo) throws MessagingException, IOException {
-        Object content = part.getContent();
-        if (content != null) {
-            messageDetailVo.setContent(content.toString());
-        } else {
-            messageDetailVo.setContent(StringPool.EMPTY);
-        }
-        messageDetailVo.setMimeType("text/html");
-    }
+            @Override
+            public void unknown(Part part) throws MessagingException, IOException {
+                log.error("id为 {} 的邮件存在未知类型:{}", vo.getMessageId(), part.getContentType());
+            }
 
-    private void addHtmlUtf8(Part part, MessageDetailVo messageDetailVo) throws MessagingException, IOException {
-        Object content = part.getContent();
-        if (content != null) {
-            messageDetailVo.setContent(content.toString());
-        } else {
-            messageDetailVo.setContent(StringPool.EMPTY);
-        }
-        messageDetailVo.setMimeType("text/html; charset=UTF-8");
-    }
+        });
 
-    private void addPlain(Part part, MessageDetailVo messageDetailVo) throws MessagingException, IOException {
-        Object content = part.getContent();
-        if (content != null) {
-            if (ObjectUtil.notEqual(messageDetailVo.getMimeType(), "text/html")) {
-                messageDetailVo.setContent(content.toString());
-                messageDetailVo.setMimeType("text/plain");
-            }
-        }
-    }
+        futureList.forEach(CompletableFuture::join);
+        vo.setMessageAttachmentList(attachmentList);
 
-    /**
-     * 文本解码
-     */
-    private String decodeText(String encodeText) throws UnsupportedEncodingException {
-        if (encodeText == null || StringPool.EMPTY.equals(encodeText)) {
-            return StringPool.EMPTY;
-        } else {
-            return MimeUtility.decodeText(encodeText);
-        }
     }
 
 }

+ 17 - 4
src/main/java/com/fjhx/email/service/impl/PersonalMailboxServiceImpl.java

@@ -9,10 +9,11 @@ import com.fjhx.email.entity.dto.MailboxInfo;
 import com.fjhx.email.mapper.PersonalMailboxMapper;
 import com.fjhx.email.service.IPersonalFolderService;
 import com.fjhx.email.service.IPersonalMailboxService;
-import com.fjhx.email.utils.EmailUtil;
+import com.fjhx.email.utils.email.ImapUtil;
 import com.sun.mail.imap.IMAPStore;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 import javax.mail.MessagingException;
 import java.util.List;
@@ -32,10 +33,11 @@ public class PersonalMailboxServiceImpl extends ServiceImpl<PersonalMailboxMappe
     @Autowired
     private IPersonalFolderService personalFolderService;
 
+    @Transactional
     @Override
     public void add(PersonalMailbox personalMailbox) {
         Long personalMailboxId = IdWorker.getId();
-
+        IMAPStore store = null;
         try {
 
             MailboxInfo mailboxInfo = new MailboxInfo();
@@ -43,9 +45,14 @@ public class PersonalMailboxServiceImpl extends ServiceImpl<PersonalMailboxMappe
             mailboxInfo.setReceivePort(personalMailbox.getReceivePort());
             mailboxInfo.setMailUser(personalMailbox.getMailUser());
             mailboxInfo.setMailPassword(personalMailbox.getMailPassword());
-            IMAPStore imapStore = EmailUtil.getIMAPStore(mailboxInfo);
 
-            List<String> folderNameList = EmailUtil.getFolderNameList(imapStore);
+            store = ImapUtil.getStore(
+                    mailboxInfo.getReceiveHost(),
+                    mailboxInfo.getMailUser(),
+                    mailboxInfo.getMailPassword()
+            );
+
+            List<String> folderNameList = ImapUtil.getFolderNameList(store);
             List<PersonalFolder> personalFolderList = folderNameList.stream().map(item -> {
                 PersonalFolder personalFolder = new PersonalFolder();
                 personalFolder.setMailboxId(personalMailboxId);
@@ -60,6 +67,12 @@ public class PersonalMailboxServiceImpl extends ServiceImpl<PersonalMailboxMappe
         } catch (MessagingException e) {
             log.error("连接邮箱异常", e);
             throw new ServiceException("连接邮箱异常:" + e.getMessage());
+        } finally {
+            try {
+                ImapUtil.closeStore(store);
+            } catch (MessagingException e) {
+                log.error("连接关闭失败");
+            }
         }
 
         personalMailbox.setId(personalMailboxId);

+ 0 - 278
src/main/java/com/fjhx/email/utils/EmailUtil.java

@@ -1,278 +0,0 @@
-package com.fjhx.email.utils;
-
-import cn.hutool.core.util.ObjectUtil;
-import com.baomidou.mybatisplus.core.toolkit.IdWorker;
-import com.baomidou.mybatisplus.core.toolkit.StringPool;
-import com.fjhx.email.entity.dto.MailboxInfo;
-import com.fjhx.email.entity.dto.ObsFile;
-import com.fjhx.email.entity.dto.SendDto;
-import com.fjhx.email.entity.vo.MessageDetailVo;
-import com.fjhx.email.enums.MailFlagEnum;
-import com.sun.mail.imap.IMAPFolder;
-import com.sun.mail.imap.IMAPStore;
-import com.sun.mail.smtp.SMTPMessage;
-import com.sun.mail.util.MailSSLSocketFactory;
-import lombok.SneakyThrows;
-
-import javax.activation.DataHandler;
-import javax.mail.*;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMultipart;
-import javax.mail.internet.MimeUtility;
-import java.io.UnsupportedEncodingException;
-import java.net.URL;
-import java.util.*;
-
-public class EmailUtil {
-
-    private static final Properties properties;
-
-    private static final Session session;
-
-    private static final HashMap<String, String> storeId = new HashMap<>();
-
-    static {
-
-        properties = new Properties();
-
-        // 开启ssl
-        properties.put("mail.imap.ssl.enable", true);
-
-        // 套接字连接超时值(毫秒)。此超时由java.net.Socket实现。默认是无限超时。
-        properties.put("mail.imap.connectiontimeout", 1000 * 60);
-
-        // 套接字读取超时值(毫秒)。此超时由java.net.Socket实现。默认是无限超时。
-        properties.put("mail.imap.timeout", 1000 * 60);
-
-        // 套接字写超时值,单位为毫秒。此超时是通过使用java.util.concurrent实现的。
-        // ScheduledExecutorService每个连接,调度线程在超时过期时关闭套接字。
-        // 因此,使用此超时的开销是每个连接一个线程。默认是无限超时。
-        properties.put("mail.imap.writetimeout", 1000 * 60);
-
-        session = Session.getInstance(properties);
-
-        storeId.put("name", "fjhx");
-        storeId.put("version", "1.0");
-        storeId.put("vendor", "fjhxClient");
-        storeId.put("support-email", "fjhx@fjhx.com");
-    }
-
-    // @SneakyThrows
-    // public static void main(String[] args) {
-    //     MailboxInfo mailboxInfo = new MailboxInfo();
-    //     mailboxInfo.setReceiveHost("imap.gmail.com");
-    //     mailboxInfo.setReceivePort(993);
-    //     mailboxInfo.setMailUser("y2428241269@gmail.com");
-    //     mailboxInfo.setMailPassword("upoujrquwweqtsuk");
-    //     IMAPStore imapStore = getIMAPStore(mailboxInfo);
-    //     System.out.println();
-    // }
-
-    // public static void main(String[] args) throws Exception {
-    //     ObsFile attachment = new ObsFile();
-    //     attachment.setFileUrl("https://os.winfaster.cn/fjhx/saas/GrandStar/2023/03/08/png/3092592f2d614d83854fad5e4c107143.png");
-    //     attachment.setFileName("board-1.png");
-    //     SendDto.Address address = new SendDto.Address();
-    //     address.setAddress("2428241269@qq.com");
-    //     SendDto sendDto = new SendDto();
-    //     sendDto.setTo(Collections.singletonList(address));
-    //     sendDto.setFileList(Collections.singletonList(attachment));
-    //     sendDto.setSubject("测试标题");
-    //     sendDto.setContent("测试内容");
-    //     sendMail("imap.qq.com", "2428241269@qq.com", "kdypajdrwfhtdihb", sendDto);
-    // }
-
-    /**
-     * 获取imapStore
-     * 993
-     */
-    public static IMAPStore getIMAPStore(MailboxInfo mailboxInfo) throws MessagingException {
-
-        IMAPStore store = (IMAPStore) session.getStore("imap");
-
-        store.connect(
-                mailboxInfo.getReceiveHost(),
-                mailboxInfo.getReceivePort(),
-                mailboxInfo.getMailUser(),
-                mailboxInfo.getMailPassword());
-
-        store.id(storeId);
-
-        return store;
-    }
-
-    /**
-     * 获取收件人、抄送人、密送人、回复人
-     */
-    @SneakyThrows
-    public static List<MessageDetailVo.MessageAddress> mailAddressList(Message message) {
-        Address[] to = message.getRecipients(Message.RecipientType.TO);
-        Address[] cc = message.getRecipients(Message.RecipientType.CC);
-        Address[] bcc = message.getRecipients(Message.RecipientType.BCC);
-        Address[] replyTo = message.getReplyTo();
-
-        List<MessageDetailVo.MessageAddress> mailAddressList = new ArrayList<>();
-        addMailAddressList(mailAddressList, to, 1);
-        addMailAddressList(mailAddressList, cc, 2);
-        addMailAddressList(mailAddressList, bcc, 3);
-        addMailAddressList(mailAddressList, replyTo, 4);
-        return mailAddressList;
-    }
-
-    /**
-     * 获取标签
-     */
-    public static String getFlags(Flags flags) {
-        if (flags == null) {
-            return StringPool.EMPTY;
-        }
-
-        Flags.Flag[] systemFlags = flags.getSystemFlags();
-        if (systemFlags.length == 0) {
-            return StringPool.EMPTY;
-        }
-
-        StringJoiner stringJoiner = new StringJoiner(",");
-        for (Flags.Flag systemFlag : systemFlags) {
-            stringJoiner.add(MailFlagEnum.getType(systemFlag));
-        }
-        return stringJoiner.toString();
-    }
-
-    /**
-     * 获取邮箱文件夹
-     */
-    public static List<String> getFolderNameList(IMAPStore imapStore) throws MessagingException {
-        IMAPFolder folder = (IMAPFolder) imapStore.getDefaultFolder();
-        Folder[] folders = folder.list();
-        List<String> mailFolderList = new ArrayList<>();
-        addMailFolderList(mailFolderList, folders);
-        return mailFolderList;
-    }
-
-    /**
-     * 发送邮件
-     */
-    public static void sendMail(String host, String user, String password, SendDto sendDto) throws Exception {
-
-        // QQ存在一个特性设置SSL加密
-        MailSSLSocketFactory sf = new MailSSLSocketFactory();
-        sf.setTrustAllHosts(true);
-
-        // 创建一个配置文件并保存
-        Properties properties = new Properties();
-        properties.put("mail.host", host);
-        properties.put("mail.user", user);
-        properties.put("mail.password", password);
-        properties.put("mail.transport.protocol", "smtp");
-        properties.put("mail.smtp.auth", true);
-        properties.put("mail.smtp.ssl.enable", true);
-        properties.put("mail.smtp.ssl.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
-
-        // 创建一个session对象
-        Session session = Session.getInstance(properties);
-
-        // 获取连接对象
-        Transport transport = session.getTransport();
-
-        // 创建邮件对象
-        SMTPMessage mimeMessage = new SMTPMessage(session);
-
-        // 邮件发送人
-        mimeMessage.setFrom(new InternetAddress(user));
-
-        List<SendDto.Address> to = sendDto.getTo();
-        if (ObjectUtil.isNotEmpty(to)) {
-            mimeMessage.setRecipients(Message.RecipientType.TO, getAddresses(to));
-        }
-
-        List<SendDto.Address> cc = sendDto.getCc();
-        if (ObjectUtil.isNotEmpty(cc)) {
-            mimeMessage.setRecipients(Message.RecipientType.CC, getAddresses(cc));
-        }
-
-        List<SendDto.Address> bcc = sendDto.getBcc();
-        if (ObjectUtil.isNotEmpty(bcc)) {
-            mimeMessage.setRecipients(Message.RecipientType.BCC, getAddresses(bcc));
-        }
-
-        List<SendDto.Address> replyTo = sendDto.getReplyTo();
-        if (ObjectUtil.isNotEmpty(replyTo)) {
-            mimeMessage.setReplyTo(getAddresses(replyTo));
-        }
-
-        // 邮件标题
-        mimeMessage.setSubject(sendDto.getSubject());
-
-        // 邮件内容大对象
-        MimeMultipart mimeMultipart = new MimeMultipart();
-
-        // 对象内添加正文
-        BodyPart content = new MimeBodyPart();
-        content.setContent(sendDto.getContent(), "text/html;charset=UTF-8");
-        mimeMultipart.addBodyPart(content);
-
-        // 对象内添加邮件
-        List<ObsFile> fileList = sendDto.getFileList();
-        if (ObjectUtil.isNotEmpty(fileList)) {
-            for (ObsFile attachment : fileList) {
-                DataHandler dataHandler = new DataHandler(new URL(attachment.getFileUrl()));
-
-                MimeBodyPart mimeBodyPart = new MimeBodyPart();
-                mimeBodyPart.setDataHandler(dataHandler);
-                mimeBodyPart.setContentID(IdWorker.getIdStr());
-                mimeBodyPart.setFileName(MimeUtility.encodeText(attachment.getFileName()));
-                mimeMultipart.addBodyPart(mimeBodyPart);
-            }
-        }
-
-        // 添加邮件内容
-        mimeMessage.setContent(mimeMultipart);
-
-        // 连接服务器
-        transport.connect(host, user, password);
-        // 发送邮件
-        transport.sendMessage(mimeMessage, new Address[]{new InternetAddress(user)});
-        // 关闭连接
-        transport.close();
-    }
-
-    private static void addMailAddressList(List<MessageDetailVo.MessageAddress> mailAddressList, Address[] addresses, Integer type) {
-        if (addresses == null) {
-            return;
-        }
-        for (Address address : addresses) {
-            InternetAddress internetAddress = (InternetAddress) address;
-
-            MessageDetailVo.MessageAddress mailAddress = new MessageDetailVo.MessageAddress();
-            mailAddress.setType(type);
-            mailAddress.setEmail(internetAddress.getAddress());
-            mailAddress.setPersonalName(internetAddress.getPersonal());
-            mailAddressList.add(mailAddress);
-        }
-    }
-
-    private static InternetAddress[] getAddresses(List<SendDto.Address> addressList) throws UnsupportedEncodingException {
-        InternetAddress[] internetAddresses = new InternetAddress[addressList.size()];
-        for (int i = 0; i < addressList.size(); i++) {
-            InternetAddress internetAddress = new InternetAddress();
-            internetAddress.setPersonal(addressList.get(i).getPersonal(), "utf-8");
-            internetAddress.setAddress(addressList.get(i).getAddress());
-            internetAddresses[i] = internetAddress;
-        }
-        return internetAddresses;
-    }
-
-    private static void addMailFolderList(List<String> mailFolderList, Folder[] folders) throws MessagingException {
-        for (Folder folder : folders) {
-            int type = folder.getType();
-            if (type == 2) {
-                addMailFolderList(mailFolderList, folder.list());
-            } else if (type == 3) {
-                mailFolderList.add(folder.getFullName());
-            }
-        }
-    }
-
-}

+ 19 - 0
src/main/java/com/fjhx/email/utils/email/AddressTypeEnum.java

@@ -0,0 +1,19 @@
+package com.fjhx.email.utils.email;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@Getter
+@AllArgsConstructor
+public enum AddressTypeEnum {
+
+    TO(1, "收件人"),
+    CC(2, "抄件人"),
+    BCC(3, "密件人"),
+    REPLY_TO(4, "回复人"),
+    ;
+
+    private final Integer key;
+    private final String value;
+
+}

+ 289 - 0
src/main/java/com/fjhx/email/utils/email/ImapUtil.java

@@ -0,0 +1,289 @@
+package com.fjhx.email.utils.email;
+
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.core.toolkit.StringPool;
+import com.sun.mail.imap.IMAPFolder;
+import com.sun.mail.imap.IMAPMessage;
+import com.sun.mail.imap.IMAPStore;
+
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeUtility;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.*;
+
+public class ImapUtil {
+
+    private static final Properties properties;
+
+    private static final Session session;
+
+    private static final HashMap<String, String> storeId;
+
+    static {
+
+        properties = new Properties();
+
+        // 开启ssl
+        properties.put("mail.imap.ssl.enable", true);
+
+        // 套接字连接超时值(毫秒)。此超时由java.net.Socket实现。默认是无限超时。
+        properties.put("mail.imap.connectiontimeout", 1000 * 60);
+
+        // 套接字读取超时值(毫秒)。此超时由java.net.Socket实现。默认是无限超时。
+        properties.put("mail.imap.timeout", 1000 * 60);
+
+        // 套接字写超时值,单位为毫秒。此超时是通过使用java.util.concurrent实现的。
+        // ScheduledExecutorService每个连接,调度线程在超时过期时关闭套接字。
+        // 因此,使用此超时的开销是每个连接一个线程。默认是无限超时。
+        properties.put("mail.imap.writetimeout", 1000 * 60);
+
+        session = Session.getInstance(properties);
+
+        storeId = new HashMap<>(4);
+        storeId.put("name", "fly");
+        storeId.put("version", "1.0");
+        storeId.put("vendor", "flyClient");
+        storeId.put("support-email", "my@fly.com");
+
+    }
+
+    // public static void main(String[] args) throws Exception {
+    //
+    //     IMAPStore imapStore = getIMAPStore("imap.qq.com", "2428241269@qq.com", "kdypajdrwfhtdihb");
+    //     // IMAPStore imapStore = getIMAPStore("imap.163.com", "18706065630@163.com", "CNZGQOIFETVQCZYV");
+    //     // IMAPStore imapStore = getIMAPStore("imap.gmail.com", "y2428241269@gmail.com", "upoujrquwweqtsuk");
+    //
+    //     IMAPFolder folder = (IMAPFolder) imapStore.getFolder("INBOX");
+    //     folder.open(Folder.READ_ONLY);
+    //     Message[] messages = folder.getMessages();
+    //
+    //     Message message = messages[messages.length - 4];
+    //
+    //     setTextAndAttachment(message, new TextAndAttachment() {
+    //
+    //         @Override
+    //         public void setPlain(String plain) {
+    //
+    //         }
+    //
+    //         @Override
+    //         public void setHtml(String html) {
+    //             // System.out.println(html);
+    //         }
+    //
+    //         @Override
+    //         public void setAttachment(String fileName, InputStream is) {
+    //
+    //             System.out.println("setAttachment");
+    //
+    //             FileUtil.writeFromStream(is, "E:\\" + fileName);
+    //
+    //         }
+    //
+    //         @Override
+    //         public void unknown(Part part) throws MessagingException {
+    //
+    //             System.out.println("This is an unknown type:" + part.getContentType());
+    //         }
+    //
+    //     });
+    //
+    //     System.out.println("end");
+    //
+    // }
+
+    /**
+     * @param host     连接地址
+     * @param user     用户名
+     * @param password 密码
+     * @return 连接信息
+     */
+    public static IMAPStore getStore(String host, String user, String password) throws MessagingException {
+        IMAPStore store = (IMAPStore) session.getStore("imap");
+        store.connect(host, user, password);
+        store.id(storeId);
+        return store;
+    }
+
+    /**
+     * 获取邮箱文件夹
+     *
+     * @param imapStore 连接信息
+     * @return 文件夹列表
+     */
+    public static List<String> getFolderNameList(IMAPStore imapStore) throws MessagingException {
+        IMAPFolder folder = (IMAPFolder) imapStore.getDefaultFolder();
+        Folder[] folders = folder.list();
+        List<String> mailFolderList = new ArrayList<>();
+        addMailFolderList(mailFolderList, folders);
+        return mailFolderList;
+    }
+
+    /**
+     * 设置邮件已读
+     *
+     * @param message 邮件
+     */
+    public static void setSeen(Message message) throws MessagingException {
+        Flags flags = message.getFlags();
+        if (!flags.contains(Flags.Flag.SEEN)) {
+            flags.add(Flags.Flag.SEEN);
+            message.setFlags(flags, true);
+        }
+    }
+
+    /**
+     * 获取标签
+     */
+    public static String getFlags(Flags flags) {
+        if (flags == null) {
+            return StringPool.EMPTY;
+        }
+
+        Flags.Flag[] systemFlags = flags.getSystemFlags();
+        if (systemFlags.length == 0) {
+            return StringPool.EMPTY;
+        }
+
+        StringJoiner stringJoiner = new StringJoiner(",");
+        for (Flags.Flag systemFlag : systemFlags) {
+            stringJoiner.add(MailFlagEnum.getType(systemFlag));
+        }
+        return stringJoiner.toString();
+    }
+
+    /**
+     * 获取邮件 收件人、抄送人、密送人、回复人
+     *
+     * @param message 邮件
+     * @return 邮件 收件人、抄送人、密送人、回复人
+     */
+    public static List<MessageAddress> getMessageAddressList(Message message) throws MessagingException {
+        Address[] to = message.getRecipients(Message.RecipientType.TO);
+        Address[] cc = message.getRecipients(Message.RecipientType.CC);
+        Address[] bcc = message.getRecipients(Message.RecipientType.BCC);
+        Address[] replyTo = message.getReplyTo();
+
+        List<MessageAddress> mailAddressList = new ArrayList<>();
+        addMailAddressList(mailAddressList, to, AddressTypeEnum.TO.getKey());
+        addMailAddressList(mailAddressList, cc, AddressTypeEnum.CC.getKey());
+        addMailAddressList(mailAddressList, bcc, AddressTypeEnum.BCC.getKey());
+        addMailAddressList(mailAddressList, replyTo, AddressTypeEnum.REPLY_TO.getKey());
+        return mailAddressList;
+    }
+
+    /**
+     * 赋值邮件基本信息
+     */
+    public static void setInfo(IMAPFolder folder, IMAPMessage message, MessageInfo messageInfo) throws MessagingException {
+        messageInfo.setUid(folder.getUID(message));
+        messageInfo.setSubject(message.getSubject());
+        messageInfo.setSentDate(message.getSentDate());
+        messageInfo.setReceivedDate(message.getReceivedDate());
+        InternetAddress sender = (InternetAddress) message.getSender();
+        if (sender != null) {
+            messageInfo.setSenderAddress(sender.getAddress());
+            messageInfo.setSenderPersonal(sender.getPersonal());
+        }
+    }
+
+    /**
+     * 赋值邮件正文以及附件
+     */
+    public static void setTextAndAttachment(Part part, TextAndAttachment textAndAttachment) throws MessagingException, IOException {
+        if (part.isMimeType("text/plain")) {
+            textAndAttachment.setPlain((String) part.getContent());
+        } else if (part.isMimeType("text/html")) {
+            textAndAttachment.setHtml((String) part.getContent());
+        } else if (part.isMimeType("multipart/*")) {
+            Multipart mp = (Multipart) part.getContent();
+            for (int i = 0; i < mp.getCount(); i++) {
+                setTextAndAttachment(mp.getBodyPart(i), textAndAttachment);
+            }
+        } else if (part.isMimeType("message/rfc822")) {
+            setTextAndAttachment((Part) part.getContent(), textAndAttachment);
+        } else {
+            Object obj = part.getContent();
+            if (obj instanceof InputStream) {
+                String fileName = decodeText(part.getFileName());
+                textAndAttachment.setAttachment(fileName, (InputStream) obj);
+            } else {
+                textAndAttachment.unknown(part);
+            }
+        }
+    }
+
+    /**
+     * 关闭文件夹连接
+     *
+     * @param folder 文件夹信息
+     */
+    public static void closeFolder(IMAPFolder folder) throws MessagingException {
+        if (folder == null) {
+            return;
+        }
+        if (folder.isOpen()) {
+            folder.close();
+        }
+    }
+
+    /**
+     * 关闭邮箱连接
+     *
+     * @param store 邮件连接
+     */
+    public static void closeStore(IMAPStore store) throws MessagingException {
+        if (store == null) {
+            return;
+        }
+        if (store.isConnected()) {
+            store.close();
+        }
+    }
+
+    /**
+     * 文本解码
+     */
+    private static String decodeText(String encodeText) throws UnsupportedEncodingException {
+        if (StrUtil.isBlank(encodeText)) {
+            return StrUtil.EMPTY;
+        } else {
+            return MimeUtility.decodeText(encodeText);
+        }
+    }
+
+    /**
+     * 添加文件夹名称
+     */
+    private static void addMailFolderList(List<String> mailFolderList, Folder[] folders) throws MessagingException {
+        for (Folder folder : folders) {
+            if (folder.getType() == Folder.HOLDS_FOLDERS) {
+                addMailFolderList(mailFolderList, folder.list());
+            } else {
+                mailFolderList.add(folder.getFullName());
+            }
+        }
+    }
+
+    /**
+     * 添加邮件地址
+     */
+    private static void addMailAddressList(List<MessageAddress> mailAddressList, Address[] addresses, int type) {
+        if (addresses == null) {
+            return;
+        }
+        for (Address address : addresses) {
+            InternetAddress internetAddress = (InternetAddress) address;
+
+            MessageAddress mailAddress = new MessageAddress();
+            mailAddress.setType(type);
+            mailAddress.setEmail(internetAddress.getAddress());
+            mailAddress.setName(internetAddress.getPersonal());
+            mailAddressList.add(mailAddress);
+        }
+    }
+
+}

+ 1 - 1
src/main/java/com/fjhx/email/enums/MailFlagEnum.java → src/main/java/com/fjhx/email/utils/email/MailFlagEnum.java

@@ -1,4 +1,4 @@
-package com.fjhx.email.enums;
+package com.fjhx.email.utils.email;
 
 import lombok.AllArgsConstructor;
 import lombok.Getter;

+ 25 - 0
src/main/java/com/fjhx/email/utils/email/MessageAddress.java

@@ -0,0 +1,25 @@
+package com.fjhx.email.utils.email;
+
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class MessageAddress {
+
+    /**
+     * 类型 1收件人(to) 2抄送人(cc) 3密送人(bcc) 4回复人(replyTo)
+     */
+    private Integer type;
+
+    /**
+     * email地址
+     */
+    private String email;
+
+    /**
+     * email名称
+     */
+    private String name;
+
+}

+ 37 - 0
src/main/java/com/fjhx/email/utils/email/MessageInfo.java

@@ -0,0 +1,37 @@
+package com.fjhx.email.utils.email;
+
+import java.util.Date;
+
+public interface MessageInfo {
+
+    /**
+     * uid
+     */
+    void setUid(long uid);
+
+    /**
+     * 标题
+     */
+    void setSubject(String subject);
+
+    /**
+     * 发送日期
+     */
+    void setSentDate(Date sentDate);
+
+    /**
+     * 服务器接收日期
+     */
+    void setReceivedDate(Date receivedDate);
+
+    /**
+     * 发件人地址
+     */
+    void setSenderAddress(String address);
+
+    /**
+     * 发件人名称
+     */
+    void setSenderPersonal(String personal);
+
+}

+ 136 - 0
src/main/java/com/fjhx/email/utils/email/SmtpUtil.java

@@ -0,0 +1,136 @@
+package com.fjhx.email.utils.email;
+
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.fjhx.email.entity.dto.ObsFile;
+import com.fjhx.email.entity.dto.SendDto;
+import com.sun.mail.smtp.SMTPMessage;
+
+import javax.activation.DataHandler;
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimeUtility;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Properties;
+
+public class SmtpUtil {
+
+    // public static void main(String[] args) throws Exception {
+    //     SendDto.FileInfo attachment = new SendDto.FileInfo();
+    //     attachment.setFileUrl("https://os.winfaster.cn/fjhx/saas/GrandStar/2023/03/08/png/3092592f2d614d83854fad5e4c107143.png");
+    //     attachment.setFileName("board-1.png");
+    //
+    //     SendDto.Address address = new SendDto.Address();
+    //     address.setAddress("18706065630@163.com");
+    //
+    //     SendDto.Address bcc = new SendDto.Address();
+    //     bcc.setAddress("2428241269@qq.com");
+    //
+    //     SendDto sendDto = new SendDto();
+    //     sendDto.setTo(Collections.singletonList(address));
+    //     sendDto.setBcc(Collections.singletonList(bcc));
+    //     sendDto.setFileInfoList(Collections.singletonList(attachment));
+    //     sendDto.setSubject("测试标题");
+    //     sendDto.setContent("测试内容");
+    //
+    //     sendMail("imap.qq.com", "2428241269@qq.com", "kdypajdrwfhtdihb", sendDto);
+    // }
+
+    /**
+     * 发送邮件
+     */
+    public static void sendMail(String host, String user, String password, SendDto sendDto) throws Exception {
+
+        // 创建一个配置文件并保存
+        Properties properties = new Properties();
+        properties.put("mail.smtp.host", host);
+        properties.put("mail.smtp.auth", true);
+        properties.put("mail.smtp.ssl.enable", true);
+
+        // auth认证
+        Authenticator authenticator = new Authenticator() {
+            @Override
+            protected PasswordAuthentication getPasswordAuthentication() {
+                return new PasswordAuthentication(user, password);
+            }
+        };
+
+        // 创建一个session对象
+        Session session = Session.getInstance(properties, authenticator);
+
+        // 创建邮件对象
+        SMTPMessage mimeMessage = new SMTPMessage(session);
+
+        // 邮件发送人
+        mimeMessage.setFrom(new InternetAddress(user));
+
+        List<SendDto.Address> to = sendDto.getTo();
+        if (ObjectUtil.isNotEmpty(to)) {
+            mimeMessage.setRecipients(Message.RecipientType.TO, getAddresses(to));
+        }
+
+        List<SendDto.Address> cc = sendDto.getCc();
+        if (ObjectUtil.isNotEmpty(cc)) {
+            mimeMessage.setRecipients(Message.RecipientType.CC, getAddresses(cc));
+        }
+
+        List<SendDto.Address> bcc = sendDto.getBcc();
+        if (ObjectUtil.isNotEmpty(bcc)) {
+            mimeMessage.setRecipients(Message.RecipientType.BCC, getAddresses(bcc));
+        }
+
+        List<SendDto.Address> replyTo = sendDto.getReplyTo();
+        if (ObjectUtil.isNotEmpty(replyTo)) {
+            mimeMessage.setReplyTo(getAddresses(replyTo));
+        }
+
+        // 邮件标题
+        mimeMessage.setSubject(sendDto.getSubject());
+
+        // 邮件内容大对象
+        MimeMultipart mimeMultipart = new MimeMultipart();
+
+        // 对象内添加正文
+        MimeBodyPart content = new MimeBodyPart();
+        content.setContent(sendDto.getContent(), "text/html; charset=utf-8");
+        mimeMultipart.addBodyPart(content);
+
+        // 对象内添加邮件
+        List<ObsFile> fileInfoList = sendDto.getFileList();
+        if (ObjectUtil.isNotEmpty(fileInfoList)) {
+            for (ObsFile attachment : fileInfoList) {
+                DataHandler dataHandler = new DataHandler(new URL(attachment.getFileUrl()));
+                MimeBodyPart mimeBodyPart = new MimeBodyPart();
+                mimeBodyPart.setDataHandler(dataHandler);
+                mimeBodyPart.setContentID(IdWorker.getIdStr());
+                mimeBodyPart.setFileName(MimeUtility.encodeText(attachment.getFileName()));
+                mimeMultipart.addBodyPart(mimeBodyPart);
+            }
+        }
+
+        // 添加邮件内容
+        mimeMessage.setContent(mimeMultipart);
+
+        // 发送
+        Transport.send(mimeMessage);
+
+    }
+
+
+    private static InternetAddress[] getAddresses(List<SendDto.Address> addressList) throws UnsupportedEncodingException {
+        InternetAddress[] internetAddresses = new InternetAddress[addressList.size()];
+        for (int i = 0; i < addressList.size(); i++) {
+            InternetAddress internetAddress = new InternetAddress();
+            internetAddress.setPersonal(addressList.get(i).getPersonal(), StandardCharsets.UTF_8.name());
+            internetAddress.setAddress(addressList.get(i).getAddress());
+            internetAddresses[i] = internetAddress;
+        }
+        return internetAddresses;
+    }
+
+}

+ 22 - 0
src/main/java/com/fjhx/email/utils/email/TextAndAttachment.java

@@ -0,0 +1,22 @@
+package com.fjhx.email.utils.email;
+
+
+import javax.mail.MessagingException;
+import javax.mail.Part;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * 赋值正文以及附件方法
+ */
+public interface TextAndAttachment {
+
+    void setPlain(String plain);
+
+    void setHtml(String html);
+
+    void setAttachment(String fileName, InputStream is);
+
+    void unknown(Part part) throws MessagingException, IOException;
+
+}

+ 131 - 0
src/main/resources/logback-spring.xml

@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+    <!--日志格式应用spring boot默认的格式,也可以自己更改-->
+    <include resource="org/springframework/boot/logging/logback/defaults.xml"/>
+
+    <!--定义日志存放的位置,默认存放在项目启动的相对路径的目录-->
+    <springProperty scope="context" name="LOG_PATH" source="log.path" defaultValue="mail-logs"/>
+
+    <!--本地开发只在控制台打印日志-->
+    <springProfile name="dev">
+
+        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+            <encoder>
+                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+                <charset>utf-8</charset>
+            </encoder>
+        </appender>
+
+        <!--默认所有的包以info-->
+        <root level="info">
+            <appender-ref ref="STDOUT"/>
+        </root>
+
+        <!--各个服务的包在本地执行的时候,打开debug模式-->
+        <logger name="com.fly" level="info" additivity="false">
+            <appender-ref ref="STDOUT"/>
+        </logger>
+
+    </springProfile>
+
+    <!--  放到服务器上不管在什么环境都只在文件记录日志,控制台(catalina.out)打印logback捕获不到的日志  -->
+    <springProfile name="!dev">
+
+        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+            <encoder>
+                <pattern>${CONSOLE_LOG_PATTERN}</pattern>
+                <charset>utf-8</charset>
+            </encoder>
+        </appender>
+
+        <!-- 日志记录器,日期滚动记录 -->
+        <appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
+
+            <!-- 正在记录的日志文件的路径及文件名 -->
+            <file>${LOG_PATH}/log_error.log</file>
+
+            <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+
+                <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
+                <fileNamePattern>${LOG_PATH}/error/log-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+
+                <!--
+                    配置了日志文件不能超过10M
+                    若超过10M,日志文件会以索引0开始,命名日志文件,例如log-error-2013-12-21.0.log
+                -->
+                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                    <maxFileSize>10MB</maxFileSize>
+                </timeBasedFileNamingAndTriggeringPolicy>
+
+                <!-- 日志最大的历史30天 -->
+                <maxHistory>30</maxHistory>
+
+            </rollingPolicy>
+
+            <!-- 追加方式记录日志 -->
+            <append>true</append>
+
+            <!-- 日志文件的格式 -->
+            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+                <pattern>${FILE_LOG_PATTERN}</pattern>
+                <charset>utf-8</charset>
+            </encoder>
+
+            <!-- 此日志文件只记录error级别的 -->
+            <filter class="ch.qos.logback.classic.filter.LevelFilter">
+                <level>error</level>
+                <onMatch>ACCEPT</onMatch>
+                <onMismatch>DENY</onMismatch>
+            </filter>
+
+        </appender>
+
+        <!-- 日志记录器,日期滚动记录 -->
+        <appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
+
+            <!-- 正在记录的日志文件的路径及文件名 -->
+            <file>${LOG_PATH}/log_total.log</file>
+
+            <!-- 日志记录器的滚动策略,按日期,按大小记录 -->
+            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
+
+                <!-- 归档的日志文件的路径,%d{yyyy-MM-dd}指定日期格式,%i指定索引 -->
+                <fileNamePattern>${LOG_PATH}/total/log-total-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+
+                <!--
+                    配置了日志文件不能超过10M
+                    若超过10M,日志文件会以索引0开始,命名日志文件,例如log-error-2013-12-21.0.log
+                -->
+                <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
+                    <maxFileSize>10MB</maxFileSize>
+                </timeBasedFileNamingAndTriggeringPolicy>
+
+                <!-- 日志最大的历史 30天 -->
+                <maxHistory>30</maxHistory>
+
+            </rollingPolicy>
+
+            <!-- 追加方式记录日志 -->
+            <append>true</append>
+
+            <!-- 日志文件的格式 -->
+            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
+                <pattern>${FILE_LOG_PATTERN}</pattern>
+                <charset>utf-8</charset>
+            </encoder>
+
+        </appender>
+
+        <!--记录到文件时,记录两类一类是error日志,一个是所有日志-->
+        <root level="info">
+            <appender-ref ref="STDOUT"/>
+            <appender-ref ref="FILE_ERROR"/>
+            <appender-ref ref="FILE_ALL"/>
+        </root>
+
+    </springProfile>
+
+</configuration>
+
+