数据分流写入Excel

2024-08-26 14:48
文章标签 数据 excel 写入 分流

本文主要是介绍数据分流写入Excel,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

数据拆分写入Excel并压缩

上篇Poi版本升级优化中讲到了如果不做poi版本升级, 遇到大数据量时, 可以通过将数据拆分的方式写入到多个Excel文件中并压缩后提供给前端下载.

1. 实现思想

(1) 设置一个阈值, 当数据量大于该阈值时就将数据量拆分进行写入Excel;

(2) 如何拆分? 数据量对阈值取模 :

为阈值的整数n倍, 则创建n个Excel文件写入, 每个Excel文件写入阈值条数据;

为阈值的小数n.xx倍, 则创建n+1个Excel文件写入, 前面每个Excel文件写入阈值条数据, 最后一个Excel 文件写入余数条数据.

(3) 将生成的多个Excel文件暂存到服务器临时下载目录中, 等所有数据写入完成后, 将临时下载目录的Excel文件进行压缩处理, 并返回一个压缩文件给前端下载.

(4) 临时下载目录的清理, 服务器的空间是有限的, 临时数据需要考虑到清理机制. 可以通过定时任务调度进行删除.

2. 编写代码

我将大部分公共逻辑进行了封装, 抽象出不同场景的数据查询方法给子类重写实现.

2.1 公共方法入口

com.poi.service.ExpExcelByPagesService#exportExcel

/*** 导出Excel文件* @param dataMap*/
public void exportExcel(Map<String, Object> dataMap) {logger.info(">>>>{}, dataMap is:{}", this.getClass().getSimpleName(), dataMap);// 统计查询的数据量int record = 0;// 阈值: 每个excel文件数据量, 也是db分页查询的数据量上限, 推荐 10000int pageSize = 10000;// 当前页码, 默认查询第一页int currentPage = 1;// 用户编号String userCode = (String) dataMap.get(CSISCONSTANT.USER_CODE);// 文件类型 .xls .xlsxString fileType = (String) dataMap.get(CSISCONSTANT.EXCEL_FILE_TYPE);// 文件类型非空判断,如果为空, 默认下载03版xlsfileType = !StringUtils.isEmpty(fileType) && Objects.equals(CSISCONSTANT.EXCEL07_EXTENSION, fileType) ? fileType :CSISCONSTANT.EXCEL03_EXTENSION;logger.info("export excel fileType is:{}", fileType);// /home/wasadmin/exportData/yyyyMMdd/yyyyMMddHHmmss+userCode/String writeDir = String.format(EXPORT_PARENT_PATH + DateUtil.format(new Date(), CSISCONSTANT.FORMAT_YYYYMMDD) + File.separator +DateUtil.format(new Date(), CSISCONSTANT.FORMATYYYYMMDDHHMMSS)) + userCode + File.separator;// 目录不存在, 则创建File writeDirFile = new File(writeDir);if (!writeDirFile.exists()) {writeDirFile.mkdirs();}// 标题行(表头) key-valueLinkedHashMap<String, Object> titleMap = getTitleMap();// 获取查询的全量数据  实际应用场景中应该只需要查询总条数, 不需要所有字段都查询出来//具体的数据应该在后面的分页查询中获取(db查询)// 因为我这里是demo,所以暂时用集合模拟全量数据查询了List<HashMap<String, Object>> dataList = getDataList();// 当前查询的数据总条数record = dataList.size();// 如果大于设置的每个excel文件数据量, 则将数据切割写入多个excel文件if (record > pageSize) {loopCreateExcel(dataMap, record, pageSize, fileType, writeDir, titleMap,dataList);} else {List<HashMap<String, Object>> pageDataList = queryDataByPages(dataMap, pageSize, 																	currentPage, 0, dataList);writeToExcel(fileType, writeDir, userCode, titleMap, currentPage, pageDataList);}// 所有文件写入到服务器后, 将它们压缩成一个zip文件给前端界面下载String zipFileName = writeDir + "EXP" + DateUtil.format(new Date(), CSISCONSTANT.FORMAT_YYYYMMDD) + DataHandleUtil.getRandomNumber(8) + 			CSISCONSTANT.ZIP_FILE_EXTENSION;ExportDataToExcelService.writeZipExcel(writeDir, zipFileName);// 设置 FILE_DOWN_LOAD_NAME 由拦截器将下载文件路径写入上下文的响应流中dataMap.put(CSISCONSTANT.FILE_DOWNLOAD_NAME, new File(zipFileName));
}

