!113 add 新增 excel 导入支持开启 Validator 数据验证

Merge pull request !113 from Yjoioooo/auto-5403234-dev-1637903026810
This commit is contained in:
疯狂的狮子Li 2021-11-26 08:40:42 +00:00 committed by Gitee
commit 7c4a104823
6 changed files with 325 additions and 3 deletions

View File

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

View File

@ -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/>"));
}
}
}
}

View File

@ -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表单默认第一个索引名转换成listEasyExcel
*
* @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
*

View File

@ -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());
}
/**
* 导出测试单表列表
*/

View File

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

View File

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