整合SpringMail连接qq邮箱

一、邮箱协议

连接邮箱有smtp、pop3、imap三种协议。

  1. smtp协议:主要用于发信。全称“Simple Mail Transfer Protocol”,即简单邮件传输协议。相当于中转站,将邮件发送到客户端。
  2. pop3协议:连接邮箱接收信件。称“Post Office Protocol 3”,POP3允许用户将服务器上的邮件下载到本地计算机上,同时删除邮件服务器上的邮件,pop3协议是接受邮件的协议。
  3. imap:连接邮箱接收信件全称“Internet Mail Access Protocol”,即交互式邮件存取协议,跟pop协议类似。但是可以与邮箱服务器及时交互,IMAP协议比POP3协议复杂的多,也是按照C/S的工作方式,现在较新的版本是IMAP4。

二、配置属性

连接邮箱需要配置相应的属性:

  • 协议
  • host
  • username
  • password
  • port
  • auth

在yml中配置如下设置:

spring:
  	mail:
      host: smtp.qq.com #个人这样就行了,企业邮箱smtp.exmail.qq.com
      username: xxxxxxxxx@qq.com
      password: xxxxxxxxx
      properties:
        mail:
          smtp:
            port: 587
            starttls:
              enable: true
              required: true
            ssl: true
          pop3:
            protocol: pop3
            host: pop.qq.com #个人这样就行了,企业邮箱pop.exmail.qq.com
            port: 110 #qq官方写的是995,但是实测只能110
            auth: true

即发件使用smtp、收件使用pop3协议;使用imap会在最后补充。

三、pom.xml的配置

<!-- mail -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- thymeleaf模板引擎 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 模板引擎 代码生成 -->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.1</version>
</dependency>
<!--避坑包-->
<dependency>
    <groupId>net.sourceforge.nekohtml</groupId>
    <artifactId>nekohtml</artifactId>
    <version>1.9.22</version>
</dependency>

mail的pom是必须的。

如果要使用模板发送就需要下面三个pom。

  • thymeleaf模板引擎,可以再项目里增加html文件,并动态的往里塞数据,最后通过邮件发出去。
  • org.apache.velocity是StringUtils.join(x,y);方法的由来,不明确是否需要。
  • net.sourceforge.nekohtml论坛老哥的闭坑包,不确定是否需要,但是项目中确实有这两个pom↓
<!--读取csv-->
<dependency>
    <groupId>net.sourceforge.javacsv</groupId>
    <artifactId>javacsv</artifactId>
    <version>2.0</version>
</dependency>

<!--读取文件编码-->
<dependency>
    <groupId>net.sourceforge.jchardet</groupId>
    <artifactId>jchardet</artifactId>
    <version>1.0</version>
</dependency>

四、发邮件(使用javaMail实现)

@Test
public void sendmail() {
    MailModel mailModel = new MailModel();
    // 设置收件人
    mailModel.setToMailList(Lists.newArrayList("1053886691@qq.com"));
    // 设置标题
    mailModel.setTitle("测试邮箱发送=w=!");
    // 设置标题
    mailModel.setContent("这里是内容!");

    String messageId = JSON.toJSONString("神秘的messageId,>w<");

    // 设置messageId
    mailModel.setMessageId(messageId);

    mailUtil.sendEmail(mailModel, false);
    System.out.println("邮件成功发送");
}

五、监听退信收信

package com.kcidea.erms.service.task.impl;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.kcidea.erms.common.constant.Constant;
import com.kcidea.erms.common.constant.Vm;
import com.kcidea.erms.common.exception.CustomException;
import com.kcidea.erms.common.util.*;
import com.kcidea.erms.dao.database.DatabaseContactMessageRelDao;
import com.kcidea.erms.dao.task.ErmsTaskDao;
import com.kcidea.erms.dao.task.ErmsTaskRecordDao;
import com.kcidea.erms.domain.task.ErmsTask;
import com.kcidea.erms.domain.task.ErmsTaskRecord;
import com.kcidea.erms.enums.common.EnumTrueFalse;
import com.kcidea.erms.enums.message.EnumMessageType;
import com.kcidea.erms.enums.task.EnumTaskState;
import com.kcidea.erms.enums.task.EnumTaskType;
import com.kcidea.erms.model.database.notify.MessageIdInfoModel;
import com.kcidea.erms.model.task.*;
import com.kcidea.erms.service.common.BaseService;
import com.kcidea.erms.service.message.MessageService;
import com.kcidea.erms.service.task.BackMailService;
import com.kcidea.erms.service.task.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Session;
import javax.mail.Store;
import javax.mail.internet.MimeMessage;
import javax.mail.search.AndTerm;
import javax.mail.search.ComparisonTerm;
import javax.mail.search.SearchTerm;
import javax.mail.search.SentDateTerm;
import java.io.File;
import java.text.MessageFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.stream.Collectors;

