疯狂的狮子Li
2021-11-26 7c4a10482326ef418dd9e0713fda3761e665accb
!113 add 新增 excel 导入支持开启 Validator 数据验证
Merge pull request !113 from Yjoioooo/auto-5403234-dev-1637903026810
已添加3个文件
已修改3个文件
328 ■■■■■ 文件已修改
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelListener.java 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelResult.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-demo/src/main/java/com/ruoyi/demo/controller/TestDemoController.java 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-demo/src/main/java/com/ruoyi/demo/domain/bo/TestDemoImportVo.java 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/demo/demo/index.vue 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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;
    }
}
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/>"));
            }
        }
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java
@@ -6,6 +6,7 @@
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 @@
        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)
     *
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 @@
        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());
    }
    /**
     * å¯¼å‡ºæµ‹è¯•单表列表
     */
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;
}
ruoyi-ui/src/views/demo/demo/index.vue
@@ -73,6 +73,16 @@
      </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"
          plain
          icon="el-icon-download"
@@ -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 @@
      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 @@
        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();
    }
  }
};