首页 > 其他分享 >使用easyPoi的动态列导出

使用easyPoi的动态列导出

时间:2024-03-29 15:01:04浏览次数:15  
标签:java String import 导出 param add new easyPoi 动态

项目背景:

有一个导出excel的需求,要求导出部分固定列和部分动态列,固定列使用字段写死,动态列使用list集合存放

成果展示:

思路:

简单说就是一个行转列的处理

1. 使用easypoi的注解方式进行导出,固定列部分使用 @Excel标注

2. 动态列使用一个List集合,用 @ExcelCollection 标注,里面的每一项就是每一个动态列标题,一个字段作为表头名称,一个字段作为对应的数据

代码:

需要的maven依赖及版本

<!--easypoi-->
<dependency>
    <groupId>cn.afterturn</groupId>
    <artifactId>easypoi-base</artifactId>
    <version>4.4.0</version>
</dependency>

<!--commons-beanutils-->
<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>

<!--cglib,动态向实体类添加字段-->
<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib-nodep</artifactId>
    <version>3.3.0</version>
</dependency>

举个栗子:

模拟自己的业务逻辑,组装好需要的数据格式调用工具类中的方法即可

public void exportTest(HttpServletResponse response) {
        ExportDemo demo1 = new ExportDemo();
        // 固定列
        demo1.setDepartName("住建局");
        demo1.setResolutionRate("82%");
        demo1.setDealCount(204);
        demo1.setTotalEvaCount(62);
        demo1.setValidEvaCount(7);
        // 动态列
        List<ExcelPoiUtils.Demo.Type> types = new ArrayList<>();
        ExcelPoiUtils.Demo.Type type1 = new ExcelPoiUtils.Demo.Type();
        type1.setTypeName("民生满意度");
        type1.setScore("35分");
        ExcelPoiUtils.Demo.Type type2 = new ExcelPoiUtils.Demo.Type();
        type2.setTypeName("卫生治理");
        type2.setScore("46分");
        ExcelPoiUtils.Demo.Type type3 = new ExcelPoiUtils.Demo.Type();
        type3.setTypeName("公共安全");
        type3.setScore("52分");
        ExcelPoiUtils.Demo.Type type4 = new ExcelPoiUtils.Demo.Type();
        type4.setTypeName("绿化面积");
        type4.setScore("65分");
        types.add(type1);
        types.add(type2);
        types.add(type3);
        types.add(type4);
        demo1.setTypes(types);

        ExportDemo demo2 = new ExportDemo();
        demo2.setDepartName("民政局");
        demo2.setResolutionRate("62%");
        demo2.setDealCount(9661);
        demo2.setTotalEvaCount(560);
        demo2.setValidEvaCount(80000);

        List<ExcelPoiUtils.Demo.Type> types2 = new ArrayList<>();
        ExcelPoiUtils.Demo.Type typeA = new ExcelPoiUtils.Demo.Type();
        typeA.setTypeName("民生满意度");
        typeA.setScore("102分");
        ExcelPoiUtils.Demo.Type typeB = new ExcelPoiUtils.Demo.Type();
        typeB.setTypeName("卫生治理");
        typeB.setScore("60分");
        ExcelPoiUtils.Demo.Type typeC = new ExcelPoiUtils.Demo.Type();
        typeC.setTypeName("公共安全");
        typeC.setScore("4分");
        ExcelPoiUtils.Demo.Type typeD = new ExcelPoiUtils.Demo.Type();
        typeD.setTypeName("绿化面积");
        typeD.setScore("88分");
        types2.add(typeA);
        types2.add(typeB);
        types2.add(typeC);
        types2.add(typeD);
        demo2.setTypes(types2);

        List<ExportDemo> list = new ArrayList<>();
        list.add(demo1);
        list.add(demo2);

        // 动态标头名称字段
        final String headerName = "typeName";
        // 动态标头值字段
        final String headerValue = "score";
        try {
            ExcelPoiUtils.dynamicExport(response, System.currentTimeMillis() + "", "我是标题行!",
                    "sheet名称", list, headerName, headerValue);
        } catch (Exception e) {
            log.error("导出错误,", e);
            throw new RuntimeException(e);
        }
    }

实体类:

package com.github.face.user.entity;

import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
import com.github.face.utils.ExcelPoiUtils;
import lombok.Data;

import java.io.Serializable;
import java.util.List;

/**
 * 动态列导出测试
 *
 * @author wangcl
 */
