笔记:使用EasyExcel导入csv文件出现编码问题,导致导入数据全为null的解决方法
笔记:使用EasyExcel导入csv文件出现编码问题,导致导入数据全为null的解决方法
通常情况下,我们使用excel导入,但是部分情况下或者领导要求,我们需要使用csv导入文件,但是csv文件模板下载之后会变成系统当前编码,而不是固定为UTF-8,如果用excel的方式打开,写入数据之后,会提示你另存为xlsx,这也说明了csv和xlsx本质是完全不同的文件。
如果这种时候你使用EasyExcel默认的编码读取csv文件,你会发现读取的数据全部都是null,因为通常情况下,windows系统如果不特意设置编码格式,默认都为GBK,所以会导致导入数据无法读取的情况,当然excel并不会出现这种问题,所以如果不是特殊需求,导入还是推荐使用Excel文件。
这里分享一个导入方法,通过人为干预导入数据的方式,去判断是否读取成功,这里只用中国常用的UTF8和GBK作为读取的编码,通常情况下是够用的,如果有需要,可以在charsets集合自行增加其他编码。
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.hexin.fms.common.excel.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.springframework.web.multipart.MultipartFile;import java.io.InputStream;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;public class ExcelUtil {/*** 多编码转换导入数据* 目前使用常用编码utf8和gbk解析文件* 如使用csv导入出现编码问题的时候* @param file 导入文件* @param clazz 类对象* @param excelTypeEnum 导入文件类型* @return 文件数据*/public static <T> ExcelResult<T> importExcelByAnyCharset(MultipartFile file, Class<T> clazz, ExcelTypeEnum excelTypeEnum) throws Exception {DefaultExcelListener<T> listener = new DefaultExcelListener<>(false);return importExcelByAnyCharset(file, clazz, excelTypeEnum, listener);}/*** 多编码转换导入数据* 目前使用常用编码utf8和gbk解析文件* 如使用csv导入出现编码问题的时候* @param file 导入文件* @param clazz 类对象* @param excelTypeEnum 导入文件类型* @param listener 监听器* @return 文件数据*/public static <T> ExcelResult<T> importExcelByAnyCharset(MultipartFile file, Class<T> clazz, ExcelTypeEnum excelTypeEnum,ExcelListener<T> listener) throws Exception {//需要校验的全部编码List<Charset> charsets = new ArrayList<>();charsets.add(StandardCharsets.UTF_8);charsets.add(Charset.forName("GBK"));//开始解析文件ExcelResult<T> result = new DefaultExcelResult<>();for(Charset charset : charsets){result = importExcelByCharset(file.getInputStream(), clazz, excelTypeEnum, listener, charset);//校验实体数据是否全部为nullif(result != null && result.getList() != null){List<T> resultList = result.getList();int size = resultList.size();int nullNums = 0;for(T t : resultList){if(t == null){nullNums++;continue;}Class<?> tClass = t.getClass();Field[] fields = tClass.getDeclaredFields();int len = fields.length;int fieldNullNums = 0;for(Field field : fields){field.setAccessible(true);if(field.get(t) == null){fieldNullNums++;}}if(len == fieldNullNums){nullNums++;}else{break;}}if(nullNums != size){break;}}}return result;}/*** 获取导入数据* 只走utf8,不校验获取数据是否为null* 如果不能保证导入文件为utf8,请使用importExcelByAnyCharset方法* @param is 导入文件的文件流* @param clazz 类对象* @param excelTypeEnum 导入文件类型* @return 文件数据*/public static <T> ExcelResult<T> importExcelByUTF8(InputStream is, Class<T> clazz, ExcelTypeEnum excelTypeEnum) {DefaultExcelListener<T> listener = new DefaultExcelListener<>(false);return importExcelByCharset(is, clazz, excelTypeEnum, listener, StandardCharsets.UTF_8);}/*** 获取导入数据* @param is 导入文件的文件流* @param clazz 类对象* @param excelTypeEnum 导入文件类型* @param listener 监听器* @param charset 用于导入文件数据转换的编码* @return 文件数据*/public static <T> ExcelResult<T> importExcelByCharset(InputStream is, Class<T> clazz, ExcelTypeEnum excelTypeEnum,ExcelListener<T> listener, Charset charset) {EasyExcel.read(is, clazz, listener).excelType(excelTypeEnum).charset(charset).sheet().doRead();return listener.getExcelResult();}
}
下面是一个默认的监听器,其中isValidate可以去掉,个人不建议在监听器里面作校验,这样导入数据之间的唯一性或者其他一些关系可能无法校验或者校验起来很麻烦。
个人建议,还是先获取到表格数据,然后再进行校验。
import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.hexin.fms.common.utils.JsonUtils;
import com.hexin.fms.common.utils.StreamUtils;
import com.hexin.fms.common.utils.ValidatorUtils;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Map;
import java.util.Set;@Slf4j
@NoArgsConstructor
public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {/*** 是否Validator检验,默认为是*/private Boolean isValidate = Boolean.TRUE;/*** excel 表头数据*/private Map<Integer, String> headMap;/*** 导入回执*/private ExcelResult<T> excelResult;public DefaultExcelListener(boolean isValidate) {this.excelResult = new DefaultExcelResult<>();this.isValidate = isValidate;}/*** 处理异常** @param exception ExcelDataConvertException* @param context Excel 上下文*/@Overridepublic void onException(Exception exception, AnalysisContext context) throws Exception {String errMsg = null;if (exception instanceof ExcelDataConvertException) {// 如果是某一个单元格的转换异常 能获取到具体行号ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;Integer rowIndex = excelDataConvertException.getRowIndex();Integer columnIndex = excelDataConvertException.getColumnIndex();errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));if (log.isDebugEnabled()) {log.error(errMsg);}}if (exception instanceof ConstraintViolationException) {ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception;Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", ");errMsg = StrUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);if (log.isDebugEnabled()) {log.error(errMsg);}}excelResult.getErrorList().add(errMsg);throw new ExcelAnalysisException(errMsg);}@Overridepublic void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {this.headMap = headMap;log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap));}@Overridepublic void invoke(T data, AnalysisContext context) {if (isValidate) {ValidatorUtils.validate(data);}excelResult.getList().add(data);}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {log.debug("所有数据解析完成!");}@Overridepublic ExcelResult<T> getExcelResult() {return excelResult;}}
用法如下:
@PostMapping(value = "/importExcel", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)public R<Void> importExcel(@RequestPart("file") MultipartFile file) throws Exception {ExcelResult<ImportVo> result = ExcelUtil.importExcelByAnyCharset(file, ImportVo.class, ExcelTypeEnum.CSV);List<ImportVo> excelList = result.getList();return importService.importExcel(excelList);}
importService.importExcel(excelList)就是你校验和保存更新删除数据的方法。