/**
 * @author majuehao
 * @version 1.0
 * @date 2022/03/07
 **/
@Slf4j
@Service
public class BackMailServiceImpl extends BaseService implements BackMailService {

    @Value("${my-config.temp-path}")
    private String TEMP_PATH;

    @Value("${spring.mail.username}")
    private String username;

    @Value("${spring.mail.password}")
    private String password;

    @Value("${spring.mail.properties.mail.pop3.protocol}")
    private String protocol;

    @Value("${spring.mail.properties.mail.pop3.host}")
    private String host;

    @Value("${spring.mail.properties.mail.pop3.port}")
    private String port;

    @Value("${spring.mail.properties.mail.pop3.auth}")
    private String auth;

    @Resource
    private MessageService messageService;

    @Resource
    private TaskService taskService;

    @Resource
    private ErmsTaskRecordDao taskRecordDao;

    @Resource
    private ErmsTaskDao taskDao;

    @Resource
    private DatabaseContactMessageRelDao databaseContactMessageRelDao;


    /**
     * 执行定时提醒退信任务
     *
     * @author majuehao
     * @date 2022/3/7 16:58
     **/
    @Override
    public void executePayRemindTask() {
        // 连接邮箱
        Store store = this.connect();

        if (store == null) {
            throw new CustomException("连接邮箱失败!");
        }

        // 获取符合时间的邮件
        Message[] messages = getMessagesByDate(store);

        if (messages == null) {
            log.info("暂无邮件!");
            return;
        }

        // 根据主题筛选qq的退信邮件
        messages = getMessagesBySubject(messages);

        // 获取附件中的信件信息
        List<BackMailModel> list = EmailUtil.getMessageId(messages);

        if (CollectionUtils.isEmpty(list)) {
            log.info("未能找到相关退信!");
            return;
        }

        // 过滤掉不属于ERMS、退信任务类型以及信息不全的信件
        list = filterMail(list);

        // 根据任务id成组
        Map<Long, List<BackMailModel>> listMap = list.stream()
                .collect(Collectors.groupingBy(x -> x.getMessageIdInfo().getTaskId(),
                        Collectors.mapping(x -> x, Collectors.toList())));

        String title = "退信提醒";
        String content = "尊敬的ERMS用户,您好!检测到通知库商邮件被退信,点击此处查看详情";

        for (Map.Entry<Long, List<BackMailModel>> entry : listMap.entrySet()) {
            List<BackMailModel> mailList = entry.getValue();
            Long sid = mailList.get(0).getMessageIdInfo().getSid();
            Long userId = mailList.get(0).getMessageIdInfo().getUserId();

            // 创建任务
            createTask(entry.getKey(), sid, userId, entry.getValue());

            // 发送站内信
            messageService.addMessageList(sid, Sets.newHashSet(userId), title, content,
                    Constant.Message.TASK_BACK_MAIL_URL, EnumMessageType.退信提醒.getValue());
        }
    }

    /**
     * 过滤掉不属于ERMS和退信任务类型的信件
     *
     * @param list 信件列表
     * @return 过滤后的列表
     * @author majuehao
     * @date 2022/3/10 16:28
     **/
    private List<BackMailModel> filterMail(List<BackMailModel> list) {
        List<BackMailModel> result = Lists.newArrayList();
        for (BackMailModel backMailModel : list) {
            MessageIdInfoModel infoModel = backMailModel.getMessageIdInfo();
            if (infoModel == null) {
                continue;
            }
            if (!Objects.equals(infoModel.getSystem(), Constant.Mail.MESSAGE_ID_SYSTEM)) {
                continue;
            }
            if (!Objects.equals(infoModel.getTaskType(), EnumTaskType.退信通知.getValue())) {
                continue;
            }
            if (null == infoModel.getTaskId() || null == infoModel.getSid() || null == infoModel.getUserId() ||
                    null == infoModel.getDid() || null == infoModel.getMessageId()) {
                continue;
            }
            result.add(backMailModel);
        }
        return result;
    }

