栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

no-excel 简单实用的excel导入导出工具类

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

no-excel 简单实用的excel导入导出工具类

项目的github地址是https://github.com/amerainc/no-excel,欢迎各位的Star和Issues

项目简述

no-excel是一个excel导入导出工具类,提供基于注解方式使用,并提供了excel下拉框和级联下拉框的生成,同时对基本类型包括时间枚举提供了自动转换的功能,旨在提供简单便捷通用的excel导入导出功能。

快速开始

引入no-excel依赖


  io.github.amerainc
  no-excel
  2021.12.24

首先创建一个测试用的实体类(测试样例在test文件夹下)

@ExcelEntity(title = "测试")
@Data
public class TestEntity{
    //require选项必填时输出会带有*好,读取时无必填字段则会抛出异常
    @ExcelField(name = "必填选项", require = true)
    private String str;
    //在枚举类的情况下,param参数可以指定excel写入读取时使用的枚举类属性
    @ExcelField(name = "枚举类转换", param = "i18n")
    private ColorEnum colorEnum;
    @ExcelField(name = "长整型")
    private Long number;
    @ExcelField(name = "时间")
    private Date date;
}
public enum ColorEnum {
    RED("红色"),
    GOLD("金黄色"),
    YELLOW("黄色");
    String i18n;
    ColorEnum(String i18n){
        this.i18n = i18n;
    }
    public String getI18n() {
        return i18n;
    }
}
excel导出

为了能够重复使用,工具类在导出后并不会主动将workbook关闭,工具类也同时实现了closeable接口,因此建议使用try-resource的方式来使用。

