不规则EXCEL导入解析匹配 ### 1、需求:
负责人上传excel规则模板,参与人上传的excel文件要满足这些规则,在满足前提的情况下,可以新增列。
#### 规则类似如下
XQMC | XQDM | XXMC | XXDM | XXXJ | XSLB | ||
苏州 | 01 | 苏州中学 | 0101 | 4 | 1 | ||
南京 | 02 | 扬州中学 | 0102 | 4 | 2 | ||
扬州 | 03 | 南京中学 | 0103 | 4 | |||
镇江中学 | 0104 | 4 | |||||
常州中学 | 0201 | 4 | |||||
无锡中学 | 0401 | 4 |
表格说明:连续的列没有空格隔开的为一组规则,同一行之间不存在多个规则对应关系。如上表格中只有三组规则,分别是XQMC、XQDM;XXMC、XXDM、XXXJ ;XSLB 。实际上传的规则可能不是如上的情况。参与人上传的excel文件中要满足这些规则,例如上传的excel文件中的某行数据是
XQMC | XQDM | XSLB | XXMC | XXXJ | XXDM |
苏州 | 01 | 2 | 镇江中学 | 4 | 0104 |
也是满足要求的。但下面的一行数据是不满足要求的,XQMC 、XQDM对应的规则是不符合规则的
XQMC | XQDM | XSLB | XXMC | XXXJ | XXDM |
南京 | 01 | 2 | 镇江中学 | 4 | 0104 |
2、解析EXCEL的工具类
借助easyexcel解析excel表格数据
<!-- https://mvnrepository.com/artifact/com.alibaba/easyexcel -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.4</version>
</dependency>
3、模块分析
- 负责人上传excel解析数据,并保存不同的规则
- 参与人上传excel解析数据,与规则相匹配,满足提交成功,有一个不满足提示失败
负责人每一次上传的excel模板规则都是不固定的,无法使用easyexcel推荐的建立model接受excel中数据。
如何将这种无法建model的数据保存到数据库中,且同一组规则相互对应。采用的是nomodel模式解析excel数据。easyexcel官方文档 中使用的nomodel模式接受对象使用的是Map<Integer,String>,实际使用的接受对象
用的List<String>
//用这种方式读取时候,监听器分析的数据将从第二行数据开始,究竟第一行数据没有读取到,原因还有待研究。
//第一行无法读取,无法满足业务需求。保存到数据库中的值key ——> value 想对应的 key存放表头,value则存下面不同的值
EasyExcel.read(fileName, new NoModleDataListener()).sheet().doRead();//接受的是Map<Integer,String>
//在处理中采用的是easyexcel过时的方法
ExcelDataListener excelDataListener = new ExcelDataListener();
ExcelReader dataReader = EasyExcelUtils.getDataReader(file, excelDataListener);
//可以从一行数据读取解析,在测试过程中发现 这种方法读取没有上面的一种方式好
//列某两行数据
//南京 02 扬州中学 0102 4 2
// 镇江中学 0104 4
//前面一种方式可以将第二行数据读去的形式和第一行数据形式一样都是
//{{"0": "南京"},{"1":”02},{"2":"扬州中学"},{"3":"0102"},{"4":"4"},{"5":2}}
//{{"0":null},{"1":null},{"2":"镇江中学"},{"3":"0104"},{"4":"4"},{"5":null}}
//下面这种方式读取的效果却是
//["南京","02","扬州中学","0102","4","2"]
//[null,null,"镇江中学","0104","4"]
dataReader.read((new Sheet(1, 0)));//接受的是List<String>
负责人上传规则模板数据解析核心思想
- 1、将每一行非空数据存放到List<List>集合中,解析每一行的数据是一个List
- 2、如果存放的数据集合空,提示空文件不能上传
- 3、如果存放的数据集合只有一条数据,默认是表头数据,解析表头数据,判断是否有重复的编码,提示不能上传只有表头的文件
- 4、如果存放的数据集合数量大于一条,默认第一条数据是表头,剩下的都是需要解析的规则。解析表头也需要判重,没有重复编码,进行下一步解析规则。解析表头数据时,连续非空的表头会存放到一个List集合中,遇到表头某列是空值的时候,当前List集合非空则保存进headList(List<List>存放规则的key,几组规则,几个list),也会根据那一列空的生成一个数组,用来存放第几列有值(数值为1),第几列是空的(数值为0),连续为1的为一组规则,遇到0时,查找下一个1起始的位置
- 5、根据存放的headList循环解析剩下的excel表格中的数据,这里就需要用到之前保存那列有表头的数组了,并结合headList中的List 的长度就能知道该规则是单一规则、双列规则,多列规则,也能知道数据取值的位置(需要注意是越界,使用dataReader.read((new Sheet(1, 0)))方法的问题)。每一次循环结束,都需要计算下一个1开始的位置。单列规则保存是最简单的只需要一个List集合,新增是需要判断是否存在。两列规则保存是用的List<Map<String,String>> 的形式,新增也需要判断是否已存在,越界问题也要注意。多列规则保存用的也是List<Map<String,String>>,只不过第二个String是一个List集合转换成的字符串,通过第一个String是否为空和List的长度与规则表头的长度作比较,就能知道,是否某一列存在空值,相等的时候,判断是否存在保存。
保存到数据库中的形式如下
规则模式 | code | value |
单列规则 | [“XSLB”] | [“1”,“2”] |
两列规则 | [“XQMC”,“XQDM”] | [{“苏州”:“01”},{“南京”:“02”},{“扬州”:“03”}] |
多列规则 | [“XXMC”,“XXDM”,“XXXJ”] | [{“苏州中学”:[“0101”,“4”]},{“扬州中学”:[“0102”,“4”]},{“南京中学”:[“0103”,“4”]},{“镇江中学”:[“0104”,“4”]},{“常州中学”:[“0201”,“4”]},{“无锡中学”:[“0401”,“4”]}} |
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* <简述>
* <详细描述>
* @author 洛必达法则不会
* @version
* @description
* @see
* @since
*/
public class LeaderExcelDataListener extends AnalysisEventListener<List<String>> {
/**
* @Fields LOGGER : 日志
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ExcelDataListener.class);
/**
* @Fields excelList : List<List<String>> 数据存放集合
*/
private List<List<String>> excelList = new ArrayList<>();
/**
* @Fields errorInfos : List<String> 错误提示信息
*/
private List<String> errorInfos = new ArrayList<>();
/**
* @Fields headList : List<Map<String, Object>> 头标题集合
*/
private List<List<String>> headList = new ArrayList<>();
/**
* @Fields ruleList : List<Map<String, String>> 保存规则的集合
*/
private List<Map<String, String>> ruleList = new ArrayList<>();
/**
* @Fields status : Integer[] 记录表头第几列有值 1:有值 0:无值
*/
private int[] status = new int[NumConstant.I1];
/**
* 构造方法
*/
public LeaderExcelDataListener() {
super();
}
/**
* <简述> 每行数据读取处理
* <详细描述>
* @author 洛必达法则不会
* @param stringList List<String>
* @param analysisContext AnalysisContext
*/
@Override
public void invoke(List<String> stringList, AnalysisContext analysisContext) {
if (stringList.isEmpty()) {
return;
} else {
excelList.add(stringList);
}
}
/**
* <简述> excel读取后解析数据
* <详细描述>
* @author 洛必达法则不会
* @param analysisContext AnalysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
if (excelList.isEmpty()) {
errorInfos.add("上传的EXCEL文件不能为空<br/>");
} else {
if (excelList.size() <= 1) {
errorInfos.add("上传的EXCEL文件规则不能为空<br/>");
} else {
//对上传的excel数据处理转换 第一行默认是编码,剩余的为对应的规则
List<String> heads = excelList.get(0);
System.out.println("表头数据读取 " + JsonUtils.toJson(heads));
status = new int[heads.size()];
//默认都是有值的
Arrays.fill(status, NumConstant.I1);
//hashMap可以实现深拷贝
List<String> ruleHeadList = new ArrayList<>();
//记录标题,用来判重
List<String> duplicateList = new ArrayList<>();
//表头是否合法
boolean flag = true;
for (int i = 0; i < heads.size(); i++) {
String key = heads.get(i);
if (StringUtils.isBlank(key)) {
status[i] = 0;
if (!ruleHeadList.isEmpty()) {
List<String> result = new ArrayList<>();
CollectionUtils.addAll(result, new String[ruleHeadList.size()]);
Collections.copy(result, ruleHeadList);
headList.add(result);
ruleHeadList.clear();
}
} else {
if (duplicateList.contains(key)) {
//判断表头是否重复
flag = false;
errorInfos.add("第 " + (i + 1) + " 列表头 " + key + " 重复<br/>");
} else {
ruleHeadList.add(key);
duplicateList.add(key);
}
}
if (i == heads.size() - 1 && !ruleHeadList.isEmpty()) {
List<String> result = new ArrayList<>();
CollectionUtils.addAll(result, new String[ruleHeadList.size()]);
Collections.copy(result, ruleHeadList);
headList.add(result);
ruleHeadList.clear();
}
}
if (flag) {
//表头合法解析下面的行数据 生成规则数据
analysisRuleValue();
}
}
}
}
/**
* <简述> 解析表头下面的数据生成规则数据
* <详细描述>
* @author 洛必达法则不会
*/
private void analysisRuleValue() {
//状态数组起始位置
int start = 0;
for (int i = 0; i < headList.size(); i++) {
//先获取数组连续为1的起始位置
start = analysisHeadCodeArray(start);
//解析多个规则存储map集合
List<Map<String, Object>> multiRuleValueList = new ArrayList<>();
//多列规则存map
HashMap<String, Object> valueMap = new HashMap<>(NumConstant.I4);
//单列规则存list
List<String> singleRules = new ArrayList<>();
int ruleLength = headList.get(i).size();
for (int j = 1; j < excelList.size(); j++) {
List<String> ruleValueList = excelList.get(j);
if (ruleLength == 1) {
//单列规则的,null即没有规则
if (start < ruleValueList.size()) {
String value = ruleValueList.get(start);
if (StringUtils.isNotBlank(value) && !singleRules.contains(value)) {
singleRules.add(value);
}
}
} else if (ruleLength == 2) {
//两列规则的
String codeKey = "";
String codeValue = "";
if (start < ruleValueList.size()) {
codeKey = ruleValueList.get(start);
}
if (start + 1 < ruleValueList.size()) {
codeValue = ruleValueList.get(start + 1);
}
if (StringUtils.isBlank(codeKey) && StringUtils.isNotBlank(codeValue)) {
errorInfos.add("第 " + (j + 1) + " 行第 " + (start + 1) + " 列不能为空<br/>");
}
if (StringUtils.isBlank(codeValue) && StringUtils.isNotBlank(codeKey)) {
errorInfos.add("第 " + (j + 1) + " 行第 " + (start + 2) + " 列不能为空<br/>");
}
if (StringUtils.isNotBlank(codeKey) && StringUtils.isNotBlank(codeValue)) {
valueMap.put(codeKey, codeValue);
if (multiRuleValueList.contains(valueMap)) {
valueMap.clear();
} else {
Map<String, Object> result = new HashMap<>(valueMap.size());
result.putAll(valueMap);
multiRuleValueList.add(result);
valueMap.clear();
}
}
} else {
//多列规则的
String codeKey = "";
String codeValue;
List<String> codeValueList = new ArrayList<>();
for (int k = start; k < start + ruleLength; k++) {
if (k < ruleValueList.size()) {
if (k == start) {
codeKey = ruleValueList.get(k);
} else if (k == start + ruleLength - 1) {
codeValue = ruleValueList.get(k);
if (StringUtils.isNotBlank(codeValue)) {
codeValueList.add(codeValue);
}
int total = StringUtils.isNotBlank(codeKey) ? (1 + codeValueList.size()) : codeValueList.size();
if (total == ruleLength) {
List<String> valueCodeResult = codeValueList;
valueMap.put(codeKey, valueCodeResult);
if (multiRuleValueList.contains(valueMap)) {
valueMap.clear();
codeValueList.clear();
} else {
Map<String, Object> result = new HashMap<>(valueMap.size());
result.putAll(valueMap);
multiRuleValueList.add(result);
valueMap.clear();
}
} else if (total != 0) {
errorInfos.add("第 " + (j + 1) + " 行第 " + (i + 1) + " 组规则有空值<br/>");
}
} else {
codeValue = ruleValueList.get(k);
if (StringUtils.isNotBlank(codeValue)) {
codeValueList.add(codeValue);
}
}
}
}
}
}
Map<String, String> stringMap = new HashMap<>(NumConstant.I1);
if (ruleLength == 1) {
stringMap.put(JsonUtils.toJson(headList.get(i)), JsonUtils.toJson(singleRules));
} else if (ruleLength > 1) {
stringMap.put(JsonUtils.toJson(headList.get(i)), JsonUtils.toJson(multiRuleValueList));
}
ruleList.add(stringMap);
//增加数组起始位置
start += headList.get(i).size();
}
}
/**
* <简述> 解析数组为1的位置
* <详细描述>
* @author 洛必达法则不会
* @return int 数组为 1 的起始位置
*/
private int analysisHeadCodeArray(int start) {
for (int i = start; i < status.length; i++) {
if (status[i] == 1) {
return i;
}
}
return -1;
}
public List<String> getErrorInfos() {
return errorInfos;
}
public List<Map<String, String>> getRuleList() {
return ruleList;
}
}
参与者上传的excel只需要解析每一行数据与负责人上传的规则是否匹配,无需保存到数据库中
#### 参与者上传excel数据解析核心思想
- 负责人上传的规则取出存放在List<Map<String,String>(第一个String表头编码,第二String相对应的规则)规则集合中
- 解析参者上传的excel文件时,需要判断是否存在空行,第一行是空的,默认没有表头,无法进行下面的规则匹配
- 第一行数据将表头存放在List中,参与人上传的excel表头是否存在重复的编码,存放在List集合时,同时也会在Map<String,Integer> 存放编码和对应的位置,方便下面进行数据匹配时,查找值方便。没有将规则汇表头String类型转换成List集合中并存在一个总的表头编码集合中,对其for循环判断上传的表头编码是否存在相对应的编码,没有则提示缺少某个编码,规则不匹配。
- 满足表头编码时,进行规则匹配也是分三种情况判断,每个判断都需要验证是否越界(dataReader.read((new Sheet(1, 0)))的问题)。也是对保存的规则几个for循环,取出第一个String转换成List集合,根据长度能知道是单列规则,两列规则还是多列规则。单列规则判断最简单,将规则值也转换成List集合,根据编码找到上传文件对应编码所在的位置,每一行数据都取其位置的值,判断规则value集合中是否包含该值,没有则说明不配。两列规则也还好,只是将规则值转换成List<Map<String,String>>的形式,上传文件中对应的值,根据编码相对应的顺序存放在Map中的key,value位置,判断是否存在。多列规则匹配时,也是想转换成List<Map<String,String>的形式,但在测试过程中使用List<Map<String, String>> multiRuleValues = JSONArray.parseObject(map.get(“rowValue”), List.class);转换的值,并不能满足需要,所以有重新处理了multiRuleValues的值,满足List<Map<String,String>>的形式。取数据时,是对保存规则的表头编码长度循环,可以取到上传文件的每一个值,注意数组越界问题,第一个存放在key中,后面的都存放在List中,解析到最后一个的时候,将key,List转换成String 形式存放在Map<String,String>中,判断是否存在。
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 版权:
* <简述> 参与者上传excel监听器
* <详细描述>
* @author 洛必达法则不会
* @version $Id$
* @description
* @see
* @since
*/
public class ParticipantExcelDataListener extends AnalysisEventListener<List<String>> {
/**
* @Fields excelList : List<List<String>> 解析的excel数据
*/
private List<List<String>> excelList = new ArrayList<>();
/**
* @Fields ruleList : List<Map<String, String>> 数据库中保存的规则集合
*/
private List<Map<String, String>> ruleList;
/**
* @Fields errorList : errorList 错误信息
*/
private List<String> errorList = new ArrayList<>();
/**
* @Fields headCodeMap : Map<String, Integer> 记录编码对应的位置
*/
private Map<String, Integer> headCodeMap = new HashMap<>(NumConstant.I16);
/**
* @Fields firstRowIsNull : boolean 第一行是否为空
*/
private boolean firstRowIsNull = true;
/**
* 有参构造方法
* @param ruleList List<Map<String, String>>
*/
public ParticipantExcelDataListener(List<Map<String, String>> ruleList) {
this.ruleList = ruleList;
}
/**
* <简述> 解析每一行数据
* <详细描述>
* @author 洛必达法则不会
* @param rowList List<String> 行数据
* @param analysisContext AnalysisContext
*/
@Override
public void invoke(List<String> rowList, AnalysisContext analysisContext) {
Integer currentRowNum = analysisContext.getCurrentRowNum();
if (rowList.isEmpty()) {
if (currentRowNum == 0) {
firstRowIsNull = false;
}
errorList.add("第 " + (currentRowNum + 1) + " 行数据不能为空 <br/>");
} else {
excelList.add(rowList);
}
}
/**
* <简述> 数据读取完后对数据的处理
* <详细描述>
* @author 洛必达法则不会
* @param analysisContext AnalysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//首行有值时,读取的数据没有空值时 进行下面的数据匹配
if (firstRowIsNull && errorList.isEmpty()) {
int size = excelList.size();
if (size == 0) {
errorList.add("上传的EXCEL表格不能为空<br />");
} else if (size == NumConstant.I1) {
//解析表头数据
List<String> headCodeList = excelList.get(0);
getRuleHeadList(headCodeList);
errorList.add("上传的EXCEL表格只有表头数据<br />");
} else {
List<String> headCodeList = excelList.get(0);
if (getRuleHeadList(headCodeList)) {
//表头符合,对数据进行解析
analysisUploadData();
}
}
}
}
/**
* <简述> 解析数据库保存的表头数据
* <详细描述>
* @author 洛必达法则不会
* @param headCodeList List<String> 读取的excel表格表头数据
* @return List<String>
*/
private boolean getRuleHeadList(List<String> headCodeList) {
//表头自身判重
boolean flag = true;
String key;
for (int i = 0; i < headCodeList.size(); i++) {
key = headCodeList.get(i);
if (headCodeMap.containsKey(key)) {
flag = false;
errorList.add("EXCEL表格中表头 " + key + " 重复<br/>");
} else {
headCodeMap.put(key, i);
}
}
//上传的规则表头,String转换存储到List集合中
List<String> result = new ArrayList<>();
for (Map<String, String> map : ruleList) {
result.addAll(JsonUtils.asList(map.get("headCode"), String.class));
}
if (result.isEmpty()) {
return false;
}
for (String head : result) {
if (!headCodeList.contains(head)) {
flag = false;
errorList.add("上传的EXCEL表格表头不包含" + head + " <br/>");
}
}
return flag;
}
/**
* <简述> 数据与上传的规则匹配
* <详细描述>
* @author 洛必达法则不会
*/
private void analysisUploadData() {
//行数据
List<String> rowValueList;
//规则模板中的key集合
List<String> keyList;
//查找出的map中的key值
for (Map<String, String> map : ruleList) {
keyList = JsonUtils.asList(map.get("headCode"), String.class);
if (keyList.size() == NumConstant.I1) {
//数据中keyList对应的value值
List<String> singleValueList = JsonUtils.asList(map.get("rowValue"), String.class);
//在上传的EXCEL表格中所在的列
int index = headCodeMap.get(keyList.get(0));
//单列规则
for (int row = 1; row < excelList.size(); row++) {
rowValueList = excelList.get(row);
if (index >= rowValueList.size()) {
errorList.add("第" + (row + 1) + " 行,表头 " + keyList.get(0) + " 所在的列,值为空<br/>");
} else {
if (!singleValueList.contains(rowValueList.get(index))) {
errorList.add("第" + (row + 1) + " 行,表头 " + keyList.get(0) + " 所在的列,值不匹配<br/>");
}
}
}
} else if (keyList.size() == NumConstant.I2) {
//两列规则
List<Map<String, String>> twoRuleValueList = JSONArray.parseObject(map.get("rowValue"), List.class);
int first = headCodeMap.get(keyList.get(0));
int last = headCodeMap.get(keyList.get(1));
Map<String, String> tempMap = new HashMap<>(NumConstant.I1);
for (int row = 1; row < excelList.size(); row++) {
rowValueList = excelList.get(row);
if (first >= rowValueList.size()) {
errorList.add("第" + (row + 1) + " 行,表头 " + keyList.get(0) + " 所在的列,值为空<br/>");
} else {
if (last < rowValueList.size()) {
tempMap.put(rowValueList.get(first), rowValueList.get(last));
if (!twoRuleValueList.contains(tempMap)) {
tempMap.clear();
errorList.add("第" + (row + 1) + " 行,表头 " + keyList.get(0) + " 和 "
+ keyList.get(1) + " 所在的列,值不匹配<br/>");
} else {
tempMap.clear();
}
}
}
if (last >= rowValueList.size()) {
errorList.add("第" + (row + 1) + " 行,表头 " + keyList.get(1) + " 所在的列,值为空<br/>");
}
}
} else if (keyList.size() > NumConstant.I2) {
//多列规则
List<Map<String, String>> multiRuleValues = JSONArray.parseObject(map.get("rowValue"), List.class);
List<Map<String, String>> multiRuleValueList = new ArrayList<>();
String code;
for (Map<String, String> stringMap : multiRuleValues) {
code = stringMap.keySet().iterator().next();
Map<String, String> temp = new HashMap<>(NumConstant.I1);
temp.put(code, JSON.toJSONString(stringMap.get(code)));
multiRuleValueList.add(temp);
}
Map<String, Object> tempMap = new HashMap<>(keyList.size());
int index;
for (int row = 1; row < excelList.size(); row++) {
rowValueList = excelList.get(row);
String keyCode = "";
List<String> codeValues = new ArrayList<>();
for (int keyIndex = 0; keyIndex < keyList.size(); keyIndex++) {
index = headCodeMap.get(keyList.get(keyIndex));
if (index >= rowValueList.size()) {
errorList.add("第" + (row + 1) + " 行,表头 " + keyList.get(keyIndex) + " 所在的列,值为空<br/>");
} else {
if (keyIndex == 0) {
keyCode = rowValueList.get(index);
} else if (keyIndex == keyList.size() - 1) {
codeValues.add(rowValueList.get(index));
tempMap.put(keyCode, JsonUtils.toJson(codeValues));
if (!multiRuleValueList.contains(tempMap)) {
errorList.add("第" + (row + 1) + " 行,表头 " + getLeyList(keyList) + " 所在的列,值为不匹配<br/>");
codeValues.clear();
tempMap.clear();
} else {
codeValues.clear();
tempMap.clear();
}
} else {
codeValues.add(rowValueList.get(index));
}
}
}
}
}
}
}
/**
* <简述> 返回key中的String字符串
* <详细描述>
* @author 洛必达法则不会
* @param keyList List<String>
* @return String
*/
private String getLeyList(List<String> keyList) {
StringBuffer stringBuffer = new StringBuffer();
for (String key : keyList) {
stringBuffer.append(key + "、");
}
String result = stringBuffer.toString();
return result.length() > 1 ? result.substring(0, result.length() -1) : result;
}
public List<String> getErrorList() {
return errorList;
}
public void setErrorList(List<String> errorList) {
this.errorList = errorList;
}
}