导入导出Excel之模板方法模式运用

一、概述

在日常的开发过程种,excel报表是常见的一个需求。

execel的操作,可以分为两种,一种是读,一种是写,其他的项目辅助性的操作都是具有共性的,我们就可以将读和写抽象出来,针对简单的Excel导入导出操作,做一个标准的实现。但是在实际的需求中,列的合并,行的合并也是常有的需求,我们可以针对这些特定的需求,快速实现复杂Execel的开发。

 

二、设计

针对上述的需求,我们采用设计模式中的模板方法模式进行实现,下面我们来看看模板方法的优点和使用场景。

模板方法的优点是:

1.封装不变部分,扩展可变部分

2.提取公共部分代码,便于维护

3.行为由父类控制,子类实现

使用场景:

1.多个子类有公用的方法,并且逻辑相同

2.重要,复杂的算法,可以把核心算法设计为模板方法,相关细节由子类实现

3.重构时把相同的代码提取到父类中,通过勾子函数(单独的方法)约束行为

 

三、模板类的实现

@Data
public abstract class AbstractExcelUtil<T>{


    /**
     * 读取数据
     * @param workbook 工作薄
     * @param columns 数组列  对应 列下标
     * @return map
     */
    protected abstract List<Map<String,Object>> readData(Workbook workbook, String[] columns);

    /**
     * 通过url导入数据
     * @param networkPath
     * @param columns
     * @return
     */
    public List<Map<String,Object>> importRemote(String networkPath,String[] columns) {
        return readData(getImportWorkbook(networkPath, PoiPool.NOT_NATIVE_FILE, getFileSuffix(networkPath)), columns);
    }

    /**
     * 通过本地文件路径导入数据
     * @param path
     * @param columns
     * @return
     */
    public List<Map<String,Object>> importNative(String path,String[] columns){
        return readData(getImportWorkbook(path,PoiPool.IS_NATIVE_FILE,getFileSuffix(path)),columns);
    }


    /**
     * 读取数据
     * @param workbook 工作薄
     * @param columns 数组列  对应 列下标
     * @param clazz 类名
     * @return T 列表
     */
    protected abstract List<T> readData(Workbook workbook, String[] columns,Class<T> clazz);

    /**
     * 获取远程导入数据
     * @param networkPath 远程文件地址
     * @param columns 需要导出的列名
     * @param clazz 类名
     * @return T 列表
     */
    public List<T> importRemote(String networkPath,String[] columns,Class<T> clazz) {
//        return getResultList(importRemote(networkPath,columns),clazz);
        return readData(getImportWorkbook(networkPath, PoiPool.NOT_NATIVE_FILE, getFileSuffix(networkPath)),columns,clazz);
    }

    /**
     * 获取本地导入数据
     * @param path 本地文件路径
     * @param columns 需要导出的列名
     * @param clazz 对象名
     * @return T 列表
     */
    public List<T> importNative(String path,String[] columns,Class<T> clazz){
//        return getResultList(importNative(path,columns),clazz);
        return readData(getImportWorkbook(path, PoiPool.IS_NATIVE_FILE, getFileSuffix(path)),columns,clazz);
    }



    //================================================ export ===============================================

    /**
     * 导出写入数据(一个sheet)
     * @param sheet sheet
     * @param style 列样式
     * @param datas 数据列表
     * @param columns 需要导出的列
     */
    protected abstract void writeData(Sheet sheet, CellStyle style, List<T> datas, String[] columns);

    /**
     * 自定义样式导出
     * @param workbook 工作薄
     * @param datas 数据列表
     * @param titles 表头
     * @param widths 宽度列表
     */
    protected abstract void writeCustomizeData(Workbook workbook,List<T> datas,String[] titles,Integer[] widths);


    //============================= export single sheet option =========================================



    public byte[] exportBytes(String sheetName, List<T> datas, CellStyle style,int sheetPageSize, String[] titles, String[] columns, Integer[] widths){
        Workbook workbook = getWorkbook(datas, sheetName, style, sheetPageSize , titles, columns,  widths);

        try(ByteArrayOutputStream outputStream = new ByteArrayOutputStream()){
            workbook.write(outputStream);
            return outputStream.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            close(workbook);
        }
        return null;
    }



    public String  exportFilePath(String excelName, List<T> datas, String[] titles, String[] columns){
        return exportFilePath(excelName,datas,excelName,titles,columns);
    }


    public String  exportFilePath(String excelName, List<T> datas, String sheetName, CellStyle style, String[] titles, String[] columns, Integer[] widths){
        return exportFilePath(excelName,datas,sheetName,style,PoiPool.DEFAULT_PAGE_SIZE,titles,columns,widths);
    }

