easyExcel关于导入导出的用法及校验

easyExcel关于导入导出的用法及校验

一、导入

(一)controller层

	@DataLog(operationName = "导入了因私出国", operationDesc = "", methodType = MethodTypeEnum.IMPORT_TYPE)
    @ApiOperation(value = "因私出国-导入")
    @ApiOperationSupport(order = 5)
    @GetMapping(Urls.Abroad.importAbroad)
    public JsonObject<Object> importAbroad(String fileName, HttpServletRequest request) {
        List<String> list = csAbroadService.importAbroad(fileName, request);
        return new JsonSuccessObject<>(list, "导入成功");
    }

请求方式用post或get都行,这里使用get是为了做校验下载异常数据文件。fileName是前端通过公共组件上传后的文件名,后端可以获取上传路径,拼一下就能取得上传后文件的绝对路径。

也可以使用multipart/form-data的请求形式上传,这时后端需要改变下接收方式:

	@DataLog(operationName = "导入了离任审计", operationDesc = "", methodType = MethodTypeEnum.IMPORT_TYPE)
    @ApiOperation(value = "离任审计-导入")
    @ApiOperationSupport(order = 12)
    @PostMapping(Urls.OffAudit.importAudit)
    public JsonObject<Object> importAudit(MultipartFile file, HttpServletRequest request) throws IOException {
        List<String> list = csOffAuditService.importAudit(file, request);
        return new JsonSuccessObject<>(list, "导入成功");
    }

(二)service层

	/**
     * 因私出国-导入
     *
     * @param fileName
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<String> importAbroad(String fileName, HttpServletRequest request) {
        List<String> recordIds = new ArrayList<>();
        String userId = userSessionService.getCurrentUserId(request);
        String nodeId = userSessionService.getCurrentUserNodeId(request);
        String upload = fileConfigProperties.getUpload();
        String filePath = "";
        filePath = upload + File.separator + fileName;

        // 读Excel
        EasyExcel.read(filePath, CsA12Do.class,
                new AbroadListener(recordIds, userId, nodeId, upload, this))
                .sheet().doRead();

        // 控制同步功能
        if (recordIds.size() == 0) {
            return null;
        }

        // by hansc 12.10 调用人员库接口
        LambdaQueryWrapper<CsA12> wrapper = Wrappers.lambdaQuery();
        wrapper.in(CsA12::getRecordid, recordIds);
        List<CsA12> list = list(wrapper);
        for (CsA12 item : list) {
            csA01Service.insertExportInPerson(item.geta00a(), item.geta1250(), item.geta1251(), request);
        }

        // 同步A00
        Integer a00 = csRecordService.synA00("a12", "A1250", "RECORDID",
                nodeId, recordIds);

        // 同步现任职务
        /*Integer job = csRecordService.synJob("a12", "A1251", "RECORDID",
                nodeId, recordIds);*/

        // 同步高管类别
        csA12Mapper.synGrade(recordIds, nodeId);

        // 删除文件
        // boolean b = DownloadUtil.deleteFile(filePath);

        return recordIds;
    }

filePath即为上传的文件的绝对路径。EasyExcel的read方法可以读取Excel的sheet页的内容。三个参数,分别是文件绝对路径,一个Class类和一个自定义的监听器:

1、 Class类对应的是读取的Excel文件的模板类,它与Excel表格列对应:
easyExcel关于导入导出的用法及校验
easyExcel关于导入导出的用法及校验
index是从0开始,CsA12Do模板类的属性和Excel表格列一一对应。

2、 自定义的监听器类需要继承easyExcel的一个父类AnalysisEventListener,详情下面继续说
easyExcel关于导入导出的用法及校验
3、 sheet()方法为指定读取哪个sheet页,参数可以填sheet页的页名或者序号,不写的话默认就是Sheet1:
easyExcel关于导入导出的用法及校验

(三)监听器

1、 easyExcel的原理是按行读取,即每次读取一行时新建一个监听器类,在监听器类中对读取到的内容进行处理。因为是通过new关键字新建监听器类,因此不能使用sprinboot的注入功能注入我们的bean对象,所以可以考虑使用构造器,将我们需要的bean对象以及一些自定义变量传进监听器中。

package com.bdsoft.cs.controller.utils;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.bdsoft.cs.entity.CodeValue;
import com.bdsoft.cs.entity.CsA12;
import com.bdsoft.cs.query.CsA12Do;
import com.bdsoft.cs.service.daily.CsAbroadService;
import com.bdsoft.cs.utils.ConvertUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @author hanshaocong
 * @since 2021-10-22
 */
