基礎備忘(發送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

總是有些基礎內容用過就忘掉了,缺少總結記錄。在此記錄下來便於以後查找也爲遇到類似問題的人一個小小的幫助。

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