likes
comments
collection
share

如何在springboot项目中定时发送飞书邮件

作者站长头像
站长
· 阅读数 70

项目中总是会接到发送邮件的需求,但是在邮件中完成条件判断和循环数据列表时,如果手动拼写,则会显得非常麻烦。本文主要采用第三方工具pebble模板引擎来渲染我们的html邮件内容。

需求

每天定时早上8.25发送邮件到指定的邮箱中。邮件内容是统计昨天的数据,并计算相应的百分比。

工具

pebble模板引擎,渲染html
hutool邮件发送工具

开发

定时任务

@Scheduled(cron = "0 25 8 * * ?")
public void sendTicketDataAnalyseEmail() throws PebbleException, IOException {

    LocalDate yesterday = LocalDate.now().minusDays(1L);

    String yesterdayFormat = yesterday.format(dateTimeFormatter);

    sendTicketDataAnalyseEmail(yesterdayFormat);

}

利用spring非常简单的实现一个定时发送程序。

模板解析伪代码

// 获取文件路径
ClassPathResource classPathResource = new ClassPathResource("/script/mail.html");
// 初始化引擎
PebbleEngine engine = new PebbleEngine.Builder().autoEscaping(false).build();
// 加载模板
PebbleTemplate pebbleTemplate = engine.getTemplate(classPathResource.getPath());
// 查询模板的数据
List<TicketDataDayAnalyseDto> ticketDataDayAnalyseDtoList = ticketService.dataAnalyse(ticketDataQueryDto);
// 加载模板数据,并执行
Map<String, Object> context = new HashMap<>();
context.put("dataList", ticketDataDayAnalyseDtoList);
context.put("format", yesterdayFormat);
Writer writer = new StringWriter();
pebbleTemplate.evaluate(writer, context);

String output = writer.toString();

// 将渲染成功的结果数据直接发送
MailUtil.send("yudongbin@wuhanpe.com", yesterdayFormat + "-刷脸乘车统计", output, true);

log.info("--------------邮件发送成功--------------");

邮件模板html

如何在springboot项目中定时发送飞书邮件

这里涉及到pebble中的for标志位使用。使用非常简单,只需要简单的配置即可实现我们需要的功能。

邮件效果

如何在springboot项目中定时发送飞书邮件

如果用户需要自定一些好看的邮件模板,可以使用163邮箱中的源码模式,先编辑好内容,在转换成源码模式,直接生成我们需要的html。我们在嵌入pebble模板的代码即可。

hutool邮件配置

这个也是非常简单,在resources目录下放置mail.setting即可。内容根据需要自行定义即可。

# 邮件服务器的SMTP地址
host = smtp.feishu.cn
# 邮件服务器的SMTP端口
port = 465
# 发件人(必须正确,否则发送失败)
from = yudongbin@wuhanpe.com
# 用户名(注意:如果使用foxmail邮箱,此处user为qq号)
user = yudongbin@wuhanpe.com
# 密码(注意,某些邮箱需要为SMTP服务单独设置密码,详情查看相关帮助)
pass= password
#使用 STARTTLS安全连接,STARTTLS是对纯文本通信协议的扩展。
startttlsEnable = true
# 使用SSL安全连接
sslEnable = true
# 指定实现javax.net.SocketFactory接口的类的名称,这个类将被用于创建SMTP的套接字
socketFactoryClass = javax.net.ssl.SSLSocketFactory
# 如果设置为true,未能创建一个套接字使用指定的套接字工厂类将导致使用java.net.Socket创建的套接字类, 默认值为true
socketFactoryFallback = true
# 指定的端口连接到在使用指定的套接字工厂。如果没有设置,将使用默认端口456
socketFactoryPort = 465
# SMTP超时时长,单位毫秒,缺省值不超时
#timeout = 0
# Socket连接超时值,单位毫秒,缺省值不超时
#connectionTimeout = 0

飞书邮件密码设置

如何在springboot项目中定时发送飞书邮件

设置后生成对应的配置地址和密码,贴到我们的mail.setting文件中即可。


这个时候就想到一个问题,现在渲染html和发送邮件是使用两个单独的工具jar包来完成的,其实可以把这两个合二为一。使用我们常用的hutool工具,直接集成pebble来进行使用即可。这样就免去了单独初始化配置的麻烦。

hutool进阶开发

通过查阅官方文档后发现支持如下几款模板引擎。

如何在springboot项目中定时发送飞书邮件

我们可以通过仿照类似的方式进行集成即可。

下载源码

可以选择下载gitee上面的源码,网速会相对快一些。

如何在springboot项目中定时发送飞书邮件

编写对应代码

其实hutool框架已经封装的相对完善的代码,我们只需要参照之前已经实现的代码类即可。

模板类

package cn.hutool.extra.template.engine.pebble;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.extra.template.AbstractTemplate;
import cn.hutool.extra.template.TemplateException;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Map;

/**
 * @author:zooooooooy
 */
public class PebbleTemplate extends AbstractTemplate {

    private final com.mitchellbosecke.pebble.template.PebbleTemplate template;

    public PebbleTemplate(com.mitchellbosecke.pebble.template.PebbleTemplate template) {
        this.template = template;
    }

    /**
    * 渲染对象
    * @param bindingMap 绑定的参数,此Map中的参数会替换模板中的变量
    * @param writer 输出
    */
    @Override
    public void render(Map<?, ?> bindingMap, Writer writer) {

        final Map<String, Object> map = Convert.convert(new TypeReference<Map<String, Object>>() {}, bindingMap);
        try {
            this.template.evaluate(writer, map);
        } catch (Exception e) {
            throw new TemplateException("pebble template parse failed, cause by: ", e);
        }
    }

