EasyExcel导入导出网上资料很多,这里就不多做描述了,总之就是一款比较轻而小,易用的excel操作工具包;这里在项目中使用到,做下笔记。以及表格中下拉框的实现;
声明:本文思路是借鉴于某大神的,然后自己进行二次改版而成的,用注解的方式来进行动态下拉列表的绑定。所以代码略微有点多。 还有另一种方式,则是将数据源传入进来直接进行绑定,这样可以做到真正的与业务分离,可以更加灵活。

EasyExcel导入

EasyExcel.read(file.getInputStream(), ImportSecurityPerson.class,
                    new PeronDataListener(securityPersonService, dto))
                    .sheet(0).doRead();

这里一句话就可以导入成功,参数为文件流,需要导出的表格的表头实体类,第三个是读取时的监听类即可。实体类如下:

@Data
@ToString(callSuper = true)
@ApiModel(value = " 安保人员导入 ", description = " 安保人员导入 ")
public class ImportSecurityPerson {
    /** 安保公司name **/
    @ApiModelProperty(value = "安保公司name")
    @ExcelProperty(value = "公司名称(必填)",index = 0)
    private String securityCompanyName;

    /** 安保人员姓名 **/
    @ApiModelProperty(value = "安保人员姓名")
    @ExcelProperty(value = "人员姓名(必填)",index = 1)
    private String securityPersonName;


    /** 安保人员性别 **/
    @ApiModelProperty(value = "安保人员性别")
    @ExcelProperty(value = "性别(必填)",index = 2)
    private String sexCode;

    /** 在职状态 **/
    @ApiModelProperty(value = "在职状态")
    @ExcelProperty(value = "状态(必填)",index = 3)
    private String inPostStatusCode;

    /** 作业区 **/
    @ApiModelProperty(value = "作业区")
    @ExcelProperty(value = "作业区(必填)",index = 4)
    private String workZoneCode;

    /** 学历 **/
    @ApiModelProperty(value = "学历")
    @ExcelProperty(value = "学历",index = 5)
    private String eduLevelCode;

    @ApiModelProperty(value = "错误信息")
    @ExcelIgnore
    private String msg;
    @ExcelIgnore
    private String uuid;

}

ExcelProperty 需要导出的表格属性,ExcelIgnore 忽略此属性。index则是顺序,value是标题;监听类中有三个重要的方法:invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context):此方法可以进行头部标题判断,判断是否是需要导入的表格;invoke(ImportSecurityPerson data, AnalysisContext context):每解析一行便会回调此方法,可以在这个方法中做一些处理;doAfterAllAnalysed(AnalysisContext context):解析完成所有数据后执行此方法, 可以进行批量入库,导入成功,错误的信息反馈(如导入成功条数,错误条数)等。

EasyExcel导出

