使用Velocity作为邮件的模板

Velocity 是一个基于Java的模板引擎。它允许任何人使用一种简单但强大的模板语言去引用Java代码中定义的对象。

Velocity的基本常用语法:https://www.cnblogs.com/xiohao/p/5788932.html

最近在做ESL的邮件报警功能,邮件内容包含两个表格,分别填充两种报警内容,需要根据系统的语言设置显示不一样的表头。

核心做法:

package com.zk.mail;

import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.app.VelocityEngine;
import org.springframework.stereotype.Component;
import org.springframework.ui.velocity.VelocityEngineUtils;
import org.springframework.util.StringUtils;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.*;
import javax.mail.internet.*;
import javax.mail.util.ByteArrayDataSource;
import java.io.*;
import java.util.*;

@Slf4j
@Component(value = "mailUtils")
public class MailUtils {

    public static final String HTML_CONTENT = "text/html;charset=UTF-8";
    public static final String ATTACHMENT_CONTENT = "text/plain;charset=gb2312";

    private static VelocityEngine velocityEngine = new VelocityEngine();

    static {
        Properties properties = new Properties();
        String basePath = "src/main/resources/mailTemplate/";
        // 设置模板的路径
        properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, basePath);
        // 初始花velocity 让设置的路径生效
        velocityEngine.init(properties);
    }

    public <T extends List> void sendEmail(T t1, T t2, String title, String[] to, String[] bcc, String templateName, EmailServerConfig config, Map<String, String> content) {
        Map map = new HashMap();
        map.put("priceTagDatas", t1);
        map.put("ApDatas", t2);
        map.put("merchantName", content.get("merchantName"));
        map.put("storeName", content.get("storeName"));
        map.put("alarmStartTime", content.get("alarmStartTime"));
        map.put("alarmEndTime", content.get("alarmEndTime"));
        Email email = new Email.Builder(title, to, null).model(map).templateName(templateName).bcc(bcc).build();
        sendEmail(email, config);
    }

    private void sendEmail(Email email, EmailServerConfig config) {
        Long startTime = System.currentTimeMillis();
        // 发件人
        try {
            MimeMessage message = this.getMessage(email, config);
            // 新建一个存放信件内容的BodyPart对象
            Multipart multiPart = new MimeMultipart();
            MimeBodyPart mdp = new MimeBodyPart();
            // 给BodyPart对象设置内容和格式/编码方式
            setContent(email);
            mdp.setContent(email.getContent(), HTML_CONTENT);
            multiPart.addBodyPart(mdp);
            // 新建一个MimeMultipart对象用来存放BodyPart对象(事实上可以存放多个)
            if (null != email.getData()) {
                MimeBodyPart attchment = new MimeBodyPart();
                ByteArrayInputStream in = new ByteArrayInputStream(email.getData());
                DataSource fds = new ByteArrayDataSource(in, email.getFileType());
                attchment.setDataHandler(new DataHandler(fds));
                attchment.setFileName(MimeUtility.encodeText(email.getFileName()));
                multiPart.addBodyPart(attchment);
                if (in != null) {
                    in.close();
                }
            }
            message.setContent(multiPart);
            message.saveChanges();
            Transport.send(message);
            Long endTime = System.currentTimeMillis();
            log.info("Email sent successfully, consume time:" + (endTime - startTime) / 1000 + "s");
        } catch (Exception e) {
            log.error("Error while sending mail.", e);
        }
    }


    private Email setContent(Email email) {
        if (StringUtils.isEmpty(email.getContent())) {
            email.setContent("");
        }
        if (!StringUtils.isEmpty(email.getTemplateName()) && null != email.getModel()) {
            String content = VelocityEngineUtils.mergeTemplateIntoString(velocityEngine, email.getTemplateName(), "UTF-8", email.getModel());
            email.setContent(content);
        }
        return email;
    }


    private MimeMessage getMessage(Email email, EmailServerConfig config) {
        MimeMessage message = null;
        try {
            if (email.getTo() == null || email.getTo().length == 0 || StringUtils.isEmpty(email.getSubject())) {
                throw new Exception("Recipient or subject is empty.");
            }

            Properties props = new Properties();
            props.setProperty("mail.smtp.host", config.getMailSmtpHost());
            props.setProperty("mail.smtp.socketFactory.class", config.getMailSmtpSocketFatoryClass());
            props.setProperty("mail.smtp.socketFactory.fallback", config.getMailSmtpSocketFatoryFallback());
            props.setProperty("mail.smtp.port", config.getMailSmtpPort());
            props.setProperty("mail.smtp.socketFactory.port", config.getMailSmtpSocketFatoryPort());
            props.setProperty("mail.smtp.auth", config.getMailSmtpAuth());

//解决553的问题,用Session.getInstance取代Session.getDefaultInstance
//            Session mailSession = Session.getDefaultInstance(props, new Authenticator() {
//                protected PasswordAuthentication getPasswordAuthentication() {
//                    return new PasswordAuthentication(config.getMailSmtpFromAddress(), //config.getMailSmtpAuthPass());
//                }
//            });

            Session mailSession = Session.getInstance(props, new Authenticator(){
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(config.getMailSmtpFromAddress(), config.getMailSmtpAuthPass());
                }});
            message = new MimeMessage(mailSession);
            message.setFrom(new InternetAddress(config.getMailSmtpFromAddress()));
            for (String mailTo : email.getTo()) {
                message.addRecipient(Message.RecipientType.TO, new InternetAddress(mailTo));
            }
            List<InternetAddress> ccAddress = new ArrayList<>();
            if (null != email.getBcc()) {
                for (String mailCC : email.getBcc()) {
                    ccAddress.add(new InternetAddress(mailCC));
                }
                message.addRecipients(Message.RecipientType.CC,
                        ccAddress.toArray(new InternetAddress[email.getBcc().length]));
            }
            message.setSentDate(new Date());
            message.setSubject(email.getSubject());
        } catch (Exception e) {
            log.error("Error while sending mail." + e.getMessage(), e);
        }
        return message;
    }
}

