通用Excel数据导入-不限导入的列顺序
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中,进行映射。
下图为对应类
最后觉得博主分享的有用可以关注下,有什么实际运用上问题也可以留言
