update 抽象 Excel 导入支持自定义监听器

This commit is contained in:
疯狂的狮子li 2021-11-26 18:06:44 +08:00
parent a2a2640d29
commit c00e397405
8 changed files with 299 additions and 240 deletions

View File

@ -0,0 +1,101 @@
package com.ruoyi.common.excel;
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.alibaba.fastjson.JSON;
import com.ruoyi.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;
import java.util.stream.Collectors;
/**
* Excel 导入监听
*
* @author Yjoioooo
* @author Lion Li
*/
@Slf4j
@NoArgsConstructor
public class DefaultExcelListener<T> extends AnalysisEventListener<T> {
/**
* 是否Validator检验默认为是
*/
private Boolean isValidate = Boolean.TRUE;
/**
* 导入回执
*/
private ExcelResult<T> excelResult;
public DefaultExcelListener(boolean isValidate) {
this.excelResult = new DefautExcelResult<>();
this.isValidate = isValidate;
}
/**
* 处理异常
*
* @param exception ExcelDataConvertException
* @param context Excel 上下文
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
String errMsg = null;
if (exception instanceof ExcelDataConvertException) {
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合doAfterAllAnalysedHeadMap使用
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
errMsg = StrUtil.format("第{}行-第{}列解析异常<br/>",
excelDataConvertException.getRowIndex() + 1,
excelDataConvertException.getColumnIndex() + 1);
if (log.isDebugEnabled()) {
log.error(errMsg);
}
}
if (exception instanceof ConstraintViolationException) {
ConstraintViolationException constraintViolationException = (ConstraintViolationException) exception;
Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
String constraintViolationsMsg = constraintViolations.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.joining(", "));
errMsg = StrUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
if (log.isDebugEnabled()) {
log.error(errMsg);
}
}
excelResult.getErrorList().add(errMsg);
throw new ExcelAnalysisException(errMsg);
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
log.debug("解析到一条头数据: {}", JSON.toJSONString(headMap));
}
@Override
public void invoke(T data, AnalysisContext context) {
if (isValidate) {
ValidatorUtils.validate(data);
}
excelResult.getList().add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.debug("所有数据解析完成!");
}
public ExcelResult<T> getExcelResult() {
return excelResult;
}
}

View File

@ -1,25 +1,63 @@
package com.ruoyi.common.utils.poi; package com.ruoyi.common.excel;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import lombok.Data; import lombok.Setter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@Data /**
public class ExcelResult<T> { * 默认excel返回对象
*
* @author Yjoioooo
* @author Lion Li
*/
public class DefautExcelResult<T> implements ExcelResult<T> {
/** 数据对象list /**
* 数据对象list
*/ */
@Setter
private List<T> list; private List<T> list;
/** 错误信息列表 */
/**
* 错误信息列表
*/
@Setter
private List<String> errorList; private List<String> errorList;
public DefautExcelResult() {
this.list = new ArrayList<>();
this.errorList = new ArrayList<>();
}
public DefautExcelResult(List<T> list, List<String> errorList) {
this.list = list;
this.errorList = errorList;
}
public DefautExcelResult(ExcelResult<T> excelResult) {
this.list = excelResult.getList();
this.errorList = excelResult.getErrorList();
}
@Override
public List<T> getList() {
return list;
}
@Override
public List<String> getErrorList() {
return errorList;
}
/** /**
* 获取导入回执 * 获取导入回执
*
* @return 导入回执 * @return 导入回执
*/ */
@Override
public String getAnalysis() { public String getAnalysis() {
int successCount = list.size(); int successCount = list.size();
int errorCount = errorList.size(); int errorCount = errorList.size();

View File

@ -0,0 +1,26 @@
package com.ruoyi.common.excel;
import java.util.List;
/**
* excel返回对象
*
* @author Lion Li
*/
public interface ExcelResult<T> {
/**
* 对象列表
*/
List<T> getList();
/**
* 错误列表
*/
List<String> getErrorList();
/**
* 导入回执
*/
String getAnalysis();
}

View File

@ -1,116 +0,0 @@
package com.ruoyi.common.utils.poi;
import cn.hutool.core.collection.CollUtil;
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.alibaba.fastjson.JSON;
import com.ruoyi.common.utils.ValidatorUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.validation.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 公共excel监听类
* @param <T>
*/
public class ExcelListener<T> extends AnalysisEventListener<T> {
private static final Logger LOGGER = LoggerFactory.getLogger(ExcelListener.class);
/** 数据对象list */
private final List<T> list = new ArrayList<>();
/** 错误信息列表 */
private final List<String> errorList = new ArrayList<>();
/** 遇到异常是否跳出导入,默认为是 */
private Boolean skipException = Boolean.TRUE;
/** 是否Validator检验默认为是 */
private Boolean isValidate = Boolean.TRUE;
/**
* 导入回执
*/
private final ExcelResult<T> excelResult = new ExcelResult<>();
public ExcelListener() {
}
public ExcelListener(boolean isValidate, boolean skipException) {
this.isValidate = isValidate;
this.skipException = skipException;
}
/**
* 处理异常
*
* @param exception ExcelDataConvertException
* @param context excel上下文
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
// 如果是某一个单元格的转换异常 能获取到具体行号
// 如果要获取头的信息 配合doAfterAllAnalysedHeadMap使用
String errMsg = null;
if (exception instanceof ExcelDataConvertException) {
ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
errMsg = StrUtil.format("第{}行-第{}列解析异常<br/>", excelDataConvertException.getRowIndex() + 1,
excelDataConvertException.getColumnIndex() + 1);
LOGGER.error(errMsg);
}
if (exception instanceof ConstraintViolationException) {
ConstraintViolationException constraintViolationException = (ConstraintViolationException)exception;
Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
String constraintViolationsMsg= CollUtil.join(constraintViolations
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toList()),
",");
errMsg = StrUtil.format("第{}行数据校验异常:{}", context.readRowHolder().getRowIndex() + 1,
constraintViolationsMsg);
LOGGER.error(errMsg);
}
errorList.add(errMsg);
if (!skipException){
throw new ExcelAnalysisException(errMsg);
}
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
LOGGER.debug("解析到一条头数据:{}", JSON.toJSONString(headMap));
}
@Override
public void invoke(T data, AnalysisContext context) {
if (isValidate) {
ValidatorUtils.validate(data);
}
list.add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
excelResult.setList(list);
excelResult.setErrorList(errorList);
LOGGER.debug("所有数据解析完成!");
}
/**
* 获取导入数据
* @return 导入数据
*/
public List<T> getList() {
return list;
}
public ExcelResult<T> getExcelResult() {
return excelResult;
}
}

View File

@ -4,9 +4,10 @@ import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.ruoyi.common.convert.ExcelBigNumberConvert; import com.ruoyi.common.convert.ExcelBigNumberConvert;
import com.ruoyi.common.excel.DefaultExcelListener;
import com.ruoyi.common.excel.ExcelResult;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUtils; import com.ruoyi.common.utils.file.FileUtils;
import org.apache.poi.ss.formula.functions.T;
import javax.servlet.ServletOutputStream; import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
@ -21,118 +22,133 @@ import java.util.List;
*/ */
public class ExcelUtil { public class ExcelUtil {
/**
* 对excel表单默认第一个索引名转换成listEasyExcel
*
* @param is 输入流
* @return 转换后集合
*/
public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
}
/** /**
* 对excel表单默认第一个索引名转换成listEasyExcel * 同步导入
* *
* @param is 输入流 * @param is 输入流
* @return 转换后集合 * @return 转换后集合
*/ */
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate, boolean skipException) { public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
ExcelListener<T> listener = new ExcelListener<>(isValidate, skipException); return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
}
/**
* 使用校验监听器处理导入
*
* @param is 输入流
* @param clazz 对象类型
* @param isValidate 是否 Validator 检验 默认为是
* @return 转换后集合
*/
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
EasyExcel.read(is, clazz, listener).sheet().doRead(); EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult(); return listener.getExcelResult();
} }
/** /**
* 对list数据源将其里面的数据导入到excel表单EasyExcel * 使用自定义监听器导入
* *
* @param list 导出数据集合 * @param is 输入流
* @param sheetName 工作表的名称 * @param clazz 对象类型
* @return 结果 * @param readListener 自定义监听器
*/ * @return 转换后集合
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) { */
try { public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, DefaultExcelListener<T> readListener) {
String filename = encodingFilename(sheetName); EasyExcel.read(is, clazz, readListener).sheet().doRead();
response.reset(); return readListener.getExcelResult();
FileUtils.setAttachmentResponseHeader(response, filename); }
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
ServletOutputStream os = response.getOutputStream();
EasyExcel.write(os, clazz)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.sheet(sheetName).doWrite(list);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
/** /**
* 解析导出值 0=,1=,2=未知 * 导出excel
* *
* @param propertyValue 参数值 * @param list 导出数据集合
* @param converterExp 翻译注解 * @param sheetName 工作表的名称
* @param separator 分隔符 * @return 结果
* @return 解析后值 */
*/ public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
public static String convertByExp(String propertyValue, String converterExp, String separator) { try {
StringBuilder propertyString = new StringBuilder(); String filename = encodingFilename(sheetName);
String[] convertSource = converterExp.split(","); response.reset();
for (String item : convertSource) { FileUtils.setAttachmentResponseHeader(response, filename);
String[] itemArray = item.split("="); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
if (StringUtils.containsAny(separator, propertyValue)) { ServletOutputStream os = response.getOutputStream();
for (String value : propertyValue.split(separator)) { EasyExcel.write(os, clazz)
if (itemArray[0].equals(value)) { .autoCloseStream(false)
propertyString.append(itemArray[1] + separator); // 自动适配
break; .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
} // 大数值自动转换 防止失真
} .registerConverter(new ExcelBigNumberConvert())
} else { .sheet(sheetName).doWrite(list);
if (itemArray[0].equals(propertyValue)) { } catch (IOException e) {
return itemArray[1]; throw new RuntimeException("导出Excel异常");
} }
} }
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/** /**
* 反向解析值 =0,=1,未知=2 * 解析导出值 0=,1=,2=未知
* *
* @param propertyValue 参数值 * @param propertyValue 参数值
* @param converterExp 翻译注解 * @param converterExp 翻译注解
* @param separator 分隔符 * @param separator 分隔符
* @return 解析后值 * @return 解析后值
*/ */
public static String reverseByExp(String propertyValue, String converterExp, String separator) { public static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder(); StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(","); String[] convertSource = converterExp.split(",");
for (String item : convertSource) { for (String item : convertSource) {
String[] itemArray = item.split("="); String[] itemArray = item.split("=");
if (StringUtils.containsAny(separator, propertyValue)) { if (StringUtils.containsAny(separator, propertyValue)) {
for (String value : propertyValue.split(separator)) { for (String value : propertyValue.split(separator)) {
if (itemArray[1].equals(value)) { if (itemArray[0].equals(value)) {
propertyString.append(itemArray[0] + separator); propertyString.append(itemArray[1] + separator);
break; break;
} }
} }
} else { } else {
if (itemArray[1].equals(propertyValue)) { if (itemArray[0].equals(propertyValue)) {
return itemArray[0]; return itemArray[1];
} }
} }
} }
return StringUtils.stripEnd(propertyString.toString(), separator); return StringUtils.stripEnd(propertyString.toString(), separator);
} }
/** /**
* 编码文件名 * 反向解析值 =0,=1,未知=2
*/ *
public static String encodingFilename(String filename) { * @param propertyValue 参数值
return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx"; * @param converterExp 翻译注解
} * @param separator 分隔符
* @return 解析后值
*/
public static String reverseByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(",");
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(separator, propertyValue)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[1].equals(value)) {
propertyString.append(itemArray[0] + separator);
break;
}
}
} else {
if (itemArray[1].equals(propertyValue)) {
return itemArray[0];
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 编码文件名
*/
public static String encodingFilename(String filename) {
return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
}
} }