Velocity的模板, 提供不同语言的模板,模板名称带上语言后缀(中文模板:mail_cn.vm)如:

<!DOCTYPE html>
<html lang="zh">
<head>
    <META http-equiv=Content-Type content='text/html; charset=UTF-8'>
    <title>Title</title>
    <style type="text/css">
        table.reference, table.tecspec {
            border-collapse: collapse;
            width: 100%;
            margin-bottom: 4px;
            margin-top: 4px;
        }
        table.reference tr:nth-child(even) {
            background-color: #fff;
        }
        table.reference tr:nth-child(odd) {
            background-color: #f6f4f0;
        }
        table.reference th {
            color: #fff;
            background-color: #555;
            border: 1px solid #555;
            font-size: 12px;
            padding: 3px;
            vertical-align: top;
        }
        table.reference td {
            line-height: 2em;
            min-width: 24px;
            border: 1px solid #d4d4d4;
            padding: 5px;
            padding-top: 7px;
            padding-bottom: 7px;
            vertical-align: top;
        }
        .article-body h3 {
            font-size: 1.8em;
            margin: 2px 0;
            line-height: 1.8em;
        }
    </style>
</head>
<body>
<h3 style=";">ESL系统报警信息</h3>
<div>
    <div>时间: $alarmStartTime 至 $alarmEndTime</div>
    <div>商家名称: $merchantName</div>
    <div>门店名称: $storeName</div>
    <div>报警内容:</div>

    #if ($priceTagDatas.size() > 0)
        <table class="reference">
            <tbody>
            <tr>价签报警</tr>
            <tr>
                <th>价签条码</th>
                <th>商品条码</th>
                <th>商品名称</th>
                <th>报警类型</th>
                <th>报警时间</th>
            </tr>
                #foreach($element in  $priceTagDatas)
                <tr>
                    <td>
                        #if($element.getDeviceMac())
                    $element.getDeviceMac()
                #end
                    </td>
                    <td>
                        #if($element.getItemBarCode())
                    $element.getItemBarCode()
                #end
                    </td>
                    <td>
                        #if($element.getItemName())
                    $element.getItemName()
                #end
                    </td>
                    <td>
                        #if($element.getFaultType())
                    $element.getFaultType()
                #end
                    </td>
                    <td>
                        #if($element.getCreatedTime())
                    $element.getCreatedTime()
                #end
                    </td>
                </tr>
                #end
            </tbody>
        </table>
    #end

    #if ($ApDatas.size() > 0)
        <table class="reference">
            <tbody>
            <tr>基站报警</tr>
            <tr>
                <th>基站名称</th>
                <th>基站MAC</th>
                <th>报警类型</th>
                <th>报警时间</th>
                <th>状态</th>
            </tr>
                #foreach($element in $ApDatas)
                <tr>
                    <td>
                        #if($element.getDeviceMac())
                    $element.getDeviceMac()
                #end
                    </td>
                    <td>
                        #if($element.getDeviceMac())
                    $element.getDeviceMac()
                #end
                    </td>
                    <td>
                        #if($element.getFaultType())
                    $element.getFaultType()
                #end
                    </td>
                    <td>
                        #if($element.getCreatedTime())
                    $element.getCreatedTime()
                #end
                    </td>
                    <td>
                        #if($element.getProcessStatus())
                    $element.getProcessStatus()
                #end
                    </td>
                </tr>
                #end
            </tbody>
        </table>
    #end

    <div style="float: left; margin-top: 300px;;">
        <p>系统邮件(请勿回复) | ESL 报警中心</p>
    </div>