public class AbroadListener extends AnalysisEventListener<CsA12Do> {

    private static final int batchSize = 1000;

    private LocalDateTime time = LocalDateTime.now();

    private List<CsA12Do> list = new ArrayList<>(batchSize);

    // 高管类别GGLB_02
    // private List<CodeValue> listOfA1252;

    // 前往国家/地区ZB01C
    private List<CodeValue> listOfA1204;

    // 出国(境)事由CGGL_DMB_01
    private List<CodeValue> listOfZdyxa1202;

    // 存储主键,返回给前端
    private List<String> recordIds;

    private String userId;

    private String nodeId;

    private String upload;

    private CsAbroadService csAbroadService;

    private static Boolean formatFlag = true; // 格式校验开关:true开,false关

    private static Boolean repeatFlag = true; // 重复校验开关


    public AbroadListener(List<String> recordIds, String userId, String nodeId, String upload, CsAbroadService csAbroadService) {
        this.recordIds = recordIds;
        this.userId = userId;
        this.nodeId = nodeId;
        this.upload = upload;
        this.csAbroadService = csAbroadService;
        // this.listOfA1252 = csAbroadService.getEntity("GGLB_02");
        this.listOfA1204 = csAbroadService.getEntity("ZB01C");
        this.listOfZdyxa1202 = csAbroadService.getEntity("CGGL_DMB_01");
    }


    @Override
    public void invoke(CsA12Do csA12Do, AnalysisContext analysisContext) {
        list.add(csA12Do);
        if (list.size() >= batchSize) {
            this.saveData();
            list.clear();
        }
    }


    /**
     * 数据导入逻辑:
     * 1、校验;2、导入
     */
    @Transactional(rollbackFor = Exception.class)
    void saveData() {
        if (verify()) {
            insert();
        }
    }


    /**
     * 校验
     *
     * @return 导入功能开关
     */
    Boolean verify() {
        String separator = File.separator;
        String fileName = "因私出国_导入数据异常.xlsx"; // 报错提示文件名
        String filePath = upload + separator + fileName; // 生成的文件的绝对路径
        List<CsA12Do> formatList = new ArrayList<>(); // 格式错误的数据
        List<CsA12Do> repeatList = new ArrayList<>(); // 重复的数据

        // ① 日期校验
        if (formatFlag) {
            for (CsA12Do item : list) {
                CsA12Do csA12Do = new CsA12Do();
                String a1201 = item.getA1201(); // 出国日期
                String a1203 = item.getA1203(); // 回国日期
                csA12Do.setA1201(ConvertUtil.isNotTime(a1201) ? a1201 : null);
                csA12Do.setA1203(ConvertUtil.isNotTime(a1203) ? a1203 : null);
                csA12Do.setA00a(item.getA00a());
                if (csA12Do.getA1201() != null || csA12Do.getA1203() != null) {
                    formatList.add(csA12Do);
                }
            }
        }

        // ② 重复性校验
        if (repeatFlag) {
            for (CsA12Do item : list) {
                CsA12Do query = new CsA12Do();
                BeanUtils.copyProperties(item, query);
                query.setA1204(ConvertUtil.getDmcod(item.getA1204(), listOfA1204));
                query.setZdyxa1202(ConvertUtil.getDmcod(item.getZdyxa1202(), listOfZdyxa1202));

                LambdaQueryWrapper<CsA12> wrapper = Wrappers.lambdaQuery();
                wrapper.eq(CsA12::geta00a, query.getA00a())
                        .eq(CsA12::geta1250, query.getA1250())
                        .eq(CsA12::geta1251, query.getA1251())
                        .eq(CsA12::geta1204, query.getA1204())
                        .apply("date_format(a1201,'%Y-%m-%d') = {0}", query.getA1201())
                        .apply("date_format(a1203,'%Y-%m-%d') = {0}", query.getA1203())
                        .eq(CsA12::getZdyxa1202, query.getZdyxa1202())
                        .eq(CsA12::getDeleteflag, 0);
                List<CsA12> list = csAbroadService.list(wrapper);

                if (list.size() > 0) {
                    repeatList.add(item);
                }
            }
        }

        return csAbroadService.downloadExcel(filePath, CsA12Do.class, formatList, repeatList, null);
    }