普通导出
   @Test
    public void writeFile() {
        //初始化要写入的数据
        List testEntityList = new ArrayList<>();
        TestEntity testEntity = new TestEntity();
        testEntity.setDate(new Date());
        testEntity.setStr("测试文字");
        testEntity.setNumber(123L);
        testEntity.setColorEnum(ColorEnum.GOLD);
        for (int i = 0; i < 100; i++) {
            testEntityList.add(testEntity);
        }

        //写入
        ExcelWriterBuilder builder = ExcelWriterBuilder.builder(TestEntity.class);
        try (ExcelWriter excelWriter = builder.build()) {
            excelWriter.writeDataAndClose(testEntityList, new FileOutputStream("createFile.xls"));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

导出的文件如下图所示,同时枚举类则会生成下拉菜单

导出模板

如果不是为了导出数据,只是为了提供一份供用户填写的模板,也可以直接调用写入模板的方法。

    @Test
    public void writeTemplate() {
        //构造模板
        try (ExcelWriter excelWriter = ExcelWriterBuilder.builder(TestEntity.class).build();
             FileOutputStream fileOutputStream = new FileOutputStream("createTemplate.xls")) {
            excelWriter.writeTemplate(fileOutputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

导出的文件如下图所示

web导出

同时也提供了web方式的导出

    @GetMapping("/test")
    public void test(HttpServletRequest request, HttpServletResponse response) {
        try (ExcelWriter excelWriter = ExcelWriterBuilder.builder(TestEntity.class).build()) {
            excelWriter.writeTemplateToResponse("测试.xls",request,response);
        }
    }

导出效果如下所示

excel导入

直接读取将数据转换成实体类列表返回

    @Test
    public void readFile() {
        String path = getClass().getResource("/").getPath();
        ExcelReaderBuilder builder = ExcelReaderBuilder.builder(TestEntity.class);
        try (ExcelReader excelReader = builder.build(new File(path + "readFile.xls"))) {
            List testEntities = excelReader.readData();
            System.out.println(testEntities);
        }
    }

同时也提供了消费者模式进行消费,消费者模式下将会对数据逐行进行转换,并直接通过消费者进行消费

    @Test
    public void readFile2() {
        String path = getClass().getResource("/").getPath()+"readFile.xls";
        ExcelReaderBuilder builder = ExcelReaderBuilder.builder(TestEntity.class);
        try (ExcelReader excelReader = builder.build(new File(path))) {
           excelReader.readData(testEntity -> {
               System.out.println(testEntity);
           });
        }
    }

如果处理比较耗时,也可以采用多线程的方式分片进行处理

    @Test
    public void readFile3() {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        String path = getClass().getResource("/").getPath()+"readFile.xls";
        ExcelReaderBuilder builder = ExcelReaderBuilder.builder(TestEntity.class);
        try (ExcelReader excelReader = builder.build(new File(path))) {
            //这里将数据分为五片,并交由五个线程处理
            excelReader.readDataConcurrent(testEntity -> {
                System.out.println(testEntity);
            },executorService,5);
        }
    }
注解 @ExcelEntity

在用来承载excel数据的实体类上使用,用于指定一些全局性的参数

@ExcelEntity(title = "测试")
@Data
public class TestEntity{
    //require选项必填时输出会带有*好,读取时无必填字段则会抛出异常
    @ExcelField(name = "必填选项", require = true)
    private String str;
    //在枚举类的情况下,param参数可以指定excel写入读取时使用的枚举类属性
    @ExcelField(name = "枚举类转换", param = "i18n")
    private ColorEnum colorEnum;
    @ExcelField(name = "长整型")
    private Long number;
    @ExcelField(name = "时间")
    private Date date;
}
属性类型必填默认值说明
titleStringexcel导出时显示的标题名
showTitlebooleantrueexcel导出时是否有标题
showHeadbooleantrueexcel导出时是否有表头
maxSizeint500excel导入时读取的最大数据量限制,超出限制会抛出异常
titleStyleClassDefaultTitleStyleProvider.class标题使用的excel样式
headRequireStyleClassDefaultHeadRequireStyleProviderProvider.class当字段为必填时,标题显示的样式
headStyleClassDefaultHeadStyleProvider.class普通表头样式
dataStyleClassDefaultDataStyleProvider.class数据样式
@ExcelField

在需要导入导出的字段上进行使用,用于定义字段相关的信息

属性类型必填默认值说明
nameString字段名称,与excel表头对应
sortint0字段的输出顺序
requirebooleanfalse字段是否必填,必填时输出表头会带有*号,读取时无必填字段则会抛出异常
converterClass>DefaultFieldConverter.class字段转换器,用于提供excel字段和实体类字段之间的转换
paramString“”用于提供给字段转换器额外的处理参数
cascadeDependString“”级联依赖字段的字段名(不填默认依赖前一个字段)
功能 字段转换器 FieldConverter

FieldConverter接口用于提供excel字段和实体类字段之间的转换,实现这个接口可以自定义字段转换的规则

parseToField(必须实现)

实现parseToField方法,用于提供excel数据转换成对应字段值的功能,下面以默认的Integer转换器为例

    @Override
    public Integer parseToField(String excelData) {
        try {
            return Integer.parseInt(excelData);
        } catch (NumberFormatException numberFormatException) {
            throw new NoExcelException("必须为整型");
        }
    }
parseToExcelData(必须实现)

实现parseToExcelData方法用于提供将字段值转换为excel数据的功能

    @Override
    public String parseToExcelData(Integer fieldData) {
        return fieldData.toString();
    }
initData

initData方法会在转换器初始化调用,以默认的日期转换器为例,在初始化时可以通过填写注解上param来指定输出到excel的日期格式

public class DefaultDateFieldConverter implements FieldConverter {
    
    private String printDateFormat = "yyyy/MM/dd HH:mm:ss";
 											......
    @Override
    public String parseToExcelData(Date fieldData) {
        return DateFormatUtil.formatDate(fieldData, this.printDateFormat);
    }

    @Override
    public void initData(ExcelFieldmeta excelFieldmeta) {
        //如果有参数则使用参数作为日期格式
        if (StrUtil.isNotBlank(excelFieldmeta.getParam())) {
            this.printDateFormat = excelFieldmeta.getParam();
        }
    }
match(用于默认字段转换器)

match方法用于默认字段转换器进行匹配使用,如果是在注解上直接指定的转换器则无需实现

    default boolean match(ExcelFieldmeta excelFieldmeta) {
        Class firstGenericType = GenericUtil.getFirstGenericType(this.getClass());
        return firstGenericType != null && firstGenericType.isAssignableFrom(excelFieldmeta.getFieldClz());
    }
order(用于默认字段转换器)

order方法用于决定默认字段转换器进行匹配时优先级越小优先级越高

    default int order() {
        return 0;
    }
isSingleton

项目对字段转换器做了缓存,通过isSingleton觉得当前字段转换器在使用时是否为单例,默认为非单例。如整型等不需要配置的转换器使用了单例,而日期转换器可以自定义输出格式,使用了非单例的形式。

    @Override
    default boolean isSingleton(){
        return false;
    }
DefaultFieldConverter(默认字段转换器)

DefaultFieldConverter是注解中默认使用的字段转换器,DefaultFieldConverter是一个代理类,通过match方法对所有的默认字段转换器进行匹配并实现代理。

DefaultFieldConverter通过spi的方式将所有的默认字段转换器放入匹配列表,并在使用时通过init方法进行初始化匹配到对应的默认字段转换器进行代理,匹配逻辑为由order决定转换器的匹配顺序,并通过match方法匹配使用首个匹配成功的字段转换器

spi如下,目前总共实现了8个默认字段转换器

com.rainc.noexcel.convert.impl.DefaultDateFieldConverter
com.rainc.noexcel.convert.impl.DefaultDoubleFieldConverter
com.rainc.noexcel.convert.impl.DefaultEnumFieldConverter
com.rainc.noexcel.convert.impl.DefaultIntegerFieldConverter
com.rainc.noexcel.convert.impl.DefaultLongFieldConverter
com.rainc.noexcel.convert.impl.DefaultShortFieldConverter
com.rainc.noexcel.convert.impl.DefaultStringFieldConverter
com.rainc.noexcel.convert.impl.DefaultObjectFieldConvert

如果想要自己的字段转换器也能通过DefaultFieldConverter进行代理,则在项目的resoures/meta-INF/services下创建文件,文件名为com.rainc.noexcel.convert.FieldConverter,并在文件中写上自定义的字段转换器的全类名

baseMapFieldConverter(基础字段映射转换器)

baseMapFieldConverter是字段映射转换器的抽象类,实现baseMapFieldConverter可以轻松实现枚举字典等具有映射关系的数据转换,并且在导出时可以通过映射值生成excel的下拉框选项

继承baseMapFieldConverter并实现方法fieldToExcelDataMap,通过实现这个方法并返回一个实体类字段到excel字段的数据映射表

下面用默认的枚举字段转换器为例,返回的键是枚举值,值在没有定义param的情况下默认用name,同时也可以通过param来指定excel的字段。同时使用linkedHashMap是因为生成下拉框数据时会使用到map的values,用linkedHashMap可以保证下拉框列表的顺序。

public class DefaultEnumFieldConverter extends baseMapFieldConverter> {

    @Override
    @SneakyThrows
    public Map, String> fieldToExcelDataMap(ExcelFieldmeta excelFieldmeta) {
        Class> fieldClz = (Class>) excelFieldmeta.getFieldClz();
        String param = excelFieldmeta.getParam();
        Enum[] enums = fieldClz.getEnumConstants();
        return Arrays.stream(enums).collect(Collectors.toMap(anEnum -> anEnum, anEnum -> {
            if (StrUtil.isEmpty(param)) {
                return anEnum.name();
            } else {
                return ReflectUtil.getFieldValue(anEnum, param).toString();
            }
        },(a,b)->a, linkedHashMap::new));
    }
}
baseCascadeConverter(基础级联字段转换器)

baseCascadeConverter是级联字段转换器的抽象类,是在字段映射转换器的基础上实现的,可以用来生成带有级联下拉框的excel字段。

继承baseCascadeConverter并实现cascadeMap,返回一个格式为Map<级联的excel值,Map<属性值,excel值>>的映射表即可实现级联的下拉框选项

下面实例实现了baseCascadeConverter,手动创建了一个和之前的ColorEnum进行级联的map数据

public class CascadeConverter extends baseCascadeConverter implements CascadeProvider {
    @Override
    public Map> cascadeMap(ExcelFieldmeta excelFieldmeta) {
        Map> map = new linkedHashMap<>();
        Map red = new linkedHashMap<>();
        red.put("bred","大红色");
        red.put("sred","小红色");
        Map yellow = new linkedHashMap<>();
        yellow.put("byellow","大黄色");
        yellow.put("syellow","小黄色");
        Map gold = new linkedHashMap<>();
        gold.put("bgold","大金黄");
        gold.put("sgold","小金黄");
        map.put("红色",red);
        map.put("黄色", yellow);
        map.put("金黄色",gold);
        return map;
    }
}

在测试用的实体类中增加级联字段用来测试,将converter设置为自定义的CascadeConverter同时将级联的依赖字段选为枚举类转换字段

@ExcelEntity(title = "测试")
@Data
public class TestEntity{
    //require选项必填时输出会带有*号,读取时无必填字段则会抛出异常
    @ExcelField(name = "必填选项", require = true)
    private String str;
    //在枚举类的情况下,param参数可以指定excel写入读取时使用的枚举类属性
    @ExcelField(name = "枚举类转换", param = "i18n")
    private ColorEnum colorEnum;
    @ExcelField(name = "长整型")
    private Long number;
    @ExcelField(name = "时间")
    private Date date;
    @ExcelField(name = "级联",converter = CascadeConverter.class,cascadeDepend = "枚举类转换")
    private String cascade;
}

使用ExcelWriter的writeTemplate方法导出excel模板并查看,级联效果如下

错误行校验

工作中经常有业务是用户直接通过excel的形式进行批量的导入操作,由于excel不可控,所以总是会产生一些错误的数据,错误行校验功能与字段转换功能紧密贴合,在字段转换时达到字段的校验功能

需要使用错误行校验首先实体类继承baseErrMsg,baseErrMsg用来存储校验的错误信息

@ExcelEntity(title = "测试")
@Getter
@Setter
@ToString
public class TestEntity extends baseErrMsg {
    //require选项必填时输出会带有*号,读取时无必填字段则会抛出异常
    @ExcelField(name = "必填选项", require = true)
    private String str;
    //在枚举类的情况下,param参数可以指定excel写入读取时使用的枚举类属性
    @ExcelField(name = "枚举类转换", param = "i18n")
    private ColorEnum colorEnum;
    @ExcelField(name = "长整型")
    private Long number;
    @ExcelField(name = "时间")
    private Date date;
    @ExcelField(name = "级联",converter = CascadeConverter.class,cascadeDepend = "枚举类转换")
    private String cascade;
}

然后再字段转换器中校验不符合的地方抛出NoExcelException异常,工具类就会捕获这个异常并写入到baseErrMsg的属性中,以默认长整型转换器为例,在解析Integer失败时抛出了必须为长整型的异常

    @Override
    public Long parseToField(String excelData) {
        try {
            return Long.parseLong(excelData);
        } catch (NumberFormatException numberFormatException) {
          throw new NoExcelException("必须为长整型");
        }
    }

这里尝试读取下面的数据,下面第一行数据有两处错误,必填选项未填,长整型也不对

下面的代码读取了这个失败文件并将成功的数据和失败的数据分离,在控制台输出成功的数据,并将失败数据导出到errFile.xls

 @Test
    public void readerrorFile() {
        //读取excel
        String path = getClass().getResource("/").getPath()+"readerrFile.xls";
        ExcelReaderBuilder builder = ExcelReaderBuilder.builder(TestEntity.class);
        List testEntities;
        try (ExcelReader excelReader = builder.build(new File(path))) {
            testEntities = excelReader.readData();
        }
        //过滤成功的数据
        List success = testEntities.stream().filter(baseErrMsg::hasNotErrMsg).collect(Collectors.toList());
        System.out.println(success);
        //过滤成功的数据
        List error = testEntities.stream().filter(baseErrMsg::hasErrMsg).collect(Collectors.toList());
        //将失败的数据重新导成excel
        try (ExcelWriter excelWriter = ExcelWriterBuilder.builder(TestEntity.class).build()) {
            excelWriter.writeDataAndClose(error,new FileOutputStream("errFile.xls"));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

可以看到结果如下图,控制台输出了成功的数据行,而excel中的导出了错误行,并且含有错误行的错误信息

忽略字段

有时候导入导出希望动态的选择某些字段,而不是对标有注解的字段进行全量的导入导出,比如继承了baseErrMsg后,在生成模板或导入数据时并不希望有错误信息行的出现,导入数据时也不希望读到excel中的错误信息

ignoreWithFieldName

通过Builder上提供的ignoreWithFieldName方法可以在导入导出时忽略这些字段,列如忽略错误信息经常用到,因此也提供了一个快捷方法

    public Builder ignoreErrMsg() {
        return this.ignoreWithFieldName(baseErrMsg::getErrMsg);
    }

使用如下,忽略指定行后构建实例,即可

    @Test
    public void writeTemplate() {
        //构造模板
        try (ExcelWriter excelWriter = ExcelWriterBuilder.builder(TestEntity.class)
                //忽略errMsg行
                .ignoreErrMsg()
                //构建
                .build();
             FileOutputStream fileOutputStream = new FileOutputStream("createTemplate.xls")) {
            excelWriter.writeTemplate(fileOutputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
修改样式 StyleProvider

通过实现StyleProvider可以自定义标题,表头,数据的样式

editStyle

实现editStyle方法能够修改单元格样式

    @Override
    public void editStyle(CellStyle style, Workbook workbook) {
        style.setFillForegroundColor(HSSFColor.HSSFColorPredefined.GREY_25_PERCENT.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        style.setBorderTop(BorderStyle.THIN);
        style.setAlignment(HorizontalAlignment.CENTER);
        style.setVerticalAlignment(VerticalAlignment.CENTER);
    }
editFont

实现editFont方法能够修改字体样式

   @Override
    public void editFont(Font font) {
        font.setBold(Boolean.FALSE);
        font.setFontHeightInPoints((short)10);
    }
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/684223.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号