整个实现过程如下:
1、实际开发中,特别是在B端产品的开发中,经常会遇到导出 excel 的功能,更进阶一点的需要我们定期生成统计报表,然后通过邮箱发送给指定的人员。
2、这样的一个功能我们将其拆分出来包括三方面的知识点:定时任务框架、excel生成、邮件发送
3、今天要带大家来实现的就是excel生成并通过邮件发送
开发环境:
- 以下演示 jdk 选用1.8版本。spring boot 采用 2.5.1 版本。
- excel 生成通过 alibaba 的 EasyExcel 组件来实现
思路:
1、生成一个excel
2、将 excel 作为附件添加到邮件中进行发送
一、实现邮箱发送
首先需要实现邮箱发送的功能,推荐使用基于 spring-boot-starter-mail
的方法实现:直接引入依赖即可,具体的实现方法,可以查看这篇文章:java发送邮箱
二、生成 excel 表格
实现邮箱发送的功能之后,然后需要将数据生成 excel 表格,这里利用 EasyExcel 来生成 excel,具体方法如下:
1、excel 表格对应的实体类:
/**
* 用于测试邮件发送 -- 实体类
*/
@Data
public class UserInfo {
@ExcelProperty("姓名")
private String userName;
@ExcelProperty("电话号码")
private String phone;
@ExcelProperty("年龄")
private Integer age;
}
2、获取数据来封装表格(这里使用假数据):
/**
* 生成表格中的假数据
*/
public class GenerateDataUtil {
public static List<UserInfo> generateData() {
List<UserInfo> dataList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
UserInfo data = new UserInfo();
data.setUserName("客户" + i);
data.setPhone("13889999999");
data.setAge(18);
dataList.add(data);
}
return dataList;
}
}
3、写生成 excel 表格的工具类(利用 EasyExcel):
/**
* excel表格生成工具
*/
public class ExcelUtil {
/**
* 生成excel文件
*
* @param fileName excel文件路径
* @param dataList 数据列表
* @param clazz 导出对象类
* @param <T>
* @return
*/
public static <T> File generateExcel(String fileName, List<T> dataList, Class<T> clazz) {
// 生成文件
File file = new File(fileName);
// 单sheet写入
EasyExcel.write(file, clazz).sheet("XXX").doWrite(dataList);
return file;
}
/**
* 生成excel文件 -- 方法优化 -- 用流
*
* @param dataList 数据列表
* @param clazz 导出对象类
* @param <T>
* @return
*/
public static <T> ByteArrayOutputStream generateExcel(List<T> dataList, Class<T> clazz) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// 单excel写入
EasyExcel.write(out, clazz).sheet("XXX").doWrite(dataList);
return out;
}
}
三、整体实现
在把上面的准备工作做好之后,就可以实现了,这里直接基于接口调用的方法实现整个过程:
1、写接口:
@Slf4j
@RestController
@RequestMapping("/excel")
public class SendEmailController {
@Autowired
private SendEmailService service;
@GetMapping("/send")
public void generateExcelAndSend() {
// 生成excel中的假数据
long start = System.currentTimeMillis();
try {
service.generateExcelAndSend2();
} catch (IOException e) {
log.error(String.format("生成excel失败,原因:%s", e));
e.printStackTrace();
} catch (MessagingException e) {
log.error(String.format("邮件发送失败,原因:%s", e));
e.printStackTrace();
} finally {
long end = System.currentTimeMillis();
System.out.println("耗时:" + (end - start));
}
}
}
2、service 层:
public interface SendEmailService {
void generateExcelAndSend() throws MessagingException, UnsupportedEncodingException;
void generateExcelAndSend2() throws MessagingException, IOException;
}
然后就是 service 层的实现层:
@Service
public class SendEmailServiceImpl implements SendEmailService {
// 注入发送邮箱工具类
@Autowired
private SendEmailUtil sendEmailUtil;
@Override
public void generateExcelAndSend() throws MessagingException, UnsupportedEncodingException {
// 调方法生成数据
List<UserInfo> userInfos = GenerateDataUtil.generateData();
String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
String fileName = String.format("%s客户统计数据-%s.xlsx", path, UUID.randomUUID());
// 生成excel文件
File excel = ExcelUtil.generateExcel(fileName, userInfos, UserInfo.class);
String subject = "客户统计数据";
String content = "客户统计数据如附件所示";
String toMail = "3088869337@qq.com";
String ccMail = "zhangfuping123456@163.com";
// 发送邮件
sendEmailUtil.sendEmail(subject, content, false, "zfp", toMail, null, null, new File[]{excel});
// 邮件发送完成后删除临时生成的excel文件
excel.delete();
}
// 方法优化
@Override
public void generateExcelAndSend2() throws MessagingException, IOException {
// 调方法生成数据
List<UserInfo> userInfos = GenerateDataUtil.generateData();
//
String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
String fileName = String.format("%s客户统计数据-%s.xlsx", path, UUID.randomUUID());
// 生成excel文件
ByteArrayOutputStream bos = ExcelUtil.generateExcel(userInfos, UserInfo.class);
String subject = "客户统计数据";
String content = "客户统计数据如附件所示";
// 收信人
String toMail = "3088869337@qq.com";
// 抄送
String ccMail = "zhangfuping123456@163.com";
// 发送邮件
sendEmailUtil.sendEmail(subject, content, false, "zfp", toMail, null, null, fileName, new ByteArrayResource(bos.toByteArray()));
// 邮件发送完成后删除临时生成的excel文件
bos.close();
}
}
提示:代码中用到一个邮箱工具类,这个就是实现邮箱发送功能,具体过程可查看这篇文章: java发送邮箱
这里再次贴出代码:
/**
* 发送邮件工具类
*/
@Component
public class SendEmailUtil {
// 注入系统相关类
@Autowired
private JavaMailSender javaMailSender;
@Autowired
private MailProperties mailProperties;
// 发送者邮箱
// @Value("${spring.mail.username}")
// private String sendMailer;
/**
* 邮件发送
*
* @param subject 邮件主题
* @param content 邮件内容
* @param contentIsHtml 内容是否为html格式
* @param fromMailPersonalName 发件人昵称
* @param toMail 收件人邮箱
* @param ccMail 抄送人邮箱
* @param bccMail 秘密抄送人邮箱
* @param fileNames 文件名(本地文件名)
*/
public void sendEmail(String subject, String content, boolean contentIsHtml, String fromMailPersonalName,
String toMail, String ccMail, String bccMail, List<String> fileNames) throws MessagingException, UnsupportedEncodingException {
// true 代表支持复杂的类型
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
/* 邮件发信人:两种写法:
1. 定义成员变量获取application.yml的username属性
@Value("${spring.mail.username}")
private String sendMailer;
helper.setFrom(sendMailer,发件人名称)
2. 注入系统类的对象,然后调方法获取,获取的值也是application.yml文件中的值
@Autowired
private MailProperties mailProperties;
helper.setFrom(mailProperties.getUsername(),发件人名称);
*/
helper.setFrom(mailProperties.getUsername(), fromMailPersonalName);
// 邮件收信人 1或多个
helper.setTo(toMail);
if (!ObjectUtils.isEmpty(ccMail)) {
// 邮件抄送人
helper.setCc(ccMail);
}
if (!ObjectUtils.isEmpty(bccMail)) {
// 邮件私密抄送人
helper.setBcc(bccMail);
}
// 邮件主题
helper.setSubject(subject);
// 邮件内容 contentIsHtml值为 true 代表支持html
helper.setText(content, contentIsHtml);
// 设置附件(注意这里的fileName必须是服务器本地文件名,不能是远程文件链接)
if (!CollectionUtils.isEmpty(fileNames)) {
for (String fileName : fileNames) {
// 添加邮件附件
FileDataSource fileDataSource = new FileDataSource(fileName);
helper.addAttachment(fileDataSource.getName(), fileDataSource);
}
}
// 发送邮件
javaMailSender.send(mimeMessage);
}
/**
* 邮件发送 -- 方法优化 -- 文件对象
*
* @param subject 邮件主题
* @param content 邮件内容
* @param contentIsHtml 内容是否为html格式
* @param fromMailPersonalName 发件人昵称
* @param toMail 收件人邮箱
* @param ccMail 抄送人邮箱
* @param bccMail 秘密抄送人邮箱
* @param files 文件对象
*/
public void sendEmail(String subject, String content, boolean contentIsHtml, String fromMailPersonalName,
String toMail, String ccMail, String bccMail, File[] files) throws MessagingException, UnsupportedEncodingException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(mailProperties.getUsername(), fromMailPersonalName);
helper.setTo(toMail);
if (!ObjectUtils.isEmpty(ccMail)) {
helper.setCc(ccMail);
}
if (!ObjectUtils.isEmpty(bccMail)) {
helper.setBcc(bccMail);
}
helper.setSubject(subject);
helper.setText(content, contentIsHtml);
// 设置附件,不能是远程文件
if (!ObjectUtils.isEmpty(files)) {
for (File file : files) {
helper.addAttachment(file.getName(), file);
}
}
javaMailSender.send(message);
}
/**
* 邮件发送 -- 方法优化 -- 使用流
*
* @param subject 邮件主题
* @param content 邮件内容
* @param contentIsHtml 内容是否为html格式
* @param fromMailPersonalName 发件人昵称
* @param toMail 收件人邮箱
* @param ccMail 抄送人邮箱
* @param bccMail 秘密抄送人邮箱
* @param fileName 文件名称
* @param fileInput 文件流
*/
public void sendEmail(String subject, String content, boolean contentIsHtml, String fromMailPersonalName,
String toMail, String ccMail, String bccMail, String fileName, InputStreamSource fileInput) throws MessagingException, UnsupportedEncodingException {
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true);
helper.setFrom(mailProperties.getUsername(), fromMailPersonalName);
helper.setTo(toMail);
if (!ObjectUtils.isEmpty(ccMail)) {
helper.setCc(ccMail);
}
if (!ObjectUtils.isEmpty(bccMail)) {
helper.setBcc(bccMail);
}
helper.setSubject(subject);
helper.setText(content, contentIsHtml);
// 设置附件,不能是远程文件
if (fileInput != null) {
helper.addAttachment(fileName, fileInput);
}
javaMailSender.send(message);
}
}
3、配置文件:
spring:
# 配置发送邮件的配置信息
mail:
# 配置smtp服务主机地址
host: smtp.qq.com
# 发送者邮箱
username: 308xxxx9337@qq.com
# 配置密码,注意不是真正的密码,而是申请到的授权码
password: jlpxxxxqhdecj
# 端口号:465或587
port: 465
# 默认的邮件编码为UTF-8
default-encoding: UTF-8
# 其他参数
properties:
mail:
# 配置SSL 加密工厂
smtp:
ssl:
# 本地测试,先放开ssl
enable: true
required: true
# 开启debug模式,这样邮件发送过程的日志会在控制台打印出来,方便排查错误
debug: true
最后运行启动类,访问接口即可。
附录
疑问?为什么采用流:
上述的实现,需要先创建一个文件然后又删除,不是很方便,于是可以采取直接用流输入输出:
也就是:
1、首先生成 excel 的方法调整为返回输出流;
2、其次发送邮件的方法调整为,直接接收输入流;
3、主方法调整:不生成文件,而是通过流来传输
邮件正文中直接显示表格数据
1、有时候我们的统计数据不是很多,会更希望我们直接在邮件中展示表格数据,而不用再单独下载附件查看,这就需要用到 HTML 格式的邮件正文的实现。
2、比较简单的实现就是循环数据集合,通过字符串拼接生成 html 的字符串。因为实现比较简单,这里就仅提供思路。
提示:可以查看实现邮箱发送的文章,里面有实现的过程
总结
excel 的生成以及邮件的发送,都应该尽可能的提取为工具类,如果实现的功能更多的更需要提取的单独的服务,通过pom 依赖引入,更大化的实现方法的通用,和业务代码与通用代码之间的解耦。