一、前言
在HTML模板方面,当时在考虑Thymeleaf和Freemarker选用两个组件之一,最后还是选用freemarker;并不是说thymeleaf差,而是就个人而言觉得freemarker上手还是比较快,而且官方文档资料齐全;同样也考虑可能模板后续的灵活变动,才选用freemarker
本次分享针对springboot下结合email和HTML模板实现
二、实现步骤
- 首先在springboot工程pom.xml文件下引入依赖组件
//email组件启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
//freemarker组件启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
- application.yml进行邮件配置,关于邮件开启SMTP服务的资料网上很多,可按照步骤开启即可
spring:
mail:
host: smtp.qq.com
username: xxxxxx@qq.com
password: cjmoshsbxmntcchi //邮件授权码
properties.mail.smtp.starttls.enable: true
properties.mail.smtp.starttls.required: true
properties.mail.smtp.ssl.enable: true
from: xxxx@qq.com //邮件发送人
- 然后先着手邮件功能封装MailService接口及MailServiceImpl接口实现(当然你也可以先进行模板部分编写,个人习惯)
- MailService代码部分
public interface MailService {
/**
* 发送普通文本邮件
* @param to 收件人
* @param subject 主题
* @param content 内容
*/
void sendSimpleMail(String to, String subject, String content);
/**
* 发送HTML邮件
* @param to 收件人
* @param subject 主题
* @param content 内容
*/
void sendHtmlMail(List<String> to, String subject, String content);
/**
* 发送带附件的邮件
* @param to 收件人
* @param subject 主题
* @param filePath 内容
*/
void sendAttachmentsMail(String to, String subject,String content, String filePath);
}
- MailServiceImpl代码部分
@Service
public class MailServiceImpl implements MailService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private JavaMailSender mailSender; //发送邮件功能
@Value("${spring.mail.from}")
private String from;
/**
* 发送普通文本邮件
*
* @param to 收件人
* @param subject 主题
* @param content 内容
*/
public void sendSimpleMail(String to, String subject, String content) {
SimpleMailMessage message = new SimpleMailMessage();
//邮件发送人
message.setFrom(from);
//邮件接收人
message.setTo(to);
//邮件主题
message.setSubject(subject);
//邮件内容
message.setText(content);
//发送邮件
mailSender.send(message);
}
/**
* 发送HTML邮件
*
* @param to 收件人
* @param subject 主题
* @param content 内容
*/
public void sendHtmlMail(List<String> to, String subject, String content) {
//获取MimeMessage对象
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper messageHelper;
try {
messageHelper = new MimeMessageHelper(message, true);
//邮件发送人
messageHelper.setFrom(from);
//邮件接收人多人
String[] obj = to.toArray(new String[to.size()]);
messageHelper.setTo(obj);
//邮件主题
messageHelper.setSubject(subject);
//邮件内容
messageHelper.setText(content, true);
//发送邮件
mailSender.send(message);
//日志输出
logger.info("邮件发送成功");
} catch (MessagingException e) {
logger.error("邮件发送异常!", e);
}
}
/**
* 发送带附件的邮件
*
* @param to 收件人
* @param subject 主题
* @param filePath 内容
*/
public void sendAttachmentsMail(String to, String subject, String content, String filePath) {
MimeMessage message = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(from);
helper.setTo(to);
helper.setSubject(subject);
helper.setText(content, true);
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = filePath.substring(filePath.lastIndexOf(File.separator));
helper.addAttachment(fileName, file);
mailSender.send(message);
//日志信息
logger.info("邮件已经发送。");
} catch (MessagingException e) {
logger.error("发送邮件时发生异常!", e);
}
}
}
- 现在邮件部分已经基本完成可以着手freemarker模板部分了,首先对于没有freemarker经验的人员建议参考官方中文文档(非常实用,能够快速入手)
- application.yml配置freemarker
#配置freemarker
spring:
freemarker:
suffix: .ftl //模板文件后缀
content-type: text/html
charset: utf-8
cache: false
template-loader-path: classpath:/templates/freemarker/ //模板存放处
check-template-location: true
- 接下来开始定义HTML模板,freemarker模板统一用ftl文件后缀命名
由于邮件客户端处于安全机制的考虑是禁止在邮件HTML中嵌入JS以及部分CSS功能的,因为防止一些前端JS恶意XSS攻击,对于邮件客户端也不太好维护
- index.ftl内容,建议用HTML表格定义的标签来书写内容,对于大多数邮件客户端都支持且没有问题
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml"
xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<!-- NAME: NEW COLLECTION -->
<!--[if gte mso 15]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG/>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>*|MC:SUBJECT|*</title>
<style type="text/css">
p {
margin: 10px 0;
padding: 0;
}
table {
border-collapse: collapse;
border: 1px solid #fffef9;
}
th {
font-weight: normal;
}
#bodyTable {
/*@editable*/
background-color: #E6A56A;
/*@editable*/
background-image: url(http://101.200.152.183:8080/static/bg.jpg);
/*@editable*/
background-repeat: no-repeat;
/*@editable*/
background-position: center;
/*@editable*/
background-size: cover;
/*@editable*/
border-top: 0;
/*@editable*/
border-bottom: 0;
}
</style>
</head>
<body>
<h3>这是${taskName},对应的自动化平台测试数据分析结果</h3>
<table align="center" border="0" cellpadding="0" height="100%" width="100%" id="bodyTable" style="color: #fff;">
<tbody>
<tr align="center" height="50px" width="100%" style="color: #fff;font-size: 30px">
<td>
<a
style="font-weight: bold;letter-spacing: normal;line-height: 100%;text-align: center;text-decoration: none;color: #FFFFFF;font-size: 30px;">自动化测试平台
</a>
</td>
</tr>
<tr style="font-size: 18px;">
<td>
<a
style="font-weight: bold;letter-spacing: normal;line-height: 100%;text-align: center;text-decoration: none;color: #fdb933;font-size: 18px;margin-left: 70px;">总计分析:
</a>
</td>
</tr>
<tr height="8px">
</tr>
<tr>
<td>
<table align="center" border="1px" cellpadding="1" height="90%" width="90%" style="color: #fff;">
<tr align="center">
<th>序号</th>
<th>总用例数</th>
<th>通过数</th>
<th>失败数</th>
<th>跳过数</th>
<th>通过率</th>
<th>失败率</th>
<th>跳过率</th>
<th>总和</th>
</tr>
<tr align="center">
<td>1</td>
<td>${globalObj.caseSummaryNum}</td>
<td>${globalObj.passNum}</td>
<td>${globalObj.failNum}</td>
<td>${globalObj.blockNum}</td>
<td>${globalObj.passRatio}</td>
<td>${globalObj.failRatio}</td>
<td>${globalObj.blockRatio}</td>
<td>100%</td>
</tr>
</table>
</td>
</tr>
<tr height="30px">
<td></td>
</tr>
<tr style="font-size: 18px">
<td>
<a
style="font-weight: bold;letter-spacing: normal;line-height: 100%;text-align: center;text-decoration: none;color: #fdb933;font-size: 18px;margin-left: 70px;">黄金流分析:
</a>
</td>
</tr>
<tr height="8px">
</tr>
<tr>
<td>
<table align="center" border="1" cellpadding="0" height="90%" width="90%" style="color: #fff;">
<tr align="center">
<th>序号</th>
<th>总用例数</th>
<th>通过数</th>
<th>失败数</th>
<th>跳过数</th>
<th>通过率</th>
<th>失败率</th>
<th>跳过滤</th>
<th>总和</th>
</tr>
<tr align="center">
<td>1</td>
<td>${goldObj.caseSummaryNum}</td>
<td>${goldObj.passNum}</td>
<td>${goldObj.failNum}</td>
<td>${goldObj.blockNum}</td>
<td>${goldObj.passRatio}</td>
<td>${goldObj.failRatio}</td>
<td>${goldObj.blockRatio}</td>
<td>100%</td>
</tr>
</table>
</td>
</tr>
<tr height="30px">
<td></td>
</tr>
<tr style="font-size: 18px;font-weight: bold;font-family: Arial;">
<td>
<a
style="font-weight: bold;letter-spacing: normal;line-height: 100%;text-align: center;text-decoration: none;color: #fdb933;font-size: 18px;margin-left: 70px;">支付分析:
</a>
</td>
</tr>
<tr height="8px">
</tr>
<tr>
<td>
<table align="center" border="1" cellpadding="0" height="90%" width="90%" style="color: #fff;">
<tr align="center">
<th>序号</th>
<th>总用例数</th>
<th>通过数</th>
<th>失败数</th>
<th>跳过数</th>
<th>通过率</th>
<th>失败率</th>
<th>跳过滤</th>
<th>总和</th>
</tr>
<tr align="center">
<td>1</td>
<td>${payObj.caseSummaryNum}</td>
<td>${payObj.passNum}</td>
<td>${payObj.failNum}</td>
<td>${payObj.blockNum}</td>
<td>${payObj.passRatio}</td>
<td>${payObj.failRatio}</td>
<td>${payObj.blockRatio}</td>
<td>100%</td>
</tr>
</table>
</td>
</tr>
<tr height="10px">
</tr>
<tr align="center" style="font-size: 20px;">
<td align="center" valign="middle" style="font-family: Arial; font-size: 18px; padding: 20px">
<table border="0" cellpadding="0" cellspacing="0" class="mcnButtonContentContainer"
style="border-collapse: separate !important;border-radius: 3px;background-color: #ED5A2E;">
<tbody>
<tr>
<td align="center" valign="middle" style="font-family: Arial; font-size: 16px; padding: 10px;">
<a class="mcnButton " title="点击进入看板" href="#" target="_blank"
style="font-weight: bold;letter-spacing: normal;line-height: 100%;text-align: center;text-decoration: none;color: #FFFFFF;">进入看板</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr height="10px">
</tbody>
</table>
</body>
</html>
- 要让freemarker模板定义的变量实现参数化效果,可以根据Javabean对象,bean对象必须要含有属性的get方法,或者通过map变量一个一个复制,当然map也可以传递对象,详情参考freemarker官方文档
- 接下来对freemarker变量数据及使用基础配置进行了相关封装参考下面FreemarkerConfigUtil 、FreemarkerDataModelUtil工具类
public class FreemarkerDataModelUtil {
private static final String SUMMARY = "caseSummaryNum";
private static final String PASS_NUM = "passNum";
private static final String FAIL_NUM = "failNum";
private static final String BLOCK_NUM = "blockNum";
public static Map<String, Object> getDataModels(CaseExcuteSummary dataModels) {
Map<String, Object> datas = new HashMap<>();
//初始化数据
datas.put(SUMMARY, dataModels.getCaseSummary());
datas.put(PASS_NUM, dataModels.getPassNum());
datas.put(FAIL_NUM, dataModels.getFailNum());
datas.put(BLOCK_NUM, dataModels.getBlockNum());
//pass、fail、block百分率转换
Integer passNum = dataModels.getPassNum();
Integer failNum = dataModels.getFailNum();
Integer summary = dataModels.getCaseSummary();
DecimalFormat df = new DecimalFormat("0.00");
String passRatio = df.format((float) passNum / summary * 100) + "%";
String failRatio = df.format((float) failNum / summary * 100) + "%";
String p = passRatio.replace("%", "");
String f = failRatio.replace("%", "");
String blockRatio = df.format(100 - Float.valueOf(p) - Float.valueOf(f)) + "%";
//百分率设值
datas.put("passRatio", passRatio);
datas.put("failRatio", failRatio);
datas.put("blockRatio", blockRatio);
return datas;
}
}
public class FreemarkerConfigUtil {
/**
* 获取HTML模板
* @param maps
* @return
*/
public static String getTemplate(Map<String,Object> maps){
Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
cfg.setClassForTemplateLoading(FreemarkerConfigUtil.class,"/templates/freemarker");
try {
Template template = cfg.getTemplate("index.ftl");
return FreeMarkerTemplateUtils.processTemplateIntoString(template, maps);
}catch (Exception e){
e.printStackTrace();
}
throw new BizException(ResultCodeEnums.MAIL_SEND_FAIL);
}
}
- 由于freemarker模板数据来源于dao数据库中,所以在此定义下数模及演示数据部分避免读者产生困惑
<!--dao部分选用的是mybatisplus组件-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1.tmp</version>
</dependency>
#数模部分
DROP TABLE IF EXISTS `case_excute_summary`;
CREATE TABLE `case_excute_summary` (
`id` bigint(20) NOT NULL COMMENT 'id',
`task_id` bigint(20) NOT NULL COMMENT '任务id',
`task_name` varchar(20) NOT NULL COMMENT '任务名称',
`case_summary` int(6) NOT NULL COMMENT '总用例数',
`pass_num` int(6) NOT NULL COMMENT '通过数',
`fail_num` int(6) NOT NULL COMMENT '失败数',
`block_num` int(6) NOT NULL COMMENT '跳过数',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `task_id` (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用例执行汇总表';
-- ----------------------------
-- Records of case_excute_summary
-- ----------------------------
INSERT INTO `case_excute_summary` VALUES ('1244796019770351618', '1244796019770351618', '黄金流测试任务', '280', '260', '18', '2', '2020-04-14 19:26:34', '2020-04-14 19:26:37');
INSERT INTO `case_excute_summary` VALUES ('1244796019770351619', '1244796019770351619', '支付测试任务', '270', '260', '5', '5', '2020-04-14 19:26:34', '2020-04-14 19:26:37');
- 根据mybatisplus框架定义entity与数据表进行关系映射,然后在定义mapper进行CRUD
@Data
@TableName("case_excute_summary")
public class CaseExcuteSummary implements Serializable {
private static final long serialVersionUID = 4915869244050353417L;
/**
* 汇总表id
*/
@TableId(value = "id",type = IdType.ID_WORKER)
private Long id;
/**
* 对应任务
*/
@TableField("task_id")
private Long taskId;
/**
* 任务名称
*/
@TableField("task_name")
private String taskName;
/**
* 用例总数
*/
@TableField("case_summary")
private Integer caseSummary;
/**
* 通过数
*/
@TableField("pass_num")
private Integer passNum;
/**
* 失败数
*/
@TableField("fail_num")
private Integer failNum;
/**
* 跳过数
*/
@TableField("block_num")
private Integer blockNum;
/**
* 创建时间
*/
@TableField("create_time")
private Date createTime;
/**
* 更新时间
*/
@TableField("update_time")
private Date updateTime;
}
public interface CaseExcuteSummaryMapper extends BaseMapper<CaseExcuteSummary> {
/**
* 用例汇总数据
* @return
*/
CaseExcuteSummary caseSummaryDetail();
}
- 开始编写测试demo了,在springboot对应的test包下运行测试用例
@SpringBootTest
class DemoApplicationTests {
@Autowired
private CaseExcuteSummaryMapper caseExcuteSummaryMapper;
@Autowired
private MailService mailService;
@Test
void contextLoads() {
List<String> to = new ArrayList<>();
to.add("xxxxx@qq.com");
to.add("xxxx@qq.com");
CaseExcuteSummary glod = new CaseExcuteSummary();
CaseExcuteSummary pay = new CaseExcuteSummary();
HashMap<String, Object> maps = new HashMap<>();
glod.setId(1244796019770351618L);
pay.setId(1244796019770351619L);
CaseExcuteSummary glods = caseExcuteSummaryMapper.selectById(glod);
CaseExcuteSummary pays = caseExcuteSummaryMapper.selectById(pay);
CaseExcuteSummary summary = caseExcuteSummaryMapper.caseSummaryDetail();
Map<String, Object> glodObj = FreemarkerDataModelUtil.getDataModels(glods);
Map<String, Object> payObj = FreemarkerDataModelUtil.getDataModels(pays);
Map<String, Object> summaryObj = FreemarkerDataModelUtil.getDataModels(summary);
maps.put("payObj",payObj);
maps.put("globalObj",summaryObj);
maps.put("goldObj",glodObj);
maps.put("taskName",glods.getTaskName());
String template = FreemarkerConfigUtil.getTemplate(maps);
mailService.sendHtmlMail(to,"自动化测试数据分析报告",template);
}
}
- 登录个人QQ邮箱查看接收到的效果展示
三、总结
在后端标准化的结合Javamail、freemarker可能还会涉及到其它框架组件,向本文中提到的mybatisplus、若只是简单的练习demo可以忽略这一块;数据部分自己通过map集合去解决就OK
文章仅个人总结不喜勿喷;存在不足之处请指正!感谢阅读