    /**
     * 创建任务
     *
     * @param sid    学校id
     * @param userId 用户id
     * @author majuehao
     * @date 2022/3/10 9:40
     **/
    private void createTask(Long taskId, Long sid, Long userId, List<BackMailModel> list) {
        List<BackMailExcelModel> excelList = Lists.newArrayList();
        log.info("创建任务...");

        // 遍历邮件列表
        for (BackMailModel mailModel : list) {
            MessageIdInfoModel info = mailModel.getMessageIdInfo();

            Long did = info.getDid();
            Long messageId = info.getMessageId();

            // 数据库名称
            String databaseName = super.findDatabaseName(sid, did);

            List<BackMailReasonModel> backMailReasonList = mailModel.getBackMailReasonList();
            for (BackMailReasonModel backMailReasonModel : backMailReasonList) {
                // 发信时间
                String sendTim = getSendTime(sid, did, messageId);
                excelList.add(new BackMailExcelModel().create(databaseName, backMailReasonModel.getTo(), sendTim,
                        backMailReasonModel.getReason()));
            }

        }

        // 根据退信中的任务id找到之前执行的任务
        TaskInfoModel mailTask = taskService.findOneTask(taskId, sid);
        if (mailTask == null) {
            throw new CustomException(Vm.ERROR_PARAMS);
        }

        // 任务名称(之前的任务时间+名称+退信通知)
        String taskName = DateTimeUtil.localDateTimeToString(mailTask.getStartTime()).concat(mailTask.getTaskName())
                .concat(Constant.SplitChar.LINE_CHAR).concat(EnumTaskType.退信通知.getName());

        ErmsTask task = taskService.addTask(sid, taskName, EnumTaskType.退信通知.getValue(),
                EnumTaskState.正在执行.getValue(), JSON.toJSONString(taskName), null, null, userId,
                Boolean.TRUE);
        ErmsTaskRecord taskRecord = new ErmsTaskRecord().setTaskId(task.getId()).setStartTime(LocalDateTime.now());

        // 生成文件名
        String resultFileName = System.currentTimeMillis() + "-result" + Constant.Suffix.XLSX_WITH_POINT;

        // 总数/成功数
        int totalCount = excelList.size();
        try {
            String resultFilePath = saveFile(excelList, resultFileName);

            String taskLog = MessageFormat.format("共{0}条数据,成功:{1},失败:{2},警告:{3},您可以下载结果文件查看详细结果。",
                    Integer.toString(totalCount), Integer.toString(totalCount),
                    Integer.toString(0), Integer.toString(0));

            TaskResultModel taskResultModel = new TaskResultModel();
            //实际导入数量是 成功+警告
            taskResultModel.create(EnumTaskState.执行完成.getValue(), totalCount,
                    totalCount, 0, resultFileName, resultFilePath, taskLog);

            //更新任务状态
            task.updateByTaskResult(taskResultModel);

            //更新任务结果日志
            taskRecord.setTaskLog(taskLog);
            log.info("任务执行成功");
        } catch (Exception ex) {
            log.error("执行数据库信息导入任务失败,原因:" + ex.getMessage());
            //更新任务状态
            task.setTaskState(EnumTaskState.执行失败.getValue());
            //更新任务结果
            taskRecord.setTaskLog(Vm.TASK_ERROR.concat(ex.getMessage()));

        } finally {
            //任务结果保存
            taskRecord.setEndTime(LocalDateTime.now());
            taskRecord.setCreatedBy(userId);
            taskRecord.setCreatedTime(LocalDateTime.now());
            taskRecordDao.insert(taskRecord);

            //更新任务
            taskDao.updateById(task);
        }

    }

    /**
     * 获取发送时间
     *
     * @param sid       学校id
     * @param did       数据库id
     * @param messageId 信息id
     * @return 发送时间
     * @author majuehao
     * @date 2022/3/10 21:08
     **/
    private String getSendTime(Long sid, Long did, Long messageId) {
        LocalDateTime date = databaseContactMessageRelDao.findSendTimeBySidDidMessageId(sid, did, messageId);
        if (date != null) {
            return DateTimeUtil.localDateTimeToString(date);
        }
        return "";
    }