@Data
public class ExportDemo implements Serializable {

    /**
     * 部门名称
     */
    @Excel(name = "部门", width = 20)
    private String departName;

    /**
     * 处理事件数
     */
    @Excel(name = "处理事件数", width = 20)
    private Integer dealCount = 0;

    /**
     * 全部评价数
     */
    @Excel(name = "全部评价数", width = 20)
    private Integer totalEvaCount = 0;

    /**
     * 有效评价数
     */
    @Excel(name = "有效评价数", width = 20)
    private Integer validEvaCount = 0;

    /**
     * 解决率
     */
    @Excel(name = "解决率", width = 20)
    private String resolutionRate;

    /**
     * 评价类型及分数
     */
    @ExcelCollection(name = "评价类型")
    private List<ExcelPoiUtils.Demo.Type> types;

    /**
     * 评价类型及分数对象
     */
    @Data
    static class Type implements Serializable {

        /**
         * 评价类型id
         */
        private Long typeId;

        /**
         * 评价类型名称(动态标题名称,此处 name = "typeName"可以随便填,以方法调用时传入的为准)
         */
        @Excel(name = "typeName", width = 20)
        private String typeName;

        /**
         * 评价类型对应分数(动态标题内容,此处 name = "score"可以随便填,以方法调用时传入的为准)
         */
        @Excel(name = "score", width = 20)
        private String score = "0.00";
    }
}

动态导出类:

处理动态表头和动态列字段的对应关系

package com.github.face.utils;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.annotation.Excel;
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.hutool.core.annotation.AnnotationUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * 使用easyPoi的动态列导出
 * <br/>固定列使用@Excel标识,动态列使用@ExcelCollection标识的List集合,手动传入集合内作为动态表头名称和值的字段名
 *
 * @author wangcl
 */
@Slf4j
public class ExcelPoiUtils {

     /**
     * 使用注解的动态列导出,实体类必须按照规定格式
     * <br/>通过反射实现,注意方法效率
     *
     * @param response    响应
     * @param fileName    文件名
     * @param title       标题
     * @param sheetName   sheet名称
     * @param dataList    导出数据
     * @param headerName  动态列标题
     * @param headerValue 动态列值
     */
    public static <T> void dynamicExport(HttpServletResponse response, String fileName, String title, String sheetName,
                                         List<T> dataList, String headerName, String headerValue) throws Exception {
        Assert.notEmpty(dataList, "没有需要导出的数据");
        List<ExcelExportEntity> entityList = new ArrayList<>();
        List<Object> list = new ArrayList<>();
        for (T t : dataList) {
            // Step1:处理标题
            Field[] fields = t.getClass().getDeclaredFields();
            int index = 0;
            Map<String, Object> map = new HashMap<>();
            for (Field field : fields) {
                field.setAccessible(true);
                Excel excel = field.getAnnotation(Excel.class);
                ExcelCollection excelCollection = field.getAnnotation(ExcelCollection.class);
                // 固定导出列
                if (excel != null) {
                    Object name = AnnotationUtil.getAnnotationValue(field, Excel.class, "name");
                    // 转换注解修饰的对象
                    ExcelExportEntity entity = convert(field, name.toString(), field.getName(), index);
                    index++;
                    entityList.add(entity);
                }
                // 自定义导出列,含有@ExcelCollection并且是List
                else if (excelCollection != null && field.getType().getName().equals(List.class.getName())) {
                    Object object;
                    object = field.get(t);
                    List<?> dynamicColl = (List<?>) object;
                    for (Object arr : dynamicColl) {
                        String key = null;
                        String val = null;
                        Field[] typeFields = arr.getClass().getDeclaredFields();
                        for (Field typeField : typeFields) {
                            typeField.setAccessible(true);
                            String fieldName = typeField.getName();
                            Excel excelItem = typeField.getAnnotation(Excel.class);
                            if (excelItem != null) {
                                if (headerName.equals(fieldName)) {
                                    Object value;
                                    try {
                                        value = typeField.get(arr);
                                        key = value.toString();
                                    } catch (IllegalAccessException e) {
                                        throw new RuntimeException(e);
                                    }
                                    // 转换注解修饰的对象
                                    ExcelExportEntity entity = convert(typeField, value.toString(), value.toString(), index);
                                    index++;
                                    entityList.add(entity);
                                } else if (headerValue.equals(fieldName)) {
                                    Object value;
                                    try {
                                        value = typeField.get(arr);
                                        val = value.toString();
                                    } catch (IllegalAccessException e) {
                                        throw new RuntimeException(e);
                                    }
                                }
                            }
                        }
                        map.put(key, val);
                    }
                }
            }
            //  Step2:处理数据,将动态列添加到实体类
            Object object = ReflectKit.getObject(t, map);
            list.add(object);
        }
        log.info(JSONObject.toJSONString(list));
        entityList = entityList.stream().filter(distinctByKey(ExcelExportEntity::getName)).collect(Collectors.toList());
        downloadExcelEntityDynamic(response, entityList, list, fileName, title, sheetName);
    }