    /**
    * 渲染对象
    * @param bindingMap 绑定的参数,此Map中的参数会替换模板中的变量
    * @param out 输出
    */
    @Override
    public void render(Map<?, ?> bindingMap, OutputStream out) {

        final Map<String, Object> map = Convert.convert(new TypeReference<Map<String, Object>>() {}, bindingMap);
        try {
            this.template.evaluate(new OutputStreamWriter(out), map);
        } catch (Exception e) {
            throw new TemplateException("pebble template parse failed, cause by: ", e);
        }

    }

}

该类的主要功能就是使用模板渲染相应的模板文件,得到我们想要的结果。

模板引擎类

package cn.hutool.extra.template.engine.pebble;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.Template;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.TemplateException;
import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.error.PebbleException;
import com.mitchellbosecke.pebble.loader.ClasspathLoader;
import com.mitchellbosecke.pebble.loader.FileLoader;
import com.mitchellbosecke.pebble.loader.StringLoader;
import org.beetl.core.GroupTemplate;

/**
 * @author:zooooooooy
 */
public class PebbleTemplateEngine implements TemplateEngine {

	private PebbleEngine engine;

	public PebbleTemplateEngine() {
	}

	public PebbleTemplateEngine(TemplateConfig config) {
		init(config);
	}

	@Override
	public TemplateEngine init(TemplateConfig config) {
		init(createEngine(config));
		return this;
	}

	/**
	 * 初始化引擎
	 * @param engine 引擎
	 */
	private void init(PebbleEngine engine){
		this.engine = engine;
	}

	/**
	 * 创建引擎
	 *
	 * @param config 模板配置
	 * @return {@link GroupTemplate}
	 */
	private static PebbleEngine createEngine(TemplateConfig config) {
		if (null == config) {
			config = TemplateConfig.DEFAULT;
		}

		PebbleEngine pebbleEngine;
		switch (config.getResourceMode()) {
			case CLASSPATH:
				ClasspathLoader classpathLoader = new ClasspathLoader();
				classpathLoader.setPrefix(StrUtil.addSuffixIfNot(config.getPath(), "/"));
				pebbleEngine = new PebbleEngine.Builder()
						.loader(classpathLoader)
						.autoEscaping(false)
						.build();
				break;
			case FILE:
				FileLoader fileLoader = new FileLoader();
				fileLoader.setPrefix(StrUtil.addSuffixIfNot(config.getPath(), "/"));
				pebbleEngine = new PebbleEngine.Builder()
						.loader(fileLoader)
						.autoEscaping(false)
						.build();
				break;
			case WEB_ROOT:
				fileLoader = new FileLoader();
				fileLoader.setPrefix(StrUtil.addSuffixIfNot(FileUtil.getAbsolutePath(FileUtil.file(FileUtil.getWebRoot(), config.getPath())), "/"));
				pebbleEngine = new PebbleEngine.Builder()
						.loader(fileLoader)
						.autoEscaping(false)
						.build();
				break;
			case STRING:
				StringLoader stringLoader = new StringLoader();
				stringLoader.setPrefix(StrUtil.addSuffixIfNot(config.getPath(), "/"));
				pebbleEngine = new PebbleEngine.Builder()
						.loader(stringLoader)
						.autoEscaping(false)
						.build();
				break;
			default:
				classpathLoader = new ClasspathLoader();
				classpathLoader.setPrefix(StrUtil.addSuffixIfNot(config.getPath(), "/"));
				pebbleEngine = new PebbleEngine.Builder()
						.loader(classpathLoader)
						.autoEscaping(false)
						.build();
				break;
		}

		return pebbleEngine;
	}

	@Override
	public Template getTemplate(String resource) {

		try {
			return new PebbleTemplate(engine.getTemplate(resource));
		} catch (PebbleException e) {
			throw new TemplateException("pebble template get template from resource failed, cause by: ", e);
		}
	}

}

该类主要是通过初始化的路径来选择不同的模板引擎,进而找到对应的文件进行渲染。

编写测试类

@Test
public void pebbleEngineTest() {
    // 字符串模板
    TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("templates").setCustomEngine(PebbleTemplateEngine.class));
    Template template = engine.getTemplate("<h3>{{ message }}</h3>");
    String result = template.render(Dict.create().set("message", "Hutool"));
    Assert.assertEquals("<h3>Hutool</h3>", result);

    //ClassPath模板
    engine = TemplateUtil.createEngine(new TemplateConfig("templates", ResourceMode.CLASSPATH).setCustomEngine(PebbleTemplateEngine.class));
    template = engine.getTemplate("pebble_test.peb");
    result = template.render(Dict.create().set("name", "Hutool"));
    Assert.assertEquals("hello, Hutool", result);
}

pom引入

<dependency>
  <groupId>com.mitchellbosecke</groupId>
  <artifactId>pebble</artifactId>
  <version>2.3.0</version>
  <scope>compile</scope>
  <optional>true</optional>
</dependency>

这里我们就完成了一个主要功能的编写。进行打包丢入到项目中就可以直接在hutool中使用到自己创建的类了。

同时有一个思考,我可不可以提一个pr到hutool官方中去,有可能会被官方收录到hutool-extra包中。

PR

我们可以按照pr的要求,先fork项目到自己仓库,在制定的分支上修改,并提交。编写测试用例,并完善注释。同时要需要遵守hutool官方对于pr的一些要求规范。

hutool-extra pr

已成功被作者合并到v6版本中。

如何在springboot项目中定时发送飞书邮件

总结

自此一个完整的邮件发送完结,后续也可以参照上述方式进行复杂的邮件内容发送。

转载自:https://juejin.cn/post/7216299104405471287
评论
请登录