EasyExcel读取写入简单使用


官方文档 https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read

一. 读取

1.1 定义实体类

@Data
public class EmailImportParam implements Serializable {
    private static final long serialVersionUID = 1L;

    @ApiModelProperty(value = "数据添加方式 1 系统自动通过邮箱域名查找备案的公司名称 2 手动输入")
    @NotNull
    @ExcelProperty(index = 0)
    private Integer dataType;

    @ApiModelProperty(value = "备用公司名称")
    @ExcelProperty(index = 1)
    private String inputCompanyName;

    @ApiModelProperty(value = "邮箱地址")
    @NotBlank
    @ExcelProperty(index = 2)
    private String email;

}
  • 如果不使用标题 @ExcelProperty(index = 0),标题从0开启
  • 如果使用标题 @ExcelProperty("数据添加方式")

1.2 不检查数据批量导入

接口实现

    @Override
    @SneakyThrows
    public Boolean importExcel(MultipartFile file) {
        EasyExcel.read(file.getInputStream(), EmailImportParam.class, new EmailDataListener(this)).sheet().doRead();
        return true;
    }

添加EmailDataListener

package com.tophant.pentestinfoinv.listener;

import cn.hutool.json.JSONUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.tophant.pentestinfoinv.common.domain.query.EmailAddParam;
import com.tophant.pentestinfoinv.common.domain.query.EmailImportParam;
import com.tophant.pentestinfoinv.service.IEmailService;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @Description email excel导入监听类
 * @Author WanFei
 * @Date 2022/7/4 16:43
 **/
@Slf4j
public class EmailDataListener implements ReadListener<EmailImportParam> {

    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 100;
    private List<EmailImportParam> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
     */
    private IEmailService emailService;

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param emailService
     */
    public EmailDataListener(IEmailService emailService) {
        this.emailService = emailService;
    }