2.2 获取标题行数据

不同应用场景, 导出的Excel文件标题行(表头)不一样, 需要进行个性化定制, 我将获取标题行数据的方法抽象出来, 提供了子类重写.

com.poi.service.ExpExcelByPagesService#getTitleMap

/*** 储存标题行(表头)数据 , 可以抽象, 在子类实现* @return*/
public abstract LinkedHashMap<String, Object> getTitleMap();

2.3 获取数据行数据

数据行的数据获取也跟上面一样, 导出的数据根据场景而定, 进行抽象.

com.poi.service.ExpExcelByPagesService#getDataList

/*** 查询全量数据, 可以抽象, 在子类实现* @return*/
public abstract List<HashMap<String, Object>> getDataList();

2.4 阈值判断是否拆分数据

上面获取到了总的数据量, 可以与设置的阈值进行比较, 判断是否需要进行数据拆分.

// 当前查询的数据总条数
record = dataList.size();
// 如果大于设置的每个excel文件数据量, 则将数据切割写入多个excel文件
if (record > pageSize) {loopCreateExcel(dataMap, record, pageSize, fileType, writeDir, titleMap, dataList);
} else {List<HashMap<String, Object>> pageDataList = queryDataByPages(dataMap, pageSize, 																currentPage, 0, dataList);writeToExcel(fileType, writeDir, userCode, titleMap, currentPage, pageDataList);
}

2.5 数据量大于阈值-拆分

if (record > pageSize) {loopCreateExcel(dataMap, record, pageSize, fileType, writeDir, titleMap, dataList);
} 

数据拆分规则, 按照分页查询出来, 每页数据需要重新设置分页查询起始位置结束位置, 从而获取到拆分的每页数据.

com.poi.service.ExpExcelByPagesService#loopCreateExcel

private void loopCreateExcel(Map<String, Object> dataMap, int record, int pageSize,String fileType, String writeDir, LinkedHashMap<String,Object> titleMap,List<HashMap<String, Object>> contentList) {int currentPage = 0;// 超过设置的单个sheet表数据上限 , 分页查询并分多个excel文件写入int loopNum = record / pageSize;int remainder = record % pageSize;logger.info("loopNum={}, remainder={} ", loopNum, remainder);String userCode = (String) dataMap.get(CSISCONSTANT.USER_CODE);for (int i = 0; i < loopNum; i++) {currentPage = i + 1; // 一页对应一个excel文件List<HashMap<String, Object>> pageDataList = queryDataByPages(dataMap, pageSize, 																	currentPage, 0, contentList);writeToExcel(fileType, writeDir, userCode, titleMap, currentPage, pageDataList);}if (remainder > 0) {currentPage = loopNum + 1;List<HashMap<String, Object>> pageDataList = queryDataByPages(dataMap, pageSize, 															currentPage, remainder, contentList);writeToExcel(fileType, writeDir, userCode, titleMap, currentPage, pageDataList);}
}

设置分页参数 com.poi.service.ExpExcelByPagesService#storePageParam

private void storePageParam(Map<String, Object> dataMap, int pageSize, int currentPage, int remainder) {// 起始位置 db查询和集合的索引定义不一样, 要注意区分起始索引是0或1
//        int startIndex = (currentPage - 1) * pageSize + 1;int startIndex = (currentPage - 1) * pageSize;// 结束位置int endIndex = currentPage * pageSize;// 如果最后一页不满pageSize条, 就是实际的余数remainder条记录if (remainder > 0) {endIndex = (currentPage - 1) * pageSize + remainder;}dataMap.put(CSISCONSTANT.START_INDEX, startIndex);dataMap.put(CSISCONSTANT.END_INDEX, endIndex);
}