    /**
     * 根据指定字段去重
     */
    private static <T> Predicate<T> distinctByKey(Function<? super T, ?> keyExtractor) {
        Set<Object> seen = ConcurrentHashMap.newKeySet();
        return t -> seen.add(keyExtractor.apply(t));
    }

    /**
     * 动态表头导出
     *
     * @param response   响应
     * @param entityList 表头列表
     * @param list       导出数据
     * @param fileName   文件名
     * @param title      标题
     * @param sheetName  sheet名称
     */
    public static void downloadExcelEntityDynamic(HttpServletResponse response, List<ExcelExportEntity> entityList,
                                                  Collection<?> list, String fileName, String title,
                                                  String sheetName) throws Exception {
        makeResponse(response, fileName);
        ExportParams exportParams;
        if (StringUtils.hasText(title)) {
            exportParams = new ExportParams(title, sheetName);
        } else {
            exportParams = new ExportParams();
            exportParams.setSheetName(sheetName);
        }
        Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entityList, list);
        ServletOutputStream output = response.getOutputStream();
        BufferedOutputStream bufferedOutPut = new BufferedOutputStream(output);
        workbook.write(bufferedOutPut);
        bufferedOutPut.flush();
        bufferedOutPut.close();
        output.close();
    }

    public static void makeResponse(HttpServletResponse response, String fileName) {
        response.setHeader("Content-Disposition",
                "attachment;filename=" + URLEncoder.encode(fileName + ".xlsx", StandardCharsets.UTF_8));
        response.setContentType("application/vnd.ms-excel;charset=UTF-8");
    }

    /**
     * 将@Excel修饰的字段转为ExcelExportEntity
     */
    private static ExcelExportEntity convert(Field typeField, String name, String key, int index) {
        Map<String, Object> annotationValueMap = AnnotationUtil.getAnnotationValueMap(typeField, Excel.class);
        ExcelExportEntity entity = JSONObject.parseObject(JSONObject.toJSONBytes(annotationValueMap), ExcelExportEntity.class);
        // 字段名和@Excel的name一致,视为动态表头列
        entity.setName(name);
        entity.setKey(key);
        entity.setOrderNum(index);
        return entity;
    }
}

反射处理类:

使用cglib动态向实体类内添加字段,添加的字段为需要动态添加的表头和对应的值

package com.github.face.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtilsBean;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * 反射获取动态对象
 *
 * @author wangcl
 */
@Slf4j
public class ReflectKit {

    /**
     * 获取添加动态字段的新对象
     *
     * @param dest        原始对象
     * @param newValueMap 要添加的字段和值
     */
    public static Object getObject(Object dest, Map<String, Object> newValueMap) throws
            InvocationTargetException, IllegalAccessException {
        PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();

        //1.获取原对象的字段数组
        PropertyDescriptor[] descriptorArr = propertyUtilsBean.getPropertyDescriptors(dest);

        //2.遍历原对象的字段数组,并将其封装到Map
        Map<String, Class> oldKeyMap = new HashMap<>(4);
        for (PropertyDescriptor it : descriptorArr) {
            if (!"class".equalsIgnoreCase(it.getName())) {
                oldKeyMap.put(it.getName(), it.getPropertyType());
                newValueMap.put(it.getName(), it.getReadMethod().invoke(dest));
            }
        }

        //3.将扩展字段Map合并到原字段Map中
        newValueMap.forEach((k, v) -> oldKeyMap.put(k, v.getClass()));

        //4.根据新的字段组合生成子类对象
        DynamicBean dynamicBean = new DynamicBean(dest.getClass(), oldKeyMap);

        //5.放回合并后的属性集合
        newValueMap.forEach((k, v) -> {
            try {
                dynamicBean.setValue(k, v);
            } catch (Exception e) {
                log.error("动态添加字段【值】出错", e);
            }
        });
        return dynamicBean.getTarget();
    }
}