    /**
     * 导入数据
     */
    void insert() {
        List<CsA12> csA12s = new ArrayList<>();

        for (CsA12Do item : list) {
            CsA12 csA12 = new CsA12();
            String uuid = UUID.randomUUID().toString();
            recordIds.add(uuid);
            BeanUtils.copyProperties(item, csA12);
            csA12.setRecordid(uuid);
            csA12.setCreatetime(time);
            csA12.setCreateuser(userId);
            csA12.setUpdatetime(time);
            csA12.setUpdateuser(userId);
            csA12.setDeleteflag(0);

            // 加权限
            csA12.setNodeid(nodeId);
            // 码表转换:前往国家/地区
            csA12.seta1204Text(item.getA1204());
            csA12.seta1204(ConvertUtil.getDmcod(item.getA1204(), listOfA1204));
            // 高管类别,先存汉字再转义
            /*csA12.seta1252Text(item.getA1252());
            csA12.seta1252(ConvertUtil.getDmcod(item.getA1252(), listOfA1252));*/
            // 出国事由
            csA12.setZdyxa1202Text(item.getZdyxa1202());
            csA12.setZdyxa1202(ConvertUtil.getDmcod(item.getZdyxa1202(), listOfZdyxa1202));
            // 日期转换:出国日期
            csA12.seta1201(ConvertUtil.stringToLocalDateTime(item.getA1201()));
            // 回国日期
            csA12.seta1203(ConvertUtil.stringToLocalDateTime(item.getA1203()));

            csA12s.add(csA12);
        }

        // 排序
        int count = csAbroadService.count() + 1;
        for (int i = 0; i < csA12s.size(); i++, count++) {
            csA12s.get(i).setPorder(BigDecimal.valueOf(count));
        }

        boolean b = csAbroadService.saveBatch(csA12s);
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        this.saveData();
    }
}

2、 代码中在监听器类中定义了需要自定义全局变量,新建监听器时通过调用含参的构造器,给新建的监听器类的对象的全局变量赋值,就可以在监听器对象中调用这些属性和类了。

easyExcel关于导入导出的用法及校验

3、 看类的目录结构可以发现,Listener接口被ReadListener接口继承,ReadListener接口又被AnalysisEventListener类实现,AnalysisEventListener类又被我们的业务类AbroadListener继承,所以在我们的自定义监听器类AbroadListener中,需要实现ReadListener接口的几个方法,见上图。

4、 invoke方法首先执行,读一行Excel新建一次监听器类,然后执行一次invoke方法。

private List<CsA12Do> list = new ArrayList<>(batchSize);

@Override
    public void invoke(CsA12Do csA12Do, AnalysisContext analysisContext) {
        list.add(csA12Do);
        if (list.size() >= batchSize) {
            this.saveData();
            list.clear();
        }
    }

5、 这个全局变量list里面存的是我们读取到的Excel行数据。batchSize是我们自定义的读取容量,根据实际业务设置大小,比如设置为100就是读取100行然后执行saveData方法。

	/**
     * 数据导入逻辑:
     * 1、校验;2、导入
     */
    @Transactional(rollbackFor = Exception.class)
    void saveData() {
        if (verify()) {
            insert();
        }
    }

6、 saveData方法中先执行校验方法verify(),没有问题后再执行写入数据操作insert()。

	/**
     * 校验
     *
     * @return 导入功能开关
     */
    Boolean verify() {
        String separator = File.separator;
        String fileName = "因私出国_导入数据异常.xlsx"; // 报错提示文件名
        String filePath = upload + separator + fileName; // 生成的文件的绝对路径
        List<CsA12Do> formatList = new ArrayList<>(); // 格式错误的数据
        List<CsA12Do> repeatList = new ArrayList<>(); // 重复的数据

        // ① 日期校验
        if (formatFlag) {
            for (CsA12Do item : list) {
                CsA12Do csA12Do = new CsA12Do();
                String a1201 = item.getA1201(); // 出国日期
                String a1203 = item.getA1203(); // 回国日期
                csA12Do.setA1201(ConvertUtil.isNotTime(a1201) ? a1201 : null);
                csA12Do.setA1203(ConvertUtil.isNotTime(a1203) ? a1203 : null);
                csA12Do.setA00a(item.getA00a());
                if (csA12Do.getA1201() != null || csA12Do.getA1203() != null) {
                    formatList.add(csA12Do);
                }
            }
        }

        // ② 重复性校验
        if (repeatFlag) {
            for (CsA12Do item : list) {
                CsA12Do query = new CsA12Do();
                BeanUtils.copyProperties(item, query);
                query.setA1204(ConvertUtil.getDmcod(item.getA1204(), listOfA1204));
                query.setZdyxa1202(ConvertUtil.getDmcod(item.getZdyxa1202(), listOfZdyxa1202));

                LambdaQueryWrapper<CsA12> wrapper = Wrappers.lambdaQuery();
                wrapper.eq(CsA12::geta00a, query.getA00a())
                        .eq(CsA12::geta1250, query.getA1250())
                        .eq(CsA12::geta1251, query.getA1251())
                        .eq(CsA12::geta1204, query.getA1204())
                        .apply("date_format(a1201,'%Y-%m-%d') = {0}", query.getA1201())
                        .apply("date_format(a1203,'%Y-%m-%d') = {0}", query.getA1203())
                        .eq(CsA12::getZdyxa1202, query.getZdyxa1202())
                        .eq(CsA12::getDeleteflag, 0);
                List<CsA12> list = csAbroadService.list(wrapper);

                if (list.size() > 0) {
                    repeatList.add(item);
                }
            }
        }

        return csAbroadService.downloadExcel(filePath, CsA12Do.class, formatList, repeatList, null);
    }