    /**
     * 保存文件
     *
     * @param exportList 导出文件数据
     * @param fileName   文件名
     * @return java.lang.String
     * @author majuehao
     * @date 2022/3/10 9:40
     **/
    private String saveFile(List<BackMailExcelModel> exportList, String fileName) {
        //当前月
        String nowMonth = DateTimeUtil.localDateToString(LocalDate.now(), Constant.Pattern.MONTH);

        //保存的路径加上月份
        String filePath = TEMP_PATH.concat(nowMonth).concat("/");
        ExcelUtil.saveExcel(exportList, "", "退信信息详情", BackMailExcelModel.class, filePath, fileName);

        //读取本地文件
        File file = new File(filePath.concat(fileName));

        //上传到文件服务器
        return MinioFileUtil.uploadFile(Constant.MinIoBucketName.ERMS, file, EnumTrueFalse.是.getValue());
    }


    /**
     * 根据邮件主题筛选邮件列表
     *
     * @param messages 邮件列表
     * @return 邮件列表
     * @author majuehao
     * @date 2022/3/8 9:33
     **/
    private Message[] getMessagesBySubject(Message[] messages) {
        Message[] messagesBySubject = new Message[messages.length];
        int index = 0;
        for (Message message : messages) {
            try {
                MimeMessage mimeMessage = (MimeMessage) message;
                String subject = ConvertUtil.getSubChinese(mimeMessage.getSubject(),
                        Constant.Encode.ISO_8859_1);
                if (Objects.equals(Constant.Mail.SUBJECT_KEY_WORD, subject)) {
                    messagesBySubject[index++] = message;
                }
            } catch (Exception e) {
                log.error("获取邮件主题失败");
            }
        }
        return messagesBySubject;
    }

