通用Excel数据导入-不限导入的列顺序

此为一个自定义excel导入方法,基于apache.poi

详细的全码,可以直接在对应程序运行,涉及到的类都有详细说明,替换即可。

其中涉及到的主要依赖有apache.poi,也有用hutool.core。不知道各位具体项目引入情况,所以就不把所以依赖写出来。hutool也一半用什么引入哪些,直接hutool-all可能也会和apche的有冲突。

先上代码

import cn.hutool.core.bean.DynaBean;
import cn.hutool.core.util.StrUtil;
//此处为公司自定义异常已做处理--可以自行抛出RuntimeException
import com.exception.RRException;
//此处为公司一个文件处理工具类,代码上有说明
import com.FileUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.poi.hssf.usermodel.HSSFDateUtil;
import org.apache.poi.ss.usermodel.*;
import org.springframework.web.multipart.MultipartFile;

import java.text.DecimalFormat;
import java.util.*;

/**
 * excel导入数据读取执行类
 */

public class ExecuteAuto {

    private static final List<String> TRUE_VALUES = Arrays.asList("1", "是", "正确", "对", "OK", "");

    private final MultipartFile file;

    private final List<ExcelConfig> header;

    private final Integer startRow;

    private final Integer startCell;

    private final Class disClazz;

    private final ResultInfo resultInfo;

    private Map<String,ExcelConfig> map;

//此处一个为前端传过来的一个excel文件,用MultipartFile接收,这为一个文件工具类一般都用它接收前段传入文件,ImportConfig为下文中一个工具类,用作数据映射关系处里。
    public ExecuteAuto(MultipartFile file, ImportConfig importConfig) {
        this.file = file;
        this.header = importConfig.getHeader();
        this.startRow = importConfig.getStartRow();
        this.disClazz = importConfig.getDisClazz();
        this.resultInfo = new ResultInfo();
        this.startCell = importConfig.getStartCell();
        this.map = new HashMap<>();

    }

    public void init(List<ExcelConfig> header){
        for (ExcelConfig excelConfig : header) {
            map.put(excelConfig.getCnName(),excelConfig);
        }

    }