分页查询数据 com.poi.service.ExpExcelByPagesService#queryDataByPages

private List<HashMap<String, Object>> queryDataByPages(Map<String, Object> dataMap,int pageSize, int currentPage,int remainder, List<HashMap<String, Object>> contentList) {// 设置分页查询参数storePageParam(dataMap, pageSize, currentPage, remainder);int startIndex = (Integer) dataMap.get(CSISCONSTANT.START_INDEX);int endIndex = (Integer) dataMap.get(CSISCONSTANT.END_INDEX);// 分页查询数据, 实际场景是db分页查询//而且也应该抽取到抽象方法中, 在子类中实现(因为不同场景查询的数据不一样)List<HashMap<String, Object>> pageDataList = contentList.subList(startIndex,endIndex);// 删除集合中的空行List<HashMap<String, Object>> newDataList = pageDataList.stream().filter(Objects::nonNull).collect(Collectors.toList());return newDataList;
}

2.6 数据写入Excel

不管数据量是在设置阈值以内, 还是大于阈值, 数据准备完成后, 就是将数据写入Excel文件中了.

List<HashMap<String, Object>> pageDataList = queryDataByPages(dataMap, pageSize, 																currentPage, 0, dataList);
writeToExcel(fileType, writeDir, userCode, titleMap, currentPage, pageDataList);

数据行和标题行数据转换的公共方法 com.poi.service.ExpExcelByPagesService#writeToExcel

private String writeToExcel(String fileType, String writeDir, String userCode,LinkedHashMap<String, Object> titleMap, int currentPage, List<HashMap<String, Object>> pageDataList) {if (CollectionUtils.isEmpty(titleMap)) {throw new RuntimeException("标题行数据不能为空");}// 封装标题行List<String> titleList = ExportDataToExcelService.getTitleList(titleMap);// 封装数据行List<List<String>> detailList = ExportDataToExcelService.getDetailList(pageDataList, titleMap);// 获取Excel文件写入的绝对路径  /home/wasadmin/exportData/20210927/202109271010103102435/EXP20210927_3102435_1.xlsString writeFileName = writeDir + "EXP" + DateUtil.format(new Date(), CSISCONSTANT.FORMAT_YYYYMMDD) + "_" + userCode + "_" +currentPage + fileType;logger.info("export excel writeFileName is : {}", writeFileName);// 生成Excel文件并写入数据ExportDataToExcelService.writeExcel(fileType, writeFileName, titleList,detailList);return writeFileName;
}

写入Excel的公共方法 com.poi.service.ExportDataToExcelService#writeExcel

public static void writeExcel(String fileType, String writeFileName, List<String> titleList, List<List<String>> detailList) {FileOutputStream out = null;try {out = new FileOutputStream(writeFileName);toWritePerExcelByOneSheet(out, titleList, detailList, fileType);} catch (IOException e) {e.printStackTrace();} finally {if (out != null) {try {out.close();} catch (IOException e) {e.printStackTrace();}}}
}

toWritePerExcelByOneSheet

private static void toWritePerExcelByOneSheet(FileOutputStream out, List<String> titleList,List<List<String>> contentList, String fileType) throws IOException {Workbook wb = getWorkbook(fileType); // 根据fileType获取HSSF或XSSF工作簿Sheet sheet = wb.createSheet(); // 创建一个sheet表createHeadRow(wb, sheet, titleList); // 创建标题行createDataRow(wb, sheet, contentList); // 创建数据行(单元格)wb.write(out); // 把相应的excel在工作簿存盘out.flush(); // 刷新if (null != out) {out.close();}
}

com.poi.service.ExportDataToExcelService#getWorkbook