    /**
     * 获取符合条件的邮件
     *
     * @param store 邮箱
     * @return javax.mail.Message[]
     * @author majuehao
     * @date 2022/3/7 17:29
     **/
    private Message[] getMessagesByDate(Store store) {
        Message[] messages = null;
        try {
            // 获得收件箱
            Folder folder = store.getFolder(Constant.Mail.INBOX);

            // 以读写模式READ_WRITE打开收件箱 只读READ_ONLY
            folder.open(Folder.READ_ONLY);

            // 获取 从昨天上午9.00开始,到今天上午9.00结束 的邮件
            SearchTerm[] st = new SearchTerm[]{
                    // 从昨天上午9.00开始
                    new SentDateTerm(ComparisonTerm.GE, DateTimeUtil.getNineAmOfYesterday()),
                    // 到今天上午9.00结束
                    new SentDateTerm(ComparisonTerm.LE, DateTimeUtil.getNineAmOfToday()),
            };

            // 设置搜索条件
            SearchTerm search = new AndTerm(st);

            // 按条件搜索邮件
            messages = folder.search(search);

            // 找到的邮件数量
            if (messages.length == 0) {
                return null;
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return messages;
    }

    /**
     * 连接邮箱
     *
     * @return void
     * @author majuehao
     * @date 2022/3/7 17:23
     **/
    private Store connect() {
        Store store = null;
        try {
            // 准备连接服务器的会话信息
            Properties props = new Properties();
            props.setProperty(Constant.Mail.POP3_PROTOCOL, protocol);
            props.setProperty(Constant.Mail.POP3_PORT, port);
            props.setProperty(Constant.Mail.POP3_HOST, host);
            props.setProperty(Constant.Mail.POP3L_AUTH, auth);

            // 创建Session实例对象
            Session session = Session.getInstance(props);

            // 创建IMAP协议的Store对象
//            store = session.getStore(protocol);

            // 连接邮件服务器
//            store.connect(username, password);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return store;
    }
}

六、邮件工具类MailUtil

可以使用模板或者直接发送。

  • 使用模板时,请配置好相应的模板已经模板参数。
  • 直接发送时,如果发送内容中有图片,也会进行单独处理
  • 对附件进行处理
package com.kcidea.erms.common.util;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.kcidea.erms.common.constant.Constant;
import com.kcidea.erms.common.exception.CustomException;
import com.kcidea.erms.model.common.MailModel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.annotation.Resource;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author yeweiwei
 * @version 1.0
 * @date 2021/11/17
 **/
@Slf4j
@Component
public class MailUtil {
    @Resource
    private JavaMailSender javaMailSender;

    @Value("${spring.mail.username}")
    private String fromEmail;

    @Resource
    private TemplateEngine templateEngine;

    @Value("${my-config.temp-path}")
    private String TEMP_PATH;

    /**
     * 群发邮件
     *
     * @param mailModel 邮件相关信息
     * @author yeweiwei
     * @date 2021/11/17 14:18
     */
    public void sendEmail(MailModel mailModel, boolean useTemplateFlag) {
        List<String> mailList = mailModel.getToMailList();
        if (CollectionUtils.isEmpty(mailList)) {
            throw new CustomException("邮件发送失败,请检查收件人是否正确!");
        }

        try {
            MimeMessage mimeMessage = javaMailSender.createMimeMessage();

            // 发信设置messageId
            mimeMessage.setHeader(Constant.Mail.MESSAGE_ID, mailModel.getMessageId());

            // 邮件帮助类
            MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, true,
                    Constant.Encode.UTF_8);
            //发送人
            messageHelper.setFrom(fromEmail);
            //发送给谁
            messageHelper.setTo(mailList.toArray(new String[0]));
            //标题
            messageHelper.setSubject(mailModel.getTitle());

            // 是否使用模板
            if (useTemplateFlag) {
                // 使用模板
                this.useTemplate(mailModel.getParamsMap(), mailModel.getTemplate(), messageHelper);
            } else {
                // 不使用模板
                this.notUseTemplate(mailModel.getContent(), messageHelper);
            }

            // 是否包含附件
            if (!Strings.isNullOrEmpty(mailModel.getFilePath())) {
                FileSystemResource fileSystemResource = new FileSystemResource(mailModel.getFilePath());
                messageHelper.addAttachment(mailModel.getFileName(), fileSystemResource);
            }
            //发送邮件
            javaMailSender.send(mimeMessage);
        } catch (Exception e) {
            log.error("邮件发送失败,原因:" + e.getMessage(), e);
            throw new CustomException("邮件发送失败,请检查邮箱填写是否正确!");
        }
    }

    /**
     * 不使用模板
     *
     * @param content       邮件内容
     * @param messageHelper 邮件帮助类
     * @author majuehao
     * @date 2022/3/8 10:19
     **/
    private void notUseTemplate(String content, MimeMessageHelper messageHelper) throws Exception {
        Map<Integer, FileSystemResource> map = Maps.newHashMap();

        // 解析文本内容
        List<String> imgPathList = Lists.newArrayList(this.getImgPathSet(content));
        if (!CollectionUtils.isEmpty(imgPathList)) {
            for (int i = 0; i < imgPathList.size(); i++) {
                String imgPath = imgPathList.get(i);
                if (Strings.isNullOrEmpty(imgPath)) {
                    continue;
                }
                content = content.replace(imgPath, Constant.NotifyContact.CID + i);

                String filePath = writeToLocal(imgPath);
                FileSystemResource res = new FileSystemResource(filePath);
                map.put(i, res);
            }
        }

        if (!Strings.isNullOrEmpty(content)) {
            messageHelper.setText(content, true);
            for (Map.Entry<Integer, FileSystemResource> entry : map.entrySet()) {
                FileSystemResource value = entry.getValue();
                Integer key = entry.getKey();
                messageHelper.addInline(String.valueOf(key), value);
            }
        }
    }

    /**
     * 使用模板
     *
     * @param paramsMap     模板参数
     * @param template      模板
     * @param messageHelper 邮件帮助类
     * @author majuehao
     * @date 2022/3/8 10:16
     **/
    private void useTemplate(Map<String, Object> paramsMap, String template, MimeMessageHelper messageHelper)
            throws MessagingException {
        //使用模板thymeleaf
        Context context = new Context();
        //定义模板数据
        context.setVariables(paramsMap);
        //获取thymeleaf的html模板,指定模板路径
        String emailContent = templateEngine.process(template, context);
        messageHelper.setText(emailContent, true);
    }

    /**
     * 获取IMG标签里的src路径
     *
     * @param htmlStr HTML文本
     * @return java.util.Set<java.lang.String>
     * @author majuehao
     * @date 2022/3/4 18:05
     **/
    private Set<String> getImgPathSet(String htmlStr) {
        if (Strings.isNullOrEmpty(htmlStr)) {
            return Sets.newHashSet();
        }
        Set<String> pics = Sets.newHashSet();
        String regExImg = "<img.*src\\s*=\\s*(.*?)[^>]*?>";
        Pattern pImage = Pattern.compile(regExImg, Pattern.CASE_INSENSITIVE);
        Matcher mImage = pImage.matcher(htmlStr);
        while (mImage.find()) {
            // 得到<img />数据
            String img = mImage.group();
            // 匹配<img>中的src数据
            Matcher matcher = Pattern.compile("src\\s*=\\s*\"?(.*?)(\"|>|\\s+)").matcher(img);
            while (matcher.find()) {
                pics.add(matcher.group(1));
            }
        }
        return pics;
    }

    /**
     * 把文件流写到本地文件
     *
     * @param imgPath 路径
     * @author majuehao
     * @date 2022/3/4 18:05
     **/
    private String writeToLocal(String imgPath) throws IOException {
        // 文件后缀名
        String suffix = imgPath.substring(imgPath.lastIndexOf(Constant.SplitChar.POINT_CHAR));
        InputStream fileInputStream = MinioFileUtil.getFileInputStream(Constant.MinIoBucketName.ERMS,
                imgPath.substring(imgPath.lastIndexOf("=") + 1));

        // 将文件流写入本地文件
        String filePath = TEMP_PATH + DateTimeUtil.localDateToString(LocalDate.now());
        File file = new File(filePath);
        if (!file.exists()) {
            file.mkdir();
        }
        String fileName = System.currentTimeMillis() + suffix;
        filePath = filePath.concat("/").concat(fileName);
        int index;
        byte[] bytes = new byte[1024];
        FileOutputStream downloadFile = new FileOutputStream(filePath);
        while ((index = fileInputStream.read(bytes)) != -1) {
            downloadFile.write(bytes, 0, index);
            downloadFile.flush();
        }
        downloadFile.close();
        fileInputStream.close();
        return filePath;
    }

}

七、邮件工具类

package com.kcidea.erms.common.util;


import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.kcidea.erms.common.constant.Constant;
import com.kcidea.erms.model.database.notify.MessageIdInfoModel;
import com.kcidea.erms.model.task.BackMailModel;
import com.kcidea.erms.model.task.BackMailReasonModel;
import lombok.extern.slf4j.Slf4j;

import javax.mail.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author majuehao
 * @version 1.0
 * @date 2022/03/07
 **/
@Slf4j
public class EmailUtil {
    private static final String multipart = "multipart/*";

    /**
     * 内容解析
     *
     * @param part part
     * @return X-OQ-MSGID
     * @author majuehao
     * @date 2022/3/7 13:18
     **/
    public static BackMailModel analysisMessage(Part part) throws Exception {
        BackMailModel backMail = new BackMailModel();
        if (!part.isMimeType(multipart)) {
            return backMail;
        }
        // messageIdInfo
        MessageIdInfoModel info = null;
        // 收件人
        String to = "";
        // 原因
        String reason = "";
        List<BackMailReasonModel> backMailReasonModelList = Lists.newArrayList();

        Multipart multipart = (Multipart) part.getContent();
        for (int i = 0; i < multipart.getCount(); i++) {
            BodyPart bodyPart = multipart.getBodyPart(i);
            if (part.isMimeType("message/rfc822")) {
                return analysisMessage((Part) part.getContent());
            }
            InputStream inputStream = bodyPart.getInputStream();

            try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
                String strLine;
                boolean nextLine = false;
                while ((strLine = br.readLine()) != null) {
                    // 虽然发送是设置的是MessageId,但是在退信时,qq会把原邮件的messageId放在X-OQ-MSGID中
                    if (strLine.startsWith(Constant.Mail.X_OQ_MSGID)) {
                        String[] split = strLine.split(Constant.Mail.X_OQ_MSGID);
                        String messageId = (split.length > 1 ? split[1].trim() : null);
                        // 解析messageId
                        info = analysisMessageId(messageId);
                    }
                    if (strLine.contains(Constant.Mail.NOT_GET_MAIL_PEOPLE)) {
                        to = getNotGetMailPeople(strLine);
                    }
                    // 如果上一行检测到 退信原因 四个字,那么这一行就把具体原因截取出来
                    if (nextLine) {
                        nextLine = false;
                        reason = getReason(strLine);
                        backMailReasonModelList.add(new BackMailReasonModel().create(to, reason));
                    }
                    if (strLine.contains(Constant.Mail.REASON)) {
                        // 检测到 退信原因 四个字,下一行将是具体原因
                        nextLine = true;
                    }
                }
            }
        }
        backMail.create(backMailReasonModelList, info);
        return backMail;
    }

    /**
     * 获取附件中的MessageId
     *
     * @param messages 信息列表
     * @return void
     * @author majuehao
     * @date 2022/3/7 13:23
     **/
    public static List<BackMailModel> getMessageId(Message[] messages) {
        List<BackMailModel> list = Lists.newArrayList();
        Arrays.stream(messages).filter(EmailUtil::isContainAttachment).forEach((message -> {
            BackMailModel backMail = null;
            try {
                backMail = EmailUtil.analysisMessage(message);
            } catch (Exception e) {
                log.error("解析MessageId失败,原因:{}", e.getMessage());
            }
            if (backMail != null) {
                list.add(backMail);
            }
        }));
        return list;
    }

    /**
     * 是否包含附件
     *
     * @param part part
     * @return 是否包含附件
     * @author majuehao
     * @date 2022/3/7 13:33
     **/
    public static boolean isContainAttachment(Part part) {
        boolean attachFlag = false;
        if (part == null) {
            return attachFlag;
        }
        try {
            if (part.isMimeType(multipart)) {
                Multipart mp = (Multipart) part.getContent();
                for (int i = 0; i < mp.getCount(); i++) {
                    BodyPart mpart = mp.getBodyPart(i);
                    String disposition = mpart.getDisposition();
                    if ((disposition != null) && ((disposition.equals(Part.ATTACHMENT))
                            || (disposition.equals(Part.INLINE)))) {
                        attachFlag = true;
                    } else if (mpart.isMimeType(multipart)) {
                        attachFlag = isContainAttachment((Part) mpart);
                    } else {
                        String contype = mpart.getContentType();
                        if (contype.toLowerCase().contains("application")) {
                            attachFlag = true;
                        }
                        if (contype.toLowerCase().contains("name")) {
                            attachFlag = true;
                        }
                    }
                }
            } else if (part.isMimeType("message/rfc822")) {
                attachFlag = isContainAttachment((Part) part.getContent());
            }
        } catch (MessagingException | IOException e) {
            e.printStackTrace();
        }
        return attachFlag;
    }

    /**
     * 解析messageId
     *
     * @param messageId messageId
     * @author majuehao
     * @date 2022/3/8 16:16
     **/
    public static MessageIdInfoModel analysisMessageId(String messageId) {
        if (Strings.isNullOrEmpty(messageId)) {
            return null;
        }
        try {
            return JSON.parseObject(messageId, MessageIdInfoModel.class);
        } catch (Exception e) {
            log.error("messageId解析失败");
            return null;
        }
    }

    /**
     * 获取无法收到邮件的邮箱的集合
     *
     * @param strLine 行数据
     * @return 无法收到邮件的邮箱的集合
     * @author majuehao
     * @date 2022/3/9 17:04
     **/
    public static String getNotGetMailPeople(String strLine) {
        String notGetMail = "";
        if (Strings.isNullOrEmpty(strLine)) {
            return notGetMail;
        }
        String regex = "[\\w!#$%&'*+/=?^_`{|}~-]+(?:\\.[\\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\\w](?:[\\w-]*[\\w])?\\.)+[\\w](?:[\\w-]*[\\w])?";
        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(strLine);
        while (matcher.find()) {
            // 得到<img />数据
            notGetMail = matcher.group();
        }
        return notGetMail;
    }

    /**
     * 获取退信原因
     *
     * @param strLine 行数据
     * @return 退信原因
     * @author majuehao
     * @date 2022/3/9 17:04
     **/
    public static String getReason(String strLine) {
        String reason = "";
        if (Strings.isNullOrEmpty(strLine)) {
            return reason;
        }
        String regex = "<[a-zA-Z]+.*?>([\\s\\S]*?)<br";
        Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(strLine);
        while (matcher.find()) {
            // 得到<td>...<br中的数据
            reason = matcher.group(1);
        }
        return reason;
    }
}

八、imap收信

  
    @Value("${spring.mail.username}")
    private String username;

