easyexcel导入导出包含 自定义列宽,下拉框,日期校验,批量导入入库完整开发流程

1. 引入maven依赖

<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>easyexcelartifactId>
    <version>2.2.7version>
dependency>

2. 创建工具类

2.1 ExcelUtil.java(设置头背景,浅绿,宋体,对齐方式)

import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.VerticalAlignment;

public class ExcelUtil {
    /**
     * 设置excel样式
     *
     * @return
     */
    public static HorizontalCellStyleStrategy getStyleStrategy() {
        // 头的策略  样式调整
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // 头背景 浅绿
        headWriteCellStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
        WriteFont headWriteFont = new WriteFont();
        // 头字号
        headWriteFont.setFontHeightInPoints((short) 14);
        // 字体样式
        headWriteFont.setFontName("宋体");
        headWriteCellStyle.setWriteFont(headWriteFont);
        // 自动换行
        headWriteCellStyle.setWrapped(true);
        // 设置细边框
        headWriteCellStyle.setBorderBottom(BorderStyle.THIN);
        headWriteCellStyle.setBorderLeft(BorderStyle.THIN);
        headWriteCellStyle.setBorderRight(BorderStyle.THIN);
        headWriteCellStyle.setBorderTop(BorderStyle.THIN);
        // 设置边框颜色 25灰度
        headWriteCellStyle.setBottomBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
        headWriteCellStyle.setTopBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
        headWriteCellStyle.setLeftBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
        headWriteCellStyle.setRightBorderColor(IndexedColors.GREY_25_PERCENT.getIndex());
        // 水平对齐方式
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        // 垂直对齐方式
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 内容的策略 宋体
        WriteCellStyle contentStyle = new WriteCellStyle();
        // 设置垂直居中
        contentStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        // 设置 水平居中
        contentStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
        WriteFont contentWriteFont = new WriteFont();
        // 内容字号
        contentWriteFont.setFontHeightInPoints((short) 12);
        // 字体样式
        contentWriteFont.setFontName("宋体");
        contentStyle.setWriteFont(contentWriteFont);
        // 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
        return new HorizontalCellStyleStrategy(headWriteCellStyle, contentStyle);
    }
}

2.2 CustomCellWriteUtil.java(自适应列宽)

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * excel自适应列宽
 */
public class CustomCellWriteUtil extends AbstractColumnWidthStyleStrategy {
    private static final int MAX_COLUMN_WIDTH = 255;
    private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap(8);

    public CustomCellWriteUtil() {
    }

    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<CellData> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
        if (needSetWidth) {
            Map<Integer, Integer> maxColumnWidthMap = (Map) CACHE.get(writeSheetHolder.getSheetNo());
            if (maxColumnWidthMap == null) {
                maxColumnWidthMap = new HashMap(16);
                CACHE.put(writeSheetHolder.getSheetNo(), maxColumnWidthMap);
            }

            Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
            if (columnWidth >= 0) {
                if (columnWidth > 255) {
                    columnWidth = 255;
                }
                Integer maxColumnWidth = (Integer) ((Map) maxColumnWidthMap).get(cell.getColumnIndex());
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    ((Map) maxColumnWidthMap).put(cell.getColumnIndex(), columnWidth);
                    writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
                }
            }
        }
    }

    private Integer dataLength(List<CellData> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData cellData = (CellData) cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        return cellData.getStringValue().getBytes().length;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }
}

2.3 DropDownSetField.java(自定义标记导出excel的下拉框注解)

import java.lang.annotation.*;

/**
 * 注解:自定义标记导出excel的下拉数据集
 */
@Documented
// 作用在字段上
@Target(ElementType.FIELD)
// 运行时有效
@Retention(RetentionPolicy.RUNTIME)
public @interface DropDownSetField {
    // 固定下拉内容
    String[] source() default {};

    // 注解内的名称,解析时要注意对应
    String name() default "";
}

2.4 ResolveDropAnnotationUtil.java

import java.util.Map;
import java.util.Optional;

/**
 * 处理导入excel下拉框注解的工具类
 */
public class ResolveDropAnnotationUtil {

