基础备忘(发送htm正文带图片并且带附件的邮件)

需求背景如下: 发送htm正文带图片并且带附件的邮件 。如题所示,任务拆解关键字为:

  • html正文
  • 带图片
  • 带附件

先介绍普通发邮件的方式

添加maven引用

  <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
  </dependency>
@Data
public  class MailInfo {
    /**
     * 邮件接收人
     */
    private String[] receiver;
    /**
     * 邮件主题
     */
    private String subject;
    /**
     * 邮件的文本内容
     */
    private String content;
    /**
     * 抄送人
     */
    private String[] cc;
    /**
     * 邮件附件的文件名
     */
    private String[] attachFileNames;
    /**
     * 邮件内容内嵌图片
     */
    private Map<String, URL> imageMap;
}

邮件信息实体类(包含发给谁、主题、内容、抄送人等)

    @Resource
    private JavaMailSender javaMailSender;
    @Resource
    private EmailConfig ec;
    @Value("${spring.mail.username}")
    private String fromUser;

	@Override
    public void sendSimpleTextEmail(MailInfo mailInfo) {
        try {
            SimpleMailMessage mailMessage = new SimpleMailMessage();
            //发件人
            mailMessage.setFrom(fromUser);
            //接收人
            mailMessage.setTo(mailInfo.getReceiver());
            //邮件主题
            mailMessage.setSubject(mailInfo.getSubject());
            //邮件抄送
            if(mailInfo.getCc()!=null){
                mailMessage.setCc(mailInfo.getCc());
            }
            //邮件内容
            mailMessage.setText(mailInfo.getContent());
            //发送邮件
            javaMailSender.send(mailMessage);
        } catch (Exception e) {
            log.error("邮件发送失败:{}", e.getMessage());
        }
    }

发送邮件工具类

发送简单文字内容邮件:先组装mailnfo信息然后调用sendSimpleTextEmail 方法发送即可。

html正文

从上面mailIfno实体类中可以看到 content 表示邮件的文本内容,这里是字符串信息,可以直接写入html字符串即可。不过我的实际项目需求是这块邮件内容需要根据不同的业务类型有不同的模板样式,因此在这里我引入thymeleaf模板引擎动态生成html。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>ognl</groupId>
            <artifactId>ognl</artifactId>
            <version>3.2</version>
        </dependency>

添加模板引擎的 maven引用

@Configuration
public class MailTemplateConfig {
    @Bean("emailTemplateEngine")
    public TemplateEngine templateEngine(){
        ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
        resolver.setPrefix("templates/");
        resolver.setSuffix(".html");
        TemplateEngine engine = new TemplateEngine();
        engine.setTemplateResolver(resolver);
        return  engine;
    }
}

模板引擎配置类,这里配置与application.yml里面配置是一样的效果

<div class="wrapper">
  <div class="content">
    <div>
      <div class="page-title">
        <img id="image0_44_3173"  width="115"  height="30" src="cid:logo" />
        <h3 class="title">- title</h3>
      </div>
      <div class="msg-title">Register Verification Code</div>
      <p class="msg-des">Continue registering with account by entering the verification code below:</p>
      <div class="msg-content">
        <div><b th:text="${code}"></b></div>
      </div>
      </div>
      <p class="msg-des">Best Regards~</p>
    </div>
  </div>
</div>
@Async
    @Override
    public void sendHtmlEmail(EmailBaseDto dto) {
        log.info("sendEmail --发送邮件开始,fromUser:{}, toUser:{}", fromUser, dto.getToUser());
        try {
            MailInfo mailInfo = new MailInfo();
            mailInfo.setReceiver(new String[]{dto.getToUser()});
            String subject="";
            switch (dto.getEmailNotificationTypeEnum()){
               
                case 用户注册验证码:
                    subject= "Verification Code" ;
                    break;
                default:
                    break;
            }
            if(StringUtils.isEmpty(subject)){
                throw  new BusinessException(RespCodeEnum.EMAIL_NOTIFICATION_TYPE_ERROR.getCode(),RespCodeEnum.EMAIL_NOTIFICATION_TYPE_ERROR.getMsg());
            }
            mailInfo.setSubject(subject);
            Context context = new Context();

            Map<String, Object> paramsMap = BeanUtil.beanToMap(dto);
            context.setVariables(paramsMap);
            String bodyString = templateEngine.process(dto.getEmailNotificationTypeEnum().getValue(), context);
            mailInfo.setContent(bodyString);
            //增加logo图片
            Map<String, URL> imageMap = this.getLogoMap();
            mailInfo.setImageMap(imageMap);
            emailUtilService.sendHtmlEmail(mailInfo);
        } catch (Exception e) {
            log.error("send  mail error", e);
            throw new BusinessException(RespCodeEnum.EMAIL_SEND_ERROR.getCode(),RespCodeEnum.EMAIL_SEND_ERROR.getMsg());
        }
    }

截取部分html片段展示动态生成邮件模板html字符串,其中 <div><b th:text="${code}"></b></div> 是模板thymeleaf的语法

   @Resource(name = "emailTemplateEngine")
    private TemplateEngine templateEngine;

