java大数据如何导出大于65535的文件

我们都知道,一个xls表格的最大存储的容量是65535条数据。如果大于这个量就会报错,然后现实中往往需要几十万的下载,那么如何解决这个问题,今天我们就从两种玩法,开始,第一种,。就是下载量小于65535的时候,

 

废话不多说,直接撸代码

一:导出功能条数小于65535的时候,可以直接使用

依赖包,自己下载

<!-- 导出用-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- alibaba easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>1.1.2-beta5</version>
</dependency>

 

工具类:

POIExportUtils:
package com.hbg.dms.util;

import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.hbg.dms.annotations.Excel;
import com.hbg.dms.exception.DmsException;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author huojg.tu
 * @version 1.0
 * @date 2020/9/10 13:07
 */
@Slf4j
public class POIExportUtils {

    public static <T> ByteArrayOutputStream export(Class<T> objClass, List<T> dataList) {
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
            Class excelClass = Class.forName(objClass.toString().substring(6));
            Method[] methods = excelClass.getMethods();
            Map<Integer, String> mapCol = new TreeMap<>();
            Map<Integer, String> mapMethod = new TreeMap<>();

            for (Method method : methods) {
                Excel excel = method.getAnnotation(Excel.class);
                if (excel != null) {
                    mapCol.put(excel.order(), excel.colName());
                    mapMethod.put(excel.order(), method.getName());
                }
            }
            HSSFWorkbook wb = new HSSFWorkbook();
            if(dataList.size()>0) {
                poiBuildBody(poiBuildHead(wb,  "sheet1", mapCol), excelClass, mapMethod, dataList);
                wb.write(outputStream);
            }
            return outputStream;
        } catch (Exception e) {
            log.error("导出Excel异常", e);
            throw new DmsException("导出失败");
        }
    }

    public static <T>  ByteArrayOutputStream export1(Class<T> objClass, List<T> dataList) {

        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            Method[] methods = objClass.getMethods();
            Map<Integer, String> mapCol = new TreeMap<>();
            Map<Integer, String> mapMethod = new TreeMap<>();

            for (Method method : methods) {
                Excel excel = method.getAnnotation(Excel.class);
                if (excel != null) {
                    mapCol.put(excel.order(), excel.colName());
                    mapMethod.put(excel.order(), method.getName());
                }
            }
            // 通过工具类创建writer,默认创建xls格式
            ExcelWriter writer = ExcelUtil.getBigWriter();
            //创建xlsx格式的
            //ExcelWriter writer = ExcelUtil.getWriter(true);
            // 一次性写出内容,使用默认样式,强制输出标题
            writer.writeHeadRow(mapCol.values());
            writer.write(dataList);
            //out为OutputStream,需要写出到的目标流
            writer.flush(outputStream);
            // 关闭writer,释放内存
            writer.close();
            return outputStream;
        } catch (Exception e) {
            log.error("导出Excel异常", e);
            throw new DmsException("导出失败");
        }
    }

    public static HSSFSheet poiBuildHead(HSSFWorkbook wb, String sheetName, Map<Integer, String> mapCol) {
        HSSFSheet sheet01 = wb.createSheet(sheetName);
        HSSFRow row = sheet01.createRow(0);
        HSSFCell cell;
        int i = 0;
        for (Map.Entry<Integer, String> entry : mapCol.entrySet()) {
            cell = row.createCell(i++);
            cell.setCellValue(entry.getValue());
        }
        return sheet01;
    }

    public static <T> void poiBuildBody(HSSFSheet sheet01, Class<T> excelClass, Map<Integer, String> mapMethod, List<T> dataList) throws Exception {
        HSSFRow r = null;
        HSSFCell c = null;

        if (dataList != null && dataList.size() > 0) {
            for (int i = 0; i < dataList.size(); i++) {
                r = sheet01.createRow(i + 1);
                //r.setHeightInPoints(25);
                int j = 0;
                for (Map.Entry<Integer, String> entry : mapMethod.entrySet()) {
                    c = r.createCell(j++);
                    Object obj = excelClass.getDeclaredMethod(entry.getValue()).invoke(dataList.get(i));
                    c.setCellValue(obj == null ? "" : obj + "");
                }
            }
        }
    }





}

