问题背景:
在做一些excel导入需求时,虽然会有一些导入模版,但你永远不知道用户是怎么用的,真正上传的是什么玩意。
可能存在的问题:
1.上传的文件后缀名称不是预期的
2.传了个空文件,表中内容为空
3.表头的内容或者顺序不是预期的
有这样类似的问题时,需要我们在程序里做提示,做拦截,避免不规范操作检验不严格而损坏了正常数据
应对方式:
在这里我们使用easyexcel来进行excel数据的导入,具体使用配置如下:
1.引入依赖
<!--easyexcel-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>
2.定义实体对象
@Data
public class RelatedPartyVO {
/**
* 主键id
*/
private Long id;
@ExcelProperty(value = "名称", index = 0) //定义第一列的表头名称
private String name;
@ExcelProperty(value = "曾用名", index = 1) //第二列
private String hisName;
@ExcelProperty(value = "类别", index = 2)
private String relatedType;
}
3.定义监听器
@Slf4j
public class RelatedPartyListener extends AnalysisEventListener<RelatedPartyVO> {
/**
* 每批读取的数据量
*/
private static final int BATCH_COUNT = 1000;
/**
* 读取到的数据列表
*/
List<RelatedPartyVO> list = new ArrayList<>();
/**
* 读取数据过程中的异常信息map
*/
public Map<String, String> map = new HashMap<>();
/**
* 读取的文件是否是空的标识 true是空 false不是空
*/
public boolean blankFlag = true;
/**
* 引入要进行具体操作的服务类
*/
private final RelatedPartyService relatedPartyService;
public RelatedPartyListener(RelatedPartyService relatedPartyService) {
this.relatedPartyService = relatedPartyService;
}
/**
* 每读取一行,对数据的具体业务判断,异常提示放入map中
*/
@Override
public void invoke(RelatedPartyVO t, AnalysisContext analysisContext) {
blankFlag = false;
if (StringUtils.isBlank(t.getName())) {
map.put("code", String.valueOf(ApiResponseCode.ERROR_202.getCode()));
map.put("msg", "导入数据表中未填写名称信息");
return;
}
list.add(t);
if (list.size() >= BATCH_COUNT) {
relatedPartyService.saveBatch(list);
list.clear();
}
}
/**
* 读取表头的信息,对表头信息进行校验
*/
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
super.invokeHeadMap(headMap, context);
try {
Map<Integer, String> indexMap = getIndexNameMap(RelatedPartyVO.class);
if (indexMap == null || indexMap.size() < 1) {
throw new ExcelAnalysisException("解析Excel出错,请按模版输入正确的excel");
}
Set<Integer> keySet = indexMap.keySet();
for (Integer key : keySet) {
if (StringUtils.isBlank(headMap.get(key))) {
throw new ExcelAnalysisException("解析Excel出错,请按模版输入正确的excel");
}
if (!headMap.get(key).equals(indexMap.get(key))) {
throw new ExcelAnalysisException("解析Excel出错,请按模版输入正确的excel");
}
}
} catch (NoSuchFieldException e) {
log.error("invokeHeadMap error", e);
}
}
/**
* 分批次解析时,最后以批次解析完成 根据业务需要进行的操作
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
relatedPartyService.saveBatch(list);
log.info("所有数据解析完成,开始移除失效数据");
}
/**
* 获取excel表头的内容,放入map,方便根据实体类对表头内容进行校验
*
* @param clazz 表头名称
* @return 导入对象备注map
* @throws NoSuchFieldException
*/
public Map<Integer, String> getIndexNameMap(Class clazz) throws NoSuchFieldException {
Map<Integer, String> result = new HashMap<>();
Field field;
Field[] fields = clazz.getDeclaredFields();
for (Field item : fields) {
field = clazz.getDeclaredField(item.getName());
field.setAccessible(true);
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
if (excelProperty != null) {
int index = excelProperty.index();
String[] values = excelProperty.value();
StringBuilder value = new StringBuilder();
for (String v : values) {
value.append(v);
}
result.put(index, value.toString());
}
}
return result;
}
}
4.读取excel,对监听器的引用和异常信息的反馈
@Override
public void uploadRelatedParty(File tempFile) {
try {
log.info("uploadRelatedParty 导入excel列表,更新当前库中的关联方状态为失效");
updateAllRelatedStatusTo(StatusEnum.DISABLE.getCode());
RelatedPartyListener partyListener = new RelatedPartyListener(this);
EasyExcel.read(tempFile.getAbsolutePath(), RelatedPartyVO.class, partyListener)
.sheet()
.doRead();
Map<String, String> map = partyListener.map;
if (map.size() != 0) {
throw new RuntimeException(map.get("msg"));
}
if (partyListener.blankFlag) {
throw new RuntimeException("客户端上传的空文件,回滚数据");
}
log.info("写入新数据完成,移除失效数据");
deleteRelatedByStatus(StatusEnum.DISABLE.getCode());
} catch (Exception e) {
log.error("导入excel出错,数据回滚后,删除新导入的标示为有效的数据,再将所有历史数据更新为有效", e);
deleteRelatedByStatus(StatusEnum.ENABLE.getCode());
updateAllRelatedStatusTo(StatusEnum.ENABLE.getCode());
throw new RuntimeException(e);
} finally {
boolean delete = tempFile.delete();
log.info("uploadRelatedParty delete file result is [{}]", delete);
}
}
5.上传excel,校验文件后缀 大小等内容
//判断导入的是否为excel文件
String fileName = file.getOriginalFilename();
if (!(fileName.endsWith(ExcelTypeEnum.XLS.getValue()) || fileName.endsWith(ExcelTypeEnum.XLSX.getValue()))) {
return getErrorJsonDto(ApiResponseCode.ERROR_202, "只接受Excel类型的文件!");
}
if (AppUtil.checkFileSize(file.getSize(), FILE_SIZE, FILE_UNIT)) {
return getErrorJsonDto(ApiResponseCode.ERROR_202, "导入文件大于10M,已超限");
}
//写入临时文件
File tempFile = new File("/tmp/mdm_related_party_info_" + System.currentTimeMillis() + ".xlsx");
file.transferTo(tempFile);
//读取文件
relatedPartyService.uploadRelatedParty(tempFile);
注意点:如果是分布式服务的话,在读取文件前,需要先加上分布式锁
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。