SpringBoot實(shí)現(xiàn)excel生成并且通過(guò)郵件發(fā)送的步驟詳解
1. 開(kāi)發(fā)環(huán)境
以下演示jdk選用1.8版本。springboot采用2.3.7.RELEASE版本。
excel生成通過(guò)alibaba的EasyExcel組件來(lái)實(shí)現(xiàn),采用最新的穩(wěn)定版本3.1.1
2. 思路
我們的核心實(shí)現(xiàn)分成兩步: 1、生成一個(gè)excel 2、將excel作為附件添加到郵件中進(jìn)行發(fā)送
于是基于此思路,我們結(jié)合EasyExcel提供的write方法來(lái)生成excel文件,該excel文件在郵件發(fā)送完成后需要?jiǎng)h除;然后通過(guò)之前我們講解過(guò)的郵件發(fā)送工具類來(lái)實(shí)現(xiàn)郵件發(fā)送
3. 實(shí)操
1、書(shū)寫郵件發(fā)送工具類,其實(shí)現(xiàn)參考上述博文
<dependency> <groupId>com.alibaba</groupId> <artifactId>easyexcel</artifactId> <version>3.1.1</version> </dependency>
3、實(shí)現(xiàn)easyExcel的數(shù)據(jù)監(jiān)聽(tīng)類
/** * @author benjamin_5 * @Description 數(shù)據(jù)監(jiān)聽(tīng)類 * @date 2022/10/5 */ @EqualsAndHashCode(callSuper = true) public class DataListener<T> extends AnalysisEventListener<T> { /** * 緩存數(shù)據(jù)列表 */ 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、創(chuàng)建生成excel文件的工具類ExcelUtil
public class ExcelUtil { /** * 生成excel文件 * @param fileName excel文件路徑 * @param dataList 數(shù)據(jù)列表 * @param clazz 導(dǎo)出對(duì)象類 * @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、創(chuàng)建導(dǎo)出數(shù)據(jù)的實(shí)體類
/** * @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 = "聯(lián)系電話") private String phone; @ExcelProperty(value = "金額") private BigDecimal amount; @ExcelProperty(value = "注冊(cè)日期") @DateTimeFormat(value = "yyyy-MM-dd") private Date createDate; }
6、實(shí)現(xiàn)excel數(shù)據(jù)生成及郵件發(fā)送的接口。為了演示方便我直接在controller中書(shū)寫了,實(shí)際工作中應(yīng)該把生成并發(fā)送的方法提取到工具類或者Service層中。
這里調(diào)用的是基于spring-boot-starter-mail實(shí)現(xiàn)的郵件發(fā)送工具類
@GetMapping("generateExcelAndSend") public void generateExcelAndSend(){ List<CustomerData> dataList = new ArrayList<>(); // 構(gòu)造假數(shù)據(jù) for (int i = 0; i < 100; i++) { CustomerData data = new CustomerData(); data.setName("客戶"+i); data.setAddress("貴州省貴陽(yáng)市觀山湖區(qū)101號(hào)"); data.setPhone("13889999999"); data.setAmount(BigDecimal.valueOf(Math.random()*10000)); data.setCreateDate(new Date()); dataList.add(data); } // 獲取資源文件存放路徑,用于臨時(shí)存放生成的excel文件 String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath(); // 文件名:采用UUID,防止多線程同時(shí)生成導(dǎo)致的文件重名 String fileName = String.format("%s客戶統(tǒng)計(jì)數(shù)據(jù)-%s.xlsx",path,UUID.randomUUID()); try { // 生成excel文件 File excel = ExcelUtil.generateExcel(fileName, dataList, CustomerData.class); // 發(fā)送郵件 String content = ""; String toMail = "wuhanxue5@sina.com"; String ccMail = "wuhanxue5@163.com"; emailSpringUtil.sendEmail("客戶統(tǒng)計(jì)數(shù)據(jù)",content,true,"郵件提醒系統(tǒng)", toMail,ccMail,null, Collections.singletonList(fileName)); // 郵件發(fā)送完成后刪除臨時(shí)生成的excel文件 excel.delete(); } catch (IOException e) { logger.error(String.format("生成excel失敗,原因:%s",e)); e.printStackTrace(); } catch (MessagingException e) { logger.error(String.format("郵件發(fā)送失敗,原因:%s",e)); e.printStackTrace(); } }
7、同時(shí)因?yàn)槲覀兊母郊Q后面添加一個(gè)了個(gè)UUID,mime.mail中的參數(shù)splitlongparameters默認(rèn)為 true,當(dāng)附件名過(guò)長(zhǎng)時(shí),他會(huì)自動(dòng)截取,就會(huì)導(dǎo)致我們接收到的附件格式變成.bin形式的。
要解決該問(wèn)題就需要將其設(shè)置為false。于是我們創(chuàng)建一個(gè)啟動(dòng)執(zhí)行類來(lái)單獨(dú)設(shè)置
@Configuration public class EmailToLongConfig { @PostConstruct private void init(){ // 解決郵件附件名稱太長(zhǎng)會(huì)自動(dòng)截取,導(dǎo)致附件變成.bin格式問(wèn)題 System.setProperty("mail.mime.splitlongparameters","false"); } }
當(dāng)然我們也可以將System.setProperty("
mail.mime.splitlongparameters","false");放到郵件發(fā)送的方法中去。
8、啟動(dòng)項(xiàng)目,瀏覽器訪問(wèn)接口測(cè)試
http://localhost:8080/excel/generateExcelAndSend
可以看到上述郵件發(fā)送成功,excel附件也接收正常,查看附件內(nèi)容也正常
優(yōu)化
上述的實(shí)現(xiàn),需要先創(chuàng)建一個(gè)文件然后又刪除,不是很方便,我們可以采取直接用流輸入輸出
1、首先生成excel的方法調(diào)整為返回輸出流
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、其次發(fā)送郵件的方法調(diào)整為,直接接收輸入流
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); // 設(shè)置附件(注意這里的fileName必須是服務(wù)器本地文件名,不能是遠(yuǎn)程文件鏈接) if (fileInput != null) { helper.addAttachment(fileName, fileInput); } javaMailSender.send(message); }
3、主方法調(diào)整:不生成文件,而是通過(guò)流來(lái)傳輸
@GetMapping("generateExcelAndSend2") public void generateExcelAndSend2() throws IOException { long start = System.currentTimeMillis(); List<CustomerData> dataList = new ArrayList<>(); // 構(gòu)造假數(shù)據(jù) for (int i = 0; i < 100; i++) { CustomerData data = new CustomerData(); data.setName("客戶"+i); data.setAddress("貴州省貴陽(yáng)市觀山湖區(qū)101號(hào)"); data.setPhone("13889999999"); data.setAmount(BigDecimal.valueOf(Math.random()*10000)); data.setCreateDate(new Date()); dataList.add(data); } // 獲取資源文件存放路徑,用于臨時(shí)存放生成的excel文件 String path = Objects.requireNonNull(this.getClass().getClassLoader().getResource("")).getPath(); // 文件名:采用UUID,防止多線程同時(shí)生成導(dǎo)致的文件重名 String fileName = String.format("%s客戶統(tǒng)計(jì)數(shù)據(jù)-%s.xlsx",path,UUID.randomUUID()); ByteArrayOutputStream out = null; try { // 生成excel文件 out = ExcelUtil.generateExcel(dataList, CustomerData.class); // 發(fā)送郵件 String content = "客戶統(tǒng)計(jì)數(shù)據(jù)如附件所示"; String toMail = "wuhanxue5@sina.com"; String ccMail = "wuhanxue5@163.com"; emailSpringUtil.sendEmail("客戶統(tǒng)計(jì)數(shù)據(jù)",content,false,"郵件提醒系統(tǒng)", 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("郵件發(fā)送失敗,原因:%s",e)); e.printStackTrace(); }finally { if(out != null){ out.close(); } long end = System.currentTimeMillis(); System.out.println("耗時(shí):" + (end - start)); } }
4、最終測(cè)試下來(lái),第二種方法要比之前方法快600ms左右
郵件正文中直接顯示表格數(shù)據(jù)
有時(shí)候我們的統(tǒng)計(jì)數(shù)據(jù)不是很多,會(huì)更希望我們直接在郵件中展示表格數(shù)據(jù),而不用再單獨(dú)下載附件查看,這就需要用到HTML格式的郵件正文的實(shí)現(xiàn)
比較簡(jiǎn)單的實(shí)現(xiàn)就是循環(huán)數(shù)據(jù)集合,通過(guò)字符串拼接生成html的字符串。因?yàn)閷?shí)現(xiàn)比較簡(jiǎn)單,這里就僅提供思路,如果有不清楚的同學(xué)可以留言提問(wèn)。后續(xù)我們單獨(dú)抽離成一個(gè)組件來(lái)實(shí)現(xiàn)生成html字符串的功能
總結(jié)
excel的生成以及郵件的發(fā)送,都應(yīng)該盡可能的提取為工具類,如果實(shí)現(xiàn)的功能更多的更需要提取的單獨(dú)的服務(wù),通過(guò)pom依賴引入,更大化的實(shí)現(xiàn)方法的通用,和業(yè)務(wù)代碼與通用代碼之間的解耦。
以上就是SpringBoot實(shí)現(xiàn)excel生成并且通過(guò)郵件發(fā)送的步驟詳解的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot實(shí)現(xiàn)excel生成的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Mybatis中使用萬(wàn)能的Map傳參實(shí)現(xiàn)
在編程中,有可能遇到我們的實(shí)體類或者數(shù)據(jù)庫(kù)中表的字段或參數(shù)過(guò)多的情況,那這時(shí)候用Map傳參是比較理想的選擇,本文就詳細(xì)的介紹一下如何使用,感興趣的可以了解下2021-07-07SpringBoot接口返回結(jié)果封裝方法實(shí)例詳解
在實(shí)際項(xiàng)目中,一般會(huì)把結(jié)果放在一個(gè)封裝類中,封裝類中包含http狀態(tài)值,狀態(tài)消息,以及實(shí)際的數(shù)據(jù)。這里主要記錄兩種方式,通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-09-09SpringBoot多線程進(jìn)行異步請(qǐng)求的處理方式
這篇文章主要介紹了SpringBoot多線程進(jìn)行異步請(qǐng)求的處理方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜2021-12-12關(guān)于spring中bean注冊(cè)的優(yōu)先級(jí)分析
Spring框架中,Bean的定義方式主要有三種:XML定義、注解掃描和配置類中的@Bean注解,在Bean注冊(cè)過(guò)程中,XML定義的GenericBeanDefinition優(yōu)先級(jí)最高2024-09-09Java實(shí)現(xiàn)數(shù)據(jù)更新和事件通知的觀察者模式
Java觀察者模式是一種行為型設(shè)計(jì)模式,用于實(shí)現(xiàn)對(duì)象間的一對(duì)多依賴關(guān)系。當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),它的所有依賴對(duì)象都會(huì)收到通知并自動(dòng)更新。觀察者模式可以實(shí)現(xiàn)松耦合,增強(qiáng)了系統(tǒng)的可維護(hù)性和可拓展性2023-04-04