如何使用:

首页根据你需要的传入分页参数

然后根据mybaties-plus 中分页查询出你需要的list ,

然后调用上面的工具类,直接使用,导出你需要的电子表格xls的格式文件。

    request.setPageNo(1);
       request.setPageSize(1000000000);
       IPage<AgentMemberRes> agentList = this.getAgentList(request);
       List<AgentExportMember> dataList = BeanCopyUtil.copyList(agentList.getRecords(), AgentExportMember.class);
       POIExportUtils.export(AgentExportMember.class, dataList, response, "代理列表" + DateUtil.formatDate(new Date()));

 

 

二:如果下载的量大于65535条数的时候,我们玩的思想:

用spring 的监听器来调用,和执行程序  ,然后把文件的内容存储到OSS阿里云上面生成zip压缩模式,然后在数据库把下载链接放到数据库中,供下载中心使用

因此我们需要创建一个监听器模式:

import com.hbg.dms.model.bo.ExportBO;
import com.hbg.dms.model.dto.base.BasePageRequest;
import org.springframework.context.ApplicationEvent;

/**
 * 导出事件
 * @author PC
 */
public class ExportEvent<Request extends BasePageRequest, Response, ExportResp> extends ApplicationEvent {
    private final ExportBO<Request, Response, ExportResp> exportBO;
    public ExportEvent(Object source) {
        super(source);
        exportBO= (ExportBO)source;
    }

    public ExportBO<Request, Response, ExportResp> getExportBO() {
        return exportBO;
    }


}

 

核心代码:这个就是我们spirng监听器里面执行的我们大数据量出来模式

package com.hbg.dms.listener;

import cn.hutool.core.date.DatePattern;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hbg.dms.constant.UploadPathConstants;
import com.hbg.dms.controller.rpc.FileDownloadRpc;
import com.hbg.dms.enums.OssFileTypeEnum;
import com.hbg.dms.model.bo.ExportBO;
import com.hbg.dms.model.dto.base.BasePageRequest;
import com.hbg.dms.model.dto.base.JsonResult;
import com.hbg.dms.model.request.FileDownloadAddRequest;
import com.hbg.dms.model.request.FileDownloadUpdateRequest;
import com.hbg.dms.util.AliOssUtil;
import com.hbg.dms.util.BeanCopyUtil;
import com.hbg.dms.util.POIExportUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 归档监听器
 * @author huojg
 */
@Slf4j
@Component
public class ExportListener<Request extends BasePageRequest,Response, ExportResp> {
    private final Logger logger = LoggerFactory.getLogger(ExportListener.class);

    @Resource
    private FileDownloadRpc fileDownloadRpc;

    @Resource
    private AliOssUtil aliOssUtil;

