4lqedz 发表于 2024-10-6 10:00:00

运用EasyExcel 导入数据,失败原由数据导出


    <h1 style="color: black; text-align: left; margin-bottom: 10px;">引言</h1>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">在<span style="color: black;">平常</span><span style="color: black;">研发</span>过程中,Excel 导入是非常<span style="color: black;">平常</span>的场景,<span style="color: black;">况且</span><span style="color: black;">亦</span>有<span style="color: black;">非常多</span>开源的项目是针对Excel的读写的,如Apache 的poi ,<span style="color: black;">近期</span>用的比较好的还是阿里的EasyExcel 开源工具。平时<span style="color: black;">咱们</span>只是简单的读取文件并写入数据库持久化<span style="color: black;">就可</span>,<span style="color: black;">然则</span>前段时间,<span style="color: black;">制品</span>搞了个<span style="color: black;">需要</span>,需要将导入失败的数据及<span style="color: black;">原由</span>写入Excel并下载,那这就有得玩了,废话不多说,上才艺。</span></p>
    <h1 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">制品</span><span style="color: black;">需要</span></h1><span style="color: black;">导入Excel数据</span><span style="color: black;">数据格式校验</span><span style="color: black;">数据合法性校验(校验数据库)</span><span style="color: black;">失败数据<span style="color: black;">供给</span>用户下载,并支持再次导入</span>
    <h1 style="color: black; text-align: left; margin-bottom: 10px;">技术选型</h1><span style="color: black;">https://github.com/alibaba/easyexcel</span><span style="color: black;"> ,Excel 读取/写入</span><span style="color: black;">https://www.xuxueli.com/xxl-job/</span><span style="color: black;"> ,做异步处理</span>
    <h1 style="color: black; text-align: left; margin-bottom: 10px;"><span style="color: black;">需要</span>实现</h1>
    <h1 style="color: black; text-align: left; margin-bottom: 10px;">项目依赖(maven)</h1><span style="color: black;">&lt;!-- easyexcle --&gt;</span>
    <span style="color: black;">&lt;<span style="color: black;">dependency</span>&gt;</span>
    <span style="color: black;">&lt;<span style="color: black;">groupId</span>&gt;</span>com.alibaba<span style="color: black;">&lt;/<span style="color: black;">groupId</span>&gt;</span>
    <span style="color: black;">&lt;<span style="color: black;">artifactId</span>&gt;</span>easyexcle<span style="color: black;">&lt;/<span style="color: black;">artifactId</span>&gt;</span>
    <span style="color: black;">&lt;<span style="color: black;">version</span>&gt;</span>2.2.6<span style="color: black;">&lt;/<span style="color: black;">version</span>&gt;</span>
    <span style="color: black;">&lt;/<span style="color: black;">dependency</span>&gt;</span>
    <span style="color: black;">&lt;!-- xxl job --&gt;</span>
    <span style="color: black;">&lt;<span style="color: black;">dependency</span>&gt;</span>
    <span style="color: black;">&lt;<span style="color: black;">groupId</span>&gt;</span>com.xuxueli<span style="color: black;">&lt;/<span style="color: black;">groupId</span>&gt;</span>
    <span style="color: black;">&lt;<span style="color: black;">artifactId</span>&gt;</span>xxl-job-core<span style="color: black;">&lt;/<span style="color: black;">artifactId</span>&gt;</span>
    <span style="color: black;">&lt;<span style="color: black;">version</span>&gt;</span>${xxl-job.version}<span style="color: black;">&lt;/<span style="color: black;">version</span>&gt;</span>
    <span style="color: black;">&lt;/<span style="color: black;">dependency</span>&gt;</span>
    <h1 style="color: black; text-align: left; margin-bottom: 10px;">文件解析</h1>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">解析导入文件,获取文件数据量,用于判定导入<span style="color: black;">是不是</span>走异步导入。</span></p><span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">EasyExcelUtils</span> </span>{

    <span style="color: black;">/**
      *
      * 解析文件,获取最后一行
      * <span style="color: black;">@param</span> inputStream 文件流
      * <span style="color: black;">@param</span>sheetNum 读取excel表格的sheetNum 索引
      *<span style="color: black;">@return</span> 总行数
      */</span>
    <span style="color: black;"><span style="color: black;">public</span> <span style="color: black;">static</span> Integer <span style="color: black;">lastNum</span><span style="color: black;">(InputStream inputStream,Integer sheetNum)</span></span>{

    Workbook wb = <span style="color: black;">null</span>;
    sheetNum = sheetNum == <span style="color: black;">null</span> ? <span style="color: black;">0</span> : sheetNum;
    <span style="color: black;">try</span>{
    wb = WorkbookFactory.create(inputStream);
    Sheet sheet = wb.getSheetAt(sheetNum);
    CellReference cellReference =<span style="color: black;">new</span> CellReference(<span style="color: black;">"A4"</span>);
    <span style="color: black;">// 处理空行</span>
    <span style="color: black;">for</span> (<span style="color: black;">int</span> i = cellReference.getRow();i &lt;= sheet.getLastRowNum();){
    <span style="color: black;">// 省略部分代码</span>
    }
    <span style="color: black;">return</span>sheet.getLastRowNum();
    }<span style="color: black;">catch</span> (Exception e){

    }
    <span style="color: black;">return</span> <span style="color: black;">0</span>;
    }
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">判定导入数据文件<span style="color: black;">是不是</span>为空,<span style="color: black;">倘若</span>为空,将返回错误信息</span></p><span style="color: black;">@RestController</span>
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">ProjectInfoController</span> </span>{
    <span style="color: black;">/**
      * 项目信息导入
      */</span>
    <span style="color: black;">@PostMapping</span>(<span style="color: black;">"/import"</span>)
    <span style="color: black;"><span style="color: black;">public</span> R <span style="color: black;">projectInfoImport</span><span style="color: black;">(MultipartFile file,HttpServletResponse response)</span></span>{
    InputStream inputStream = <span style="color: black;">null</span>;
    <span style="color: black;">int</span> lastNum = <span style="color: black;">0</span>;
    <span style="color: black;">try</span> {
    lastNum = EasyExcelUtils.lastNum(file.getInputStream());
    }<span style="color: black;">catch</span>(IOException e){
    <span style="color: black;">// 省略部分代码</span>
    }
    <span style="color: black;">if</span> (lastNum &lt;= <span style="color: black;">0</span> ){
    <span style="color: black;">throw</span>CustomExcetpoin(<span style="color: black;">500</span>,<span style="color: black;">"导入文件数据为空,请重新上传"</span>);
    }

    }
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">文件解析拿到导入数据的数据量,与系统配置的文件导入上限值进行判定,<span style="color: black;">倘若</span>大于上限值将走异步处理(异步导入,请查看异步“异步导入”导入内容)。</span></p><span style="color: black;">@RestController</span>
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">ProjectInfoController</span> </span>{

    <span style="color: black;">@Resource</span>
    <span style="color: black;">private</span> AsyncExcelService asyncExcelService;
    <span style="color: black;">/**
      * 项目信息导入
      */</span>
    <span style="color: black;">@PostMapping</span>(<span style="color: black;">"/import"</span>)
    <span style="color: black;"><span style="color: black;">public</span> R <span style="color: black;">projectInfoImport</span><span style="color: black;">(MultipartFile file,HttpServletResponse response)</span></span>{
    InputStream inputStream = <span style="color: black;">null</span>;
    <span style="color: black;">int</span> lastNum = <span style="color: black;">0</span>;
    <span style="color: black;">try</span>{
    lastNum = EasyExcelUtils.lastNum(file.getInputStream());
    }<span style="color: black;">catch</span>(IOException e){
    <span style="color: black;">// 省略部分代码</span>
    }
    <span style="color: black;">if</span> (lastNum &lt;= <span style="color: black;">0</span> ){
    <span style="color: black;">throw</span> CustomExcetpoin(<span style="color: black;">500</span>,<span style="color: black;">"导入文件数据为空,请重新上传"</span>);
    }
    <span style="color: black;">// 获取系统配置的导入上限值</span>Integer importMax = asyncExcelService.asyncProjectImportMax();<span style="color: black;">if</span> (lastNum &gt; importMax ){
    <span style="color: black;">// 达到上限,走异步</span>
    asyncExcelService.asyncProjectImport(file,response);
    <span style="color: black;">return</span> R.success(<span style="color: black;">"数据导入成功,因数据量比<span style="color: black;">很强</span>,已转为异步导入"</span>);
    }
    <span style="color: black;">// 省略其他代码</span>
    }
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">AsyncExcelService 接口实现</span></p><span style="color: black;">/**
      * 异步导出/导入 service
      */</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">interface</span> <span style="color: black;">AsyncExcelService</span> </span>{

    <span style="color: black;">/** 默认导入数据上限 **/</span>
    Integer DEFAULT_IMPORT_DATA_MAX = <span style="color: black;">500</span>;

    <span style="color: black;">/**
      * 获取最大导入上限值,超过则走异步
      */</span>
    <span style="color: black;">Integer <span style="color: black;">getImportMax</span><span style="color: black;">()</span></span>;

    <span style="color: black;">/**
      * 异步导入数据
      */</span>
    <span style="color: black;"><span style="color: black;">void</span> <span style="color: black;">asyncProjectImport</span><span style="color: black;">(MultipartFile file,HttpServletResponse response)</span></span>;
    }
    <span style="color: black;">@Service</span>
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">AsyncExcelServiceImpl</span> <span style="color: black;">implements</span> <span style="color: black;">AsyncExcelService</span> </span>{
    <span style="color: black;">@Resource</span>
    <span style="color: black;">private</span> IParamtersClient paramtersClient;

    <span style="color: black;">@Override</span>
    <span style="color: black;"><span style="color: black;">public</span> Integer <span style="color: black;">getImportMax</span><span style="color: black;">()</span></span>{
    Integer value = getParamVaule(<span style="color: black;">"paramName"</span>,Integer<span style="color: black;">.<span style="color: black;">class</span>)</span>;
    <span style="color: black;">return</span>value ==<span style="color: black;">null</span> ? DEFAULT_IMPORT_DATA_MAX : value;
    }

    <span style="color: black;">/**
      * 调用框架接口获取系统参数
      *
      */</span>
    <span style="color: black;">private</span> &lt;T&gt; <span style="color: black;">T <span style="color: black;">getParamVaule</span><span style="color: black;">(String name,Class&lt;T&gt; clazz)</span></span>{
    CCBHousingUser user = SecureUtil.getUser();<span style="color: black;">// 省略部分代码</span>

    <span style="color: black;">// 获取系统配置参数</span>Parameters parameters = paramtersClient.getParamterByCodeAndOrg(name,user.getOrganizationId());<span style="color: black;">// 省略部分代码</span>
    }
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">其中,IParamtersClient 属于框架<span style="color: black;">供给</span>的feign 接口,<span style="color: black;">亦</span><span style="color: black;">能够</span><span style="color: black;">按照</span>自己的<span style="color: black;">实质</span>场景实现<span style="color: black;">关联</span><span style="color: black;">规律</span>。</span></p>
    <h1 style="color: black; text-align: left; margin-bottom: 10px;">数据合法校验</h1>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">导入数据文件解析<span style="color: black;">运用</span>的是alibaba <span style="color: black;">供给</span>的 EasyExcel 开源工具,<span style="color: black;">咱们</span>需要在 EasyExcel 工具的<span style="color: black;">基本</span>上做<span style="color: black;">有些</span><span style="color: black;">加强</span>处理,如:导入格式校验、导入表头校验、导入数据格式校验等,<span style="color: black;">倘若</span><span style="color: black;">出现</span>校验失败,将错误信息写入错误报告(excel)输出到客户端。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">定义easyexcel 导入文件到列与实体映射关系,将<span style="color: black;">运用</span>到 easyexcel 到@ExcleProperty 注解进行关系绑定</span></p><span style="color: black;">@Data</span>
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">ProjectInfoExcelDTO</span> </span>{
    <span style="color: black;">@ExcelProperty(index=0,value=<span style="color: black;">"序列号"</span>)</span>
    <span style="color: black;">private</span> String number;

    <span style="color: black;">@ExcelProperty(index=1,value=<span style="color: black;">"项目名<span style="color: black;">叫作</span>"</span>)</span>
    <span style="color: black;">private</span> String name;

    <span style="color: black;">// 省略其他字段属性</span>
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">注解 @ExcleProperty 常用属性</span></p><span style="color: black;">index,与excel文件中,表头列的索引位置对应(从0<span style="color: black;">起始</span>)</span><span style="color: black;">value,与excel文件中,表头列的名<span style="color: black;">叫作</span>相对应</span><span style="color: black;">converter,指定解析数据时,该列需要<span style="color: black;">运用</span>的数据转换器,转换器实现Converter接口</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">定义校验错误的数据结构类型</span></p>@Data
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">ExcelChcekErrDTO</span>&lt;T&gt; {</span>
    <span style="color: black;">private</span> T t;

    <span style="color: black;">private</span> String errMsg;
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">备注:@Data 属于 lombok 工具,简化Bean的封装,感兴趣的<span style="color: black;">朋友</span>,<span style="color: black;">能够</span><span style="color: black;">自动</span>查阅资料。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">定义Excel导入校验返回的数据VO</span></p>@Data
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">ExcelCheckResultVO</span>&lt;<span style="color: black;">T</span>&gt; </span>{

    <span style="color: black;">/** 校验成功的数据 **/</span>
    <span style="color: black;">private</span> <span style="color: black;">List</span>&lt;T&gt; successDatas;

    <span style="color: black;">/** 校验失败的数据 **/</span>
    <span style="color: black;">private</span> <span style="color: black;">List</span>&lt;ExcelChcekErrDTO&gt; errData;
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">定义数据解析监听器EasyExcelListener</span></p><span style="color: black;">@Data</span>
    <span style="color: black;">// 省略部分注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">EasyExcelListener</span>&lt;<span style="color: black;">T</span>&gt; <span style="color: black;">extends</span> <span style="color: black;">AnalysisEventListener</span>&lt;<span style="color: black;">T</span>&gt; </span>{
    <span style="color: black;">// 省略部分代码</span>
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">定义excel 业务校验管理器 ExcelCheckManager,需要做业务校验的(与数据库匹配等)需要实现该接口</span></p><span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">interface</span> <span style="color: black;">ExcelCheckManager</span>&lt;<span style="color: black;">T</span>&gt; </span>{

    <span style="color: black;">ExcelCheckResultVO <span style="color: black;">checkImportExcle</span><span style="color: black;">(List&lt;T&gt; datas)</span></span>;
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;"><span style="color: black;">表头校验</span></strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;"><span style="color: black;">运用</span>EasyExcelListener 用来监听数据解析过程,其中,invokHeadMap <span style="color: black;">办法</span>将在解析完成excel表头时将被执行</span></p><span style="color: black;">@Data</span>
    <span style="color: black;">// 省略部分注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">EasyExcelListener</span>&lt;<span style="color: black;">T</span>&gt; <span style="color: black;">extends</span> <span style="color: black;">AnalysisEventListener</span>&lt;<span style="color: black;">T</span>&gt; </span>{
    <span style="color: black;">/** excel 对象的反射类 **/</span>
    <span style="color: black;">private</span> Class&lt;T&gt; clazz;

    <span style="color: black;">private</span> ExcelCheckManager&lt;T&gt; excelCheckManager;

    <span style="color: black;"><span style="color: black;">public</span> <span style="color: black;">EasyExcelListener</span><span style="color: black;">(ExcelCheckManager&lt;T&gt; excelCheckManager,Class&lt;T&gt; clazz)</span></span>{
    <span style="color: black;">this</span>.clazz = clazz;
    <span style="color: black;">this</span>.excelCheckManager = excelCheckManager;
    }<span style="color: black;">@Override</span>
    <span style="color: black;"><span style="color: black;">public</span> <span style="color: black;">void</span> <span style="color: black;">invokHeadMap</span><span style="color: black;">(Map&lt;Integer,String&gt; headMap,AnalysisContext context)</span></span>{

    <span style="color: black;">super</span>.invokHeadMap(headMap,context);<span style="color: black;">// 反射获取实体到属性值</span>
    Map&lt;Integer,String&gt; indexNameMap = getIndexNameMap(clazz);
    <span style="color: black;">// 将 headMap 与 indexNameMap 进行对比,<span style="color: black;">是不是</span>完全匹配</span>Set&lt;Integer&gt; keySet = indexNameMap.keySet();<span style="color: black;">for</span> (Integer key : keySet ){
    <span style="color: black;">if</span> (StringUtils.isEmpty(headMap.get(key)){
    <span style="color: black;">throw</span> ExcelAnalysisExcetpion(<span style="color: black;">"数据解析错误,请传入正确的excel格式"</span>);
    }
    <span style="color: black;">if</span>(!headMap.get(key).equals(indexNameMap.get(key)){<span style="color: black;">throw</span> ExcelAnalysisExcetpion(<span style="color: black;">"数据解析错误,请传入正确的excel格式"</span>);
    }
    }

    }

    <span style="color: black;">/**
      * 反射获取解析数据实体的<span style="color: black;">@ExcleProperty</span>的value
      */</span>
    <span style="color: black;"><span style="color: black;">public</span> Map&lt;Integer,String&gt; <span style="color: black;">getIndexNameMap</span><span style="color: black;">(Class clazz)</span></span>{

    Map&lt;Integer,String&gt; result = <span style="color: black;">new</span>HashMap&lt;&gt;();
    Field field;
    Field[] fields = clazz.getDeclaredFields();<span style="color: black;">for</span> (<span style="color: black;">int</span> i = <span style="color: black;">0</span>; i &lt; fields.length; i++){
    field = clazz.getDeclaredField(fields.getName());
    field.setAccessible(<span style="color: black;">true</span>);
    ExcelProperty excleProperty = field.getAnnotation(ExcelProperty<span style="color: black;">.<span style="color: black;">class</span>)</span>;
    <span style="color: black;">if</span> (excelProperty != <span style="color: black;">null</span>){
    <span style="color: black;">int</span>index = excleProperty.index();
    String[] values = excleProperty.value();
    StringBuilder value =<span style="color: black;">new</span> StringBuilder();
    <span style="color: black;">for</span>(String v : values ){
    value.append(v);
    }
    result.put(index,value.toString());
    }
    }<span style="color: black;">return</span> result;
    }

    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;"><span style="color: black;">数据非空、格式校验</span></strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">数据非空校验、格式校验,<span style="color: black;">咱们</span>将<span style="color: black;">运用</span>hibernate-validator 校验器进行校验格式。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">定义validator 工具类</span></p><span style="color: black;">@component</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">EasyExcelValidatorHelper</span> </span>{
    <span style="color: black;">private</span> static Validtor validtor;

    <span style="color: black;">@Autowired</span>
    <span style="color: black;">public</span> EasyExcelValidatorHelper(Validtor validtor){
    <span style="color: black;">this</span>.EasyExcelValidatroHelper.validtor = validtor;
    }<span style="color: black;">public</span> static &lt;T&gt; String validateEntity(T obj) throws NoSuchFieldException{
    StringBuilder result = new StringBuilder();
    <span style="color: black;">// 执行校验</span>
    Set&lt;ConstraionViolation&lt;T&gt;&gt; <span style="color: black;">set</span> = validtor.validate(obj,Default<span style="color: black;">.<span style="color: black;">class</span>);</span>
    <span style="color: black;">// 组装结果</span>
    <span style="color: black;">if</span>(<span style="color: black;">set</span> != <span style="color: black;">null</span> &amp;&amp; !<span style="color: black;">set</span>.isEmpty()){
    <span style="color: black;">for</span> (ConstraionViolation&lt;T&gt; cv : <span style="color: black;">set</span>){
    Field declaredField = obj.getClass.getDeclaredField(cv.getPropertiyPath().toString());
    ExcelProperty<span style="color: black;">annotation</span>= declaredField.getAnnotation(ExcelProperty<span style="color: black;">.<span style="color: black;">class</span>);</span>
    result.append(<span style="color: black;">annotation</span>.value[<span style="color: black;">0</span>]+<span style="color: black;">":"</span>+cv.getMessage()).append(<span style="color: black;">";"</span>);
    }
    }
    <span style="color: black;">return</span> result;
    }
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">数据格式校验,<span style="color: black;">运用</span>EasyExcelListener 用来监听数据解析过程,其中,invok <span style="color: black;">办法</span>将逐行解析excel数据的时候将被调用</span></p><span style="color: black;">@Data</span>
    <span style="color: black;">// 省略部分注解</span>
    <span style="color: black;">public</span> <span style="color: black;">class</span> EasyExcelListener&lt;T&gt; <span style="color: black;">extends</span> AnalysisEventListener&lt;T&gt; {

    <span style="color: black;">/** 标记<span style="color: black;">是不是</span>执行数据解析 **/</span>
    <span style="color: black;">private</span> <span style="color: black;">boolean</span> baseMatching = <span style="color: black;">false</span>;

    <span style="color: black;">/** 解析成功的数据 **/</span>
    <span style="color: black;">private</span> List&lt;T&gt; successList = <span style="color: black;">new</span> ArrayList&lt;&gt;();

    <span style="color: black;">/** 解析失败的数据 **/</span>
    <span style="color: black;">private</span> List&lt;ExcelCheckErrDTO&lt;T&gt;&gt; errList = <span style="color: black;">new</span>ArrayList&lt;&gt;();<span style="color: black;">/** excel 对象的反射类 **/</span>
    <span style="color: black;">private</span> Class&lt;T&gt; clazz;

    <span style="color: black;">private</span> List&lt;T&gt; list;

    <span style="color: black;">private</span> ExcelCheckManager&lt;T&gt; excelCheckManager;

    <span style="color: black;">public</span>EasyExcelListener(ExcelCheckManager&lt;T&gt; excelCheckManager,Class&lt;T&gt; clazz){<span style="color: black;">this</span>.clazz = clazz;
    <span style="color: black;">this</span>.excelCheckManager = excelCheckManager;
    }

    <span style="color: black;">@Override</span>
    <span style="color: black;">public</span> <span style="color: black;">void</span>invok(T t,AnalysisContext context){<span style="color: black;">// 数据解析/转换完成,标记进入到解析起</span>
    baseMatching = <span style="color: black;">true</span>;
    <span style="color: black;">String</span> errMsg;
    <span style="color: black;">try</span> {
    <span style="color: black;">// 调用验证器验证数据格式</span>
    errMsg = EasyExcelValidatorHelper.validateEntity(t);
    }<span style="color: black;">catch</span>(Exception e){
    errMsg =<span style="color: black;">"解析数据出错"</span>;
    <span style="color: black;">// 省略部分代码</span>
    }
    <span style="color: black;">// 校验不<span style="color: black;">经过</span></span>
    <span style="color: black;">if</span> (!StringUtils.isEmpty(errMsg){
    <span style="color: black;">// 将错误数据放入错误列表中</span>
    ExcelChcekErrDTO errDTO = <span style="color: black;">new</span>ExcelChcekErrDTO(t,errMsg);
    errList.add(errDTO);
    }<span style="color: black;">else</span>{
    <span style="color: black;">// 校验成功</span>
    list.add(t);
    }
    <span style="color: black;">if</span> (list.size() &gt; <span style="color: black;">1000</span>){
    <span style="color: black;">// 业务校验</span>ExcelCheckResultVO excelCheckResultVO = excelCheckManager.checkImportExcel(list);
    successList.addAll(excelCheckResultVO.getSuccessDatas());
    errList.addAll(excelCheckResultVO.getErrDatas());
    list.clear();
    }
    }<span style="color: black;">/**
      * 所有数据解析完成后调用此<span style="color: black;">办法</span>
      */</span>
    <span style="color: black;">@Override</span>
    <span style="color: black;">public</span> <span style="color: black;">void</span>doAfterAllAnalysed(AnalysisContext context){
    ExcelCheckResultVO excelCheckResultVO = excelCheckManager.checkImportExcel(list);
    successList.addAll(excelCheckResultVO.getSuccessDatas());
    errList.addAll(excelCheckResultVO.getErrDatas());
    list.clear();
    }<span style="color: black;">@Override</span>
    <span style="color: black;">public</span> <span style="color: black;">void</span> invokHeadMap(Map&lt;Integer,<span style="color: black;">String</span>&gt; headMap,AnalysisContext context){

    <span style="color: black;">super</span>.invokHeadMap(headMap,context);<span style="color: black;">// 反射获取实体到属性值</span>
    Map&lt;Integer,<span style="color: black;">String</span>&gt; indexNameMap = getIndexNameMap(clazz);
    <span style="color: black;">// 将 headMap 与 indexNameMap 进行对比,<span style="color: black;">是不是</span>完全匹配</span>Set&lt;Integer&gt; keySet = indexNameMap.keySet();<span style="color: black;">for</span> (Integer key : keySet ){
    <span style="color: black;">if</span> (StringUtils.isEmpty(headMap.get(key)){
    <span style="color: black;">throw</span> ExcelAnalysisExcetpion(<span style="color: black;">"数据解析错误,请传入正确的excel格式"</span>);
    }
    <span style="color: black;">if</span>(!headMap.get(key).equals(indexNameMap.get(key)){
    <span style="color: black;">throw</span> ExcelAnalysisExcetpion(<span style="color: black;">"数据解析错误,请传入正确的excel格式"</span>);
    }
    }

    }

    <span style="color: black;">/**
      * 反射获取解析数据实体的@ExcleProperty 的value
      */</span>
    <span style="color: black;">public</span> Map&lt;Integer,<span style="color: black;">String</span>&gt; getIndexNameMap(Class clazz){

    Map&lt;Integer,<span style="color: black;">String</span>&gt; result = <span style="color: black;">new</span>HashMap&lt;&gt;();
    Field field;
    Field[] fields = clazz.getDeclaredFields();<span style="color: black;">for</span> (int i = <span style="color: black;">0</span>; i &lt; fields.length; i++){
    field = clazz.getDeclaredField(fields.getName());
    field.setAccessible(<span style="color: black;">true</span>);
    ExcelProperty excleProperty = field.getAnnotation(ExcelProperty.class);
    <span style="color: black;">if</span> (excelProperty != <span style="color: black;">null</span>){
    int index = excleProperty.index();<span style="color: black;">String</span>[] values = excleProperty.value();
    StringBuilder value = <span style="color: black;">new</span> StringBuilder();
    <span style="color: black;">for</span> (<span style="color: black;">String</span>v : values ){
    value.append(v);
    }
    result.put(index,value.toString());
    }
    }<span style="color: black;">return</span> result;
    }



    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">对需要进行校验对字段添加注解</span></p><span style="color: black;">@Data</span>
    <span style="color: black;">// 省略其他注解</span>
    public class ProjectInfoExcelDTO {
    <span style="color: black;">@ExcelProperty</span>(index=<span style="color: black;">0</span>,value=<span style="color: black;">"序列号"</span>)
    private String number;

    <span style="color: black;">@ExcelProperty</span>(index=<span style="color: black;">1</span>,value=<span style="color: black;">"项目名<span style="color: black;">叫作</span>"</span>)
    <span style="color: black;">@NotBlank</span>(message = <span style="color: black;">"请填写项目名<span style="color: black;">叫作</span>"</span>)
    private String name;

    <span style="color: black;">// 省略其他字段属性</span>
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">validator 常用注解传送门(</span><span style="color: black;">validator 常用注解</span><span style="color: black;">)。</span></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">EasyExcel 读取数据,并调用格式校验</span></p><span style="color: black;">@RestController</span>
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">ProjectInfoController</span> </span>{

    <span style="color: black;">@Resource</span>
    <span style="color: black;">private</span>AsyncExcelService asyncExcelService;<span style="color: black;">@Resource</span>
    <span style="color: black;">private</span> ProjectInfoService projectInfoService;
    <span style="color: black;">/**
      * 项目信息导入
      */</span>
    <span style="color: black;">@PostMapping</span>(<span style="color: black;">"/import"</span>)
    <span style="color: black;"><span style="color: black;">public</span> R <span style="color: black;">projectInfoImport</span><span style="color: black;">(MultipartFile file,HttpServletResponse response)</span></span>{
    InputStream inputStream = <span style="color: black;">null</span>;
    <span style="color: black;">int</span> lastNum = <span style="color: black;">0</span>;
    <span style="color: black;">try</span>{
    lastNum = EasyExcelUtils.lastNum(file.getInputStream());
    }<span style="color: black;">catch</span>(IOException e){
    <span style="color: black;">// 省略部分代码</span>
    }
    <span style="color: black;">if</span> (lastNum &lt;= <span style="color: black;">0</span> ){
    <span style="color: black;">throw</span> CustomExcetpoin(<span style="color: black;">500</span>,<span style="color: black;">"导入文件数据为空,请重新上传"</span>);
    }
    <span style="color: black;">// 获取系统配置的导入上限值</span>Integer importMax = asyncExcelService.asyncProjectImportMax();<span style="color: black;">if</span> (lastNum &gt; importMax ){
    <span style="color: black;">// 达到上限,走异步</span>
    asyncExcelService.asyncProjectImport(file,response);
    <span style="color: black;">return</span> R.success(<span style="color: black;">"数据导入成功,因数据量比<span style="color: black;">很强</span>,已转为异步导入"</span>);
    }
    <span style="color: black;">// 省略部分代码</span>

    <span style="color: black;">// 实例数据解析监听器</span>
    EasyExcelListener&lt;ProjectInfoDTO&gt; easyExcleListener = <span style="color: black;">new</span>EasyExcelListener(projectInfoService,ProjectInfoDTO<span style="color: black;">.<span style="color: black;">class</span>)</span>;
    <span style="color: black;">// 文件读取/解析,并注册监听器</span>
    EasyExcle.read(file.getInputStream(),ProjectInfoDTO<span style="color: black;">.<span style="color: black;">class</span>,<span style="color: black;">easyExcleListener</span>).<span style="color: black;">sheet</span>(1).<span style="color: black;">doRead</span>()</span>;
    <span style="color: black;">// 获取错误数据</span>List&lt;ExcelCheckErrDTO&lt;ProjectInfoExcelDTO&gt;&gt; errList = easyExcleListener.getErrList();<span style="color: black;">// 获取解析成功到数据</span>List&lt;ProjectinfoExcelDTO&gt; successList = easyExcleListener.getSuccessList();<span style="color: black;">// <span style="color: black;">倘若</span>错误数据不为空,将错误数据写入到excel文件,并输出到浏览器</span>
    <span style="color: black;">// 省略代码</span>

    <span style="color: black;">// 将成功到数据,批量写入到数据库中</span>
    <span style="color: black;">// 省略代码</span>


    <span style="color: black;">// 省略其他代码</span>
    }
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">ProjectInfoService 声明与实现,<span style="color: black;">由于</span>需要做业务数据到校验,<span style="color: black;">因此呢</span>ProjectInfoService 需要继承 ExcelCheckManager 验证管理器</span></p><span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">interface</span> <span style="color: black;">ProjectInfoService</span> <span style="color: black;">extends</span> <span style="color: black;">ExcelCheckManager</span></span>{

    }
    <span style="color: black;">@Service</span>
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">ProjectInfoServiceImpl</span> <span style="color: black;">implements</span> <span style="color: black;">ProjectInfoService</span> </span>{
    <span style="color: black;">// 省略部分代码</span>

    <span style="color: black;">@Override</span>
    <span style="color: black;"><span style="color: black;">public</span> ExcelCheckResultVO <span style="color: black;">checkImportExcel</span><span style="color: black;">(List&lt;ProjectInfoExcelDTO&gt; datas)</span></span>{
    <span style="color: black;">// 省略代码</span>
    }
    }<p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><strong style="color: blue;"><span style="color: black;">输出错误报告</span></strong></p>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">文件校验完成之后,<span style="color: black;">倘若</span><span style="color: black;">无</span>完全<span style="color: black;">经过</span>,需要将错误对数据以及错误信息<span style="color: black;">经过</span>easyExcel 输出到客户端。</span></p><span style="color: black;">@RestController</span>
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;"><span style="color: black;">class</span> <span style="color: black;">ProjectInfoController</span> </span>{

    <span style="color: black;">@Resource</span>
    <span style="color: black;">private</span> AsyncExcelService asyncExcelService;

    <span style="color: black;">@Resource</span>
    <span style="color: black;">private</span> ProjectInfoService projectInfoService;
    <span style="color: black;">/**
      * 项目信息导入
      */</span>
    <span style="color: black;">@PostMapping</span>(<span style="color: black;">"/import"</span>)
    <span style="color: black;"><span style="color: black;">public</span> R <span style="color: black;">projectInfoImport</span><span style="color: black;">(MultipartFile file,HttpServletResponse response)</span></span>{
    InputStream inputStream = <span style="color: black;">null</span>;
    <span style="color: black;">int</span> lastNum = <span style="color: black;">0</span>;
    <span style="color: black;">try</span> {
    lastNum = EasyExcelUtils.lastNum(file.getInputStream());
    }<span style="color: black;">catch</span>(IOException e){
    <span style="color: black;">// 省略部分代码</span>
    }
    <span style="color: black;">if</span> (lastNum &lt;= <span style="color: black;">0</span> ){
    <span style="color: black;">throw</span> CustomExcetpoin(<span style="color: black;">500</span>,<span style="color: black;">"导入文件数据为空,请重新上传"</span>);
    }
    <span style="color: black;">// 获取系统配置的导入上限值</span>Integer importMax = asyncExcelService.asyncProjectImportMax();<span style="color: black;">if</span> (lastNum &gt; importMax ){
    <span style="color: black;">// 达到上限,走异步</span>
    asyncExcelService.asyncProjectImport(file,response);
    <span style="color: black;">return</span> R.success(<span style="color: black;">"数据导入成功,因数据量比<span style="color: black;">很强</span>,已转为异步导入"</span>);
    }
    <span style="color: black;">// 省略部分代码</span>

    <span style="color: black;">// 实例数据解析监听器</span>
    EasyExcelListener&lt;ProjectInfoDTO&gt; easyExcleListener = <span style="color: black;">new</span> EasyExcelListener(projectInfoService,ProjectInfoDTO<span style="color: black;">.<span style="color: black;">class</span>)</span>;
    <span style="color: black;">// 文件读取/解析,并注册监听器</span>
    EasyExcle.read(file.getInputStream(),ProjectInfoDTO<span style="color: black;">.<span style="color: black;">class</span>,<span style="color: black;">easyExcleListener</span>).<span style="color: black;">sheet</span>(1).<span style="color: black;">doRead</span>()</span>;
    <span style="color: black;">// 获取错误数据</span>List&lt;ExcelCheckErrDTO&lt;ProjectInfoExcelDTO&gt;&gt; errList = easyExcleListener.getErrList();<span style="color: black;">// 获取解析成功到数据</span>
    List&lt;ProjectinfoExcelDTO&gt; successList = easyExcleListener.getSuccessList();
    <span style="color: black;">// <span style="color: black;">倘若</span>错误数据不为空,将错误数据写入到excel文件,并输出到浏览器</span>
    <span style="color: black;">if</span> (errList.size() &gt; <span style="color: black;">0</span> ){
    <span style="color: black;">// 省略部分代码</span>
    }
    <span style="color: black;">// 将成功到数据,批量写入到数据库中</span>
    <span style="color: black;">// 省略代码</span>


    <span style="color: black;">// 省略其他代码</span>
    }
    }<h1 style="color: black; text-align: left; margin-bottom: 10px;">异步导入</h1>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">异步导入操作,将思考几个问题:</span></p><span style="color: black;">导入文件存到什么<span style="color: black;">地区</span>?当一个同步请求结束之后,后续<span style="color: black;">咱们</span>想再次拿到该请求到数据,<span style="color: black;">咱们</span>应该<span style="color: black;">思虑</span>将文件放到某一个单独到<span style="color: black;">地区</span>,<span style="color: black;">供给</span><span style="color: black;">咱们</span>二次<span style="color: black;">运用</span>,<span style="color: black;">例如</span>:自己到文件服务器、oss 存储等,<span style="color: black;">这儿</span><span style="color: black;">咱们</span><span style="color: black;">运用</span>自己的文件服务器。</span><span style="color: black;">怎么异步执行?<span style="color: black;">咱们</span><span style="color: black;">能够</span><span style="color: black;">运用</span>新启用一个本地线程去执行<span style="color: black;">咱们</span>的操作,不影响当前请求主线程的操作,<span style="color: black;">亦</span>是<span style="color: black;">能够</span>的,<span style="color: black;">然则</span><span style="color: black;">思虑</span>到执行重试问题,<span style="color: black;">咱们</span>将<span style="color: black;">运用</span>(#xxl-job)分布式调度系统,进行调度执行任务。</span><span style="color: black;">客户<span style="color: black;">怎样</span>查看任务执行状态?<span style="color: black;">咱们</span>需要<span style="color: black;">供给</span>一个任务执行日志列表,让用户<span style="color: black;">能够</span>清晰的看到<span style="color: black;">这次</span>导出的任务<span style="color: black;">是不是</span>执行完成/<span style="color: black;">是不是</span>存在导入错误。</span><span style="color: black;">怎么将错误报告输出给到客户?<span style="color: black;">咱们</span>需要将导入到错误报告文件(excel)上传至文件服务器,<span style="color: black;">供给</span>用户二次或多次下载<span style="color: black;">运用</span>;<span style="color: black;">同期</span>,需要将文件信息<span style="color: black;">保留</span>至任务执行日志信息中,为用户<span style="color: black;">供给</span>下载入口。</span>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">定义通用的job handler 父类 AsyncTaskHandler ,所有需要<span style="color: black;">运用</span>xxl-job 发起异步任务和给xxl-job 发起回调,都需要继承AsyncTaskHandler ,并实现execute 抽象<span style="color: black;">办法</span>。</span></p><span style="color: black;">public</span> <span style="color: black;">abstract</span> <span style="color: black;">class</span>AsyncTaskHandler &lt;T<span style="color: black;">extends</span> AsyncTaskPramsDTO&gt; {

    <span style="color: black;">/** xxl-job server 端<span style="color: black;">供给</span>的创建任务接口 uri **/</span>
    <span style="color: black;">private</span> final <span style="color: black;">static</span> <span style="color: black;">String</span> JOB_ADMIN_URI = <span style="color: black;">"/outapi/asyn/"</span>;

    <span style="color: black;">/** 与xxl-job server 通讯的加密密钥对 **/</span>
    <span style="color: black;">@Setter</span>
    <span style="color: black;">protected</span> <span style="color: black;">String</span> publicKey;

    <span style="color: black;">/**
      * xxl-job server 回调对<span style="color: black;">办法</span>
      */</span>
    <span style="color: black;">public</span> <span style="color: black;">abstract</span> ReturnT&lt;<span style="color: black;">String</span>&gt; execute(<span style="color: black;">String</span> params);

    <span style="color: black;">/**
      * 向xxl-job 发起调度任务
      */</span>
    <span style="color: black;">public</span> JobResponseDTO sendTask(T prams){
    prams.setUser(<span style="color: black;">null</span>);

    <span style="color: black;">// 省略部分代码,<span style="color: black;">关联</span>内容,请<span style="color: black;">查找</span>xxl-job server 端所<span style="color: black;">供给</span>的接口文档</span>

    <span style="color: black;">// 将 params 中的 user 对象<span style="color: black;">保留</span>至redis 中,xxl-job 接口有长度限制</span>
    }

    <span style="color: black;">public</span> <span style="color: black;">abstract</span> RedisUtil getRedisUtil();

    <span style="color: black;">public</span> <span style="color: black;">abstract</span> JobProperties getJobProperties();

    <span style="color: black;">/** 回调<span style="color: black;">办法</span>名<span style="color: black;">叫作</span> **/</span>
    <span style="color: black;">public</span> <span style="color: black;">abstract</span> <span style="color: black;">String</span> getHandlerName();
    }
    定义 AsyncTaskPramsDTO 异步参数实体
    <span style="color: black;">@Data</span>
    <span style="color: black;">// 省略其他注解</span>
    <span style="color: black;">public</span> <span style="color: black;">class</span> AsyncTaskPramsDTO {

    <span style="color: black;">private</span> <span style="color: black;">String</span> requestId;

    }<h1 style="color: black; text-align: left; margin-bottom: 10px;">数据导出</h1>
    <p style="font-size: 16px; color: black; line-height: 40px; text-align: left; margin-bottom: 15px;"><span style="color: black;">数据导出功能常指,客户想将系统中的<span style="color: black;">关联</span>(<span style="color: black;">根据</span><span style="color: black;">查找</span><span style="color: black;">要求</span>筛选)数据<span style="color: black;">经过</span>excel形式<span style="color: black;">保留</span>到自己本地。在数据导出过程中,需要<span style="color: black;">经过</span>数据筛选<span style="color: black;">要求</span>将数据从系统数据库中筛选出来,<span style="color: black;">而后</span><span style="color: black;">经过</span><span style="color: black;">必定</span>格式(excel导出模版格式)写入到excel中,最后输出到客户端(浏览器)<span style="color: black;">供给</span>客户下载<span style="color: black;">保留</span>到本地。</span></p>




7wu1wm0 发表于 2024-11-16 23:18:20

你的努力一定会被看见,相信自己,加油。

b1gc8v 发表于 6 天前

期待与你深入交流,共探知识的无穷魅力。

1fy07h 发表于 前天 22:25

楼主发的这篇帖子,我觉得非常有道理。
页: [1]
查看完整版本: 运用EasyExcel 导入数据,失败原由数据导出