View File

@ -5,14 +5,13 @@ import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit; import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.AddGroup; import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup; import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.core.validate.QueryGroup; import com.ruoyi.common.core.validate.QueryGroup;
import com.ruoyi.common.enums.BusinessType; import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.ValidatorUtils; import com.ruoyi.common.utils.ValidatorUtils;
import com.ruoyi.common.utils.poi.ExcelResult; import com.ruoyi.common.excel.ExcelResult;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.demo.domain.TestDemo; import com.ruoyi.demo.domain.TestDemo;
import com.ruoyi.demo.domain.bo.TestDemoBo; import com.ruoyi.demo.domain.bo.TestDemoBo;
@ -69,7 +68,7 @@ public class TestDemoController extends BaseController {
return iTestDemoService.customPageList(bo); return iTestDemoService.customPageList(bo);
} }
@ApiOperation("导入测试单表") @ApiOperation("导入测试-校验")
@ApiImplicitParams({ @ApiImplicitParams({
@ApiImplicitParam(name = "file", value = "导入文件", dataType = "java.io.File", required = true), @ApiImplicitParam(name = "file", value = "导入文件", dataType = "java.io.File", required = true),
}) })
@ -77,7 +76,7 @@ public class TestDemoController extends BaseController {
@PreAuthorize("@ss.hasPermi('demo:demo:import')") @PreAuthorize("@ss.hasPermi('demo:demo:import')")
@PostMapping("/importData") @PostMapping("/importData")
public AjaxResult<Void> importData(@RequestPart("file") MultipartFile file) throws Exception { public AjaxResult<Void> importData(@RequestPart("file") MultipartFile file) throws Exception {
ExcelResult<TestDemoImportVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true, true); ExcelResult<TestDemoImportVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true);
List<TestDemoImportVo> volist = excelResult.getList(); List<TestDemoImportVo> volist = excelResult.getList();
List<TestDemo> list = BeanUtil.copyToList(volist, TestDemo.class); List<TestDemo> list = BeanUtil.copyToList(volist, TestDemo.class);
iTestDemoService.saveAll(list); iTestDemoService.saveAll(list);

View File

@ -1,13 +1,9 @@
package com.ruoyi.demo.domain.bo; package com.ruoyi.demo.domain.bo;
import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
@ -18,16 +14,14 @@ import javax.validation.constraints.NotNull;
* @author Lion Li * @author Lion Li
* @date 2021-07-26 * @date 2021-07-26
*/ */
@Data @Data
@ApiModel("测试单表业务对象") @ApiModel("测试单表业务对象")
public class TestDemoImportVo { public class TestDemoImportVo {
/** /**
* 部门id * 部门id
*/ */
@ApiModelProperty("部门id") @ApiModelProperty("部门id")
@NotNull(message = "部门id不能为空") @NotNull(message = "部门id不能为空")
@ExcelProperty(value = "部门id") @ExcelProperty(value = "部门id")
private Long deptId; private Long deptId;
@ -35,7 +29,7 @@ public class TestDemoImportVo {
/** /**
* 用户id * 用户id
*/ */
@ApiModelProperty("用户id") @ApiModelProperty("用户id")
@NotNull(message = "用户id不能为空") @NotNull(message = "用户id不能为空")
@ExcelProperty(value = "用户id") @ExcelProperty(value = "用户id")
private Long userId; private Long userId;
@ -43,7 +37,7 @@ public class TestDemoImportVo {
/** /**
* 排序号 * 排序号
*/ */
@ApiModelProperty("排序号") @ApiModelProperty("排序号")
@NotNull(message = "排序号不能为空") @NotNull(message = "排序号不能为空")
@ExcelProperty(value = "排序号") @ExcelProperty(value = "排序号")
private Long orderNum; private Long orderNum;
@ -51,7 +45,7 @@ public class TestDemoImportVo {
/** /**
* key键 * key键
*/ */
@ApiModelProperty("key键") @ApiModelProperty("key键")
@NotBlank(message = "key键不能为空") @NotBlank(message = "key键不能为空")
@ExcelProperty(value = "key键") @ExcelProperty(value = "key键")
private String testKey; private String testKey;
@ -59,8 +53,9 @@ public class TestDemoImportVo {
/** /**
* *
*/ */
@ApiModelProperty("") @ApiModelProperty("")
@NotBlank(message = "值不能为空") @NotBlank(message = "值不能为空")
@ExcelProperty(value = "") @ExcelProperty(value = "")
private String value; private String value;
} }

View File

@ -79,7 +79,7 @@
size="mini" size="mini"
@click="handleImport" @click="handleImport"
v-hasPermi="['demo:demo:import']" v-hasPermi="['demo:demo:import']"
>导入</el-button> >导入(校验)</el-button>
</el-col> </el-col>
<el-col :span="1.5"> <el-col :span="1.5">
<el-button <el-button