    /**
     * 读取excel文件执行方法
     * @return ResultInfo保存读取excel中数据
     */
    public ResultInfo execute() {
        init(header);
        //此处FileUtil为公司一方法,用作读取文件后缀为.xls还是xlsx。创建不同的Workbook----可自行抉择或处理,或者直接创建HSSFWorkbook或者XSSFWorkbook
        Workbook workbook = FileUtil.getWorkBook(file);
        int sheetNum = workbook.getNumberOfSheets();
        List<Object> list = new LinkedList<>();

        Map<String,String> errorMap = new HashMap<>();
        Integer successNums = 0;
        Integer errorNums = 0;

        for (int i = 0; i < sheetNum; i++) {
            //获取页数据
            Sheet sheet = workbook.getSheetAt(i);
            //获取当前页的行数
            int rowNum = sheet.getPhysicalNumberOfRows();
            if (rowNum < startRow){
                //此处为公司自定义异常,可自行抛出RuntimeException异常
                throw new RRException("没有数据表中");
            }
            Row fistHead = sheet.getRow(startRow - 2);
            loop1:for (int y = startRow - 1; y < rowNum; y++) {
                StringBuilder stringBuilder = new StringBuilder();
                //获取当前行数据
                Row row = sheet.getRow(y);
                //列数
                int cellNum = row.getPhysicalNumberOfCells();
                //创造个DynaBean来存一行的数据
                Object o = null;
                try {
                    o = disClazz.newInstance();
                } catch (Exception var) {
                    throw new RRException("传入Class进行newInstance出错"+var);
                }
                DynaBean dynaBean = DynaBean.create(o);
                for (int x = startCell - 1; x < cellNum; x++) {
                    //记录当前行,应为y为下表从0开始,所以加1
                    int rowNow = y+1;
                    Cell cell = row.getCell(x);
                    //当前列的列明
                    Cell headCell = fistHead.getCell(x);
                    headCell.setCellType(CellType.STRING);
                    String columnName = headCell.getStringCellValue();
                    ExcelConfig h = map.get(columnName);
                    if (h == null){
                        throw new RRException(columnName+":列名不存在");
                    }
                    Object value = null;
                    //是否有类型转换-数据处理
                    if (h.getFormat() == null){
                        try {
                            //进行数据类型匹配赋值
                            value = readValue(cell,h);
                            if (!h.isMustInput() || value != null && !StrUtil.isBlank(value + "")) {
                                dynaBean.set(h.getName(), value);
                            } else {
                                stringBuilder.append(h.getCnName()).append("为必填,但未填写");
                                errorMap.put("第"+rowNow+"行",stringBuilder.toString());
                                errorNums++;
                                continue loop1;
                            }
                        }catch (Exception var) {
                            stringBuilder.append(String.format("数据读取失败:第%d行 [%s]列", row.getRowNum(), h.getCnName())).append(var);
                            errorMap.put("第"+rowNow+"行",stringBuilder.toString());
                            errorNums++;
                            continue loop1;
                        }
                    }else {

                        try {
                            if (cell != null) {
                                value = readValue(cell,h);
                            }
                        }catch (Exception var){
                            stringBuilder.append(String.format("数据读取失败:第%d行 [%s]列", row.getRowNum(), h.getCnName())).append(var);
                            errorMap.put("第"+rowNow+"行",stringBuilder.toString());
                            errorNums++;
                            continue loop1;
                        }
                        try {
                        //这为记录的数据处理,为ExcelConfig中一个属性
                            value = h.getFormat().format(h, value, o);
                            if (h.isMustInput() && (value == null || StrUtil.isBlank(value + ""))) {
                                stringBuilder.append(h.getCnName()).append("为必填,但未填写。");
                                errorMap.put("第"+rowNow+"行",stringBuilder.toString());
                                errorNums++;
                                continue loop1;
                            } else if (value != null) {

                                if (value.getClass() == h.getType()) {
                                    dynaBean.set(h.getName(), value);
                                } else {
                                    stringBuilder.append(h.getCnName()).append("数据类型不匹配--").append(value);
                                    errorMap.put("第"+rowNow+"行",stringBuilder.toString());
                                    errorNums++;
                                    continue loop1;
                                }
                            }
                        }catch (Exception e){
                            stringBuilder.append(String.format("数据读取失败:第%d行 [%s]列", row.getRowNum(), h.getName())).append(e);
                            errorMap.put("第"+rowNow+"行",stringBuilder.toString());
                            errorNums++;
                            continue loop1;
                        }
                    }
                }
                list.add(o);
                successNums++;
            }
        }
        resultInfo.setErrorMap(errorMap);
        resultInfo.setSuccessRowNum(successNums);
        resultInfo.setErrorRowNum(errorNums);
        resultInfo.setJsonString(JSONObject.toJSONString(list));
        return resultInfo;
    }