省略……

Context context = new Context();
            Map<String, Object> paramsMap = BeanUtil.beanToMap(emailApprovalNotificationDto);
            context.setVariables(paramsMap);
String bodyString = templateEngine.process(dto.getEmailNotificationTypeEnum().getValue(), context);
        mailInfo.setContent(bodyString)

这里面 Map<String, Object> paramsMap 是对应着前面模板引擎中<div><b th:text="${code}"></b></div>动态参数内容,如 code 那么在 paramsMap 需要有与之对应的键。最后再调用templateEngine.process` 渲染成html字符串。

带附件

    public MimeBodyPart createAttachment(String filename) throws Exception {
        //创建保存附件的MimeBodyPart对象,并加入附件内容和相应的信息
        MimeBodyPart attachPart = new MimeBodyPart();
        FileDataSource fsd = new FileDataSource(filename);
        attachPart.setDataHandler(new DataHandler(fsd));
        attachPart.setFileName(fsd.getName());
        return attachPart;
    }
  MimeMultipart allMultipart = new MimeMultipart();
	allMultipart.addBodyPart(contentPart);
            //创建用于组合邮件正文和附件的MimeMultipart对象
            for (int i = 0; i < mailInfo.getAttachFileNames().length; i++) {
                allMultipart.addBodyPart(createAttachment(mailInfo.getAttachFileNames()[i]));
            }

从本地文件路径读取文件,并创建MimeBodyPart对象,像附件、图片啥的都是要构建这个对象MimeBodyPart,下图是类图关系。

image

带图片

MimeMultipart allMultipart = new MimeMultipart();
MimeBodyPart contentPart = createContent(mailInfo.getContent(), mailInfo.getImageMap());
public MimeBodyPart createContent(String body, Map<String, URL> map) throws Exception {
        //创建代表组合Mime消息的MimeMultipart对象,将该MimeMultipart对象保存到MimeBodyPart对象
        MimeBodyPart contentPart = new MimeBodyPart();
        MimeMultipart contentMultipart = new MimeMultipart("related");

        //创建用于保存HTML正文的MimeBodyPart对象,并将它保存到MimeMultipart中
        MimeBodyPart htmlBodyPart = new MimeBodyPart();
        htmlBodyPart.setContent(body, "text/html;charset=UTF-8");
        contentMultipart.addBodyPart(htmlBodyPart);

        if (map != null && map.size() > 0) {
            Set<Map.Entry<String, URL>> set = map.entrySet();
            for (Map.Entry<String, URL> entry : set) {
                //创建用于保存图片的MimeBodyPart对象,并将它保存到MimeMultipart中
                MimeBodyPart gifBodyPart = new MimeBodyPart();
                URLDataSource uds=new URLDataSource(entry.getValue());

                gifBodyPart.setDataHandler(new DataHandler(uds));
                gifBodyPart.setContentID(entry.getKey());    //cid的值
                contentMultipart.addBodyPart(gifBodyPart);
            }
        }

        //将MimeMultipart对象保存到MimeBodyPart对象
        contentPart.setContent(contentMultipart);
        return contentPart;
    }

同上面的带附件一样,也是构建MimeBodyPart,但是需要注意的是 MimeMultipart contentMultipart = new MimeMultipart("related"); 其中 MimeMultipart 有不同的 subtype,默认是mixed 表示类似带附件那种(附件与正文分开),另一种则是 related 既附件与content是有关联关系的,也就是咱们现在要做的正文中嵌入图片。

可以看到图片这个 MimeBodyPart中,存在一对方法

                gifBodyPart.setContentID(entry.getKey());    //cid的值
				gifBodyPart.setDataHandler(new DataHandler(uds));

其中 setContentID 中的cid值与我们前面 html模板字符串中 <b th:text="${code}"></b> 对应,即:在此示例中setContentID 方法里面 entry.getKey的值为字符串 code然后JavaMailSender在发送的时候会通过keygifBodyPart中查找key=codeDataHandler信息。gifBodyPart.setDataHandler(new DataHandler(uds)); 如下所示这里的 DataHandler有 DataSource类型的构造方法

    public DataHandler(DataSource ds) {
	// save a reference to the incoming DS
	dataSource = ds;
	oldFactory = factory; // keep track of the factory
    }

同时DataSource有两个实现类 FileDataSourceURLDataSource 其中FileDataSource 是读取本地文件路径时使用的,URLDataSource 是为了外部网络资源或者springboot打包成jar后本地路径无法直接取到时使用的。

springboot打jar后,本地ide环境可以正常取到resource下资源,但是部署后取不到只能用流的方式读取,而spring-boot-starter-mail又没有提供流的实现入参,因此可以用 URLDataSource 实现类传入resource下的url地址,由spring-boot-starter-mail发送时去读。

image

总是有些基础内容用过就忘掉了,缺少总结记录。在此记录下来便于以后查找也为遇到类似问题的人一个小小的帮助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章