使用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(){@Overrideprotected 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 {@Resourceprivate StoreRepository storeRepository;@Resourceprivate MerchantRepository merchantRepository;@Resourceprivate AgencyAlarmConfigRepository agencyAlarmConfigRepository;@Resourceprivate AlarmRepository alarmRepository;@Resourceprivate MailUtils mailUtils;@Overridepublic 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>() {@Overridepublic 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
總結
以上是生活随笔為你收集整理的使用Velocity作为邮件的模板的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RT Thread之 Uart2 操作
- 下一篇: 跨境电商——亚马逊日本站(上)