</div>
</body>
</html>

发送邮件是在一个定时任务中,定时任务的代码如:

package com.zk.quartz;

import com.zk.dao.*;
import com.zk.mail.AlarmEmailTitle;
import com.zk.mail.EmailServerConfig;
import com.zk.mail.MailUtils;
import com.zk.model.*;
import com.zk.service.MailSenderService;
import com.zk.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.data.jpa.domain.Specification;

import javax.annotation.Resource;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by zk on 2019/3/28.
 */
@Slf4j
public class AlarmJob implements Job {
    @Resource
    private StoreRepository storeRepository;

    @Resource
    private MerchantRepository merchantRepository;

    @Resource
    private AgencyAlarmConfigRepository agencyAlarmConfigRepository;

    @Resource
    private AlarmRepository alarmRepository;

    @Resource
    private MailUtils mailUtils;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobDataMap jobDataMap = jobExecutionContext.getJobDetail().getJobDataMap();
        List faultTypeList = (List) jobDataMap.get("faultTypeList");
        String merchantId = (String) jobDataMap.get("merchantId");
        String storeId = (String) jobDataMap.get("storeId");
        String sendTO = (String) jobDataMap.get("sendTo");
        String language = (String) jobDataMap.get("language");
        String templateName = "mail_" + language + ".vm";

        List<Alarm> alarmList = alarmRepository.findAll(getSpecification(merchantId, storeId, faultTypeList));

        if (alarmList.size() == 0) {
            log.info("Alarm job run without alarms for storeId: " + storeId);
            return;
        }

        String merchantName = merchantRepository.findByMerchantIdAndFlag(merchantId, 1).getMerchantName();
        String storeName = storeRepository.findByStoreIdAndFlag(storeId, 1).getStoreName();

        List<Alarm> priceTagAlarmList = alarmList.stream().filter(alarm -> "2".equals(alarm.getAlarmType())).collect(Collectors.toList());
        List<Alarm> apAlarmList = alarmList.stream().filter(alarm -> "1".equals(alarm.getAlarmType())).collect(Collectors.toList());

        Date alarmStartTime = alarmList.stream().map(alarm -> DateUtils.stringToDateTime(alarm.getCreatedTime())).min(Comparator.naturalOrder()).get();
        Date alarmEndTime = alarmList.stream().map(alarm -> DateUtils.stringToDateTime(alarm.getCreatedTime())).max(Comparator.naturalOrder()).get();

        Map<String, String> content = new HashMap<>(4);
        content.put("merchantName", merchantName);
        content.put("storeName", storeName);
        content.put("alarmStartTime", DateUtils.format(alarmStartTime));
        content.put("alarmEndTime", DateUtils.format(alarmEndTime));

        AgencyAlarmConfig agencyAlarmConfig = agencyAlarmConfigRepository.findConfigByAgencyId(merchantId);
        agencyAlarmConfig.setTestMail(sendTO);

        String[] toArr = sendTO.split(",");

        EmailServerConfig config = getEmailServerConfig(agencyAlarmConfig);
        mailUtils.sendEmail(priceTagAlarmList, apAlarmList, AlarmEmailTitle.getTitleFromLanguage(language), toArr, null, templateName, config, content);