7、 校验方法中对日期格式和数据重复性两个内容进行了校验,当日期格式不符合标准的正则时或者Excel表格的数据与数据库现有数据有重复时,将这些数据记录在Excel中并通过浏览器下载。

二、导出

1、 下载走的是我自定义的一个公共方法downloadExcel()

	/**
     * 下载异常Excel,可指定排除列
     *
     * @param filePath      模板路径
     * @param clz           模板输出实体类
     * @param formatList    格式异常数据集合
     * @param repeatList    重复数据集合
     * @param excludeColumn 指定不导出的列名
     * @return
     */
    @Override
    public Boolean downloadExcel(String filePath, Class<?> clz, List<?> formatList, List<?> repeatList, List<String> excludeColumn) {
        // 若异常数据源都为空,则导入数据无异常,正常执行导入
        if (ConvertUtil.listIsNull(formatList) && ConvertUtil.listIsNull(repeatList)) {
            return true;
        }

        Boolean flag = true;  // 导入功能开关,默认开
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(filePath, clz).excludeColumnFiledNames(excludeColumn).build();
            if (ConvertUtil.listIsNotNull(formatList)) {
                WriteSheet sheet = EasyExcel.writerSheet("格式异常页").build(); // 一个Excel文件两个Sheet页
                excelWriter.write(formatList, sheet);
            }
            if (ConvertUtil.listIsNotNull(repeatList)) {
                WriteSheet sheet = EasyExcel.writerSheet("数据重复页").build();
                excelWriter.write(repeatList, sheet);
            }
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }

        try {
            if (ConvertUtil.listIsNotNull(formatList) || ConvertUtil.listIsNotNull(repeatList)) { // 当数据源不为空时才下载
                DownloadUtil.download(filePath);
                flag = false; // 若下载了Excel,则不执行导入功能,导入功能开关关闭
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            DownloadUtil.deleteFile(filePath);
        }
        return flag;
    }

2、下载Excel用的也是easyExcel的write方法,即导出方法。

	excelWriter = EasyExcel.write(filePath, clz).excludeColumnFiledNames(excludeColumn).build();

write方法有两个参数,一个是生成的文件的绝对路径(含文件后缀),另一个是生成的表格对应的模板类,这里用的还是CsA12Do类,和导入共用了一个模板类:

	@ColumnWidth(20)

这个注解控制Excel表格的列宽,

    @ExcelProperty(index = 0, value = "高管姓名")

这个主键控制了表头列的位置和列名。

	excludeColumnFiledNames(excludeColumn)

该方法指定哪些表头列不输出,参数为列名的集合(即CsA12Do的属性名)

	WriteSheet sheet = EasyExcel.writerSheet("数据重复页").build();

writerSheet方法设置sheet页的名字

	excelWriter.write(repeatList, sheet);

write方法将数据源和表格模板绑定,即填充数据

	DownloadUtil.download(filePath);

自定义了一个下载方法,代码附在后面

3、verify方法校验完,如果有问题,则将问题数据导出到Excel中,通过浏览器下载下来;
校验完没有问题,则执行insert方法,也就是写入操作,写入操作不多说。

4、doAfterAllAnalysed方法为监听器类生成后最后执行的一个方法。