    public String  exportFilePath(String excelName, List<T> datas, String sheetName, CellStyle style,int sheetPageSize, String[] titles, String[] columns, Integer[] widths){
        if(sheetPageSize > PoiPool.DEFAULT_PAGE_SIZE){
            sheetPageSize = PoiPool.DEFAULT_PAGE_SIZE;
        }
        Workbook workbook = getWorkbook(datas, sheetName, style, sheetPageSize, titles, columns,  widths);
        return getExportPath(excelName,workbook);
    }

    private Workbook getWorkbook( List<T> datas, String sheetName,CellStyle style,int sheetPageSize, String[] titles, String[] columns, Integer[] widths){
        Workbook workbook = createXssfWorkbook();
        if(style == null){
            style = getDefaultCellStyle(workbook);
        }

        int size = datas.size();
        if(size <= sheetPageSize){
            generateSheet(workbook,sheetName,datas,style,titles,columns,widths);
        }else {
            final CellStyle fStyle = style;
            //计算总页码
            int page = size / sheetPageSize;
            page =  size % sheetPageSize == 0 ? page : ++page;

            //使用线程池处理
            ThreadPoolExecutor batchExportJobPool = new ThreadPoolExecutor(
                    3,
                    10,
                    60L,
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<Runnable>(2000),
                    new ThreadFactory() {
                        @Override
                        public Thread newThread(Runnable r) {
                            return new Thread(r, "excel batch export job-" + r.hashCode());
                        }
                    });

            List<CompletableFuture<Void>> list = Lists.newArrayList();
            List<T> ts;

            AtomicInteger atomicInteger = new AtomicInteger();


            try {
                for(int i = 1; i <= page ; i++){
                    if(i == 1){
                        ts = datas.subList(0, sheetPageSize);
                        list.add(AsyncExport(workbook,sheetName,ts,fStyle,titles,columns,widths,batchExportJobPool));
                    }else if(i == page){
                        ts = datas.subList(sheetPageSize * ( i - 1 ), size);
                        list.add(AsyncExport(workbook,getCurrentSheetName(sheetName,atomicInteger),ts,fStyle,titles,columns,widths,batchExportJobPool));
                    }else {
                        ts = datas.subList(sheetPageSize * ( i - 1 ), sheetPageSize * i);
                        list.add(AsyncExport(workbook,getCurrentSheetName(sheetName,atomicInteger),ts,fStyle,titles,columns,widths,batchExportJobPool));
                    }
                    //当分页太大,需要等待,防止sheet名称 在多线程下出错
                    if(page > 3){
                        Thread.sleep(110);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }


            batchExportJobPool.shutdown();
            getSyncResult(list);
        }
        return workbook;
    }

    private String getCurrentSheetName(String sheetName,AtomicInteger atomicInteger){
        return sheetName.concat("-").concat(String.valueOf(atomicInteger.incrementAndGet()));
    }


    private CompletableFuture<Void> AsyncExport(Workbook workbook,String sheetName,List<T> datas,CellStyle style, String[] titles, String[] columns, Integer[] widths,ThreadPoolExecutor batchExportJobPool){
        return CompletableFuture.runAsync(() -> {
            generateSheet(workbook,sheetName,datas,style,titles,columns,widths);
        }, batchExportJobPool);
    }

    private void getSyncResult(List<CompletableFuture<Void>> list){
        try {
            for (CompletableFuture result : list){
                result.get(60,TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    private void generateSheet(Workbook wb, String sheetName, List<T> datas, CellStyle style,String[] titles, String[] columns, Integer[] widths){
        Sheet sheet = createSheet(wb, sheetName);
        if(widths != null && widths.length > 0){
            setColumnWidth(sheet,widths);
        }
        createTitleHead(sheet,style,titles);
        writeData(sheet,style,datas,columns);
    }

    //=================================================== export multi sheet option =====================================================================

    public String  exportCustomize(String excelName, List<T> datas, String[] titles, Integer[] widths){
        Workbook  workbook = createXssfWorkbook();

        writeCustomizeData(workbook,datas,titles,widths);
        return getExportPath(excelName,workbook);
    }

   


}

四、标准的Execel类的实现(子类使用模板类的相关方法)

public class StandardExcelUtil<T> extends AbstractExcelUtil<T> {

    @Override
    protected List<Map<String,Object>> readData(Workbook workbook, String[] columns) {
        try {
            Sheet sheetAt = workbook.getSheetAt(0);

            checkTable(sheetAt,columns);

            int columnsSize = columns.length;

            List<Map<String,Object>> result = Lists.newArrayList();
            Map<String,Object> rowMap;

            int countRow = sheetAt.getPhysicalNumberOfRows();
            Row row;
            for(int i = 1; i < countRow; i++){
                row = sheetAt.getRow(i);
                rowMap = new HashMap<>();
                for(int j = 0; j < columnsSize; j++){
                    rowMap.put(columns[j], getCellValue(row.getCell(j)));
                }
                result.add(rowMap);
            }


            return result;
        } finally {
            close(workbook);
        }
    }

    @Override
    protected List<T> readData(Workbook workbook, String[] columns, Class<T> clazz) {
        try {
            Sheet sheetAt = workbook.getSheetAt(0);

            checkTable(sheetAt,columns);

            int columnsSize = columns.length;

            List<T> result = Lists.newArrayList();
            T t;
            Map<String,Object> rowMap;

            int countRow = sheetAt.getPhysicalNumberOfRows();
            Row row;
            for(int i = 1; i < countRow; i++){
                row = sheetAt.getRow(i);
                rowMap = new HashMap<>();
                for(int j = 0; j < columnsSize; j++){
                    rowMap.put(columns[j], getCellValue(row.getCell(j)));
                }
                result.add(BeanUtil.fillBeanWithMapIgnoreCase(rowMap,ReflectUtil.newInstance(clazz),true));
            }

            return result;
        }finally {
            close(workbook);
        }
    }

    @Override
    protected void writeData(Sheet sheet, CellStyle style, List<T> datas, String[] columns) {
        T t;
        Row row;
        for (int i = 0; i < datas.size() ; i++) {
            t = datas.get(i);
            row = sheet.createRow((i + 1));
            for(int j = 0; j < columns.length; j++){
                try {
                    setCellGBKValue(style,row.createCell(j, CellType.STRING),getValue(t, columns[j]));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void checkTable(Sheet sheetAt,String[] columns){
        //get table head
        Row headRow = sheetAt.getRow(0);
        if(headRow.getPhysicalNumberOfCells() != columns.length){
            throw new RuntimeException("The actual number of columns does not match the expected number of columns");
        }
    }


    @Override
    protected void writeCustomizeData(Workbook workbook,List<T> datas,String[] titles,Integer[] widths) {
        //标准工具类,不提供自定义实现
    }


}

五、进行测试

public class ExcelTest {

    public static void main(String[] args) throws Exception{
        System.out.println(export()); //200000行 多线程(网络IO类型,多线程的效果会更高) 13s 单线程 18s
//        importNative();
//        importRemote();
//        importRemoteMap();
    }

    private static String export(){
        List<User>  list = Lists.newArrayList();
        long start = System.currentTimeMillis();
        for(int i =0;i<200000;i++){
            User user = new User();
            user.setAge(10+i);
            user.setName("aaa"+i);
            user.setCreateTime(LocalDateTime.now());
            list.add(user);
        }
        long end = System.currentTimeMillis();
        System.out.println("生成数据花费时间:" + (end - start));

        String result = new StandardExcelUtil().exportFilePath("测试2222", list, new String[]{"用户名", "年龄", "创建时间"}, new String[]{"name", "age", "createTime"});

        System.out.println("生成Excel花费时间:" + (System.currentTimeMillis() - end));
        return result;
    }

    /**
     * 注意 当执行导入操作时时,格式化的时间将无法转换成LocalTime,需要重新定义Date 类型进行接收
     * 如果没有进行格式化,则可以按照原类型接收
     */
    private static void importNative(){
        String[] columns = new String[]{"name", "age", "createTime"};
//        List<User> users = new StandardExcelUtil<User>().importNative("C:/Users/Administrator/Desktop/excelExport/20190920/测试22220190920093738.xlsx", columns, User.class);
        List<UserVO> users = new StandardExcelUtil<UserVO>().importNative("C:/Users/Administrator/Desktop/excelExport/20190920/测试222220190920101839.xlsx", columns, UserVO.class);
        users.stream().forEach(obj -> System.out.println(obj.toString()));
    }

    private static void importRemote(){
        String[] columns = new String[]{"name", "age", "createTime"};
        List<UserVO> users = new StandardExcelUtil<UserVO>().importRemote("http://localhost:8080/zip/test.xlsx", columns, UserVO.class);
        users.stream().forEach(obj -> System.out.println(obj.toString()));

    }

    private static void importRemoteMap(){
        String[] columns = new String[]{"name", "age", "createTime"};
        List<Map<String, Object>> maps = new StandardExcelUtil<UserVO>().importRemote("http://localhost:8080/zip//%E6%B5%8B%E8%AF%95222220190920101839.xlsx", columns);
        maps.stream().forEach(obj -> System.out.println(obj.toString()));

    }
}

六、总结

以上代码为使用POI实现,如有错误,欢迎大家匹配指正!!!

最后贴出github源码:https://github.com/awyFamily/awy-common-all/tree/master/common-excel

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值