        for(Alarm alarm : alarmList) {
            alarm.setHasSent(true);
            alarmRepository.save(alarm);
        }
    }

    private EmailServerConfig getEmailServerConfig(AgencyAlarmConfig agencyAlarmConfig) {
        EmailServerConfig config = new EmailServerConfig();
        config.setMailSmtpHost(agencyAlarmConfig.getSendServer());
        config.setMailSmtpSocketFatoryClass("javax.net.ssl.SSLSocketFactory");
        config.setMailSmtpSocketFatoryFallback("false");
        config.setMailSmtpPort("465");
        config.setMailSmtpSocketFatoryPort("465");
        config.setMailSmtpAuth("true");
        config.setMailSmtpFromAddress(agencyAlarmConfig.getAccount());
        config.setMailSmtpAuthPass(agencyAlarmConfig.getPassword());
        return config;
    }

    private Specification<Alarm> getSpecification(String merchantId, String storeId, List<String> typeList) {
        return new Specification<Alarm>() {
            @Override
            public Predicate toPredicate(Root<Alarm> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<Predicate>();
                Predicate predicate = null;
                if (StringUtils.isNotBlank(merchantId)) {
                    predicate = criteriaBuilder.equal(root.get("merchantId"), merchantId);
                    predicates.add(predicate);
                }
                if (StringUtils.isNotBlank(storeId)) {
                    predicate = criteriaBuilder.equal(root.get("storeId"), storeId);
                    predicates.add(predicate);
                }
                if (typeList != null && typeList.size() > 0) {
                    CriteriaBuilder.In<String> in = criteriaBuilder.in(root.get("faultType"));
                    for (String type : typeList) {
                        in.value(type);
                    }
                    predicates.add(in);
                }

                predicate =  criteriaBuilder.isNull(root.get("hasSent"));
                predicates.add(predicate);

                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        };
    }
}

 

后记:部署后遇到了两个坑

1)Velocity找不到模板文件

在我本地运行的时候并没有这种问题,试了很多种方法,最后只能使用绝对路径,修改MailUtils中velocityEngine的Velocity.FILE_RESOURCE_LOADER_PATH的值:

    static {
        Properties properties = new Properties();
        // 将basePath修改为服务器上的绝对路径, 并将模板文件上传到该路径下。
        // String basePath = "src/main/resources/mailTemplate/";
        String basePath = "/usr/local/esl/";
        
        properties.setProperty(Velocity.FILE_RESOURCE_LOADER_PATH, basePath);
        velocityEngine.init(properties);
    }

问题解决。

2)使用了163邮箱作为测试服务器,遇到了邮件被认为是垃圾邮件的问题:

解决方法:将邮件抄送一份给发送账号,在MailUtils的getMessage方法中,添加以下代码:

List<InternetAddress> ccAddress = new ArrayList<>();

//            if (null != email.getBcc()) {
//                for (String mailCC : email.getBcc()) {
//                    ccAddress.add(new InternetAddress(mailCC));
//                }
//                message.addRecipients(Message.RecipientType.CC,
//                        ccAddress.toArray(new InternetAddress[email.getBcc().length]));
//            }

ccAddress.add(new InternetAddress(config.getMailSmtpFromAddress()));
message.addRecipients(Message.RecipientType.CC, ccAddress.toArray(new InternetAddress[1]));

成功解决554 DT:SPM问题!

后记2:解决邮件发送中出现553问题

在本地用单测进行邮件发送,都没有问题。但是部署之后,通过前端调用接口的方式,经常会出现553的问题,如:

553意味着mail from和登录的邮箱账号存在不一致的情况,考虑到部署后首次发送是成功的,想到会不会是前一次登录的账号信息被保留下来了,观察代码,mail from和account的信息分别设置如:

     Session mailSession = Session.getDefaultInstance(props, new Authenticator() {
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(config.getMailSmtpFromAddress(), config.getMailSmtpAuthPass());
                }
            });
     message = new MimeMessage(mailSession);
     message.setFrom(new InternetAddress(config.getMailSmtpFromAddress()));

跟进到Session.getDefaultInstance的代码发现,defaultSession是一个类静态变量,首次登录一个邮箱后这个session就会被保留下来,导致和后续的测试账户不匹配从而报错553。找到原因之后,使用Session.getInstance()方法取代Session.getDefaultInstance()去重新new一个session,问题得到解决。

 

 

转载于:https://my.oschina.net/u/4042451/blog/3066884

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值