项目场景:
最近做到一个业务需求是,要做一个物品管理页面的excel模板导出,导出的excel信息填充后做导入使用
问题描述:
接着上次做出的’半动态导出‘功能后,开始编写相应的导入功能,一开始思路没有分析好,往传统的模板导入走去了,想着只要字段属性足够就能配合接收各种模板的导入(这个也是以往我做导入功能的局限:往往一种业务的导入功能就得创建相应字段的对象),等我成功接收数据后,发现值是收到了,没有办法取出做判断;后面选择用gridExcel,配合动态生成的模板 实现无实体类的导入功能:
1. 导入jar包
<!-- 无实体类导入-->
<!-- https://mvnrepository.com/artifact/com.github.liuhuagui/gridexcel -->
<dependency>
<groupId>com.github.liuhuagui</groupId>
<artifactId>gridexcel</artifactId>
<version>2.3</version>
</dependency>
2. 导入工具类设计:
public static Workbook getWorkBook(MultipartFile file) {
//创建Workbook工作薄对象,表示整个excel
Workbook workbook = null;
try {
//获取excel文件的io流
InputStream is = file.getInputStream();
workbook = WorkbookFactory.create(is);
} catch (InvalidFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (org.apache.poi.openxml4j.exceptions.InvalidFormatException e) {
e.printStackTrace();
}
return workbook;
}
public static String getCellValue(Cell cell) {
String cellValue = "";
if (cell == null) {
return cellValue;
}
//把数字当成String来读,避免出现1读成1.0的情况
if (cell.getCellType() == Cell.CELL_TYPE_NUMERIC) {
cell.setCellType(Cell.CELL_TYPE_STRING);
}
//判断数据的类型
switch (cell.getCellType()) {
case Cell.CELL_TYPE_NUMERIC: //数字
cellValue = String.valueOf(cell.getNumericCellValue());
break;
case Cell.CELL_TYPE_STRING: //字符串
cellValue = String.valueOf(cell.getStringCellValue());
break;
case Cell.CELL_TYPE_BOOLEAN: //Boolean
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case Cell.CELL_TYPE_FORMULA: //公式
cellValue = String.valueOf(cell.getCellFormula());
break;
case Cell.CELL_TYPE_BLANK: //空值
cellValue = "";
break;
case Cell.CELL_TYPE_ERROR: //故障
cellValue = "非法字符";
break;
default:
cellValue = "未知类型";
break;
}
return cellValue;
}
/**
* 读入excel文件,解析后返回
*
* @param file excel 文件
* @return list中的每个数组是每一行excel,list中的第一条数据是excel的表头,其余之后的是excel中的数据
*/
public static List<String[]> readExcel(MultipartFile file) throws IOException {
//获得Workbook工作薄对象
Workbook workbook = getWorkBook(file);
//创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回
List<String[]> list = new ArrayList<String[]>();
if (workbook != null) {
//只读取第一个sheet页
for (int sheetNum = 0; sheetNum < 1; sheetNum++) {
//获得当前sheet工作表
Sheet sheet = workbook.getSheetAt(sheetNum);
if (sheet == null) {
continue;
}
//获得当前sheet的开始行
int firstRowNum = sheet.getFirstRowNum();
//获得当前sheet的结束行
int lastRowNum = sheet.getLastRowNum();
//循环所有行
for (int rowNum = firstRowNum; rowNum <= lastRowNum; rowNum++) {
//获得当前行
Row row = sheet.getRow(rowNum);
if (row == null) {
continue;
}
//获得当前行的开始列
int firstCellNum = row.getFirstCellNum();
//获得当前行的列数
int lastCellNum;
if (rowNum == 0) {
lastCellNum = row.getPhysicalNumberOfCells();
} else {
lastCellNum = list.get(0).length;
}
String[] cells = new String[lastCellNum]; //需要更改 是数组长度 “row.getPhysicalNumberOfCells()”
//循环当前行
for (int cellNum = firstCellNum; cellNum < lastCellNum; cellNum++) {
//需要更改循环长度 “lastCellNum”
Cell cell = row.getCell(cellNum);
cells[cellNum] = getCellValue(cell);
}
list.add(cells);
}
}
}
return list;
}
由于动态模板的设计,接收的数据因为模板的原因会接收到空值,这里做下数据处理方便后续模板校验
/**
* excel接收数据加工
**/
public static List<List<String>> dealData(List<String[]> data){
//数组转为集合
List<List<String>> datas = new ArrayList<>();
for (String[] arr:data) {
List<String> list1= Arrays.asList(arr);
List<String> arrList = new ArrayList<String>(list1);
datas.add(arrList);
}
//根据列头去空
for(List<String> list : datas){
for (int i = 0; i < list.size(); i++) {
String str = list.get(i).trim();
int length = str.length();
if(str == "" ||str ==null ||length == 0) {
list.remove(i);
i=i-1;
}
}
}
return datas;
}
/**
* 对比集合是否相同(无重复项)
**/
public static boolean isEquals(List<String> list1, List<String> list2){
if (list1 == null && list2 == null) {
return true;
}else if(list1 == null || list2 == null) {
return false;
}else if(list1.size() != list2.size()) {
return false;
}
Set<String> set1 = new TreeSet<>(list1);
Set<String> set2 = new TreeSet<>(list2);
return set1.equals(set2);
}
3. Controller层设计:
/**
* 文件导入(无实体类导入)
**/
@PostMapping("/office/goods/import")
@ResponseBody
public ResponseResult upload(@RequestParam("file") MultipartFile multipartFile,String orgCode,String typeCode, String personCode,String flag) throws Exception {
try{
if (multipartFile != null && multipartFile.getSize() > 0) {
//返回的第一条数据是表头信息
//data 解析出来的excel数据, list第一条数据是表头数据,第二条数据开始为 表头下的内容
List<String[]> data = GhPOIUtils.readExcel(multipartFile);
//过滤空数数据(数组转集合,循环去空)
List<List<String>> datas = GhPOIUtils.dealData(data);
//校验模板
//step1 获取表列名
OfficeTypeContentVo officeTypeContent = new OfficeTypeContentVo();
officeTypeContent.setTypeCode(typeCode);
List<OfficeTypeContentVo> moderList = officeTypeService.getTypeContent(officeTypeContent);
//step2 模板格式对比
if(datas.get(0).size()-1== moderList.size()){
List<String>titleList = datas.get(0);
List<String>tempList = new ArrayList<>();
for(OfficeTypeContentVo vo:moderList){
tempList.add(vo.getColumnName());
}
titleList.remove(0);
Boolean res =GhPOIUtils.isEquals(titleList,tempList);
if(!res){
log.error("模板错误:导入列名不匹配!");
return WebUtil.success("模板错误:导入列名不匹配!!");
}
}else{
log.error("模板错误:导入列数不匹配!");
return WebUtil.success("模板错误:导入列数不匹配!");
}
//信息对象生成
List<OfficeGoodsVo>addList = new ArrayList<>();
//...... //数据导入
boolean addresult = true;
for(OfficeGoodsVo vo: addList){
addresult = officeGoodsService.addGoods(vo);
if(!addresult){
throw new BusinessException("文件导入失败!");
}
}
return WebUtil.success("文件导入成功!");
}
return WebUtil.success("空文件!");
} catch (Exception e) {
return WebUtil.success("文件导入操作异常!");
}
}
参考资料:
总结:
这次的动态导入导出功能的实现,过程中也走了不少弯路,但也让我进一步了解了各式excel工具类以及他们的差异,后续的代码优化想到也会慢慢更上。加油,每天多跨出一步!