0. 引言

实践开发中,特别是在B端产品的开发中,咱们经常会遇到导出excel的功用,更进阶一点的需求咱们定期生成统计报表,然后经过邮箱发送给指定的人员。

这样的一个功用咱们将其拆分出来包括三方面的知识点:定时使命结构、excel生成、邮件发送

今天要带咱们来完成的便是excel生成并经过邮件发送

1. 开发环境

以下演示jdk选用1.8版别。springboot选用2.3.7.RELEASE版别。

excel生成经过alibaba的EasyExcel组件来完成,选用最新的安稳版别3.1.1

2. 思路

咱们的中心完成分红两步: 1、生成一个excel 2、将excel作为附件增加到邮件中进行发送

于是根据此思路,咱们结合EasyExcel提供的write办法来生成excel文件,该excel文件在邮件发送完成后需求删去;然后经过之前咱们解说过的邮件发送东西类来完成邮件发送

邮件发送东西类不再独自解说,不清楚的同学能够检查之前几期的内容:

springboot:java完成邮件及附件发送、HTML正文的三种办法(一)

springboot:java完成邮件及附件发送、HTML正文的三种办法(二)

springboot:java完成邮件及附件发送、HTML正文的三种办法(三)

3. 实操

1、书写邮件发送东西类,其完成参考上述博文

2、引入excel依靠:

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.1.1</version>
</dependency>

3、完成easyExcel的数据监听类

/**
 * @author benjamin_5
 * @Description 数据监听类
 * @date 2022/10/5
 */
@EqualsAndHashCode(callSuper = true)
public class DataListener<T> extends AnalysisEventListener<T> {
    /**
     * 缓存数据列表
     */
    private final List<T> dataList = new ArrayList<>();
    @Override
    public void invoke(T data, AnalysisContext context) {
        dataList.add(data);
    }
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
    }
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        this.invokeHeadMap(ConverterUtils.convertToStringMap(headMap,context),context);
    }
    public List<T> getDataList() {
        return dataList;
    }
}

4、创立生成excel文件的东西类ExcelUtil

public class ExcelUtil {
    /**
     * 生成excel文件
     * @param fileName excel文件途径
     * @param dataList 数据列表
     * @param clazz 导出对象类
     * @param <T>
     * @return
     * @throws IOException
     */
    public static <T> File generateExcel(String fileName, List<T> dataList, Class<T> clazz) throws IOException {
        // 生成文件
        File excel = new File(fileName);
        // excel写入
        EasyExcel.write(excel,clazz).sheet(0).doWrite(dataList);
        return excel;
    }
}

5、创立导出数据的实体类

/**
 * @author benjamin_5
 * @Description
 * @date 2022/10/5
 */
@Data
public class CustomerData implements Serializable {
    @ExcelProperty(value = "客户称号")
    @ColumnWidth(value = 25)
    private String name;
    @ExcelProperty(value = "客户地址")
    @ColumnWidth(value = 50)
    private String address;
    @ExcelProperty(value = "联系电话")
    private String phone;
    @ExcelProperty(value = "金额")
    private BigDecimal amount;
    @ExcelProperty(value = "注册日期")
    @DateTimeFormat(value = "yyyy-MM-dd")
    private Date createDate;
}

6、完成excel数据生成及邮件发送的接口。为了演示便利我直接在controller中书写了,实践工作中应该把生成并发送的办法提取到东西类或许Service层中。

这儿调用的是根据spring-boot-starter-mail完成的邮件发送东西类

@GetMapping("generateExcelAndSend")
    public void generateExcelAndSend(){
        List<CustomerData> dataList = new ArrayList<>();
        // 构造假数据
        for (int i = 0; i < 100; i++) {
            CustomerData data = new CustomerData();
            data.setName("客户"+i);
            data.setAddress("贵州省贵阳市观山湖区101号");
            data.setPhone("13889999999");
            data.setAmount(BigDecimal.valueOf(Math.random()*10000));
            data.setCreateDate(new Date());
            dataList.add(data);
        }
        // 获取资源文件寄存途径,用于暂时寄存生成的excel文件
        String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
        // 文件名:选用UUID,避免多线程一起生成导致的文件重名
        String fileName = String.format("%s客户统计数据-%s.xlsx",path,UUID.randomUUID());
        try {
            // 生成excel文件
            File excel = ExcelUtil.generateExcel(fileName, dataList, CustomerData.class);
            // 发送邮件
            String content = "";
            String toMail = "wuhanxue5@sina.com";
            String ccMail = "wuhanxue5@163.com";
            emailSpringUtil.sendEmail("客户统计数据",content,true,"邮件提示体系",
                    toMail,ccMail,null, Collections.singletonList(fileName));
            // 邮件发送完成后删去暂时生成的excel文件
            excel.delete();
        } catch (IOException e) {
            logger.error(String.format("生成excel失利,原因:%s",e));
            e.printStackTrace();
        } catch (MessagingException e) {
            logger.error(String.format("邮件发送失利,原因:%s",e));
            e.printStackTrace();
        }
    }

