Java动态行动态列复杂excel表格导出

Java动态行动态列复杂excel表格导出

easyexcel官网

监听器填充公式学习来源

java为实体动态添加字段学习来源

1.背景

要求导出一个表格,如下图,其中箱数为动态的(不确定有多少箱,需要从数据库获取)【动态列】,货件中商品数量也为了动态的【动态行】;其中总箱重、总体积、发货量三个量是通过excel函数生成(要求能联动,所以要用函数;否则程序直接计算后进行填充更方便),其余数据都是从数据库进行获取。

在这里插入图片描述

2.分析

由于这个表格比较复杂,直接操作单元格工作量巨大;后选择easyexcel模板填充模式进行数据导出;大致思路为两次导出;第一次根据原始模板将箱数获取到处理动态列,处理结果作为第二次导出的模板;第二次导出根据上次导出的模板获取行数据,处理动态行;最后将第一次导出的临时模板删除。

3.环境

easyexcel:2.2.9 ;poi:3.17

*选择更高版本也是可以的,但是要注意easyexcel于poi对应的兼容版本,其他maven用到什么自己补充哈

            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi</artifactId>
                <version>3.17</version>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>3.17</version>
            </dependency>
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml-schemas</artifactId>
                <version>3.17</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>2.2.9</version>
            </dependency>

4.编写模板

将数据分区,data1区的数据值均为单个所以我们在第一次临时模板时就可以将data1区的数据值填充好;data2以及data3区的数据我们导出临时模板时先不进行处理,但是需要注意,我们仍需要将填充对象进行保留,导出临时模板主要处理box区数据,生成好对应的箱列以及箱列下面对应的填充对象;

在这里插入图片描述

5.代码实现