/*** 根据要生成的文件类型创建HSSF或者XSSF工作簿* @param fileType .xls .xlsx* @return*/
public static Workbook getWorkbook(String fileType) {Workbook wb = null;switch (fileType) {case CSISCONSTANT.EXCEL03_EXTENSION:wb = new HSSFWorkbook(); // 创建工作簿 2003版excelbreak;case CSISCONSTANT.EXCEL07_EXTENSION:default:wb = new XSSFWorkbook(); // 创建工作簿 2007版excelbreak;}return wb;
}

createHeadRow, createDataRow 在博客Poi实现Excel导出已经给出过代码实现, 这里就不重复了.

2.7 文件压缩下载

所有拆分的数据写入Excel文件后, 将服务器临时下载目录的Excel文件压缩成一个zip文件后, 返回给前端下载.

// 所有文件写入到服务器后, 将它们压缩成一个zip文件给前端界面下载
String zipFileName = writeDir + "EXP" + DateUtil.format(new Date(), CSISCONSTANT.FORMAT_YYYYMMDD) + DataHandleUtil.getRandomNumber(8) + 		CSISCONSTANT.ZIP_FILE_EXTENSION;
ExportDataToExcelService.writeZipExcel(writeDir, zipFileName);
// 设置 FILE_DOWN_LOAD_NAME 由拦截器将下载文件路径写入上下文的响应流中
dataMap.put(CSISCONSTANT.FILE_DOWNLOAD_NAME, new File(zipFileName));

ExportDataToExcelService#writeZipExcel在博客Poi实现Excel导出已经给出过代码实现, 这里就不重复了.

2.8 子类实现

因为标题行和数据行的数据在不同场景下是不一样的, 所以对这两部的数据获取进行抽象处理, 需要在子类中重写.

子类 com.poi.service.impl.ExpExcelByPageImpl

/*** 类描述:分流导出数据到Excel* @Author wang_qz* @Date 2021/9/27 20:04* @Version 1.0* 调用{@link ExpExcelByPagesService#exportExcel} 方法分流写入Excel* 需要重写{@link ExpExcelByPagesService#getTitleMap()} 和 * {@link ExpExcelByPagesService#getDataList()} 方法*/
public class ExpExcelByPageImpl extends ExpExcelByPagesService {@Overridepublic LinkedHashMap<String, Object> getTitleMap() {LinkedHashMap<String, Object> titleMap = new LinkedHashMap<>();titleMap.put("id", "序号");titleMap.put("name", "姓名");titleMap.put("age", "年龄");titleMap.put("gender", "性别");return titleMap;}/*** 查询全量数据, 实际场景中可能是查询db* @return*/@Overridepublic List<HashMap<String, Object>> getDataList() {List<HashMap<String, Object>> dataList = new ArrayList<>();for (int i = 1; i <= 50000; i++) {HashMap<String, Object> data = new HashMap<>();data.put("id", i);data.put("name", "admin" + i);data.put("age", 10 * i);data.put("gender", i % 2 == 0 ? "男" : "女");dataList.add(data);}return dataList;}
}

3. 单元测试

3.1 测试代码

com.test.poi.PoiExcelTest#testExpExcelByPagesService

@Test
public void testExpExcelByPagesService() {Map<String, Object> dataMap = new HashMap<>();dataMap.put(CSISCONSTANT.USER_CODE, "3102435");
//  dataMap.put(CSISCONSTANT.EXCEL_FILE_TYPE, CSISCONSTANT.EXCEL07_EXTENSION);dataMap.put(CSISCONSTANT.EXCEL_FILE_TYPE, CSISCONSTANT.EXCEL03_EXTENSION);ExpExcelByPagesService service = new ExpExcelByPageImpl();service.exportExcel(dataMap);
}

3.2 测试结果

我在上面的子类中模拟的是5万条数据, 阈值设置的是1万, 所以会生成5个Excel文件并压缩.
请添加图片描述
打开压缩包
在这里插入图片描述
打开Excel文件, 查看写入的数据
在这里插入图片描述

4. Web端测试

4.1 编写Controller

com.poi.controller.ExcelController#downloadExcel3

/*** 经过服务器临时下载目录中转的实现-数据分流写入多个Excel且压缩后下载* @param response* @throws MyException* @throws IOException*/
@RequestMapping("/downloadExcel3")
public void downloadExcel3(HttpServletResponse response) throws MyException, IOException {// 设置响应头response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 设置防止中文名乱码String filename = URLEncoder.encode("用户信息", "utf-8");// 文件下载方式(附件下载还是在当前浏览器打开) xxx.zip文件response.setHeader("Content-disposition", "attachment;filename=" + filename + CSISCONSTANT.ZIP_FILE_EXTENSION);Map<String, Object> dataMap = new HashMap<>();dataMap.put(CSISCONSTANT.USER_CODE, "3102435");dataMap.put(CSISCONSTANT.EXCEL_FILE_TYPE, CSISCONSTANT.EXCEL03_EXTENSION);ExpExcelByPagesService service = new ExpExcelByPageImpl();service.exportExcel(dataMap);// 数据分流写入多个Excel且压缩后下载File downloadFile = (File) dataMap.get(CSISCONSTANT.FILE_DOWNLOAD_NAME);// 因为将文件存在了服务器的临时下载目录,所以需要读取服务器上的文件写入响应流中FileInputStream read = new FileInputStream(downloadFile);ServletOutputStream out = response.getOutputStream();byte[] bys = new byte[1024];while (read.read(bys) != -1) {out.write(bys, 0, bys.length);out.flush();}out.close();
}

4.2 测试结果

启动tomcat应用, 在浏览器访问 http://localhost:8080/excel/downloadExcel3, 查看下载效果:

在这里插入图片描述
打开压缩包, 也是生成的5个Excel文件

在这里插入图片描述

5. 临时下载目录清理

临时下载目录的清理, 服务器的空间是有限的, 临时数据需要考虑到清理机制. 可以通过定时任务调度进行删除 . 我使用的是Quartz框架, 当然也有很多其他方式实现定时任务调度, 比如:

(1) SpringBoot注解@EnableScheduling + @Scheduled

(2) java.util.Timer + java.util.TimerTask

(3) ScheduledExecutorService

(4) Quartz

(5) Spring Task

(6) 分布式任务调度实现: 推荐许雪里老师的 xxl-job

5.1 清理任务实现

添加依赖

<dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.1</version>
</dependency><dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz-jobs</artifactId><version>2.3.1</version>
</dependency>

如果是SpringBoot,就不用上面的依赖,直接引入starter,会自动导入上面的依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-quartz</artifactId><version>2.3.1.RELEASE</version>
</dependency>

代码实现 com.timer.ClearExcelDownloadTimer

package com.timer;import cn.hutool.core.date.DateUtil;
import com.constant.CSISCONSTANT;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.io.File;
import java.util.Calendar;
import java.util.Date;/*** 删除服务器上excel文件临时下载目录(一个小时之前的导出目录)* 每个小时调度一次*/
public class ClearExcelDownloadTimer {private static final Logger logger = LoggerFactory.getLogger(ClearExcelDownloadTimer.class);private static final String EXPORT_PARENT_PATH = CSISCONSTANT.TEMP_DOWNLOAD_DIR;public void task() {logger.info(">>>{} start", this.getClass().getSimpleName());String today = DateUtil.format(new Date(), CSISCONSTANT.FORMAT_YYYYMMDD);// 获取excel导出的服务器目录 /home/wasadmin/exportDataString exportParentDir = EXPORT_PARENT_PATH;String exportTodayDir = exportParentDir + File.separator + today +File.separator;File exportTodayDirFile = new File(exportTodayDir);if (!exportTodayDirFile.exists()) {logger.info("execel temporary download directory {} not exist !", exportTodayDirFile.getAbsolutePath());return;}// 获取当天导出目录下的所有子目录, 将需要删除的子目录筛选出来  // FilenameFilter, 下面使用了jdk8语法File[] files = exportTodayDirFile.listFiles((File dir, String name) -> {boolean flag = false;// name >>> 202109271010103102435  yyyyMMddHHmmss+userCodeFile file = new File(dir.getAbsolutePath() + File.separator + name);if (file.isDirectory()) {// yyyyMMddHHmmss userCode长度7Date subDirDateTime = DateUtil.parse(name.substring(0, name.length() - 7), CSISCONSTANT.FORMATYYYYMMDDHHMMSS);Calendar calendar = Calendar.getInstance();calendar.setTime(new Date());
//                calendar.add(Calendar.HOUR_OF_DAY, -1); // 当前时间-1hcalendar.add(Calendar.MINUTE, -2); // 测试用 2分钟之前的临时下载文件全部删除Date substrDate = calendar.getTime();flag = substrDate.compareTo(subDirDateTime) > 0;}return flag;});if (files != null && files.length > 0) {for (File file : files) {// 删除目录  yyyyMMddHHmmss+userCode 下面的excel或zip文件deleteFile(file);}} else {logger.info("there's no match condition temporary download directory to remove in {} !", exportTodayDirFile.getAbsolutePath());}// 20String delTodayTime = "20";// 判断当前时间, 如果到了晚上20点, 就将excel文件下载的日期目录删除if (new Date().compareTo(DateUtil.parse(today + delTodayTime.trim(), "yyyyMMddHH")) >= 0 &&exportTodayDirFile.delete()) {logger.info("remove excel temporary download directory {} successfully !", exportTodayDirFile.getAbsolutePath());}}// 递归删除文件private void deleteFile(File destFile) {if (destFile.isFile()) {boolean isDel = destFile.delete();if (isDel) {logger.info("remove temporary excel {} successfully !",destFile.getAbsolutePath());}} else {// destFile是目录, 循环删除, 因为含有文件的目录无法删除成功File[] files = destFile.listFiles();for (File file : files) {// 递归调用deleteFile(file);}// 删除完目录里面的文件后, 再删除当前空目录boolean del = destFile.delete();if (del) {logger.info("remove excel temporary download directory {} successfully !", destFile.getAbsolutePath());}}}
}

5.2 定时任务调度配置

classapth下面新增spring-timer.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--实例化定时执行的任务类--><bean id="myJob" class="com.timer.ClearExcelDownloadTimer"/><!--配置触发任务 myJobDetail--><bean id="myJobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"><property name="targetObject" ref="myJob"/><property name="targetMethod" value="task"/></bean><!--配置触发器myTrigger--><bean id="myTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean"><property name="jobDetail" ref="myJobDetail"/><property name="cronExpression" value="0/5 * * * * ?"/></bean><!--配置调度器scheduler--><bean id="myScheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"><property name="triggers"><list><ref bean="myTrigger"/></list></property></bean></beans>

5.3 初始化加载定时任务调度配置

webapp/WEB-INF/web.xml文件中配置如下:

<!--配置加载spring ioc容器的文件路径,多个配置文件可以使用逗号隔开(也可以使用模糊匹配)-->
<context-param><param-name>contextConfigLocation</param-name><param-value>classpath:applicationContext.xml, classpath:spring-timer.xml</param-value>
</context-param>

6. 数据拆分完整代码

com.poi.service.ExpExcelByPagesService

package com.poi.service;import cn.hutool.core.date.DateUtil;
import com.constant.CSISCONSTANT;
import com.util.DataHandleUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;import java.io.File;
import java.util.*;
import java.util.stream.Collectors;/*** 分流写入Excel文件 , 大部分公共逻辑已经封装好, 不同应用场景的数据查询部分需要在具体子类中* 重写下面两个方法* {@link ExpExcelByPagesService#getTitleMap()}  和  * {@link ExpExcelByPagesService#getDataList()}* @see ExpExcelByPagesService#exportExcel*/
public abstract class ExpExcelByPagesService {private static final Logger logger = LoggerFactory.getLogger(ExpExcelByPagesService.class);private static final String EXPORT_PARENT_PATH = CSISCONSTANT.TEMP_DOWNLOAD_DIR;/*** 导出Excel文件* @param dataMap*/public void exportExcel(Map<String, Object> dataMap) {logger.info(">>>>{}, dataMap is:{}", this.getClass().getSimpleName(),dataMap);// 统计查询的数据量int record = 0;// 每个excel文件数据量, 也是db分页查询的数据量上限, 推荐 10000int pageSize = 10000;// 当前页码, 默认查询第一页int currentPage = 1;// 用户编号String userCode = (String) dataMap.get(CSISCONSTANT.USER_CODE);// 文件类型 .xls .xlsxString fileType = (String) dataMap.get(CSISCONSTANT.EXCEL_FILE_TYPE);// 文件类型非空判断,如果为空, 默认下载03版xlsfileType = !StringUtils.isEmpty(fileType) && Objects.equals(CSISCONSTANT.EXCEL07_EXTENSION, fileType) ? fileType : CSISCONSTANT.EXCEL03_EXTENSION;logger.info("export excel fileType is:{}", fileType);// /home/wasadmin/exportData/yyyyMMdd/yyyyMMddHHmmss+userCode/String writeDir = String.format(EXPORT_PARENT_PATH + DateUtil.format(new Date(), CSISCONSTANT.FORMAT_YYYYMMDD) + File.separator +DateUtil.format(new Date(), CSISCONSTANT.FORMATYYYYMMDDHHMMSS)) + userCode + File.separator;// 目录不存在, 则创建File writeDirFile = new File(writeDir);if (!writeDirFile.exists()) {writeDirFile.mkdirs();}// 标题行(表头) key-valueLinkedHashMap<String, Object> titleMap = getTitleMap();// 获取查询的全量数据  实际应用场景中应该只需要查询总条数, 不需要所有字段都查询出来//具体的数据应该在后面的分页查询中获取(db查询)// 因为我这里是demo,所以暂时用集合模拟全量数据查询了List<HashMap<String, Object>> dataList = getDataList();// 当前查询的数据总条数record = dataList.size();// 如果大于设置的每个excel文件数据量, 则将数据切割写入多个excel文件if (record > pageSize) {loopCreateExcel(dataMap, record, pageSize, fileType, writeDir, titleMap, dataList);} else {List<HashMap<String, Object>> pageDataList = queryDataByPages(dataMap, 																pageSize, currentPage, 0, dataList);writeToExcel(fileType, writeDir, userCode, titleMap, currentPage, pageDataList);}// 所有文件写入到服务器后, 将它们压缩成一个zip文件给前端界面下载String zipFileName = writeDir + "EXP" + DateUtil.format(new Date(), CSISCONSTANT.FORMAT_YYYYMMDD) + DataHandleUtil.getRandomNumber(8) + CSISCONSTANT.ZIP_FILE_EXTENSION;ExportDataToExcelService.writeZipExcel(writeDir, zipFileName);// 设置 FILE_DOWN_LOAD_NAME 由拦截器将下载文件路径写入上下文的响应流中dataMap.put(CSISCONSTANT.FILE_DOWNLOAD_NAME, new File(zipFileName));}/*** 生成excel, 并写入数据* @param fileType* @param writeDir* @param userCode 3102435* @param titleMap* @param currentPage* @param pageDataList* @return 返回写入服务器的excel文件路径*/private String writeToExcel(String fileType, String writeDir, String userCode,LinkedHashMap<String, Object> titleMap, int currentPage, List<HashMap<String, Object>> pageDataList) {if (CollectionUtils.isEmpty(titleMap)) {throw new RuntimeException("标题行数据不能为空");}// 封装标题行List<String> titleList = ExportDataToExcelService.getTitleList(titleMap);// 封装数据行List<List<String>> detailList = ExportDataToExcelService.getDetailList(pageDataList, titleMap);// 获取Excel文件写入的绝对路径  /home/wasadmin/exportData/20210927/202109271010103102435/EXP20210927_3102435_1.xlsString writeFileName = writeDir + "EXP" + DateUtil.format(new Date(), CSISCONSTANT.FORMAT_YYYYMMDD) + "_" + userCode + "_" + currentPage + fileType;logger.info("export excel writeFileName is : {}", writeFileName);// 生成Excel文件并写入数据ExportDataToExcelService.writeExcel(fileType, writeFileName,titleList, detailList);return writeFileName;}/*** 分割数据, 分多个excel文件写入数据* @param dataMap* @param record* @param pageSize* @param fileType* @param writeDir* @param titleMap* @param contentList*/private void loopCreateExcel(Map<String, Object> dataMap, int record, int pageSize, String fileType, String writeDir, LinkedHashMap<String, Object> titleMap,List<HashMap<String, Object>> contentList) {int currentPage = 0;// 超过设置的单个sheet表数据上限 , 分页查询并分多个excel文件写入int loopNum = record / pageSize;int remainder = record % pageSize;logger.info("loopNum={}, remainder={} ", loopNum, remainder);String userCode = (String) dataMap.get(CSISCONSTANT.USER_CODE);for (int i = 0; i < loopNum; i++) {currentPage = i + 1; // 一页对应一个excel文件List<HashMap<String, Object>> pageDataList = queryDataByPages(dataMap, 														pageSize, currentPage, 0, contentList);writeToExcel(fileType, writeDir, userCode, titleMap, currentPage,pageDataList);}if (remainder > 0) {currentPage = loopNum + 1;List<HashMap<String, Object>> pageDataList = queryDataByPages(dataMap, 													pageSize, currentPage, remainder, contentList);writeToExcel(fileType, writeDir, userCode, titleMap, currentPage,pageDataList);}}/*** 分页查询数据* @param dataMap* @param pageSize* @param currentPage* @param remainder* @param contentList* @return*/private List<HashMap<String, Object>> queryDataByPages(Map<String, Object> dataMap, int pageSize, int currentPage, int remainder, List<HashMap<String, Object>> contentList) {// 设置分页查询参数storePageParam(dataMap, pageSize, currentPage, remainder);int startIndex = (Integer) dataMap.get(CSISCONSTANT.START_INDEX);int endIndex = (Integer) dataMap.get(CSISCONSTANT.END_INDEX);// 分页查询数据, 实际场景是db分页查询, 而且也应该抽取到抽象方法中, // 在子类中实现(因为不同场景查询的数据不一样)List<HashMap<String, Object>> pageDataList = contentList.subList(startIndex, endIndex);// 删除集合中的空行List<HashMap<String, Object>> newDataList = 	pageDataList.stream().filter(Objects::nonNull).collect(Collectors.toList());return newDataList;}/*** 设置分页查询的起始和结束索引参数* @param dataMap* @param pageSize* @param currentPage* @param remainder*/private void storePageParam(Map<String, Object> dataMap, int pageSize, int currentPage, int remainder) {// 起始位置 db查询和集合的索引定义不一样, 要注意区分起始索引是0或1
//        int startIndex = (currentPage - 1) * pageSize + 1;int startIndex = (currentPage - 1) * pageSize;// 结束位置int endIndex = currentPage * pageSize;// 如果最后一页不满pageSize条, 就是实际的余数remainder条记录if (remainder > 0) {endIndex = (currentPage - 1) * pageSize + remainder;}dataMap.put(CSISCONSTANT.START_INDEX, startIndex);dataMap.put(CSISCONSTANT.END_INDEX, endIndex);}/*** 储存标题行(表头)数据 , 可以抽象, 在子类实现* @return*/public abstract LinkedHashMap<String, Object> getTitleMap();/*** 查询全量数据, 可以抽象, 在子类实现* @return*/public abstract List<HashMap<String, Object>> getDataList();
}

相关推荐

Poi版本升级优化

StringTemplate实现Excel导出

Poi模板技术

SAX方式实现Excel导入

DOM方式实现Excel导入

Poi实现Excel导出

EasyExcel实现Excel文件导入导出

EasyPoi实现excel文件导入导出

个人博客

欢迎各位访问我的个人博客: https://www.crystalblog.xyz/

备用地址: https://wang-qz.gitee.io/crystal-blog/

这篇关于数据分流写入Excel的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1108872

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

pandas数据过滤

Pandas 数据过滤方法 Pandas 提供了多种方法来过滤数据,可以根据不同的条件进行筛选。以下是一些常见的 Pandas 数据过滤方法,结合实例进行讲解,希望能帮你快速理解。 1. 基于条件筛选行 可以使用布尔索引来根据条件过滤行。 import pandas as pd# 创建示例数据data = {'Name': ['Alice', 'Bob', 'Charlie', 'Dav

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者