response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("安保人员", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
List<SecurityPersonVo> securityPersonVos = securityPersonService.listSecurityPersons(getUserCharacter(), dto);
EasyExcel.write(response.getOutputStream(), SecurityPersonVo.class)
                    .sheet(0).sheetName("人员").doWrite(securityPersonVos);

首先设置response的信息,securityPersonVos 为数据源。导出的参数为第一个:输出流;第二个:导出的实体类,和导入是一样的;导出非常简单,对于样式的话,也可以在write后进行调用registerWriteHandler方法,写自定义样式就可以满足。

EasyExcel下载模板

其实下载模板和导出是一样的,只是在这里加了一些初始化的数据,如下拉内容项。下载模板的实体类有一点点特殊,就是采用了注解方式进行绑定下拉的内容。进入正题:

response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("安保人员导入模板", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
//用作需要传入的入参,如无须参数,则为空
Map<String, String> map = new HashMap<>();
map.put("clientId", user.getCustomerId());
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
WriteSheet writeSheet = EasyExcelUtil.writeSelectedSheet(DownLoadSecurityPerson.class,
                    0, "安保人员", map);
 //写
excelWriter.write(new ArrayList<String>(), writeSheet);
//手动关闭
excelWriter.finish();

以上是下载模板的代码,具体看下这个工具类:EasyExcelUtil

我们先看下表格实体类:

@Data
@ToString(callSuper = true)
@ApiModel(value = " 治安人员导入 ", description = " 治安人员导入 ")
public class DownLoadSecurityPerson {
    /** 安保公司name **/
    @ApiModelProperty(value = "安保公司name")
    @ExcelProperty(value = "公司名称(必填)",index = 0)
    private String securityCompanyName;

    /** 安保人员姓名 **/
    @ApiModelProperty(value = "安保人员姓名")
    @ExcelProperty(value = "人员姓名(必填)",index = 1)
    private String securityPersonName;


    /** 安保人员性别 **/
    @ApiModelProperty(value = "安保人员性别")
    @ExcelSelected(source = {"男","女"})
    @ExcelProperty(value = "性别(必填)",index = 2)
    private String sexCode;

    /** 在职状态 **/
    @ApiModelProperty(value = "在职状态")
    @ExcelSelected(source = {"在职","离职"})
    @ExcelProperty(value = "状态(必填)",index = 3)
    private String inPostStatusCode;

    /** 作业区 **/
    @ApiModelProperty(value = "作业区")
    @ExcelSelected(sourceClass = ZoneExcelSelectImpl.class)
    @ExcelProperty(value = "作业区(必填)",index = 4)
    private String workZoneCode;

    /** 学历 **/
    @ApiModelProperty(value = "学历")
    @ExcelSelected(sourceClass = EduExcelSelectImpl.class)
    @ExcelProperty(value = "学历",index = 5)
    private String eduLevelCode;
}

模板标题类属性使用了ExcelSelected注解,用来绑定下拉内容,如何是固定的值,则采用source 来赋予,如果是动态的,则动态获取绑定。

注解类

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {
    /**
     * 固定下拉内容
     */
    String[] source() default {};

    /**
     * 动态下拉内容
     */
    Class<? extends ExcelDynamicSelect>[] sourceClass() default {};

    /**
     * 设置下拉框的起始行,默认为第二行
     */
    int firstRow() default 1;

    /**
     * 设置下拉框的结束行,默认为最后一行
     */
    int lastRow() default 0x10000;
}

注解解析类

@Data
@Slf4j
public class ExcelSelectedResolve {
    /**
     * 下拉内容
     */
    private String[] source;

    /**
     * 设置下拉框的起始行,默认为第二行
     */
    private int firstRow;

    /**
     * 设置下拉框的结束行,默认为最后一行
     */
    private int lastRow;
    public String[] resolveSelectedSource(ExcelSelected excelSelected, Map<String,String> map) {
        if (excelSelected == null) {
            return null;
        }
        // 获取固定下拉框的内容
        String[] source = excelSelected.source();
        if (source.length > 0) {
            return source;
        }

        // 获取动态下拉框的内容
        Class<? extends ExcelDynamicSelect>[] classes = excelSelected.sourceClass();
        if (classes.length > 0) {
            try {
                ExcelDynamicSelect excelDynamicSelect = classes[0].newInstance();
                String[] dynamicSelectSource = excelDynamicSelect.getSource(map);
                if (dynamicSelectSource != null && dynamicSelectSource.length > 0) {
                    return dynamicSelectSource;
                }
            } catch (InstantiationException | IllegalAccessException e) {
                log.error("解析动态下拉框数据异常", e);
            }
        }
        return null;
    }
}

动态数据源接口

public interface ExcelDynamicSelect {
    /**
     * 获取动态生成的下拉框可选数据
     * @return 动态生成的下拉框可选数据
     */
    String[] getSource(Map<String,String> map);
}

动态数据源实现类

public class EduExcelSelectImpl implements ExcelDynamicSelect{
    @Override
    public String[] getSource(Map<String,String> map) {
        SecurityPersonService bean = SpringContextUtil.getBean(SecurityPersonService.class);
        Map<String, String> stringStringMap = bean.mapDict();
        Collection<String> values = stringStringMap.values();
        return values.toArray(new String[values.size()]);
    }
}

public class ZoneExcelSelectImpl implements ExcelDynamicSelect{

    @Override
    public String[] getSource(Map<String,String> map) {
        SecurityPersonService bean = SpringContextUtil.getBean(SecurityPersonService.class);
        Map<String, String> stringStringMap = bean.mapCustomerDict(map.get("clientId"));
        Collection<String> values = stringStringMap.values();
        return values.toArray(new String[values.size()]);
    }
}

接下来是工具类

public class EasyExcelUtil {
    /**
     * 创建即将导出的sheet页(sheet页中含有带下拉框的列)
     * @param head 导出的表头信息和配置
     * @param sheetNo sheet索引
     * @param sheetName sheet名称
     * @param map 入参
     * @return sheet页
     */
    public static <T> WriteSheet writeSelectedSheet(Class<T> head, Integer sheetNo, String sheetName,Map<String,String> map) {
        Map<Integer, ExcelSelectedResolve> selectedMap = this.resolveSelectedAnnotation(head,map);

        return EasyExcel.writerSheet(sheetNo, sheetName)
                .head(head)
                .registerWriteHandler(new CommentWriteHandler()) //创建批注
                .registerWriteHandler(new CustomColumnWidthStyle()) //自适应宽度
                .registerWriteHandler(new CustomSheetWriteHandler(selectedMap)) //绑定下拉数据源
                .build();
    }
    /**
     * 解析表头类中的下拉注解
     * @param head 表头类
     * @param <T> 泛型
     * @return Map<下拉框列索引, 下拉框内容> map
     */
    private static <T> Map<Integer, ExcelSelectedResolve> resolveSelectedAnnotation(Class<T> head,Map<String,String> map) {
    	//用来存储对应的列与值对象
        Map<Integer, ExcelSelectedResolve> selectedMap = new HashMap<>();

        // getDeclaredFields(): 返回全部声明的属性;getFields(): 返回public类型的属性
        Field[] fields = head.getDeclaredFields();
        for (int i = 0; i < fields.length; i++){
            Field field = fields[i];
            // 解析注解信息
            ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (selected != null) {
                ExcelSelectedResolve excelSelectedResolve = new ExcelSelectedResolve();
                String[] source = excelSelectedResolve.resolveSelectedSource(selected,map);
                if (source != null && source.length > 0){
                    excelSelectedResolve.setSource(source);
                    excelSelectedResolve.setFirstRow(selected.firstRow());
                    excelSelectedResolve.setLastRow(selected.lastRow());
                    if (property != null && property.index() >= 0){
                        selectedMap.put(property.index(), excelSelectedResolve);
                    } else {
                        selectedMap.put(i, excelSelectedResolve);
                    }
                }
            }
        }

        return selectedMap;
    }
}

最重要的在绑定类CustomSheetWriteHandler

@Data
@AllArgsConstructor
public class CustomSheetWriteHandler implements SheetWriteHandler {
    private final Map<Integer, ExcelSelectedResolve> selectedMap;

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        // 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行
        // 这里可以对cell进行任何操作
        Sheet sheet = writeSheetHolder.getSheet();
        DataValidationHelper helper = sheet.getDataValidationHelper();
        selectedMap.forEach((k, v) -> {
            // 设置下拉列表的行: 首行,末行,首列,末列
            CellRangeAddressList rangeList = new CellRangeAddressList(v.getFirstRow(), v.getLastRow(), k, k);
            // 设置下拉列表的值
            DataValidationConstraint constraint = helper.createExplicitListConstraint(v.getSource());
            // 设置约束
            DataValidation validation = helper.createValidation(constraint, rangeList);
            // 阻止输入非下拉选项的值
            validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
            validation.setShowErrorBox(true);
            validation.setSuppressDropDownArrow(true);
            validation.createErrorBox("提示", "请选择下拉选项中的内容");
            sheet.addValidationData(validation);
        });
    }
}

至此,就完成了下载模板带有下拉选项的表格。
完整代码GitHub: https://github.com/qinyunsurd/EasyExcel

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