ソースを参照

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

疯狂的狮子li 3 年 前
コミット
c00e397405

+ 101 - 0
ruoyi-common/src/main/java/com/ruoyi/common/excel/DefaultExcelListener.java

@@ -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;
+    }
+
+}

+ 44 - 6
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelResult.java → ruoyi-common/src/main/java/com/ruoyi/common/excel/DefautExcelResult.java

@@ -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.util.StrUtil;
-import lombok.Data;
+import lombok.Setter;
 
 import java.util.ArrayList;
 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;
-    /** 错误信息列表 */
+
+    /**
+     * 错误信息列表
+     */
+    @Setter
     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 导入回执
      */
+    @Override
     public String getAnalysis() {
         int successCount = list.size();
         int errorCount = errorList.size();

+ 26 - 0
ruoyi-common/src/main/java/com/ruoyi/common/excel/ExcelResult.java

@@ -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();
+}

+ 0 - 116
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelListener.java

@@ -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;
-    }
-
-}

+ 117 - 101
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java

@@ -4,9 +4,10 @@ import cn.hutool.core.util.IdUtil;
 import com.alibaba.excel.EasyExcel;
 import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
 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.file.FileUtils;
-import org.apache.poi.ss.formula.functions.T;
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.HttpServletResponse;
@@ -21,118 +22,133 @@ import java.util.List;
  */
 public class ExcelUtil {
 
-	/**
-	 * 对excel表单默认第一个索引名转换成list(EasyExcel)
-	 *
-	 * @param is 输入流
-	 * @return 转换后集合
-	 */
-	public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
-		return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
-	}
+    /**
+     * 同步导入
+     *
+     * @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表单默认第一个索引名转换成list(EasyExcel)
+     * 使用校验监听器处理导入
      *
-     * @param is 输入流
+     * @param is            输入流
+     * @param clazz         对象类型
+     * @param isValidate    是否 Validator 检验 默认为是
      * @return 转换后集合
      */
-    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate, boolean skipException) {
-        ExcelListener<T> listener = new ExcelListener<>(isValidate, skipException);
+    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();
         return listener.getExcelResult();
     }
 
-	/**
-	 * 对list数据源将其里面的数据导入到excel表单(EasyExcel)
-	 *
-	 * @param list      导出数据集合
-	 * @param sheetName 工作表的名称
-	 * @return 结果
-	 */
-	public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
-		try {
-			String filename = encodingFilename(sheetName);
-			response.reset();
-			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异常");
-		}
-	}
+    /**
+     * 使用自定义监听器导入
+     *
+     * @param is            输入流
+     * @param clazz         对象类型
+     * @param readListener  自定义监听器
+     * @return 转换后集合
+     */
+    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, DefaultExcelListener<T> readListener) {
+        EasyExcel.read(is, clazz, readListener).sheet().doRead();
+        return readListener.getExcelResult();
+    }
+
+    /**
+     * 导出excel
+     *
+     * @param list      导出数据集合
+     * @param sheetName 工作表的名称
+     * @return 结果
+     */
+    public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
+        try {
+            String filename = encodingFilename(sheetName);
+            response.reset();
+            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=未知
-	 *
-	 * @param propertyValue 参数值
-	 * @param converterExp  翻译注解
-	 * @param separator     分隔符
-	 * @return 解析后值
-	 */
-	public static String convertByExp(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[0].equals(value)) {
-						propertyString.append(itemArray[1] + separator);
-						break;
-					}
-				}
-			} else {
-				if (itemArray[0].equals(propertyValue)) {
-					return itemArray[1];
-				}
-			}
-		}
-		return StringUtils.stripEnd(propertyString.toString(), separator);
-	}
+    /**
+     * 解析导出值 0=男,1=女,2=未知
+     *
+     * @param propertyValue 参数值
+     * @param converterExp  翻译注解
+     * @param separator     分隔符
+     * @return 解析后值
+     */
+    public static String convertByExp(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[0].equals(value)) {
+                        propertyString.append(itemArray[1] + separator);
+                        break;
+                    }
+                }
+            } else {
+                if (itemArray[0].equals(propertyValue)) {
+                    return itemArray[1];
+                }
+            }
+        }
+        return StringUtils.stripEnd(propertyString.toString(), separator);
+    }
 
-	/**
-	 * 反向解析值 男=0,女=1,未知=2
-	 *
-	 * @param propertyValue 参数值
-	 * @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);
-	}
+    /**
+     * 反向解析值 男=0,女=1,未知=2
+     *
+     * @param propertyValue 参数值
+     * @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";
-	}
+    /**
+     * 编码文件名
+     */
+    public static String encodingFilename(String filename) {
+        return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
+    }
 
 }

+ 3 - 4
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestDemoController.java

@@ -5,14 +5,13 @@ import com.ruoyi.common.annotation.Log;
 import com.ruoyi.common.annotation.RepeatSubmit;
 import com.ruoyi.common.core.controller.BaseController;
 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.validate.AddGroup;
 import com.ruoyi.common.core.validate.EditGroup;
 import com.ruoyi.common.core.validate.QueryGroup;
 import com.ruoyi.common.enums.BusinessType;
 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.demo.domain.TestDemo;
 import com.ruoyi.demo.domain.bo.TestDemoBo;
@@ -69,7 +68,7 @@ public class TestDemoController extends BaseController {
         return iTestDemoService.customPageList(bo);
     }
 
-    @ApiOperation("导入测试单表")
+    @ApiOperation("导入测试-校验")
     @ApiImplicitParams({
         @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')")
     @PostMapping("/importData")
     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<TestDemo> list = BeanUtil.copyToList(volist, TestDemo.class);
         iTestDemoService.saveAll(list);

+ 6 - 11
ruoyi-demo/src/main/java/com/ruoyi/demo/domain/bo/TestDemoImportVo.java

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

+ 1 - 1
ruoyi-ui/src/views/demo/demo/index.vue

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