    /**
     * 这个每一条数据解析都会来调用
     *
     * @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(EmailImportParam data, AnalysisContext context) {
        log.info("解析到一条数据:{}", JSONUtil.toJsonStr(data));
        cachedDataList.add(data);
        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 所有数据解析完成了 都会来调用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 这里也要保存数据,确保最后遗留的数据也存储到数据库
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        List<EmailAddParam> emailAddParams = cachedDataList.stream().map(emailImportParam -> new EmailAddParam()
                .setEmails(Set.of(emailImportParam.getEmail()))
                .setDataType(emailImportParam.getDataType())
                .setInputCompanyName(emailImportParam.getInputCompanyName()))
                .collect(Collectors.toList());
        emailAddParams.forEach(emailService::create);
        log.info("存储数据库成功!");
    }
}

1.3 检查数据一次性导入

    @Override
    @SneakyThrows
    public Boolean importExcel(MultipartFile file) {
        File newFile = MultipartFileUtil.multipartFileToFile(file);
        Assert.isFalse(ObjectUtil.isNull(file) || ObjectUtil.isNull(newFile), "文件不能为空!");
        // 获取文件类型
        String fileType = FileUtil.getType(newFile);
        log.info("上传文件的扩展名: {}", fileType);
        Assert.notNull(fileType, "上传文件拓展名为空!");
        Assert.isTrue(StrUtil.containsAnyIgnoreCase(fileType, "xls", "xlsx"), StrUtil.format("不允许上传文件类型: {}", fileType));

        List<EmailImportParam> emailList = new ArrayList<>();
        List<String> emptyList = new ArrayList<>();
        EasyExcel.read(file.getInputStream()).head(EmailImportParam.class)
                .sheet()
                .registerReadListener(new AnalysisEventListener<EmailImportParam>() {
                    @Override
                    public void invoke(EmailImportParam data, AnalysisContext context) {
                        // 获取当前index
                        Integer currentRowNum = context.readRowHolder().getRowIndex();
                        String errorMsg = "";
                        // 检查非空
                        EmailDataTypeEnum emailDataTypeEnum = EnumUtil.likeValueOf(EmailDataTypeEnum.class, data.getDataType());
                        if (ObjectUtil.isNull(emailDataTypeEnum)) {
                            errorMsg = errorMsg.concat("数据添加方式 不能为空");
                        }
                        if (emailDataTypeEnum == EmailDataTypeEnum.INPUT && StrUtil.isBlank(data.getInputCompanyName())) {
                            errorMsg = errorMsg.concat("备用公司名称 不能为空");
                        }
                        if (StrUtil.isBlank(data.getEmail())) {
                            errorMsg = errorMsg.concat("邮箱地址 不能为空");
                        }
                        if (StrUtil.isNotBlank(errorMsg)) {
                            errorMsg = StrUtil.format("第 {} 行 ", currentRowNum + 1).concat(errorMsg);
                            emptyList.add(errorMsg);
                        }
                        emailList.add(data);
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext context) {
                        log.info("数据读取完毕");
                    }
                }).doRead();
        Assert.isFalse(CollUtil.isNotEmpty(emptyList), "邮箱: {}", CollUtil.join(emptyList, ", "));

        // 检查重复邮箱
        String repeatEmails = emailList
                .stream()
                .collect(Collectors.groupingBy(EmailImportParam::getEmail, Collectors.counting()))
                .entrySet()
                .stream()
                .filter(e -> e.getValue() > 1)
                .map(Map.Entry::getKey)
                .collect(Collectors.joining(", "));
        Assert.isFalse(StrUtil.isNotBlank(repeatEmails), "邮箱: {}重复, 请检查excel文件", repeatEmails);

        Set<String> emails = emailList.stream().map(EmailImportParam::getEmail).collect(Collectors.toSet());
        InfoInvUtil.checkEmail(emails);

        // 查询已存在邮箱
        String existEmails = this.lambdaQuery()
                .eq(Email::getDelFlag, false)
                .in(Email::getEmail, emails)
                .select(Email::getEmail)
                .list()
                .stream()
                .map(Email::getEmail)
                .collect(Collectors.joining(", "));
        Assert.isFalse(StrUtil.isNotBlank(existEmails), StrUtil.format("邮箱: {} 已存在, 请检查excel文件", existEmails));

        // 添加到数据库
        List<EmailAddParam> emailAddParams = emailList.stream().map(emailImportParam -> new EmailAddParam()
                        .setEmails(Set.of(emailImportParam.getEmail()))
                        .setDataType(emailImportParam.getDataType())
                        .setInputCompanyName(emailImportParam.getInputCompanyName()))
                .collect(Collectors.toList());
        emailAddParams.forEach(this::create);
        return true;
    }

MultipartFileUtil工具类

package com.tophant.pentestinfoinv.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Objects;

/**
 * File转换工具
 *
 * @author zhoqua.luo@tophant.com
 * @date 06-21-0021-2021
 */
@Slf4j
public class MultipartFileUtil {
    /**
     * MultipartFile转换为File
     *
     * @param file multipartFile
     * @return File
     * @throws Exception
     */
    public static File multipartFileToFile(MultipartFile file) {
        try {
            File toFile = null;
            if (file.equals("") || file.getSize() <= 0) {
                file = null;
            } else {
                InputStream ins = null;
                ins = file.getInputStream();
                toFile = new File(Objects.requireNonNull(file.getOriginalFilename()));
                inputStreamToFile(ins, toFile);
                ins.close();
            }
            return toFile;
        } catch (Exception e) {
            log.warn("MultipartFile转化为File失败!");
            return null;
        }
    }