    public static String[] resove(DropDownSetField dropDownSetField, String[] strings) {
        if (!Optional.ofNullable(dropDownSetField).isPresent()) {
            return null;
        }
 
        // 获取固定下拉信息
        String[] source = dropDownSetField.source();
        if (null != source && source.length > 0) {
            return source;
        }
 
        if (null != strings && strings.length > 0) {
            try {
                String[] dynamicSource = strings;
                if (null != dynamicSource && dynamicSource.length > 0) {
                    return dynamicSource;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return null;
    }
    
 
    //插入到map中
    private void insertMap(Map<Integer, String[]> map, String[] params, DropDownSetField dropDownSetField, int i) {
        String[] sources = ResolveDropAnnotationUtil.resove(dropDownSetField, params);
        if (null != sources && sources.length > 0) {
            map.put(i, sources);
        }
    }
}

3. 具体用法

TestController.java

package com.example.demo;

public class TestController {

    /**
     * 文件上传
     * 

* 1. 创建excel对应的实体对象 *

* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器 *

* 3. 直接读即可 */ @RequestMapping("/upload") @ResponseBody public ResultData upload(MultipartFile file) { try { // XxxSaveDataListener 导入监听器 在下面 EasyExcel.read(file.getInputStream(), XxxImport.class, new XxxSaveDataListener(XxxService)).sheet().doRead(); return ResultData.ok("上传成功"); } catch (IOException e) { return ResultData.ok(e.getMessage()); } } /** * 导出指标库记录 * * @param response * @param XxxImports 导出模板实体类 */ @PostMapping("/export") @ResponseBody public void exportXxx(HttpServletResponse response, @RequestBody List<XxxImport> xxxImports) { String fileName = "ExportData.xlsx"; OutputStream fileOutputStream = null; List<XxxImport> exportDataList = new ArrayList<>(); try { for (XxxImport xxxImport : xxxImports) { XxxImport xxxImport1 = new XxxImport(); xxxImport1.setParentLibraryId(xxxImport.getParentLibraryId()); xxxImport1.setCode(xxxImport.getCode()); xxxImport1.setName(xxxImport.getName()); //转换指标类型; xxxImport1.setTargetTypeId(getByTypeIdReturnName(xxxImport.getTargetTypeId())); xxxImport1.setOrigin(xxxImport.getOrigin()); //转换 定性 定量 Long category = Long.parseLong(xxxImport.getCategory()); xxxImport1.setCategory(category == 0 ? "定性" : "定量"); xxxImport1.setDefinition(xxxImport.getDefinition()); xxxImport1.setStatisticalcaliber(xxxImport.getStatisticalcaliber()); xxxImport1.setStandard(xxxImport.getStandard()); xxxImport1.setStatisticUnit(xxxImport.getStatisticUnit()); xxxImport1.setPrecisions(xxxImport.getPrecisions()); xxxImport1.setRule(xxxImport.getRule()); xxxImport1.setEffectDate(xxxImport.getEffectDate()); xxxImport1.setExpireDate(xxxImport.getExpireDate()); exportDataList.add(xxxImport1); } //文件以流形式返回前端下载 response.setHeader("Content-Disposition", "attachment;filename=" + fileName); response.setContentType("application/x-download"); response.setCharacterEncoding("UTF-8"); response.addHeader("Pargam", "no-cache"); response.addHeader("Cache-Control", "no-cache"); response.flushBuffer(); fileOutputStream = response.getOutputStream(); ExcelWriter excelWriter = EasyExcel.write(fileOutputStream, XxxImport.class) .registerWriteHandler(new CustomCellWriteUtil()) .registerWriteHandler(ExcelUtil.getStyleStrategy()) .build(); WriteSheet sheet = EasyExcel.writerSheet(0, "指标库导出数据").build(); excelWriter.write(exportDataList, sheet); excelWriter.finish(); fileOutputStream.flush(); fileOutputStream.close(); } catch (Exception e) { log.error("导出出错,原因:{}", e); } } @GetMapping("/download/dropDownTemplate") public void downTemplate(HttpServletResponse response) { try { // 获取该类声明的所有字段 Field[] fields = XxxImport.class.getDeclaredFields(); // 响应字段对应的下拉集合 Map<Integer, String[]> map = new HashMap<>(); Field field = null; // 循环判断哪些字段有下拉数据集,并获取 for (int i = 0; i < fields.length; i++) { field = fields[i]; // 解析注解信息 DropDownSetField dropDownSetField = field.getAnnotation(DropDownSetField.class); if (null != dropDownSetField) { String name = dropDownSetField.name(); if (!StringUtils.isEmpty(name)) { // DropDownNameEnum 对应下拉框注解里面的字段名称,要和导出实体类中的name属性对应上 if (name.equals(DropDownNameEnum.TARGET_CATEGORY.getName())) { List<String> list = new ArrayList<>(); list.add("定性"); list.add("定量"); String[] params = list.toArray(new String[list.size()]); insertMap(map, params, dropDownSetField, i); } } else { insertMap(map, null, dropDownSetField, i); } } } //文件以流形式返回前端下载 String fileName = "ImportTemplate.xlsx"; OutputStream fileOutputStream = null; response.setHeader("Content-Disposition", "attachment;filename=" + fileName); response.setContentType("application/x-download"); response.setCharacterEncoding("UTF-8"); response.addHeader("Pargam", "no-cache"); response.addHeader("Cache-Control", "no-cache"); response.flushBuffer(); fileOutputStream = response.getOutputStream(); ExcelWriter excelWriter = EasyExcel.write(fileOutputStream, XxxImport.class) .registerWriteHandler(new XxxImportHandler(map)) .registerWriteHandler(new CustomCellWriteUtil()) .registerWriteHandler(ExcelUtil.getStyleStrategy()).build(); WriteSheet sheet = EasyExcel.writerSheet(0, "指标库导入模板").build(); excelWriter.write(null, sheet); excelWriter.finish(); fileOutputStream.flush(); fileOutputStream.close(); } catch (Exception e) { log.info("下载指标库导入模板出错,原因:{}", e); } } /** * excel导入下拉框 数据放入map集合 * * @param map * @param params * @param dropDownSetField * @param i */ private void insertMap(Map<Integer, String[]> map, String[] params, DropDownSetField dropDownSetField, int i) { String[] sources = ResolveDropAnnotationUtil.resove(dropDownSetField, params); if (null != sources && sources.length > 0) { map.put(i, sources); } } }

4. 创建下拉框枚举类,导入导出实体类及监听器

4.1 下拉框枚举类
DropDownNameEnum .java

/**
 * excel模板下拉框名称枚举
 */
public enum DropDownNameEnum {
    TARGET_CATEGORY("Drop001","类别"),
    PERIOD_SET("Drop002", "周期");
    /**
     * 指标类型id
     */
    private String code;
    /**
     * 指标类型名称
     */
    private String name;

    private DropDownNameEnum(String code, String name){
        this.code = code;
        this.name = name;
    }

    public String getCode() {
        return code;
    }

    public String getName() {
        return name;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public void setName(String name) {
        this.name = name;
    }
}

4.2 导入实体类

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.gaiaworks.cn.opm.biz.util.excel.DropDownSetField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class XxxImport {

    /**
     * 父指标
     */
    @ExcelProperty(value = "父指标")
    private String parentLibraryId;

    /**
     * 代码
     */
    @ExcelProperty(value = "代码")
    private String code;

    /**
     * 名称
     */
    @ExcelProperty(value = "名称")
    private String name;

    /**
     * 类型
     */
    @ExcelProperty(value = "类型")
    private String targetTypeId;

    /**
     * 来源
     */
    @ExcelProperty(value = "来源")
    private String origin;

    /**
     * 0:定性,1:定量
     * 这里的name  要和上面下拉框枚举类的名称对应起来
     */
    @ExcelProperty(value = "类别")
    @DropDownSetField(name = "类别")
    private String category;
    /**
     * 定义
     */
    @ExcelProperty(value = "定义")
    private String definition;

    /**
     * 口径
     */
    @ExcelProperty(value = "口径")
    private String statisticalcaliber;

    /**
     * 评分标准
     */
    @ExcelProperty(value = "评分标准")
    private String standard;


    @ExcelProperty(value = "统计单位")
    private String statisticUnit;

    @ExcelProperty(value = "精度")
    private String precisions;

    @ExcelProperty(value = "计算规则")
    private String rule;

    /**
     * 生效日期(这里加index是用在导入模板的日期校验)
     */
    @ExcelProperty(value = "有效开始日期",index = 12)
    private String effectDate;

    /**
     * 失效日期(这里加index是用在导入模板的日期校验)
     */
    @ExcelProperty(value = "有效结束日期",index = 13)
    private String expireDate;

}

4.3 导入模板的读取类及实现批量入库监听器
XxxSaveDataListener.java

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * 导入模板的读取类及实现批量入库
 */
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class XxxSaveDataListener extends AnalysisEventListener<XxxImport> {

    /**
     * 每隔30条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 30;
    List<XxxImport> list = new ArrayList<XxxImport>();
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private XxxService xxxService;

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     */
    public XxxSaveDataListener(XxxService xxxService) {
        this.xxxService = xxxService;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(XxxImport data, AnalysisContext context) {
        list.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            list.clear();
        }
    }

    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("解析失败,但是继续解析下一行:" + exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            log.info("第" + excelDataConvertException.getRowIndex() + "行,第" + excelDataConvertException.getColumnIndex() + "列解析异常");
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 批量入库
     */
    @Transactional
    public void saveData() {
        // service->mapper->batchInsert(list) 实现批量入库
    }
}

4.4 下拉框校验及日期校验
XxxImportHandler.java

import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.springframework.stereotype.Component;

import java.util.Map;

@Component
public class XxxImportHandler implements SheetWriteHandler {

    // 这里的map对应步骤3中的insertMap插入的下拉框自定义数据集合
    private Map<Integer, String[]> map = null;

    public XxxImportHandler(Map<Integer, String[]> map) {
        this.map = map;
    }

    @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();
        // k 为存在下拉数据集的单元格下表 v为下拉数据集
        map.forEach((k, v) -> {
            // 下拉列表约束数据
            DataValidationConstraint constraint = helper.createExplicitListConstraint(v);
            // 设置下拉单元格的首行 末行 首列 末列
            CellRangeAddressList rangeList = new CellRangeAddressList(1, 65536, k, k);
            // 设置约束
            DataValidation validation = helper.createValidation(constraint, rangeList);
            // 阻止输入非下拉选项的值
            validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
            validation.setShowErrorBox(true);
            validation.setSuppressDropDownArrow(true);
            validation.createErrorBox("提示", "请选择下拉框内的数据");
            sheet.addValidationData(validation);
        });
        //设置验证生效的范围(excel起始行,结束行,起始列,结束列)
        CellRangeAddressList addressList = new CellRangeAddressList(1, 65536, 12, 13);
        //设置验证方式(Date(1990, 1, 1)是excel的日期函数,能成功解析,写成"1990-01-01"解析失败)
        //需要其他日期格式,修改第四个参数"yyyy-MM-dd",eg:"yyyy-MM-dd HH:mm:ss"
        DataValidationConstraint constraint = helper.createDateConstraint(DataValidationConstraint.OperatorType.BETWEEN, "Date(1990, 1, 1)", "Date(9999, 12, 31)", "yyyy-MM-dd");
        //创建验证对象
        DataValidation dataValidation = helper.createValidation(constraint, addressList);
        //错误提示信息
        dataValidation.createErrorBox("提示", "请输入[yyyy-MM-dd]格式日期,范围:[1990-01-01,9999-12-31]");
        dataValidation.setShowErrorBox(true);
        //验证和工作簿绑定
        sheet.addValidationData(dataValidation);
    }
}

5. 实现效果

下拉框校验,仅能选择下拉框内的数据,不按规则输入效果如下:
easyexcel导入导出包含 自定义列宽,下拉框,日期校验,批量导入入库完整开发流程_第1张图片
日期校验,仅能输入yyyy-MM-dd格式(需要其他格式,在4.4中修改),不按规则输入效果如下:
easyexcel导入导出包含 自定义列宽,下拉框,日期校验,批量导入入库完整开发流程_第2张图片

6. 补充校验

如果需要校验其他类型,修改4.4中的helper后的构造方法即可

DataValidationConstraint constraint = helper.createDateConstraint(DataValidationConstraint.OperatorType.BETWEEN, "Date(1990, 1, 1)", "Date(9999, 12, 31)", "yyyy-MM-dd");
package org.apache.poi.ss.usermodel;

import org.apache.poi.ss.util.CellRangeAddressList;

/**
 * @author Radhakrishnan J
 * 
 */
public interface DataValidationHelper {
	// 创建公式列表约束
	DataValidationConstraint createFormulaListConstraint(String listFormula);
    // 创建显式列表约束
	DataValidationConstraint createExplicitListConstraint(String[] listOfValues);
    // 创建数字约束
	DataValidationConstraint createNumericConstraint(int validationType,int operatorType, String formula1, String formula2);
	// 创建文本长度约束
	DataValidationConstraint createTextLengthConstraint(int operatorType, String formula1, String formula2);
	// 创建小数约束
	DataValidationConstraint createDecimalConstraint(int operatorType, String formula1, String formula2);
	// 创建整数约束
	DataValidationConstraint createIntegerConstraint(int operatorType, String formula1, String formula2);
	// 创建日期约束
	DataValidationConstraint createDateConstraint(int operatorType, String formula1, String formula2,String dateFormat);
	// 创建时间约束
	DataValidationConstraint createTimeConstraint(int operatorType, String formula1, String formula2);
	// 创建自定义约束
	DataValidationConstraint createCustomConstraint(String formula);
	// 创建验证
	DataValidation createValidation(DataValidationConstraint constraint,CellRangeAddressList cellRangeAddressList);
}

你可能感兴趣的:(后端,java,easyexcel)