    @EventListener
    @Async("taskExecutor")
    public void onExportEvent(ExportEvent<Request, Response, ExportResp> event) {
        ExportBO<Request, Response, ExportResp> exportBO = event.getExportBO();
        //文件待下载,生成记录到数据库
        FileDownloadAddRequest request = new FileDownloadAddRequest();
        request.setContentDesc(exportBO.getFileName());
        // 文件格式 1 excel; 2 :zip
        if (exportBO.getCompress()) {
            request.setFileFormat(2);
        } else {
            request.setFileFormat(1);
        }

        request.setBackUserId(exportBO.getBackUserId());
        request.setBackUserName(exportBO.getBackUserName());
        // 文件来源 hbg:惠啵购, dms:经销商管理系统
        request.setSourceFrom("dms");
        FileDownloadUpdateRequest updateRequest = new FileDownloadUpdateRequest();
        //try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); ZipOutputStream zipOutputStream = new ZipOutputStream(outputStream)) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ZipOutputStream zipOutputStream = null;
        try {
            zipOutputStream = new ZipOutputStream(outputStream);
            //调用HBG服务保存记录
            JsonResult<Integer> saveResult = fileDownloadRpc.save(request);
            updateRequest.setId(saveResult.getData());

            //获取数据,同实例 串行导出,避免内存占用过大
            synchronized (this) {
                byte[] byteArr = getDataList(exportBO, zipOutputStream);
                try {
                    //需要先关闭zip,后关闭outputStream
                    zipOutputStream.flush();
                    zipOutputStream.close();
                    outputStream.close();
                } catch (IOException e) {
                    logger.error("FileListener.onFileEvent" + e.getMessage(), e);
                }
                String fileName;
                if (exportBO.getCompress()) {
                    byteArr = outputStream.toByteArray();
                    fileName = exportBO.getFileName() + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATETIME_PATTERN)) + ".zip";
                } else {
                    fileName = exportBO.getFileName() + "_" + LocalDateTime.now().format(DateTimeFormatter.ofPattern(DatePattern.PURE_DATETIME_PATTERN)) + exportBO.getSuffix();
                }
                String downloadUrl = aliOssUtil.uploadFile(OssFileTypeEnum.DOCUMENT, byteArr, exportBO.getSubPath(), fileName);
                updateRequest.setDownloadUrl(downloadUrl);
                updateRequest.setFileStatus(1);
            }
            //调用HBG服务 更新状态
            fileDownloadRpc.updateStatus(updateRequest);
        } catch (Exception e) {
            logger.error("ExportListener.onExportEvent" + e.getMessage(), e);
            if (updateRequest.getId() != null) {
                updateRequest.setFileStatus(2);
                fileDownloadRpc.updateStatus(updateRequest);
            }
        }
    }

    /**
     * 抽象数据获取方法 , 并且写入到流
     */
    private byte[] getDataList(ExportBO<Request, Response, ExportResp> exportBO, ZipOutputStream zipOutputStream) throws IOException {
        if (exportBO.getBatchSize() == null || exportBO.getBatchSize() <= 0) {
            exportBO.setBatchSize(10_000);
        }
        //一次最多查询50000条
        if (exportBO.getBatchSize() > 50_000) {
            exportBO.setBatchSize(50_000);
        }
        exportBO.getParamRequest().setPageSize(exportBO.getBatchSize());
        IPage<Response> dataPage;
        int index = 0;
        //是否应该继续
        boolean shouldContinue;
        do {
            if (exportBO.getMaxPage() != null && exportBO.getMaxPage() < 1) {
                logger.error("用户设置的最大页数小于1,不查询数据");
                break;
            }
            exportBO.getParamRequest().setPageNo(++index);
            //分页查询
            dataPage = exportBO.getDataSupplierFunc().apply(exportBO.getParamRequest());
            if (dataPage.getRecords().size() > 0) {
                List<ExportResp> exportResponseList = BeanCopyUtil.copyList(dataPage.getRecords(), exportBO.getExportRespClass());
                ByteArrayOutputStream outputStream = POIExportUtils.export(exportBO.getExportRespClass(), exportResponseList);
                byte[] byteArray = outputStream.toByteArray();
                //选择不压缩,则只查询一次
                if (!exportBO.getCompress()) {
                    return byteArray;
                }
                zipAppend(byteArray, exportBO.getFileName(), zipOutputStream, index);
            }
            shouldContinue = dataPage.getPages() > dataPage.getCurrent() && (exportBO.getMaxPage() == null || exportBO.getMaxPage() > dataPage.getCurrent());
        } while(shouldContinue);
        return null;
    }

    //压缩
    protected static void zipAppend(byte[] byteArray, String zipEntityName, ZipOutputStream zipStream, int index) throws IOException {
        if(byteArray!=null && byteArray.length > 0){
            ZipEntry zipEntry = new ZipEntry(new String(zipEntityName.getBytes(), StandardCharsets.UTF_8) + "_" +index +".xls");
            zipStream.putNextEntry(zipEntry);
            zipStream.write(byteArray);
            zipStream.closeEntry();
        }
    }

}

 

如何使用:

  ExportBO<MemberSearchReq, AgentMemberRes, AgentMemberExportRes> exportBO = new ExportBO<>();
       exportBO.setParamRequest(request);
       exportBO.setDataSupplierFunc(this::getAgentList);
       exportBO.setBatchSize(50000);
       exportBO.setExportRespClass(AgentMemberExportRes.class);
       exportBO.setFileName("代理列表");
       exportBO.setSubPath(UploadPathConstants.MEMBER_LIST);
       //exportBO.setCompress(true);
       //exportBO.setSuffix(".xls");
       // 发布导出事件,等待异步导出
       publisher.publishEvent(new ExportEvent<>(exportBO));

 