    /**
     * 读取cell中数据类型,并用ExcelConfig中存的对应实体类型匹配,赋对应类型值
     * @param cell
     * @param header;excel配置类
     * @return
     */
    private Object readValue(Cell cell, ExcelConfig header) {
        if (cell == null) {
            return null;
        } else {
            String value;
            if (header.getType() == String.class) {
                if (Objects.equals(CellType.NUMERIC, cell.getCellTypeEnum())) {
                    if (!header.getName().equals("mobile") && !header.getName().equals("username")) {
                        return (cell.getNumericCellValue() + "").trim();
                    } else {
                        DecimalFormat df = new DecimalFormat("0");
                        return (df.format(cell.getNumericCellValue()) + "").trim();
                    }
                } else {
                    cell.setCellType(CellType.STRING);
                    value = cell.getStringCellValue();
                    return value == null ? "" : value.trim();
                }
            } else if (header.getType() == Date.class) {
                return Objects.equals(CellType.NUMERIC, cell.getCellTypeEnum()) ? HSSFDateUtil.getJavaDate(cell.getNumericCellValue()) : cell.getDateCellValue();
            } else {
                Double valu;
                if (header.getType() == Integer.class) {
                    if (Objects.equals(CellType.NUMERIC, cell.getCellTypeEnum())) {
                        valu = cell.getNumericCellValue();
                        return valu.intValue();
                    } else {
                        value = cell.getStringCellValue();
                        return StrUtil.isNotBlank(value) ? Integer.parseInt(value) : null;
                    }
                } else if (header.getType() == Long.class) {

                    if (Objects.equals(CellType.NUMERIC, cell.getCellTypeEnum())) {
                        valu = cell.getNumericCellValue();
                        return valu.longValue();
                    } else {
                        value = cell.getStringCellValue();
                        return StrUtil.isNotBlank(value) ? Integer.parseInt(value) : null;
                    }
                } else if (header.getType() == Short.class) {
                    if (Objects.equals(CellType.NUMERIC, cell.getCellTypeEnum())) {
                        valu = cell.getNumericCellValue();
                        return valu.shortValue();
                    } else {
                        value = cell.getStringCellValue();
                        return StrUtil.isNotBlank(value) ? Short.parseShort(value) : null;
                    }
                } else if (header.getType() == Float.class) {
                    if (Objects.equals(CellType.NUMERIC, cell.getCellTypeEnum())) {
                        valu = cell.getNumericCellValue();
                        return valu.floatValue();
                    } else {
                        value = cell.getStringCellValue();
                        return StrUtil.isNotBlank(value) ? Float.parseFloat(value) : null;
                    }
                } else if (header.getType() == Double.class) {
                    if (Objects.equals(CellType.NUMERIC, cell.getCellTypeEnum())) {
                        return cell.getNumericCellValue();
                    } else {
                        value = cell.getStringCellValue();
                        return StrUtil.isNotBlank(value) ? Double.parseDouble(value) : null;
                    }
                } else if (header.getType() == Boolean.class) {
                    value = null;
                    if (Objects.equals(CellType.NUMERIC, cell.getCellTypeEnum())) {
                        value = cell.getNumericCellValue() + "";
                    } else {
                        value = cell.getStringCellValue();
                    }

                    return value != null && TRUE_VALUES.contains(value.trim());
                } else {
                    throw new RRException("不支持的数据类型");
                }
            }
        }
    }


}

这个为上个代码块用到的ImportConfig

import lombok.Data;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Excel导入工具类
 * Auth:向前看GO_ON
 * CreateTime:
 */
@Data
public class ImportConfig {

    private List<ExcelConfig> header;

    private Map<String,ExcelConfig> loginPk;

    private Class disClazz;
    //起始行-默认第二行
    private int startRow = 2;
    //起始列-默认第一列
    private int startCell = 1;

//ExcelConfig为下个代码块涉及到的类
    public ImportConfig(Class distClazz, ExcelConfig... headers) {
        this.disClazz = distClazz;
        if (headers != null && headers.length != 0) {
            Map<String, ExcelConfig> headerMap = new LinkedHashMap();
            this.loginPk = new LinkedHashMap();
            ExcelConfig[] var4 = headers;
            int var5 = headers.length;

            for(int var6 = 0; var6 < var5; ++var6) {
                ExcelConfig header = var4[var6];
//                if (header.isLogicPk()) {
//                    this.loginPk.put(header.getName(), header);
//                }

                if (headerMap.containsKey(header.getName())) {
                    throw new RuntimeException("不允许有重复字段");
                }

                try {
                    Field field = distClazz.getDeclaredField(header.getName());
                    header.setType(field.getType());
                } catch (Exception var9) {
                    throw new RuntimeException(String.format("%s在%s中不存在", header.getName(), distClazz.getSimpleName()));
                }

                headerMap.put(header.getName(), header);
            }

            this.header = Arrays.asList(headers);
        } else {
            throw new RuntimeException("header 必须指定");
        }
    }

}