public void compositeFill() {
	//模板路径
	String templateFileName = "D:\\template.xlsx";
	//临时模板存放路径
	String fileName = "D:\\"+ System.currentTimeMillis() + ".xlsx";
	try{
		//第一次导出
		ExcelWriter excelWriter = EasyExcel.write(fileName).withTemplate(templateFileName).build();
		WriteSheet writeSheet = EasyExcel.writerSheet().build();
		//默认为垂直处理[VERTICAL],处理box区需要水平处理定义为[HORIZONTAL]
		FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
		//处理box区箱信息以及箱对应的填充对象,
		excelWriter.fill(new FillWrapper("box", box()), fillConfig, writeSheet);
		//处理data2区填充对象,将填充对象原样保留,用于第二次导出时使用
		excelWriter.fill(new FillWrapper("data2", data2()), writeSheet);
		//处理data3区填充对象,将填充对象原样保留,用于第二次导出时使用方式合上面相同,如果要运行可以注掉本行和data3()方法
		excelWriter.fill(new FillWrapper("data3", data3()), writeSheet);
		//填充data1区数据
		excelWriter.fill(new FillWrapper("data1", data1()), writeSheet);
		excelWriter.finish();
	
		//第二次导出[写数据],此时临时模板已经被写在fileName路径下,第二次导出使用该临时模板作为模板进行填充数据
		//导出文件所在路径
		String fileName2 = "D:\\" + "目标文件.xlsx";
		ExcelWriter excel = EasyExcel.write(fileName2).withTemplate(fileName).build();
		//sheet处理添加监听,用于处理发货量列的sum函数
		WriteSheet Sheet = EasyExcel.writerSheet().registerWriteHandler(new EasyExcelCellWriteHandler()).build();
		FillConfig config = FillConfig.builder().direction(WriteDirectionEnum.HORIZONTAL).build();
		//填充data2区数据,从数据库获取,处理为List<JSONObject>,各箱数量通过反射进行处理,给原对象添加字段并赋值然后转为JSONObject添加进list
		excel.fill(new FillWrapper("data2",data2List()), Sheet);
		//从数据库查询data3区数据,放入List中,将list替换下面data3List;这里就不在模拟数据了,运行时将本行注了
        excel.fill(new FillWrapper("data3",data3List()), config, Sheet);
			
		//设置强制计算公式:不然公式会以字符串的形式显示在excel中
        Workbook workbook = excel.writeContext().writeWorkbookHolder().getWorkbook();
        workbook.setForceFormulaRecalculation(true);
        excel.finish();
	}finally {
        //删除临时模板
        File file = new File(fileName);
        if(file.exists() && file.isFile()){
            file.delete();
        }
    }	
}

	@Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Box {
        private String boxId;
        private String boxNum;
    }
	private static List<Box> box() {
        List<Box> list = new ArrayList<>();
        for (int i = 1; i < 20; i++) {
            Box data = new Box();
            data.setBoxId("箱" + i);
            data.setBoxNum(String.format("{data2.temolate_%d}", i));
            list.add(data);
        }
        return list;
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class ExportFBAShipmentTemplate {
        private String sellerSku;
        private String asin;
        private String sellerSkuName;
        private String fnsku;
        private String specNo;
        private String declareNum;

    }
    
    private static List<ExportFBAShipmentTemplate> data2() {
        List<ExportFBAShipmentTemplate> list = new ArrayList<>();
        ExportFBAShipmentTemplate template = new ExportFBAShipmentTemplate();
        list.add(template);
        template.setSellerSku("{data2.sellerSku}");
        template.setAsin("{data2.asin}");
        template.setSellerSkuName("{data2.sellerSkuName}");
        template.setFnsku("{data2.fnsku}");
        template.setSpecNo("{data2.specNo}");
        template.setDeclareNum("{data2.declareNum}");
        return list;
    }
    //data3()方法与data2()方法类似,这里就不在重复书写了
	private static List<xxx> data3() {
        List<xxx> list = new ArrayList<>();
        xxx template = new xxx();
        list.add(template);
        ...
        return list;
    }

	private static List<Map<String, Object>> data1() {
        List<Map<String, Object>> list = new ArrayList<>();
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("amazonShipmentId", "编码");
        map.put("shipmentName", "名称");
        //其余数据也时一样的模式put进map
        ...
        list.add(map);
        return list;
    }

    private List<Map<String, Object>> data2List() {
        List<Map<String, Object>> list = ListUtils.newArrayList();
        Map<String, Object> map = new HashMap<>();
        map.put("sellerSku", "123");
        map.put("asin", 123);
        map.put("sellerSkuName", "sellerSkuName");
        map.put("fnsku", "fnsku");
        map.put("declareNum", 3);
        map.put("temolate_1", 12);
        map.put("temolate_2", 12);
        map.put("temolate_3", 12);
        Map<String, Object> map1 = new HashMap<>();
        map1.put("sellerSku", "123");
        map1.put("asin", 123);
        map1.put("sellerSkuName", "sellerSkuName");
        map1.put("fnsku", "fnsku");
        map1.put("declareNum", 3);
        map1.put("temolate_1", 12);
        map1.put("temolate_2", 12);
        map1.put("temolate_3", 12);
        Map<String, Object> map2 = new HashMap<>();
        map2.put("sellerSku", "123");
        map2.put("asin", 123);
        map2.put("sellerSkuName", "sellerSkuName");
        map2.put("fnsku", "fnsku");
        map2.put("declareNum", 3);
        map2.put("temolate_1", 12);
        map2.put("temolate_2", 12);
        map2.put("temolate_3", 12);
        list.add(map);
        list.add(map1);
        list.add(map2);
        return list;
    }

第一次导出生成的临时模板如下图

在这里插入图片描述

第二次导出目标文件如下图

在这里插入图片描述

6.监听器填充公式类

import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import groovy.util.logging.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;

import java.util.List;

@Slf4j
public class EasyExcelCellWriteHandler implements CellWriteHandler {

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
                                 Head head, Integer columnIndex, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Cell cell,
                                Head head, Integer relativeRowIndex, Boolean isHead) {

    }

    @Override
    public void afterCellDataConverted(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData, Cell cell, Head head, Integer integer, Boolean aBoolean) {

    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<CellData> list, Cell cell, Head head, Integer integer, Boolean aBoolean) {
        //sheet页码
        Integer sheetNo = writeSheetHolder.getSheetNo();
        //第一页
        if (sheetNo == 0) {
            //行
            int rowNum = cell.getRowIndex() + 1;
            //列
            int columnNum = cell.getColumnIndex();
            if(columnNum == 6 && rowNum > 10){
                cell.setCellFormula("SUM(I"+rowNum+":ZZ"+rowNum+")");
            }
        }
    }
}