    @Value("${spring.mail.password}")
    private String password;

    @Value("${spring.mail.properties.mail.pop3.protocol}")
    private String protocol;

    @Value("${spring.mail.properties.mail.pop3.host}")
    private String host;

    @Value("${spring.mail.properties.mail.pop3.port}")
    private String port;

    @Value("${spring.mail.properties.mail.pop3.auth}")
    private String auth;
  @Test
    public void getSendBox() {
        IMAPFolder folder = null;
        IMAPStore store = null;
        try {
            String host = "imap.qq.com";
            int port = 993;
            final String SSL_FACTORY = "javax.net.ssl.SSLSocketFactory";
            Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
            /* Security.addProvider(new com.sun.net.ssl.internal.ssl.Provider());
                            这里有一个错我是这么解决的(Windows -> Preferences,Java/Compiler/Errors/Warnings->
            Deprecated and restricted API, Forbidden reference (access rules),原始设定为Error修改为Warning)*/
            Properties props = System.getProperties();
            props.setProperty("mail.imap.socketFactory.class", SSL_FACTORY);
            props.setProperty("mail.imap.socketFactory.port", "993");
            props.setProperty("mail.store.protocol", "imap");
            props.setProperty("mail.imap.host", host);
            props.setProperty("mail.imap.port", "993");
            props.setProperty("mail.imap.auth.login.disable", "true");
            Session session = Session.getDefaultInstance(props, null);
            session.setDebug(false);

            store = (IMAPStore) session.getStore("imap");  // 使用imap会话机制,连接服务器
            store.connect(host, port, username, password);

            folder = (IMAPFolder) store.getFolder("Sent Messages"); //收件箱

            Folder defaultFolder = store.getDefaultFolder();
            Folder[] allFolder = defaultFolder.list();

            for (int i = 0; i < allFolder.length; i++) {
                System.out.println("这个是服务器中的文件夹=" + allFolder[i].getFullName());
            }
            // 使用只读方式打开收件箱
            folder.open(Folder.READ_WRITE);
            int size = folder.getMessageCount();
            System.out.println("这里是打印的条数==" + size);
            Message[] mess = folder.getMessages();
            //  Message message = folder.getMessage(size);
            for (int i = 0; i < 5; i++) {
                String from = mess[i].getFrom()[0].toString();
                String subject = mess[i].getSubject();
                Date date = mess[i].getSentDate();
                System.out.println("From: " + from);
                System.out.println("Subject: " + subject);
                System.out.println("Date: " + date);
                System.out.println("====================");
            }
               /* String from = message.getFrom()[0].toString();
                String subject = message.getSubject();
                Date date = message.getSentDate();*/
            /* BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); */

        } catch (MessagingException e) {
            e.printStackTrace();
        } finally {
            try {
                if (folder != null) {
                    folder.close(false);
                }
                if (store != null) {
                    store.close();
                }
            } catch (MessagingException e) {
                e.printStackTrace();
            }
        }
        System.out.println("接收完毕!");
    }

try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
String strLine;
boolean nextLine = false;
while ((strLine = br.readLine()) != null) {
// 虽然发送是设置的是MessageId,但是在退信时,qq会把原邮件的messageId放在X-OQ-MSGID中
if (strLine.startsWith(Constant.Mail.X_OQ_MSGID)) {
String[] split = strLine.split(Constant.Mail.X_OQ_MSGID);
String messageId = (split.length > 1 ? split[1].trim() : null);
// 解析messageId
info = analysisMessageId(messageId);
}
if (strLine.contains(Constant.Mail.NOT_GET_MAIL_PEOPLE))

// 如果上一行检测到 退信原因 四个字,那么这一行就把具体原因截取出来
if (nextLine) {
nextLine = false;
reason = getReason(strLine);
toReasonsMap.put(to, reason);
}
if (strLine.contains(Constant.Mail.REASON)) {
// 检测到 退信原因 四个字,下一行将是具体原因
nextLine = true;
}
}

        }

/**
* 获取退信原因
*
* @param strLine 行数据
* @return 退信原因
* @author majuehao
* @date 2022/3/9 17:04
**/
public static String getReason(String strLine) {
String reason = "";
if (Strings.isNullOrEmpty(strLine)) {
return reason;
}
String regex = "<[a-zA-Z]+.?>([\s\S]?)<br";
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(strLine);
while (matcher.find()) {
// 得到...<br中的数据
reason = matcher.group(1);
}
return reason;
}

log.info("未能找到相关退信!");

log.info("创建任务...");

log.info("任务执行成功");

工具书:参考工具;

标准/法规:标准;

事实数据:事实;数据;

;:;

,:;

文摘目录:文摘引文

科技报告:其他;

Q.E.D.