这个为上个代码块用到的ExcelConfig

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

/**
 * excel导入配置类型
 * Auth:向前看GO_ON
 */

@Data
public class ExcelConfig {

    //字段名
    private String name;
    //对应表中列名
    private String cnName;
    //是否必填
    private boolean mustInput;
    //此类为下个代码块中内容---用作数据处理,比如导入为1我们转为是,或者为,是我们得转为1,或者做数据校验处理。
    @JsonIgnore
    private ExcelColFormat format;
    //字段数据类型
    private Class type;

    public ExcelConfig(String name,String cnName) {
        this.cnName = cnName;
        this.name = name;
        this.mustInput = false;
    }

    public ExcelConfig(String name,String cnName,boolean mustInput) {
        this.cnName = cnName;
        this.name = name;
        this.mustInput = mustInput;
    }

    public ExcelConfig(String name,String cnName,boolean mustInput,ExcelColFormat format) {
        this.cnName = cnName;
        this.name = name;
        this.mustInput = mustInput;
        this.format = format;
    }

}

这里是ExcelColFormat

//为一个接口,后面用直接用匿名写,在ExcelConfig中使用
public interface ExcelColFormat<T> {
    Object format(ExcelConfig header, Object value, T row);
}

####整体使用为上述几个类。下面为演示

//这只是serviceImp中一个方法,没有截取完整的类,loginUser为登录人信息不用管
    @Override
    public Result consumerImport(MultipartFile file, LoginUser loginUser) {
		//创建个上面写的ImportConfig,构造方法中传入指定的接收类类型,然后就是ExcelConfig数组,ClientConsumer为数据接收类
        ImportConfig importConfig = new ImportConfig(ClientConsumer.class,
        //创建一个ExcelConfig类,并传入对应接收的类中字段名,其次是对应excel表中列名
                new ExcelConfig("receipts","单据编号"),
                //此处的第三个参数为是否必填,也是在ExcelConfig几个构造中一个
                new ExcelConfig("clientId","客户ID",true),
                //
                new ExcelConfig("purchaserName","购方名称",true,((header, value, row) -> {
                //此处的value为object对象,所以加“”转为String,可以自行转换
                    String s = value+"";
                    //此处进行数据处理,可以看是否合规传入的数据,一些判断之类
                    //返回对应类型,如果做了数据转换,返回转化后类型即可
                    return s;
                })))
                );
        ExecuteAuto executeConfig = new ExecuteAuto(file,importConfig);
        //ResultInfo此类为用来接收返回结果的。下面给出
        ResultInfo resultInfo = executeConfig.execute();
        //取出对应json数据
        String jsonObject = resultInfo.getJsonString();
        //类型转换为自己需要的类---此处ClientConsumer为自己的一个提取数据类。及上文传入的
        List<ClientConsumer> list = JSONObject.parseArray(jsonObject,ClientConsumer.class);
        //获取到想要的接收对象后,在对数据进行相应处理存储即可
       return null;
    }

这里是接收处理结果的对象

import lombok.Data;

import java.util.Map;

/**
 *用于excel数据读取后信息存储;
 * Auth:向前看GO_ON
 */
@Data
public class ResultInfo {

    //错误信息记录
    private Map<String,String> errorMap;
    //获取成功的集合转为jsonString数据
    private String jsonString;
    //成功条数
    private Integer successRowNum;
    //失败条数
    private Integer errorRowNum;

}

整体的使用代码就为上面这些,然后上面的这类都放在一个工具类下的我是,个人可以根据情况创建。这是根据表头名去自信匹配数据信息。表头名可以少于ExcelConfig数组中对应关系,但不能多。其中表头对应信息可以看出其实也是可以动态设置的额,只用将表头模板存入数据库,就可根据不同模板,在数据库中查询对应模板的表头,然后进行赋值放入ExcelConfig的cnName中,进行映射。
下图为对应类
在这里插入图片描述

最后觉得博主分享的有用可以关注下,有什么实际运用上问题也可以留言