定义实体类:

package com.hbg.dms.model.bo;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.api.R;
import com.hbg.dms.constant.UploadPathConstants;
import com.hbg.dms.model.dto.base.BasePageRequest;
import io.micrometer.core.lang.Nullable;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import org.apache.poi.ss.formula.functions.T;

import java.util.List;
import java.util.function.Function;

/**
 * 抽象的导出事件对象
 * @author zxf
 * @date 2019/11/11 14:48
 */
@Getter
@Setter
public class ExportBO<Request extends BasePageRequest, Response, ExportResp> {

    /**
     *  数据来源入参参数
     */
    private Request paramRequest;

    /**
     * 数据来源方法:用于获取数据
     * 一般使用分页后台管理对应的分页接口:需要保证只有一个入参
     */
    private Function<Request, IPage<Response>> dataSupplierFunc;

    /**
     * 需要构建一个导出用的对象,
     * 程序将使用 {@link com.hbg.dms.annotations.Excel} 来获取excel的列标题和顺序
     * 转换的数据导出对象
     */
    private Class<ExportResp> exportRespClass;

    /**
     * 可选参数
     * 批量导出,每次查询的数据条数
     * 注意:如果选择了不压缩,那么只会导出第一页的数据,全部导出需要设置比较大的size
     * 适用于数据量小于50000的数据
     * 大于5w的强制只查询5W
     *
     */
    private Integer batchSize = 10000;

    /**
     * 可选参数
     * 希望导出的最大页数
     * 为空或小于1则查询全部
     */
    private Integer maxPage;

    /**
     * 文件名,建议带正确的后缀
     */
    private String fileName;

    /**
     * 可选参数
     * 文件名后缀,带点号
     * eg: .xls
     * 默认 .xlsx
     */
    private String suffix = ".xls";

    /**
     * 可选参数
     * 是否压缩
     * 默认 是
     *
     */
    private Boolean compress = true;

    /**
     * 可选参数
     * 使用子路径,会生成子路径文件夹,作为更细的划分
     * @see UploadPathConstants
     */
    private String subPath;


    /**
     * dms获取不到session的用户信息,需要前端传递此参数
     * 后台操作的用户Id
     */
    private Long backUserId;

    /**
     * dms获取不到session的用户信息,需要前端传递此参数
     * 操作人姓名
     */
    private String backUserName;


}
package com.hbg.dms.model.dto.base;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;


@ApiModel("分页参数")
public class BasePageRequest {

    @ApiModelProperty(value = "当前页码", required = true)
    private Integer pageNo;

    @ApiModelProperty(value = "每页条数", required = true)
    private Integer pageSize;

    public int getPageNo() {
        if(pageNo == null || pageNo <= 0){
            pageNo = 1;
        }
        return pageNo;
    }

    public int getPageSize() {
        if(pageSize == null  || pageSize <= 0){
            pageSize = 10;
        }
        return pageSize;
    }

    @ApiModelProperty(hidden = true)
    public <T> Page<T> getPage() {
        return new Page<>(getPageNo(), getPageSize());
    }


    public void setPageNo(Integer pageNo) {
        this.pageNo = pageNo;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }
}

通过上面的定义实体类:

ExportBO

我们通过执行分页的list集合。处理代码

参数解析:

request:请求参数
this::getAgentList:查询分页出来的list
AgentMemberExportRes.class:导出的实体类
setSubPath:路径名称
publishEvent:发送通知

Excel:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Excel {
    /**
     * 列名称
     * @return
     */
    String colName();

    /**
     * 顺序
     * @return
     */
    int order();
}
导出的实体类上面,这样写:
 
   @Excel(colName = "上级id", order = 1)
    public Integer getSupperId() {
        return supperId;
    }

 

 

这样可以导出,几十万的是数据量。

 

带分页--大数据量导出的功能就此完成--你们的精华,就是,监听器实现的代码。同学看不懂的,可以联系我要源码。收费10元

 

posted @ 2020-11-30 18:11  技术专家  阅读(1461)  评论(0)    收藏  举报