动态代理类:

package com.github.face.utils;

import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;

import java.util.Map;

/**
 * 动态代理类
 *
 * @author wangcl
 */
public 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);
        Object object = generator.create();


        return object;
    }
}


提示:

jdk9以上默认关闭反射,需要手动开启,需要手动添加启动参数

针对本项目:
--add-opens java.base/java.lang=ALL-UNNAMED
针对jdk9以上版本的反射:
--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.io=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED
--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.base/java.math=ALL-UNNAMED

idea开启方式如下

没有测试过接口效率,有错误的地方欢迎大家指正

标签:java,String,import,导出,param,add,new,easyPoi,动态
From: https://blog.csdn.net/qq_40603183/article/details/137143714

相关文章

  • 牛客竞赛动态规划专题班数位dp例题
    题单A-0的个数这题算是一个思维题。我的做法是就是统计每一位的0有多少个。例如\(4032\)个位的零有\(403\)种十位的零有\(40*10\)种百位的零有\(3*100+33\)种,即千位去\([1,3]\)个位低两位取\([00,99]\),或者千位取\(4\)低两位取\([00,33]\)千位不能取零#include<......
  • 动态规划 选择dp:多重背包+多重背包puls----中专生刷算法
    不了解动态规划和选择dp的同学先看一下这两篇文章动态规划:选择dp及优化01背包问题-CSDN博客动态规划:完全背包问题----中专生刷算法-CSDN博客然后我们来做题普通题+进阶题,图文详解,化零为整的解决多重背包puls问题!!!多重背包输入格式输出格式输出一个整数,表示最......
  • LeetCodeHot100 动态规划 70. 爬楼梯 118. 杨辉三角 198. 打家劫舍 279. 完全平方
    70.爬楼梯https://leetcode.cn/problems/climbing-stairs/description/?envType=study-plan-v2&envId=top-100-likedpublicintclimbStairs(intn){if(n<=1)returnn;int[]dp=newint[n+1];dp[1]=1;dp[2]=2;......
  • 线段树 动态开点
    红色圈出来的4个点就是查出来的其中两个?圈是没有被开出来的区间就是0,其实答案就是[3,3]+[6,8]动态开点模板:structTree{intl,r,n,ls,rs;}voidupdate(int&t,intl,intr,intpos,intn){if(!t){t=++cnt;tree[t].......
  • PPT图片高清导出方法
    1. 退出所有微软相关的程序,即关掉所有microsoftoffice程序(PPT,word,Excel);键盘按windows键(简称Win键,在ctrl和alt键之间那个)+R键,以打开运行命令,直接输入regedit,回车;直接修改图中地址为\HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\PowerPoint\Options2.点击菜单栏编辑-......
  • CorelDraw (CDR) VBA 实现导出贴图坐标
    创作上位机动画时,喜欢用Corel做画面设计,毕竟不管是亚控还是力控还是wincc,画图都太难受了.贴图动画要贴准的话,最好用坐标精确对齐.所以写了这段代码,用来把Corel中的坐标写入文本文件,做上位机画面时,就可以使用这些坐标进行贴图了.上代码1SubMacro1()2Di......
  • 动态开点线段树
    求区间最值importsyssys.setrecursionlimit(1000000)classNode:__slots__=["left","right","val","tag"]def__init__(self,left=None,right=None,val=0,tag=0):self.left=leftself.righ......
  • HTML精美登录页面,(动态渐变效果+稍微透明效果)
    最近,学校留的html作业做出来十分简陋学校作业如上图所示,今天我来教大家做一个精美的登录页面。以下是精美的登录页面。HTML精美登录页面接下来我来带大家写代码一,HTML代码<bodyclass="meau"><divclass="formBox"><formaction=""class="FORMF">......
  • .NET C#导出解决方案的NuGet依赖关系
    前言公司项目需要写DS设计文档,文档需要标识出来你的解决方案文件下的所有项目都使用了NuGet哪些第三方依赖,我们都知道sln下面的所有.csproj文件中的节点下会标识出对应的依赖,但一个一个对比又太麻烦(主要是懒),有时候一个sln能有10几个project项目,能不能写脚本一键导出这些依赖关......
  • Mybatis进阶之动态SQL
    1、MyBatis获取参数值的两种方式MyBatis获取参数值的两种方式:${}和#{}${}的本质就是字符串拼接,#{}的本质就是占位符赋值${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日......