7.java为实体动态添加字段工具类

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.springframework.cglib.beans.BeanGenerator;
import org.springframework.cglib.beans.BeanMap;

import java.beans.PropertyDescriptor;
import java.util.Map;

/**
 * @author: jack
 * @Date: 2019/5/15 21:30
 * @Description: 动态添加类的属性
 */
public class ReflectUtil {
    public static Object getTarget(Object dest, Map<String, Object> addProperties) {
        PropertyUtilsBean propertyUtilsBean =new PropertyUtilsBean();
        PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(dest);
        Map<String, Class> propertyMap = Maps.newHashMap();
        for(PropertyDescriptor d : descriptors) {
            if(!"class".equalsIgnoreCase(d.getName())) {
                propertyMap.put(d.getName(), d.getPropertyType());
            }
        }
        // add extra properties
        addProperties.forEach((k, v) -> propertyMap.put(k, v.getClass()));
        // new dynamic bean
        DynamicBean dynamicBean =new DynamicBean(dest.getClass(), propertyMap);
        // add old value
        propertyMap.forEach((k, v) -> {
            try{
                // filter extra properties
                if(!addProperties.containsKey(k)) {
                    dynamicBean.setValue(k, propertyUtilsBean.getNestedProperty(dest, k));
                }
            }catch (Exception e) {
                e.printStackTrace();
            }
        });
        // add extra value
        addProperties.forEach((k, v) -> {
            try{
                dynamicBean.setValue(k, v);
            }catch (Exception e) {
                e.printStackTrace();
            }
        });
        Object target = dynamicBean.getTarget();
        return target;
    }

    public static class DynamicBean {
        /**
         * 目标对象
         */
        private Object target;

        /**
         * 属性集合
         */
        private BeanMap beanMap;

        public DynamicBean(Class superclass, Map<String, Class> propertyMap) {
            this.target = generateBean(superclass, propertyMap);
            this.beanMap = BeanMap.create(this.target);
        }


        /**
         * bean 添加属性和值
         *
         * @param property
         * @param value
         */
        public void setValue(String property, Object value) {
            beanMap.put(property, value);
        }

        /**
         * 获取属性值
         *
         * @param property
         * @return
         */
        public Object getValue(String property) {
            return beanMap.get(property);
        }

        /**
         * 获取对象
         *
         * @return
         */
        public Object getTarget() {
            return this.target;
        }


        /**
         * 根据属性生成对象
         *
         * @param superclass
         * @param propertyMap
         * @return
         */
        private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
            BeanGenerator generator =new BeanGenerator();
            if(null != superclass) {
                generator.setSuperclass(superclass);
            }
            BeanGenerator.addProperties(generator, propertyMap);
            return generator.create();
        }
    }

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Student {
        private String name;
        private String email;
    }


    public static void main(String[] args) throws Exception{
        Student student = Student.builder().name("jack").email("xy123zk@163.com").build();

        System.out.println(student.toString());
        Map<String,Object> properties = Maps.newHashMap();
        properties.put("address","浙江杭州");
        properties.put("age",26);
        ObjectMapper mapper = new ObjectMapper();
        String json = mapper.writeValueAsString(getTarget(student,properties));
        System.out.println(json);
    }
}

8.中间踩过的坑

  • 使用监听器填充完公式后需要强制刷新,否则不生效,在excel中展示的是字符串

  • 模板文件放在本地或者在本地运行时放项目resources\file\templates目录下没有问题,到项目上运行报错找不到文件[通过绝对路径,相对路径都不可以,可能是部署的jar包的问题]; 解决:通过相对路径获取到流[InputStream]然后处理

    原代码:ExcelWriter excelWriter = EasyExcel.write(路径).withTemplate(路径).build();

    更新后的代码:ExcelWriter excelWriter = EasyExcel.write(路径).withTemplate(InputStream).build();

    • 报错:Can not found file.
    • cannot be resolved to absolute file path because it does not reside in the file system: jar:file:/opt/项目!/BOOT-INF/classes!/file/templates/template.xlsx
  • 26
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值