最近刚做了一个项目,需要用excel文件上传,然后需要完成对数据的校验,然后在校验完成后发现有问题的需要对有问题的数据进行添加批注、字体变红后返回excel文件。如果数据格式之类的没有问题就需要进行导入做更新或者添加的操作。
出于想偷懒的心态,我一开始就想到要用easyExcel来处理本次项目。
这是我用的版本
<dependency>
<groupId>com.alibabagroupId>
<artifactId>easyexcelartifactId>
<version>2.2.6version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>3.17version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>3.17version>
dependency>
在开始写代码的时候我就在想,我需要将错误的数据回显,就要记录那一行那一列出错,那就肯定不能使用数据转换器来做数据转换,还得避免数据类型异常,所以我接受excel的实体必须全部是String才行。因为一旦发生报错,我只能知道报错的那个单元格,而同处于报错那一行的,列在报错单元格后面的是否有错我就不知道了。
我这里实体类采用的是用注解来标识需要校验的字段,然后接受类型全部是String,如下:
@Data
public class ReadStudentAllInfo {
/**
* 姓名
*/
@ExcelProperty(value = {"学生个人基础信息", "*学生姓名"}, index = 0)
@CheckValueEmpty(message = "必填,不可为空")
private String name;
/**
* 个人标识码(学籍号)
*/
@ExcelProperty(value = {"学生个人基础信息", "*个人标识码(学籍号)"}, index = 1)
@CheckValueEmpty(message = "必填,不可为空")
private String studentCode;
/**
* 国籍/地区
*/
@ExcelProperty(value = {"学生个人基础信息", "*国籍/地区"}, index = 2)
@CheckValueEmpty(message = "必填,不可为空")
private String nationality;
/**
* 港澳台侨
*/
@ExcelProperty(value = {"学生个人基础信息", "*港澳台侨"}, index = 3)
@CheckValueEmpty(message = "必填,不可为空")
private String hmt;
/**
* 身份证件类型
*/
@ExcelProperty(value = {"学生个人基础信息", "*身份证件类型"}, index = 4)
@CheckValueEmpty(message = "必填,不可为空")
private String cardType;
/**
* 身份证件号
*/
@ExcelProperty(value = {"学生个人基础信息", "*身份证件号"}, index = 5)
@CheckIdentityCard(message = "身份证号码未填或填写错误")
private String cardNumber;
/**
* 性别
*/
@ExcelProperty(value = {"学生个人基础信息", "*性别"}, index = 6, converter = SexStringToNumberConverter.class)
@CheckNumberEmpty(message = "必填,不可为空")
private Integer sex;
/**
* 出生日期
*/
@ExcelProperty(value = {"学生个人基础信息", "出生日期(不导入系统)"}, index = 7)
private String birth;
}
然后监听器如下:
public class AnalysisFailEventListener extends AnalysisEventListener<ReadStudentAllInfo> {
/**
* 记录错误
*/
private Map<Integer, Map<Integer, FailRecord>> failListMap = new HashMap<>();
/**
* excel中读取的数据
*/
private List<ReadStudentAllInfo> excelDatas = new ArrayList<>();
public AnalysisFailEventListener(Map<String, String> changeMap) {
this.changeMap = changeMap;
}
@Override
public void extra(CellExtra extra, AnalysisContext context) {
super.extra(extra, context);
}
/**
* easyExcel 每次读取一行数据
*/
@Override
public void invoke(ReadStudentAllInfo readStudentAllInfo, AnalysisContext analysisContext) {
excelDatas.add(readStudentAllInfo);
Map<String, ExcelCellBo> propertyNameMap = getPropertyNameMap(true, analysisContext);
//获取注解的校验结果 记录
validate(readStudentAllInfo, propertyNameMap);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
/**
* 在解析excel时报错时抓取的信息
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException convertException = (ExcelDataConvertException) exception;
FailRecord failRecord = new FailRecord();
failRecord.setRow(convertException.getRowIndex());
failRecord.setColumn(convertException.getColumnIndex());
failRecord.setMsg(exception.getCause().getMessage());
failRecord.setMsg("填写数据格式错误!");
Map<Integer, FailRecord> map = new HashMap<>();
map.put(convertException.getColumnIndex(), failRecord);
failListMap.put(convertException.getRowIndex(), map);
}
}
/**
* 返回错误信息
*/
public Map<Integer, Map<Integer, FailRecord>> getFailListMap() {
return failListMap;
}
/**
* 返回数据
*/
public List<ReadStudentAllInfo> getExcelDatas() {
return this.excelDatas;
}
/**
* 注解校验结果
*/
private boolean validate(Object e, Map<String, ExcelCellBo> propertyNameMap) {
boolean validateResult = true;
Set<ConstraintViolation<Object>> validateSet = Validation.buildDefaultValidatorFactory().getValidator().validate(e, Default.class);
if (validateSet != null && !validateSet.isEmpty()) {
validateResult = false;
FailRecord failRecord;
Map<Integer, FailRecord> map = new HashMap<>();
Integer row = 0;
for (ConstraintViolation<Object> constraint : validateSet) {
Path propertyPath = constraint.getPropertyPath();
String propertyName = propertyPath.toString();
ExcelCellBo bo = propertyNameMap.get(propertyName);
failRecord = new FailRecord();
failRecord.setHeadName(bo.getHeadName());
row = bo.getRowIndex();
Object invalidValue = constraint.getInvalidValue();
if (invalidValue != null) {
failRecord.setValue(invalidValue.toString());
} else {
failRecord.setValue(null);
}
failRecord.setColumn(bo.getColumnIndex());
failRecord.setRow(row);
failRecord.setMsg(constraint.getMessage());
map.put(bo.getColumnIndex(), failRecord);
}
failListMap.put(row, map);
}
return validateResult;
}
/**
* 分析当前上下文 获取行列号
*/
private Map<String, ExcelCellBo> getPropertyNameMap(boolean isSingleHeader, AnalysisContext analysisContext) {
Map<String, ExcelCellBo> propertyNameMap = new HashMap<>(16);
ReadRowHolder readRowHolder = analysisContext.readRowHolder();
Integer rowIndex = readRowHolder.getRowIndex();
ReadHolder readHolder = analysisContext.currentReadHolder();
ExcelReadHeadProperty excelReadHeadProperty = readHolder.excelReadHeadProperty();
Collection<ExcelContentProperty> values;
if (isSingleHeader) {
Map<Integer, ExcelContentProperty> contentPropertyMap = excelReadHeadProperty.getContentPropertyMap();
values = contentPropertyMap.values();
} else {
//也适用于单行表头
Map<String, ExcelContentProperty> fieldNameContentPropertyMap = excelReadHeadProperty.getFieldNameContentPropertyMap();
values = fieldNameContentPropertyMap.values();
}
ExcelCellBo bo;
for (ExcelContentProperty contentProperty : values) {
bo = new ExcelCellBo();
bo.setRowIndex(rowIndex);
bo.setColumnIndex(contentProperty.getHead().getColumnIndex());
bo.setFieldName(contentProperty.getHead().getFieldName());
//多行表头
bo.setHeadName(String.join(",", contentProperty.getHead().getHeadNameList()));
bo.setField(contentProperty.getField());
propertyNameMap.put(contentProperty.getHead().getFieldName(), bo);
}
return propertyNameMap;
}
@Data
public class ExcelCellBo {
private Field field;
private String fieldName;
private String headName;
private Integer columnIndex;
private Integer rowIndex;
}
执行代码
public class ExcelUpload {
public void uploadStudentInfoExcel(MultipartFile file, HttpServletResponse response) {
//获取格式有误或者错误的数据
AnalysisFailEventListener failEventListener = new AnalysisFailEventListener(databaseStudentCodeMap,changeMap);
EasyExcel.read(file.getInputStream(), ReadStudentAllInfo.class, failEventListener).sheet().doRead();
Map<Integer, Map<Integer, FailRecord>> failListMap = failEventListener.getFailListMap();
//存在错误数据,返回填充好错误信息的excel
if (failListMap.size() > 0) {
downloadExcel(file.getInputStream(), failListMap, response);
return;
}
//不存在错误数据就开始更新插入之类的操作了
}
/**
* 返回excel
*
* @param inputStream 文件流
* @param failListMap 错误的数据
* @param response 响应体
* @author tzh
*/
private void downloadExcel(InputStream inputStream, Map<Integer, Map<Integer, FailRecord>> failListMap,
HttpServletResponse response) throws IOException {
OutputStream osOut = null;
try {
Workbook workbook = WorkbookFactory.create(inputStream);
compileExcel(workbook, failListMap);
String fileName = "学生学籍信息";
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 形成输出流
osOut = response.getOutputStream();
// 将指定的字节写入此输出流
workbook.write(osOut);
// 刷新此输出流并强制将所有缓冲的输出字节被写出
osOut.flush();
// 关闭流
osOut.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (osOut != null) {
osOut.close();
}
}
}
/**
* 编辑excel
*
* @param workbook workbook
* @param failListMap 错误数据
* @author tzh
*/
private void compileExcel(Workbook workbook, Map<Integer, Map<Integer, FailRecord>> failListMap) {
Sheet sheet = workbook.getSheetAt(0);
Set<Integer> rows = failListMap.keySet();
//初始化所有字体颜色
initFontColor(workbook, sheet);
for (Integer row : rows) {
Row row1 = sheet.getRow(row);
Map<Integer, FailRecord> map = failListMap.get(row);
Set<Integer> cloumns = map.keySet();
for (Integer cloumn : cloumns) {
Cell cell = row1.getCell(cloumn);
if (cell == null) {
Cell cell1 = row1.createCell(cloumn);
fillStyle(cell1, workbook, sheet, row, cloumn, map);
} else {
fillStyle(cell, workbook, sheet, row, cloumn, map);
}
}
}
}
/**
* 初始化所有字体颜色
*/
private void initFontColor(Workbook workbook, Sheet sheet) {
Cell cell2 = sheet.getRow(2).getCell(0);
CellStyle cellStyle = sheet.getRow(2).getCell(0).getCellStyle();
//设置垂直居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = workbook.createFont();
//设置字体为正常
font.setColor(Font.COLOR_NORMAL);
cellStyle.setFont(font);
cell2.setCellStyle(cellStyle);
}
/**
* 填充错误的颜色和批注样式
*/
private void fillStyle(Cell cell, Workbook workbook, Sheet sheet, Integer row, Integer cloumn, Map<Integer,
FailRecord> map) {
if (cell.toString().length() == 0) {
cell.setCellValue(map.get(cloumn).getMsg());
}
CellStyle cellStyle = workbook.createCellStyle();
//设置垂直居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
Font font = workbook.createFont();
//设置字体为红色
font.setColor(Font.COLOR_RED);
cellStyle.setFont(font);
Drawing<?> drawingPatriarch = sheet.createDrawingPatriarch();
Comment comment = drawingPatriarch.createCellComment(new XSSFClientAnchor(0, 0, 0, 0,
(short) (row + 0), cloumn, (short) (row + 1), cloumn + 1));
comment.setString(new XSSFRichTextString(map.get(cloumn).getMsg()));
cell.setCellComment(comment);
cell.setCellStyle(cellStyle);
}
}
--------------最后感谢大家的阅读,愿大家技术越来越流弊!--------------
--------------也希望大家给我点支持,谢谢各位大佬了!!!--------------