Browse Source

!113 add 新增 excel 导入支持开启 Validator 数据验证
Merge pull request !113 from Yjoioooo/auto-5403234-dev-1637903026810

疯狂的狮子Li 3 years ago
parent
commit
7c4a104823

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

@@ -0,0 +1,116 @@
+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;
+    }
+
+}

+ 40 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelResult.java

@@ -0,0 +1,40 @@
+package com.ruoyi.common.utils.poi;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class ExcelResult<T> {
+
+    /** 数据对象list
+     */
+    private List<T> list;
+    /** 错误信息列表 */
+    private List<String> errorList;
+
+    /**
+     * 获取导入回执
+     * @return 导入回执
+     */
+    public String getAnalysis() {
+        int successCount = list.size();
+        int errorCount = errorList.size();
+        if (successCount == 0) {
+            return "读取失败,未解析到数据";
+        } else {
+            if (errorList.size() == 0) {
+                return StrUtil.format("恭喜您,全部读取成功!共{}条", successCount);
+            } else {
+                return StrUtil.format("部分读取成功,其中成功{}条,失败{}条,错误信息如下:<br/>{}",
+                    successCount,
+                    errorCount,
+                    CollUtil.join(errorList, "<br/>"));
+            }
+
+        }
+    }
+}

+ 14 - 0
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java

@@ -6,6 +6,7 @@ import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy
 import com.ruoyi.common.convert.ExcelBigNumberConvert;
 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;
@@ -30,6 +31,19 @@ public class ExcelUtil {
 		return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
 	}
 
+
+    /**
+     * 对excel表单默认第一个索引名转换成list(EasyExcel)
+     *
+     * @param is 输入流
+     * @return 转换后集合
+     */
+    public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate, boolean skipException) {
+        ExcelListener<T> listener = new ExcelListener<>(isValidate, skipException);
+        EasyExcel.read(is, clazz, listener).sheet().doRead();
+        return listener.getExcelResult();
+    }
+
 	/**
 	 * 对list数据源将其里面的数据导入到excel表单(EasyExcel)
 	 *

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

@@ -1,27 +1,31 @@
 package com.ruoyi.demo.controller;
 
+import cn.hutool.core.bean.BeanUtil;
 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.utils.poi.ExcelUtil;
+import com.ruoyi.demo.domain.TestDemo;
 import com.ruoyi.demo.domain.bo.TestDemoBo;
+import com.ruoyi.demo.domain.bo.TestDemoImportVo;
 import com.ruoyi.demo.domain.vo.TestDemoVo;
 import com.ruoyi.demo.service.ITestDemoService;
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiParam;
+import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.constraints.NotEmpty;
@@ -65,6 +69,21 @@ public class TestDemoController extends BaseController {
         return iTestDemoService.customPageList(bo);
     }
 
+    @ApiOperation("导入测试单表")
+    @ApiImplicitParams({
+        @ApiImplicitParam(name = "file", value = "导入文件", dataType = "java.io.File", required = true),
+    })
+    @Log(title = "测试单表", businessType = BusinessType.IMPORT)
+    @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);
+        List<TestDemoImportVo> volist = excelResult.getList();
+        List<TestDemo> list = BeanUtil.copyToList(volist, TestDemo.class);
+        iTestDemoService.saveAll(list);
+        return AjaxResult.success(excelResult.getAnalysis());
+    }
+
     /**
      * 导出测试单表列表
      */

+ 66 - 0
ruoyi-demo/src/main/java/com/ruoyi/demo/domain/bo/TestDemoImportVo.java

@@ -0,0 +1,66 @@
+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;
+
+/**
+ * 测试单表业务对象 test_demo
+ *
+ * @author Lion Li
+ * @date 2021-07-26
+ */
+
+@Data
+@ApiModel("测试单表业务对象")
+public class TestDemoImportVo {
+
+
+    /**
+     * 部门id
+     */
+	@ApiModelProperty("部门id")
+    @NotNull(message = "部门id不能为空")
+    @ExcelProperty(value = "部门id")
+    private Long deptId;
+
+    /**
+     * 用户id
+     */
+	@ApiModelProperty("用户id")
+    @NotNull(message = "用户id不能为空")
+    @ExcelProperty(value = "用户id")
+    private Long userId;
+
+    /**
+     * 排序号
+     */
+	@ApiModelProperty("排序号")
+    @NotNull(message = "排序号不能为空")
+    @ExcelProperty(value = "排序号")
+    private Long orderNum;
+
+    /**
+     * key键
+     */
+	@ApiModelProperty("key键")
+    @NotBlank(message = "key键不能为空")
+    @ExcelProperty(value = "key键")
+    private String testKey;
+
+    /**
+     * 值
+     */
+	@ApiModelProperty("值")
+    @NotBlank(message = "值不能为空")
+    @ExcelProperty(value = "值")
+    private String value;
+}

+ 67 - 0
ruoyi-ui/src/views/demo/demo/index.vue

@@ -71,6 +71,16 @@
           v-hasPermi="['demo:demo:remove']"
         >删除</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="el-icon-upload2"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['demo:demo:import']"
+        >导入</el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="warning"
@@ -164,11 +174,34 @@
         <el-button @click="cancel">取 消</el-button>
       </div>
     </el-dialog>
+    <!-- 用户导入对话框 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload
+        ref="upload"
+        :limit="1"
+        accept=".xlsx, .xls"
+        :headers="upload.headers"
+        :action="upload.url + '?updateSupport=' + upload.updateSupport"
+        :disabled="upload.isUploading"
+        :on-progress="handleFileUploadProgress"
+        :on-success="handleFileSuccess"
+        :auto-upload="false"
+        drag
+      >
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
 import { listDemo, pageDemo, getDemo, delDemo, addDemo, updateDemo } from "@/api/demo/demo";
+import {getToken} from "@/utils/auth";
 
 export default {
   name: "Demo",
@@ -198,6 +231,19 @@ export default {
       open: false,
       // 创建时间时间范围
       daterangeCreateTime: [],
+      // 用户导入参数
+      upload: {
+        // 是否显示弹出层(用户导入)
+        open: false,
+        // 弹出层标题(用户导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/demo/demo/importData"
+      },
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -353,11 +399,32 @@ export default {
         this.loading = false;
       });
     },
+    /** 导入按钮操作 */
+    handleImport() {
+      this.upload.title = "用户导入";
+      this.upload.open = true;
+    },
     /** 导出按钮操作 */
     handleExport() {
       this.download('demo/demo/export', {
         ...this.queryParams
       }, `demo_${new Date().getTime()}.xlsx`)
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true });
+      this.getList();
+    },
+    // 提交上传文件
+    submitFileForm() {
+      this.$refs.upload.submit();
     }
   }
 };