    /**
     * 获取流文件
     * @param ins
     * @param file
     */
    private static void inputStreamToFile(InputStream ins, File file) {
        try {
            OutputStream os = new FileOutputStream(file);
            int bytesRead = 0;
            byte[] buffer = new byte[8192];
            while ((bytesRead = ins.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            ins.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

二. 写入

2.1 写入

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        EasyExcel.write(byteArrayOutputStream, EmailExcelVO.class)
                // 注册自定义拦截器
                .registerWriteHandler(new CustomCellWriteConfig())
                .registerWriteHandler(new CustomColumnWidthConfig())
                .registerWriteHandler(new CustomRowHeightConfig())
                .sheet("邮箱")
                .doWrite(excelVOList);

2.2 自定义拦截器

2.2.1 设置样式
package com.tophant.component.starter.excel.common.config;

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.*;

import java.util.List;

/**
 * @Description 设置样式
 * @Author WanFei
 * @Date 2022/5/13 12:13
 */
public class CustomCellWriteConfig implements CellWriteHandler {
    private Workbook workbook;
    private CellStyle cellStyle;

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        if (workbook == null) {
            // 放在里面,避免重复创建对象,导致报错 The maximum number of Cell Styles was exceeded. You can define up to 64000 style in a .xlsx Workbook
            workbook = writeSheetHolder.getSheet().getWorkbook();
            cellStyle = workbook.createCellStyle();
            // 居中
            cellStyle.setAlignment(HorizontalAlignment.LEFT);
            cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            cellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
            // 设置边框
            cellStyle.setBorderBottom(BorderStyle.THIN);
            cellStyle.setBorderLeft(BorderStyle.THIN);
            cellStyle.setBorderRight(BorderStyle.THIN);
            cellStyle.setBorderTop(BorderStyle.THIN);
            // 自动换行
            cellStyle.setWrapText(true);
        }
        // 配置生效
        cell.setCellStyle(cellStyle);
    }
}

2.2.2 自适应宽度
package com.tophant.component.starter.excel.common.config;

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description 自适应宽度
 * @Author WanFei
 * @Date 2022/5/13 13:59
 */
public class CustomColumnWidthConfig extends AbstractColumnWidthStyleStrategy {
    private Map<Integer, Map<Integer, Integer>> CACHE = new HashMap<>();

    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer integer, Boolean isHead) {
        boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
        if (needSetWidth) {
            Map<Integer, Integer> maxColumnWidthMap = CACHE.computeIfAbsent(writeSheetHolder.getSheetNo(), k -> new HashMap<>());

            Integer columnWidth = this.dataLength(cellDataList, cell, isHead);
            if (columnWidth >= 0) {
                if (columnWidth > 254) {
                    columnWidth = 254;
                }

                Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
                if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                    maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
                    Sheet sheet = writeSheetHolder.getSheet();
                    sheet.setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
                }

            }
        }
    }

    /**
     * 计算长度
     * @param cellDataList
     * @param cell
     * @param isHead
     * @return
     */
    private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData<?> cellData = cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        // 换行符(数据需要提前解析好)
                        int index = cellData.getStringValue().indexOf("\n");
                        return index != -1 ?
                                cellData.getStringValue().substring(0, index).getBytes().length + 1 : cellData.getStringValue().getBytes().length + 1;
                    case BOOLEAN:
                        return cellData.getBooleanValue().toString().getBytes().length;
                    case NUMBER:
                        return cellData.getNumberValue().toString().getBytes().length;
                    default:
                        return -1;
                }
            }
        }
    }
}

2.2.3 自适应行高
package com.tophant.component.starter.excel.common.config;

import com.alibaba.excel.write.style.row.AbstractRowHeightStyleStrategy;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;

import java.util.Iterator;

/**
 * @Description 自适应行高
 * @Author WanFei
 * @Date 2022/5/13 14:03
 */
public class CustomRowHeightConfig extends AbstractRowHeightStyleStrategy {
    /**
     * 默认高度
     */
    private static final Integer DEFAULT_HEIGHT = 300;

    @Override
    protected void setHeadColumnHeight(Row row, int relativeRowIndex) {
    }

    @Override
    protected void setContentColumnHeight(Row row, int relativeRowIndex) {
        Iterator<Cell> cellIterator = row.cellIterator();
        if (!cellIterator.hasNext()) {
            return;
        }

        // 默认为 1行高度
        Integer maxHeight = 1;
        while (cellIterator.hasNext()) {
            Cell cell = cellIterator.next();
            switch (cell.getCellType()) {
                case STRING:
                    if (cell.getStringCellValue().contains("\n")) {
                        int length = cell.getStringCellValue().split("\n").length;
                        maxHeight = Math.max(maxHeight, length);
                    }
                    break;
                default:
                    break;
            }
        }

        row.setHeight((short) (maxHeight * DEFAULT_HEIGHT));
    }
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值