7、一起由于咱们的附件称号后边增加一个了个UUID,mime.mail中的参数splitlongparameters默认为 true,当附件名过长时,他会主动截取,就会导致咱们接纳到的附件格局变成.bin方式的。

要处理该问题就需求将其设置为false。于是咱们创立一个发动履行类来独自设置

@Configuration
public class EmailToLongConfig {
    @PostConstruct
    private void init(){
        // 处理邮件附件称号太长会主动截取,导致附件变成.bin格局问题
        System.setProperty("mail.mime.splitlongparameters","false");
    }
}

当然咱们也能够将System.setProperty("mail.mime.splitlongparameters","false");放到邮件发送的办法中去。

8、发动项目,浏览器拜访接口测试http://localhost:8080/excel/generateExcelAndSend

springboot:实现excel生成并且通过邮件发送

能够看到上述邮件发送成功,excel附件也接纳正常,检查附件内容也正常

springboot:实现excel生成并且通过邮件发送

优化

上述的完成,需求先创立一个文件然后又删去,不是很便利,咱们能够采取直接用流输入输出

1、首要生成excel的办法调整为返回输出流

public static <T> ByteArrayOutputStream generateExcel(List<T> dataList, Class<T> clazz) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        // excel写入
        EasyExcel.write(out,clazz).sheet(0).doWrite(dataList);
        return out;
    }

2、其次发送邮件的办法调整为,直接接纳输入流

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);
        // 设置附件(注意这儿的fileName有必要是服务器本地文件名,不能是远程文件链接)
        if (fileInput != null) {
            helper.addAttachment(fileName, fileInput);
        }
        javaMailSender.send(message);
    }

3、主办法调整:不生成文件,而是经过流来传输

@GetMapping("generateExcelAndSend2")
    public void generateExcelAndSend2() throws IOException {
        long start = System.currentTimeMillis();
        List<CustomerData> dataList = new ArrayList<>();
        // 构造假数据
        for (int i = 0; i < 100; i++) {
            CustomerData data = new CustomerData();
            data.setName("客户"+i);
            data.setAddress("贵州省贵阳市观山湖区101号");
            data.setPhone("13889999999");
            data.setAmount(BigDecimal.valueOf(Math.random()*10000));
            data.setCreateDate(new Date());
            dataList.add(data);
        }
        // 获取资源文件寄存途径,用于暂时寄存生成的excel文件
        String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath();
        // 文件名:选用UUID,避免多线程一起生成导致的文件重名
        String fileName = String.format("%s客户统计数据-%s.xlsx",path,UUID.randomUUID());
        ByteArrayOutputStream out = null;
        try {
            // 生成excel文件
            out = ExcelUtil.generateExcel(dataList, CustomerData.class);
            // 发送邮件
            String content = "客户统计数据如附件所示";
            String toMail = "wuhanxue5@sina.com";
            String ccMail = "wuhanxue5@163.com";
            emailSpringUtil.sendEmail("客户统计数据",content,false,"邮件提示体系",
                    toMail,ccMail,null, fileName, new ByteArrayResource(out.toByteArray()));
        } catch (IOException e) {
            logger.error(String.format("生成excel失利,原因:%s",e));
            e.printStackTrace();
        } catch (MessagingException e) {
            logger.error(String.format("邮件发送失利,原因:%s",e));
            e.printStackTrace();
        }finally {
            if(out != null){
                out.close();
            }
            long end = System.currentTimeMillis();
            System.out.println("耗时:" + (end - start));
        }
    }

4、最终测试下来,第二种办法要比之前办法快600ms左右

邮件正文中直接显示表格数据

有时候咱们的统计数据不是很多,会更希望咱们直接在邮件中展现表格数据,而不必再独自下载附件检查,这就需求用到HTML格局的邮件正文的完成

比较简单的完成便是循环数据集合,经过字符串拼接生成html的字符串。由于完成比较简单,这儿就仅提供思路,假如有不清楚的同学能够留言发问。后续咱们独自抽离成一个组件来完成生成html字符串的功用

演示源码

以上演示的源码能够在如下地址中下载

git源码地址

总结

excel的生成以及邮件的发送,都应该尽可能的提取为东西类,假如完成的功用更多的更需求提取的独自的服务,经过pom依靠引入,更大化的完成办法的通用,和事务代码与通用代码之间的解耦。

仍是那句话,看十遍,不如动手操作一遍。