P.S.
1、附上下载方法:

public static void download(String filePath) throws IOException {
        HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
        download(filePath, response, request);
    }

    public static void download(String filePath, HttpServletResponse response, HttpServletRequest request) throws IOException {
        ServletOutputStream os = null;

        try {
            if (response == null) {
                response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
            }

            ByteArrayOutputStream out = null;
            FileInputStream inStream = null;
            BufferedInputStream bis = null;
            File file = new File(filePath);
            String fileName = file.getName();
            if (request.getHeader("User-Agent").toLowerCase().indexOf("msie") <= 0 && request.getHeader("User-Agent").indexOf("like Gecko") <= 0) {
                fileName = new String(fileName.replaceAll(" ", "").getBytes("UTF-8"), "ISO8859-1");
            } else {
                fileName = URLEncoder.encode(fileName, "UTF-8");
            }

            if (file.exists()) {
                response.reset();
                response.setContentType("application/octet-stream");
                response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
                os = response.getOutputStream();
                out = new ByteArrayOutputStream();
                inStream = new FileInputStream(filePath);
                bis = new BufferedInputStream(inStream);

                for(int c = bis.read(); c != -1; c = bis.read()) {
                    out.write(c);
                }

                bis.close();
                inStream.close();
                os.write(out.toByteArray());
            }
        } catch (IOException var13) {
            logger.error("异常信息", var13);
        } finally {
            if (os != null) {
                os.close();
            }

        }

    }

这个方法不能自定义下载的文件名,可以改造下:

	/**
     * 下载文件
     *
     * @param filePath     文件绝对路径
     * @param templateName 文件名
     * @throws IOException
     */
    public static void download(String filePath, String templateName) throws IOException {
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
        download(filePath, templateName, response, request);
    }


    /**
     * 下载文件
     *
     * @param filePath     文件绝对路径
     * @param templateName 文件名
     * @param response
     * @param request
     * @throws IOException
     */
    public static void download(String filePath, String templateName, HttpServletResponse response, HttpServletRequest request) throws IOException {
        ServletOutputStream os = null;

        try {
            if (response == null) {
                response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
            }

            ByteArrayOutputStream out = null;
            FileInputStream inStream = null;
            BufferedInputStream bis = null;
            File file = new File(filePath);
            String fileName = file.getName();

            if (StringUtility.isNotNull(templateName)) {
                fileName = templateName;
            }

            if (request.getHeader("User-Agent").toLowerCase().indexOf("msie") <= 0 && request.getHeader("User-Agent").indexOf("like Gecko") <= 0) {
                fileName = new String(fileName.replaceAll(" ", "").getBytes("UTF-8"), "ISO8859-1");
            } else {
                fileName = URLEncoder.encode(fileName, "UTF-8");
            }

            if (file.exists()) {
                response.reset();
                response.setContentType("application/octet-stream");
                response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
                os = response.getOutputStream();
                out = new ByteArrayOutputStream();
                inStream = new FileInputStream(filePath);
                bis = new BufferedInputStream(inStream);

                for (int c = bis.read(); c != -1; c = bis.read()) {
                    out.write(c);
                }

                bis.close();
                inStream.close();
                os.write(out.toByteArray());
            }
        } catch (IOException var13) {
            var13.printStackTrace();
        } finally {
            if (os != null) {
                os.close();
            }
        }
    }

2、附上日期正则校验:

	// 年月日正则,标准格式:2021-12-23, 2021-1-1
    public static String regex = "^((((19|20)\\d{2})-(0?[13-9]|1[012])-(0?[1-9]|[12]\\d|30))|(((19|20)\\d{2})-(0?[13578]|1[02])-31)|(((19|20)\\d{2})-0?2-(0?[1-9]|1\\d|2[0-8]))|((((19|20)([13579][26]|[2468][048]|0[48]))|(2000))-0?2-29))$";

	/**
     * 日期格式校验:不是标准时间格式
     *
     * @param time
     * @return
     */
    public static boolean isNotTime(String time) {
        return !isTime(time);
    }


    /**
     * 日期格式正则校验:是标准时间格式
     *
     * @param time 日期格式:2021-12-21, 2021-1-1
     * @return
     */
    public static boolean isTime(String time) {
        return time != null && Pattern.matches(regex, time);
    }

以上

上一篇:随笔001-2022.02.03


下一篇:动力节点的MySQL的34题目的第7题的我的参考答案:求薪水的平均等级最低的部门的名称