EasyExcel简介

easyexcel 是阿里巴巴开源的一款excel 解析工具,底层逻辑也是基于apache poi进行二次开发的。不同的是,再读写数据的时候,采用 sax 模式一行一行解析,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)在并发量很大的情况下,依然能稳定运行!easyexcel支持采用注解方式进行导出、导入!

引入依赖

<!-- EasyExcel -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>2.2.7</version>
</dependency>

<!--csv文件操作-->
<dependency>
    <groupId>net.sourceforge.javacsv</groupId>
    <artifactId>javacsv</artifactId>
    <version>2.0</version>
</dependency>

AnalysisEventListener读取监听

对象命名时严格遵守驼峰命名法,如果忽略可能会导致读取数据失败。 

通用读取监听类

import com.baomidou.mybatisplus.toolkit.ReflectionKit;

/**
 * excel通用读取监听类
 */
public class ExcelListener<T> extends AnalysisEventListener<T> {
  /**
   * 自定义用于暂时存储data 可以通过实例获取该值
   */
  private final List<T> list = new ArrayList<>();
  
  /**
   * 这个每一条数据解析都会来调用
   *
   * @param data
   * @param context
   */
  @Override
  public void invoke(T data, AnalysisContext context) {
    // 如果一行Excel数据均为空值,则不装载该行数据
    if (isLineNullValue(data)) {
      return;
    }
    if (context.readRowHolder().getRowIndex() > 2) {
      System.out.println("解析到一条数据:{}", JSON.toJSONString(data));
      list.add(data);
    }
  }

  /**
   * 所有数据解析完成了 都会来调用
   *
   * @param context
   */
  @Override
  public void doAfterAllAnalysed(AnalysisContext context) {
    String sheetName = context.readWorkbookHolder().getExcelType().equals(ExcelTypeEnum.CSV) ? "CSV" : context.readSheetHolder().getSheetName();
    System.out.println(sheetName + " 所有数据解析完成,数据条数:" + list.size());
  }

  /**
   * 返回所有记录
   *
   * @return
   */
  public List<T> getRows() {
    return list;
  }

  /**
   * 判断整行单元格数据是否均为空
   */
  private boolean isLineNullValue(Object data) {
    if (data == null) {
      return true;
    }
    List<Field> fields = Arrays.stream(data.getClass().getDeclaredFields())
        .filter(f -> f.isAnnotationPresent(ExcelProperty.class))
        .collect(Collectors.toList());
    List<Boolean> lineNullList = new ArrayList<>(fields.size());
    for (Field key : fields){
      if (ReflectionKit.getMethodValue(data, key.getName()) == null) {
        lineNullList.add(Boolean.TRUE);
      } else {
        lineNullList.add(Boolean.FALSE);
      }
    }
    return lineNullList.stream().allMatch(Boolean.TRUE::equals);
  }
}

通用非model类型监听

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class NoModelDataListener extends AnalysisEventListener<Map<Integer, String>> {

  /**
   * Excel数据缓存结构
   */
  private List<Map<String, String>> list = new ArrayList<>();

  /**
   * Excel表头(列名)数据缓存结构
   */
  private Map<Integer, String> headTitleMap = new HashMap<>();

  @Override
  public void invoke(Map<Integer, String> data, AnalysisContext context) {
    //excel的行号
    Integer rowIndex = context.readRowHolder().getRowIndex();
    log.info("行号:{},Excel数据:{}", rowIndex, getRowContentWithHeaderKey(data));
    list.add(getRowContentWithHeaderKey(data));
  }

  /**
   * 解析表头数据,解析第一行触发
   **/
  @Override
  public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
    headTitleMap = new HashMap<>();
    for (Integer i : headMap.keySet()) {
      String value = "";
      if (headMap.get(i) != null) {
        value = headMap.get(i).trim();
      }
      headTitleMap.put(i, value);
    }
  }

  @Override
  public void doAfterAllAnalysed(AnalysisContext analysisContext) {
    log.info("数据解析完成!");
  }

  /**
   * 按照表头作为map的key,提供Excel行数据。兼容key重复。行乱序
   *
   * @param data
   * @return
   */
  public Map<String, String> getRowContentWithHeaderKey(Map<Integer, String> data) {
    Map<String, String> dataWithHeader = new IdentityHashMap<>();
    data.forEach((index, value) -> {
      dataWithHeader.put(headTitleMap.get(index), value);
    });
    return dataWithHeader;
  }

  public List<Map<String, String>> getList() {
    return list;
  }
}

单sheet海量excel的数据读取

根据设置每批次数据量的大小,当list里面数据量达到设置每批次最大数据量时,进行入库操作,海量数据的话,通常每批次1000的速度最快,可以避免内存溢出

public void invoke(CallExcelVO data, AnalysisContext context) {
	list.add(data);
	// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
	if (list.size() >= BATCH_COUNT) {
		saveData();
		//存储完成清理 list
		list.clear();
	}
}

 读取Excel内容

ExcelListener listener = new ExcelListener();
String fileName = "demo.xlsx";
EasyExcel.read(fileName, DemoData.class, listener).sheet().autoTrim(true).doRead();
List<DemoData> dataList = listener.getRows();

//EasyExcel3.3 导入csv
ExcelListener listener = new ExcelListener();
ExcelReader excelReader = EasyExcel.read(inputStream, listener).build();
String code = CsvReader.getFileCharset(fileName);
System.out.println("文件编码为:" + code);
//excelReader = EasyExcel.read(inputStream, listener).excelType(ExcelTypeEnum.CSV).charset(Charset.forName(code)).build();
inputStream.close();


Integer sheetSize = 1;//csv
if (excelReader != null) {
    List<ReadSheet> readSheets = excelReader.excelExecutor().sheetList();
    sheetSize = readSheets.size();
}
for (int sheetNo = 0; sheetNo < sheetSize; sheetNo++) {
   ExcelUtil.readExcel(fileName, sheetNo);
}

接收外部参数,需添加构造方法

public ExcelListener(String fileName) {
  this.fileName = fileName;
}

ExcelListener parseListener = new ExcelListener("a.xls");

读类

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
import java.io.InputStream;
import java.util.List;

/**
 * 定制化Excel读写工具类
 */
public class ExcelUtil {

  /**
   * 单sheet版本Excel读取 从Excel中读取文件,读取的文件是一个DTO类
   *
   * @param inputStream 文件流
   * @param clazz       行数据类型
   * @param rowIndex    表示Excel的表头的行数,默认为1,设置为1时,表示第一行是表头,从第二行是表数据
   * @param sheetNo     sheet编号 从0开始
   */
  public static <T> List<T> readExcelOneSheet(InputStream inputStream, final Class<?> clazz, Integer rowIndex, Integer sheetNo) {
    // 1.创建监听类
    ExcelListener<T> listener = new ExcelListener<>();
    // 2.构建工作簿对象的输入流
    ExcelReader excelReader = EasyExcel.read(inputStream, clazz, listener).build();
    // 3.构建工作表对象的输入流,默认是第一张工作表
    ReadSheet readSheet = EasyExcel.readSheet(sheetNo).headRowNumber(rowIndex).build();
    // 4.读取信息,每读取一行都会调用监听类的 invoke 方法
    excelReader.read(readSheet);
    // 5.关闭流,如果不关闭,读的时候会创建临时文件,到时磁盘会崩的
    excelReader.finish();
    return listener.getRows();
  }

  /**
   * 将Excel中的日期数值转换为对应的日期字符串,单元格格式设置为文本可以看数字。 45457.4227662037 => 2024-06-14 10:08:46.999
   *
   * @param excelDate Excel中的日期数值,表示从1900年1月1日起的天数与时间的组合。
   * @return 对应的日期字符串,格式为yyyy-MM-dd HH:mm:ss。
   */
  public static String getExcelDateFormat2Date(double excelDate) {
    Calendar calendar = Calendar.getInstance();

    //整数部分代表天数(天数取决于所用的日期系统,1900或1904系统)
    calendar.set(1900, Calendar.JANUARY, 0);
    calendar.add(Calendar.DAY_OF_MONTH, (int) excelDate - 1);

    // Calculate the time portion of the day
    double fractionOfDay = excelDate - (int) excelDate;
    int hours = (int) (fractionOfDay * 24);
    int minutes = (int) ((fractionOfDay * 24 * 60) % 60);
    int seconds = (int) ((fractionOfDay * 24 * 60 * 60) % 60);
    int milliseconds = (int) (((fractionOfDay * 24 * 60 * 60 * 1000) % 1000));

    calendar.set(Calendar.HOUR_OF_DAY, hours);
    calendar.set(Calendar.MINUTE, minutes);
    calendar.set(Calendar.SECOND, seconds);
    calendar.set(Calendar.MILLISECOND, milliseconds);

    Date date = calendar.getTime();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
    return sdf.format(date);
  }
}

EasyExcel导入invoke函数不执行,不报错

  • 重写hasNext方法时写成返回false了
  • 使用easyexcel时默认是第一张sheet页,不是第一张sheet
  • 将不使用的字段使用@ExcelIgnore标注 
  • EasyExcel和Lombok结合使用导致的问题,去掉@Accessors(chain = true)

EasyExcel 相关参数

  • readListener 监听器,在读取数据的过程中会不断的调用监听器。
  • converter 转换器,默认加载了很多转换器。也可以自定义,如果使用的是 registerConverter,那么该转换器是全局的,如果要对单个字段生效,可以在 ExcelProperty 注解的 converter 指定转换器。
  • headRowNumber 需要读的表格有几行头数据。默认有一行头,也就是认为第二行开始起为数据。
  • head 与 clazz 二选一。读取文件头对应的列表,会根据列表匹配数据,建议使用 class。
  • autoTrim 字符串、表头等数据自动 trim
  • sheetNo 需要读取 Sheet 的编码,建议使用这个来指定读取哪个 Sheet。
  • sheetName 根据名字去匹配 Sheet,excel 2003 不支持根据名字去匹配。 

Excel工具类

根据EasyExcel Model 导出工具类,CSV支持easyexcel获取ExcelProperty

ExportUtil

package net.demo.excel.common.util;

import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.csvreader.CsvWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * @desc: 根据EasyExcel Model 导出工具类
 * @Author: Swift
 * @Date: 2019-08-26 15:18

*/
public class ExportUtil {

    /**
     * 获取ExcelProperty Value
     * @return
     */
    public static <T extends BaseRowModel> String[] getFieldNames(Class<T> tClass) {
        Field[] fields = tClass.getDeclaredFields();
        List<String> headers = new ArrayList<>();
        for (Field field: fields) {
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (property != null) {
                String[] s = property.value();
                if (s.length > 0) {
                    headers.add(s[0]);
                }
            }
        }
        String[] strings = new String[headers.size()];
        headers.toArray(strings);
        return strings;
    }

    /**
     * 获取filedName
     * @param vo
     * @param <T>
     * @return
     */
    public static <T extends BaseRowModel>  String[] getFields(T vo) {
        Field[] fields = vo.getClass().getDeclaredFields();
        List<String> columns = new ArrayList<>();
        for (Field field: fields) {
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (property != null) {
                try {
                    field.setAccessible(true);
                    columns.add(field.get(vo)==null ? "" : field.get(vo).toString());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                    throw new RuntimeException("写入内容到csv失败!");
                }
            }
        }

        String[] strings = new String[columns.size()];
        columns.toArray(strings);
        return strings;
    }

    public static <T extends BaseRowModel> void export(String type, List<T> vos, HttpServletResponse response, Class<T> t
    ) throws IOException {

        Boolean isCsv = "csv".equals(type);
        String fileName = "data" + CalendarUtils.getCurrentTime();
        response.setCharacterEncoding("utf-8");
        if (isCsv) {
            fileName += ".csv";
            response.setContentType("text/csv;charset=UTF-8");
        } else {
            fileName += ".xlsx";
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        }
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);

        ServletOutputStream outputStream = response.getOutputStream();

        if (isCsv) {
            CsvWriter csvWriter = new CsvWriter(outputStream, ',', Charset.forName("GBK"));
            csvWriter.writeRecord(getFieldNames(t));
            for (T vo : vos) {
                String[] fields = getFields(vo);
                csvWriter.writeRecord(fields);
            }
            csvWriter.close();
        } else {
            ExcelWriter excelWriter = new ExcelWriter(outputStream, ExcelTypeEnum.XLSX, true);

            Sheet sheet = new Sheet(1, 0, t);
            excelWriter.write(vos, sheet);
            sheet.setAutoWidth(true);
            excelWriter.finish();
        }
        outputStream.flush();
    }

    public static <T extends BaseRowModel> void export(String type,String fileName, List<T> vos, HttpServletResponse response, Class<T> t
    ) throws IOException {

        Boolean isCsv = "csv".equals(type);
//        String fileName = "data" + CalendarUtils.getCurrentTime();
        response.setContentType("multipart/form-data");
        response.setCharacterEncoding("utf-8");
        if (isCsv) {
            fileName += ".csv";
        } else {
            fileName += ".xlsx";
        }
        response.setHeader("Content-disposition", "attachment;filename=" + fileName);

        ServletOutputStream outputStream = response.getOutputStream();

        if (isCsv) {
            CsvWriter csvWriter = new CsvWriter(outputStream, ',', Charset.forName("GBK"));
            csvWriter.writeRecord(getFieldNames(t));
            for (T vo : vos) {
                String[] fields = getFields(vo);
                csvWriter.writeRecord(fields);
            }
            csvWriter.close();
        } else {
            ExcelWriter excelWriter = new ExcelWriter(outputStream, ExcelTypeEnum.XLSX, true);

            Sheet sheet = new Sheet(1, 0, t);
            excelWriter.write(vos, sheet);
            sheet.setAutoWidth(true);
            excelWriter.finish();
        }
        outputStream.flush();
    }

    /**
     * 根据Excel模板,批量导入数据
     * @param file  导入的Excel
     * @param clazz 解析的类型
     * @return  解析完成的数据
     */
    public static List<?> importExcel(MultipartFile file, Class<?> clazz){
        if (file == null || file.isEmpty()){
            throw new RuntimeException("没有文件或者文件内容为空!");
        }
        List<Object> dataList = null;
        BufferedInputStream ipt = null;
        try {
            InputStream is = file.getInputStream();
            // 用缓冲流对数据流进行包装
            ipt = new BufferedInputStream(is);
            // 数据解析监听器
            ExcelListener<Object> listener = new ExcelListener<>();
            // 读取数据
            EasyExcel.read(ipt, clazz,listener).sheet().doRead();
            // 获取去读完成之后的数据
            dataList = listener.getDataList();
        } catch (Exception e){
            log.error(String.valueOf(e));
            throw new RuntimeException("数据导入失败!" + e);
        }
        return dataList;
    }
}

WriterFactory

导出工厂实现了xlsx与csv 两种方式

package net.demo.excel.common.export;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import javax.servlet.http.HttpServletResponse;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.demo.excel.common.constants.Constants;
import net.demo.excel.common.constants.ExportTypeEnum;
import net.demo.excel.common.export.csv.CsvExport;
import net.demo.excel.common.export.excel.ExcelExport;


/**
 * 导出工厂
 */
@Data
@Slf4j
public class WriterFactory {

  /**
   * 返回文件流方式
   *
   * @param key
   * @param fileName
   * @param response
   * @return
   */
  public static ExportWriter getExportWriter(String key, String fileName,
      HttpServletResponse response) {
    ExportWriter exportWriter = null;
    try {
      OutputStream os = response.getOutputStream();
      ExportTypeEnum exportTypeEnum = ExportTypeEnum.findByType(key);
      fileName = fileName + exportTypeEnum.getSuffix();
      exportWriter = generateExportWriter(exportTypeEnum, os);
      response.setContentType("application/force-download");
      // 设置文件名
      response.addHeader("Content-Disposition",
          "attachment;filename=" + URLEncoder.encode(fileName, "utf-8"));
    } catch (UnsupportedEncodingException e) {
      log.error("UnsupportedEncoding:", e);
    } catch (IOException e) {
      log.error("IOException:", e);
    }
    return exportWriter;
  }

  /**
   * 输出到文件方式
   *
   * @param key
   * @param fileName
   * @return
   */
  public static ExportWriter getExportWriter(String key, String fileName)
      throws FileNotFoundException {
    ExportTypeEnum exportTypeEnum = ExportTypeEnum.findByType(key);
    String filePath =
        Constants.EXPORT_TMP_DIR + File.separator + fileName + exportTypeEnum.getSuffix();
    File file = new File(filePath);
    OutputStream os = new FileOutputStream(file);
    ExportWriter exportWriter = generateExportWriter(exportTypeEnum, os);
    exportWriter.setFilePath(filePath);
    return exportWriter;
  }

  private static ExportWriter generateExportWriter(ExportTypeEnum exportTypeEnum, OutputStream os) {
    ExportWriter exportWriter = null;
    switch (exportTypeEnum) {
      case EXCEL:
        exportWriter = new ExcelExport(os);
        break;
      case CSV:
        exportWriter = new CsvExport(os);
        break;
      default:
    }
    return exportWriter;
  }
}

ExportModel抽象类

package net.demo.excel.common.export.model;

import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.csvreader.CsvWriter;

/**
 * 抽象实体
 */
public abstract class ExportModel {

  public abstract ExcelWriter getExcelWriter();

  public abstract Sheet getSheet();

  public abstract CsvWriter getCsvWriter();
}

CsvModel 实体类 

package net.demo.excel.common.export.csv.model;

import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.csvreader.CsvWriter;
import lombok.Data;
import net.demo.excel.common.export.model.ExportModel;

/**
 * csv相关实体
 */
@Data
public class CsvModel extends ExportModel {

  private CsvWriter csvWriter;

  @Override
  public ExcelWriter getExcelWriter() {
    return null;
  }

  @Override
  public Sheet getSheet() {
    return null;
  }

  @Override
  public CsvWriter getCsvWriter() {
    return csvWriter;
  }

  public CsvModel(CsvWriter csvWriter) {
    this.csvWriter = csvWriter;
  }
}

ExcelModel 实体类 

package net.demo.excel.common.export.excel.model;

import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Sheet;
import com.csvreader.CsvWriter;
import lombok.Data;
import net.demo.excel.common.export.model.ExportModel;

/**
 * excel,csv相关实体
 */
@Data
public class ExcelModel extends ExportModel {

  private ExcelWriter excelWriter;

  private Sheet sheet;

  @Override
  public ExcelWriter getExcelWriter() {
    return excelWriter;
  }

  @Override
  public Sheet getSheet() {
    return sheet;
  }

  @Override
  public CsvWriter getCsvWriter() {
    return null;
  }

  public ExcelModel(ExcelWriter excelWriter, Sheet sheet) {
    this.excelWriter = excelWriter;
    this.sheet = sheet;
  }
}

ExportWriter接口

package net.demo.excel.common.export;

import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.fastjson.JSONArray;
import java.util.List;
import net.demo.excel.common.bean.Column;

/**
 * 导出接口
 */
public interface ExportWriter {

  public static final int EXPORT_PAGE_SIZE = 5000;

  /**
   * 写入表头 结构为{"code":"字段名","title":"显示名"}
   *
   * @param columnList
   */
  public void writeTitle(List<Column> columnList);

  public <T extends BaseRowModel> void writeTitle(Class<T> t);

  /**
   * 添加内容 data的Map结构为{"字段名":"字段值"}
   *
   * @param dataList
   * @param columnList
   */
  public void appendContent(JSONArray dataList, List<Column> columnList);

  public <T extends BaseRowModel> void appendContent(List<T> vos, Class<T> t);

  /**
   * 关闭文件流
   */
  public void close();

  public String getFilePath();

  public void setFilePath(String filePath);

}

Column

package net.demo.excel.common.bean;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class Column {

  /**
   * 英文名称
   */
  private String code;

  /**
   * 中文名称
   */
  private String title;
}

ExcelExport实现类

model.getSheet() 的sheet的初始化在ExcelExport中完成。new sheet(1, 0, ExcelProperty)第3个参数com.alibaba.excel.annotation.ExcelProperty通过注解来生成表头。

package net.demo.excel.common.export.excel;

import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.demo.excel.common.bean.Column;
import net.demo.excel.common.export.ExportWriter;
import net.demo.excel.common.export.excel.model.ExcelModel;
import net.demo.excel.common.export.model.ExportModel;
import net.demo.excel.common.util.ExportUtil;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * excel具体实现
 */
@Slf4j
@Data
public class ExcelExport implements ExportWriter {

	private ExportModel model;

	private OutputStream os;

	private String filePath;

	/**
	 * 初始化
	 *
	 * @param os
	 */
	public ExcelExport(OutputStream os) {
		ExcelWriter writer = new ExcelWriter(os, ExcelTypeEnum.XLSX);
		Sheet sheet = new Sheet(1, 0);
		sheet.setSheetName("1");
		this.model = new ExcelModel(writer, sheet);
		this.os = os;
	}

	/**
	 * 写标题
	 *
	 * @param columnList
	 */
	@Override
	public void writeTitle(List<Column> columnList) {
		List<List<String>> dataList = new ArrayList<List<String>>();
		//组装标题行
		for (int i = 0; i < columnList.size(); i++) {
			List<String> titleList = new ArrayList<String>();
			Column column = columnList.get(i);
			String title = column.getTitle();
			titleList.add(title);
			dataList.add(titleList);
		}
		model.getSheet().setHead(dataList);
	}

	/**
	 * 写标题
	 */
	@Override
	public <T extends BaseRowModel> void writeTitle(Class<T> t) {
		List<List<String>> dataList = new ArrayList<List<String>>();
		String[] columns = ExportUtil.getFieldNames(t);
		//组装标题行
		for (int i = 0; i < columns.length; i++) {
			List<String> titleList = new ArrayList<String>();
			String title = columns[i];
			titleList.add(title);
			dataList.add(titleList);
		}
		model.getSheet().setHead(dataList);
	}

	/**
	 * 内容追加
	 *
	 * @param dataList
	 * @param columnList
	 */
	@Override
	public void appendContent(JSONArray dataList, List<Column> columnList) {
		List<List<String>> dataArray = new ArrayList<List<String>>();
		//组装数据行
		for (Object obj : dataList) {
			JSONObject json = (JSONObject) JSONObject.toJSON(obj);
			List<String> rowList = new ArrayList<>();
			for (int j = 0; j < columnList.size(); j++) {
				Column columnMap = columnList.get(j);
				String code = columnMap.getCode();
				Object value = json.get(code);
				String valueStr = value == null ? "" : value.toString();
				rowList.add(valueStr);
			}
			dataArray.add(rowList);
		}
		model.getExcelWriter().write1(dataArray, model.getSheet());
	}

	/**
	 * 追加内容
	 *
	 * @param vos
	 * @param t
	 * @param <T>
	 */
	@Override
	public <T extends BaseRowModel> void appendContent(List<T> vos, Class<T> t) {
        //--start 修复sheet行数据为null时,列对齐错位--
        List<List<Object>> dataArray = new ArrayList<List<Object>>();
        for (T vo : vos) {
          Field[] fields = vo.getClass().getDeclaredFields();
          List<Object> rowList = new ArrayList<>();
          for (Field field : fields) {
            ExcelProperty property = field.getAnnotation(ExcelProperty.class);
            if (property != null) {
              try {
                field.setAccessible(true);
                Object fieldValue = field.get(vo) == null ? "" : field.get(vo);
                rowList.add(fieldValue);
              } catch (IllegalAccessException e) {
                e.printStackTrace();
                throw new RuntimeException("写入内容到xlsx失败!");
              }
            }
          }
          dataArray.add(rowList);
        }
		model.getExcelWriter().write(dataArray, model.getSheet());
		//--end 修复api行数据为null时,列对齐错位--
    
		model.getSheet().setClazz(t); //注解来生成表头
		//model.getExcelWriter().write(vos, model.getSheet());
		model.getSheet().setAutoWidth(true);
	}

	/**
	 * 关闭流
	 */
	@Override
	public void close() {
		ExcelWriter writer = model.getExcelWriter();
		try {
			if (writer != null) {
				writer.finish();
			}
			os.close();
		} catch (IOException e) {
			log.error("os close error", e);
		}
	}

	@Override
	public String getFilePath() {
		return filePath;
	}

	@Override
	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}
}

CsvReader文件读取

import com.alibaba.excel.util.FileUtils;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;

/**
 * CSV读取工具类
 */
public class CsvReader {

  /**
   * 列分隔符
   */
  private static final String WORDS_SPLIT = ",";

  /**
   * 读取200万条数据
   */
  private static final int READ_LINES = Integer.MAX_VALUE;

  /**
   * csv、txt 文件处理
   *
   * @param filePath 文件路径
   * @return
   */
  public static Map<Integer, Map<Integer, String>> getTxtCsvFileContent(String filePath) {
    Map<Integer, Map<Integer, String>> map = new HashMap<>();
    try {
      Map<Integer, String> rowContent = getTxtCsvLineContentMap(
          FileUtils.openInputStream(new File(filePath)), filePath, READ_LINES);
      if (rowContent != null) {
        rowContent.forEach((k, v) -> {
          Map<Integer, String> tmp = new LinkedHashMap<>();
          String[] row = v.split(WORDS_SPLIT);
          for (int i = 0; i < row.length; i++) {
            tmp.put(i, row[i]);
          }
          map.put(k, tmp);
        });
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

    return map;
  }

  /**
   * 兼容Excel多sheet格式
   *
   * @param filePath
   * @return
   */
  public static List<Map<Integer, Map<Integer, String>>> getExcelStyleData(String filePath) {
    List sheet = new ArrayList<>();
    Map<Integer, Map<Integer, String>> sheetData = getTxtCsvFileContent(filePath);
    sheet.add(0, sheetData);
    return sheet;
  }

  /**
   * csv、txt 文件第一行表头
   *
   * @param filePath 文件路径
   * @param charset
   * @return
   */
  public static Map<Integer, String> getTxtCsvFileHeader(String filePath, String charset) {
    Map<Integer, String> headerMap = new LinkedHashMap<>();

    try {
      if (StringUtils.isBlank(charset)) {
        charset = getFileCharset(filePath);
        if (StringUtils.isBlank(charset)) {
          charset = StandardCharsets.UTF_8.name();
        }
      }
      InputStream is = new BufferedInputStream(FileUtils.openInputStream(new File(filePath)));
      // 解析平面文件
      BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
      // 获取第一行的数据当做字段
      String headContent = reader.readLine();
      if (StringUtils.isNotBlank(headContent)) {
        if (isMessyCode(headContent, Charset.forName(charset)) && !charset.equals(StandardCharsets.UTF_8.name())) {
          System.out.println("文件编码:" + charset + ",内容存在乱码,自动修正编码为utf8");
          is.close();
          reader.close();
          return getTxtCsvFileHeader(filePath, StandardCharsets.UTF_8.name());
        }
        String[] row = headContent.split(WORDS_SPLIT);
        for (int i = 0; i < row.length; i++) {
          headerMap.put(i, row[i]);
        }
      }
      is.close();
      reader.close();
    } catch (IOException e) {
      e.printStackTrace();
    }

    return headerMap;
  }

  /**
   * 获取 txt/csv 文件前 size 行数据
   *
   * @param inputStream
   * @param filePath
   * @param size
   * @return
   * @throws IOException
   */
  public static Map<Integer, String> getTxtCsvLineContentMap(InputStream inputStream,
      String filePath, int size) throws IOException {
    Map<Integer, String> map = new LinkedHashMap<>();
    String charset = getFileCharset(filePath);
    if (StringUtils.isBlank(charset)) {
      charset = StandardCharsets.UTF_8.name();
    }
    InputStream is = new BufferedInputStream(inputStream);
    // 解析平面文件
    BufferedReader reader = new BufferedReader(new InputStreamReader(is, charset));
    // 获取第一行的数据当做字段
    String line = reader.readLine();
    if (StringUtils.isBlank(line)) {
      is.close();
      reader.close();
      return null;
    }
    int fileLineNum = getFileLineNum(filePath);
    if (fileLineNum < size) {
      size = fileLineNum;
    }
    for (int i = 1; i <= size; i++) {
      String nowLineContent = reader.readLine();
      nowLineContent = StringUtils.remove(nowLineContent, "\"");
      nowLineContent = StringUtils.remove(nowLineContent, "\t");
      Integer lineIndex = i + 1;
      if (StringUtils.isNotBlank(nowLineContent)) {
        if (lineIndex % 200 == 0) {
          System.out.println("读取csv第" + lineIndex + "/" + size + "内容:" + nowLineContent);
        }
        map.put(lineIndex, nowLineContent);
      }
    }
    is.close();
    reader.close();
    return map;
  }

  /**
   * 高效获取大文件的行数:3千万行只要1秒钟
   *
   * @param filePath
   * @return int
   */
  public static int getFileLineNum(String filePath) {
    int fileLines = 0;
    try (LineNumberReader lineNumberReader = new LineNumberReader(new FileReader(filePath))) {
      lineNumberReader.skip(Long.MAX_VALUE);
      int lineNumber = lineNumberReader.getLineNumber();
      //实际上是读取换行符数量 , 所以需要+1
      fileLines = lineNumber + 1;
    } catch (IOException e) {
      e.printStackTrace();
    }
    return fileLines;
  }

  /**
   * 根据关键字获取表头
   *
   * @param filePath
   * @param headerKey
   * @return
   */
  public static Map<Integer, String> getTxtCsvFileHeaderContainsKeywords(String filePath,
      String headerKey) {
    Map<Integer, String> headerMap = new LinkedHashMap<>();
    try {
      InputStream inputStream = new FileInputStream(filePath);
      Map<Integer, String> tmp = CsvReader.getTxtCsvLineContentMap(inputStream, filePath, 50);
      for (Map.Entry<Integer, String> entry : tmp.entrySet()) {
        String[] row = entry.getValue().split(CsvReader.WORDS_SPLIT);
        if (row.length > 0) {
          row[0] = StringUtils.remove(row[0], "\"");
          row[0] = StringUtils.remove(row[0], "\t");
          String title = StringUtils.deleteWhitespace(row[0]);
          for (int i = 0; i < row.length; i++) {
            if (title.equals(headerKey)) {
              headerMap.put(i, row[i]);
            }
          }
          if (title.equals(headerKey)) {
            break;
          }
        }
      }
      inputStream.close();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
    return headerMap;
  }

  /**
   * 获取 txt/csv/dat 文件编码
   *
   * @param filePath 文件路径
   * @return
   */
  private static String getFileCharset(String filePath) {
    String charset = "UTF-8";

    byte[] bytes = new byte[3];
    try (InputStream inputStream = new FileInputStream(filePath)) {
      inputStream.read(bytes);
      if (bytes[0] == (byte) 0xEF && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF) {
        charset = "UTF-8";
      } else if (bytes[0] == (byte) 0xFE && bytes[1] == (byte) 0xFF) {
        charset = "UTF-16BE";
      } else if (bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE) {
        charset = "UTF-16LE";
      } else {
        //或GB2312,即ANSI
        charset = "GBK";
      }
      inputStream.close();
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }

    return charset;
  }

  /**
   * 判断在指定编码下是否包含乱码 输出 false,表示不包含乱码字符
   *
   * @param str
   * @param charset
   * @return
   */
  public static boolean isMessyCode(String str, Charset charset) {
    String decodedStr = new String(str.getBytes(charset), charset);
    return !decodedStr.equals(str);
  }
}

CsvExport实现类

package net.demo.excel.common.export.csv;

import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.csvreader.CsvWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.demo.excel.common.bean.Column;
import net.demo.excel.common.export.ExportWriter;
import net.demo.excel.common.export.csv.model.CsvModel;
import net.demo.excel.common.export.model.ExportModel;
import net.demo.excel.common.util.ExportUtil;

/**
 * csv具体实现
 */
@Slf4j
@Data
public class CsvExport implements ExportWriter {

	private ExportModel model;

	private OutputStream os;

	private String filePath;

	/**
	 * 初始化
	 *
	 * @param os
	 */
	public CsvExport(OutputStream os) {
		CsvWriter csvWriter = new CsvWriter(os, ',', Charset.forName("GBK"));
		this.model = new CsvModel(csvWriter);
		this.os = os;
	}

	/**
	 * 写标题
	 *
	 * @param columnList
	 */
	@Override
	public void writeTitle(List<Column> columnList) {
		String[] headers = new String[columnList.size()];
		for (int i = 0; i < columnList.size(); i++) {
			Column tmp = columnList.get(i);
			headers[i] = tmp.getTitle();
		}
		try {
			model.getCsvWriter().writeRecord(headers);
		} catch (IOException e) {
			log.error("写入标题到csv失败!", e);
			throw new RuntimeException("写入标题到csv失败!");
		}
	}

	@Override
	public <T extends BaseRowModel> void writeTitle(Class<T> t){
		try {
			model.getCsvWriter().writeRecord(ExportUtil.getFieldNames(t));
		} catch (IOException e) {
			log.error("写入标题到csv失败!", e);
			throw new RuntimeException("写入标题到csv失败!");
		}
	}

	/**
	 * 内容追加
	 *
	 * @param dataList
	 * @param columnList
	 */
	@Override
	public void appendContent(JSONArray dataList, List<Column> columnList) {
		String[] content = null;
		List<String> codeList = new ArrayList<String>();
		for (int i = 0; i < columnList.size(); i++) {
			Column tmp = columnList.get(i);
			String code = tmp.getCode();
			codeList.add(code);
		}
		try {
			for (Object obj : dataList) {
				JSONObject json = (JSONObject) JSONObject.toJSON(obj);
				content = new String[codeList.size()];
				for (int i = 0; i < codeList.size(); i++) {
					String code = codeList.get(i);
					Object value = json.get(code);
					content[i] = value == null ? "" : value.toString();
				}
				model.getCsvWriter().writeRecord(content);
			}
		} catch (Exception e) {
			log.error("写入内容到csv失败!", e);
			throw new RuntimeException("写入内容到csv失败!");
		}
	}

	@Override
	public <T extends BaseRowModel> void appendContent(List<T> vos, Class<T> t) {
		try {
			for (T vo : vos) {
				String[] fields = ExportUtil.getFields(vo);
				model.getCsvWriter().writeRecord(fields);
			}
		} catch (IOException e) {
			log.error("写入内容到csv失败!", e);
			throw new RuntimeException("写入内容到csv失败!");
		}
	}

	/**
	 * 关闭
	 */
	@Override
	public void close() {
		CsvWriter csvWriter = model.getCsvWriter();
		if (csvWriter != null) {
			csvWriter.close();
		}
		try {
			os.close();
		} catch (IOException e) {
			log.error("os close error", e);
		}
	}

	@Override
	public String getFilePath() {
		return filePath;
	}

	@Override
	public void setFilePath(String filePath) {
		this.filePath = filePath;
	}
}

创建导入数据模板类

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
import lombok.experimental.Accessors;
 
import javax.validation.constraints.NotEmpty;
import java.io.Serializable;
 
/**
 * 数据导入的Excel模板实体
 */
@Data
public class ImportExcelVo implements Serializable {
    private static final long serialVersionUID = 1L;

    //使用index读取解决表头文字频繁变动问题
    @ExcelProperty(index = 0)
    private String name;
 
    @ColumnWidth(20)
    @ExcelProperty(value = "公司联系电话")
    private String phone;
 
    @ColumnWidth(28)
    @ExcelProperty(value = "公司统一社会信用代码")
    private String creditCode;
 
    @ColumnWidth(15)
    @ExcelProperty(value = "区域", index = 3)
    private String province;
 
    @ColumnWidth(15)
    @ExcelProperty(value = "公司法人")
    private String legalPerson;
 
    @ExcelProperty(value = "备注")
    private String remark;

    @DateTimeFormat("yyyy-MM-dd")  //指定日期格式
    private Date date;
}

invoke时间格式,需要使用easyExcel中的@ExcelProperty注解,并指定日期格式

  public void invoke(Object data, AnalysisContext context) {
    // 读取日期属性并进行处理
    if (rowData.get(columnIndex) instanceof Date) {
      Date date = (Date) rowData.get(columnIndex);
      // 在这里对日期进行处理
      SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
      String dateStr = sdf.format(date);
      //其他属性的处理...
      System.out.println(data);
    }
  }

创建数据导出模板

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import lombok.Data;
import lombok.experimental.Accessors;
 
import java.io.Serializable;
 
/**
 * 资质信息导出实体
 */
@Data   // Lombok注解,用于生成getter setter
@Accessors(chain = true) //Lombok注解,链式赋值使用
public class ExportExcelVo extends BaseRowModel {
    private static final long serialVersionUID = 1L;
 
    @ColumnWidth(25)
    @ExcelProperty(value = "企业名称")
    private String name;
 
    @ColumnWidth(25)
    @ExcelProperty(value = "社会统一信用代码")
    private String creditCode;
 
    @ColumnWidth(15)
    @ExcelProperty(value = "曾用名", converter = NullConverter.class)
    private String formerName;
 
    @ColumnWidth(15)
    @ExcelProperty(value = "公司法人")
    private String legalPerson;
 
    @ExcelProperty(value = "区域")
    private String province;
 
    @DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
    @ExcelProperty(value = "录入时间")
    private String createTime;
 
    @ColumnWidth(15)
    @ExcelProperty(value = "公司股东")
    private String stockholder;
 
    @ExcelProperty(value = "企业联系方式")
    private String contact;
 
    @NumberFormat("#.##%")		// 使用百分比格式格式化这个数字
    @ExcelProperty("数字")
    private Double doubleData;
}

ExcelProperty注解

可以定义表头的名称。这个注解还提供了index、order两个属性,可以定义列的位置和顺序。

  •  1)关于index

如果所有字段都不加index的话,默认index会从0开始,最早的声明的字段的名字的index的值就是0。之后字段的index就从0开始递增,依次类推。index是几决定了该字段数据会赋值给Excel中的第几列。如果不想按这个顺序把数据写到Excel当中。那么就可以手动设置index的值,把字段写到想要的列中去。如果index相同,直接会抛出异常,因为程序无法判断这个列放那个字段。

  • 2)关于order

index和order虽然都决定顺序,但是两者语义不同:如果order和index同时使用,index优先占据位置,order再进行排序。index=-1的话,使用jJava进行默认排序。order的默认值为Integer.MAX_VALUE,其中order的值越小,列越靠前

其他注解

  • @DateTimeFormat 日期转换,用String去接收excel日期格式的数据会调用这个注解。@DateTimeFormat(value = "yyyy-MM-dd HH:mm:ss")指定Excel中的日期类型
  • @NumberFormat 数字转换,用String去接收excel数字格式的数据会调用这个注解。
  • @ColumnWith  设置列宽度。只有一个参数value,value的单位是字符长度,最大可以设置255个字符。

@ExcelIgnore

EasyExcel组件标注在成员变量上,默认所有字段都会和excel去匹配,加了这个注解会忽略该字段

使用方法

/**
 * Excel批量导入数据
 *
 * @param file 导入文件
 */
@RequestMapping(value = "/import", method = RequestMethod.POST)
public CommonResponse<String> importEvents(MultipartFile file) {
  try {
    List<?> list = ExportUtil.importExcel(file, ImportExcelVo.class);
    System.out.println(list);
    return CommonResponse.success("数据导入完成");
  } catch (Exception e) {
    return CommonResponse.error("数据导入失败!" + e.getMessage());
  }
}


//生成excel文件
try {
    exportWriter = WriterFactory.getExportWriter(
        dto.getExportType() == null ? "csv" : dto.getExportType(), fileName);
    exportWriter.writeTitle(PhoneExportVO.class);
    exportWriter.appendContent(list, PhoneExportVO.class);
} catch (Exception e) {
    log.error("导出失败", e);
    throw new BaseException("导出失败");
} finally {
    if (exportWriter != null) {
        exportWriter.close();
    }
}


ExportWriter exportWriter = WriterFactory.getExportWriter(exportType, fileName);
List<Column> columns = trunkAreaVOResp.getColumns();
exportWriter.writeTitle(columns);
JSONArray dataList = (JSONArray) JSON.toJSON(trunkAreaVOResp.getTrunkArea());
exportWriter.appendContent(dataList, columns);
exportWriter.close();

Excel中日期格式

在Excel中,日期是以所谓的“序列号”存储的,这种序列号实际上是一个浮点数(因此有时被称为“double格式”),它代表了自1900年1月1日以来的天数。整数部分表示天数,而小数部分表示一天内的小时、分钟和秒数。例如,1900年1月1日在Excel中被存储为数字1.0,因为这是序列的起始点。那么,2023年1月1日将被存储为一个较大的数字,因为这一天距离1900年1月1日更远。具体来说,Excel日期序列号的计算方式如下:

  • 整数部分代表天数,例如,2023年1月1日是44622(取决于所用的日期系统,1900或1904系统)。
  • 小数部分代表一天中的时间。例如,0.5表示中午12:00,因为一天有24小时,所以0.5即为24小时的一半。

在大多数编程环境中,程序通常读取单元格的数值内容,而不是其显示的日期格式,因此了解日期规则就能够还原出日期内容

EasyExcel自定义转换器Converter

Timestamp 转换器

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
 
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

public class TimestampConverter implements Converter<Timestamp> {

  @Override
  public Class<Timestamp> supportJavaTypeKey() {
    return Timestamp.class;
  }

  @Override
  public CellDataTypeEnum supportExcelTypeKey() {
    return CellDataTypeEnum.STRING;
  }

  /**
   * 将excel对象转换为Java对象
   */
  @Override
  public Timestamp convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
      GlobalConfiguration globalConfiguration) throws Exception {
    return null;
  }


  /**
   * 将 Java 对象转换为 excel 对象
   */
  @Override
  public CellData convertToExcelData(Timestamp timestamp, ExcelContentProperty contentProperty,
      GlobalConfiguration globalConfiguration) throws Exception {
    return new CellData(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(timestamp));
  }
}

NullConverter

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

public class NullConverter implements Converter<String> {

	/**
	 * 回到 Java 中的对象类型
	 *
	 * @return 支持 Java 类
	 */
	@Override
	public Class supportJavaTypeKey() {
		return String.class;
	}

	/**
	 * * 返回 excel 中的对象枚举
	 * * @return 支持 {@link Cell DataTypeEnum}
	 * */
	@Override
	public CellDataTypeEnum supportExcelTypeKey() {
		return CellDataTypeEnum.STRING;
	}

	/**
	 * 将excel对象转换为Java对象
	 *
	 * @param cellData
	 * Excel 单元格数据。NotNull。
	 * @param contentProperty
	 * 内容属性。可空。
	 * @param globalConfiguration
	 * 全局配置。NotNull。
	 * @return 要放入 Java 对象的数据
	 * @抛出异常
	 *             例外。
	 */
	@Override
	public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
		return "-".equals(cellData.getStringValue()) ? null : cellData.getStringValue();
	}

	/**
	 * 将 Java 对象转换为 excel 对象
	 *
	 * @参数值
	 * Java 数据.NotNull。
	 * @param contentProperty
	 * 内容属性。可空。
	 * @param globalConfiguration
	 * 全局配置。NotNull。
	 * @return 数据放入 Excel
	 * @抛出异常
	 *             例外。
	 */
	@Override
	public CellData convertToExcelData(String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
		return new CellData<>(null == value ? "-" : value);
	}
}

ExcelToCsvConverter

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Map;

/**
 * Excel转换CSV的工具类
 *
 * @author hudy
 */
public class ExcelToCsvConverter {

  /**
   * 将Excel文件转换为CSV文件
   *
   * @param sourceFilePath Excel文件路径
   * @param targetFilePath CSV文件输出路径
   */
  public static void convertExcelToCsv(String sourceFilePath, String targetFilePath) {
    // 使用try-with-resources语句确保输入输出流的关闭
    try (InputStream inputStream = new FileInputStream(sourceFilePath);
        OutputStream outputStream = new FileOutputStream(targetFilePath)) {

      // 使用EasyExcel框架读取Excel文件并转换为CSV格式
      EasyExcel.read(inputStream, null, new ExcelToCsvListener(outputStream)).sheet().doRead();

    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * Excel到CSV的监听器,用于处理每行数据并写入CSV
   */
  private static class ExcelToCsvListener extends AnalysisEventListener<Map<Integer, String>> {

    private final OutputStream outputStream;

    public ExcelToCsvListener(OutputStream outputStream) {
      this.outputStream = outputStream;
    }

    /**
     * 处理每行Excel数据,将其写入CSV格式
     */
    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {
      writeData(data, context);
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
      try {
        outputStream.write(getCsvBom());
        writeData(headMap, context);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    /**
     * 将行数据转换为CSV格式并写入输出流
     */
    private void writeData(Map<Integer, String> data, AnalysisContext context) {
      try {
        StringBuilder rowContent = new StringBuilder();
        // 构建CSV行内容
        for (int i = 0; i < data.size(); i++) {
          if (i > 0) {
            rowContent.append(",");
          }
          rowContent.append(data.get(i));
        }
        // 写入行内容到输出流
        if (rowContent.length() > 0) {
          outputStream.write((rowContent.toString() + "\n").getBytes());
        }
        if (context.readRowHolder().getRowIndex() % 100 == 0) {
          System.out.println("解析到Excel行号:" + context.readRowHolder().getRowIndex());
        }
      } catch (IOException e) {
        throw new RuntimeException("写入CSV时发生错误", e);
      }
    }

    /**
     * 防止导出Csv乱码
     *
     * @return
     */
    private byte[] getCsvBom() {
      return new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF};
    }

    /**
     * 当所有Excel数据被解析完成时调用,用于关闭输出流
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
      try {
        System.out.println("所有数据解析完成!");
        outputStream.flush();
        outputStream.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

  // 示例主方法,用于演示类的使用
  public static void main(String[] args) {
    String sourceFilePath = "zjx.xlsx";
    String targetFilePath = "zjx.csv";
    convertExcelToCsv(sourceFilePath, targetFilePath);
  }
}

StatusConverter

import com.test.project.StatusEnum;

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;

/**
 * 状态枚举转换器
 */
public class StatusConverter implements Converter<Integer> {

  @Override
  public Class<Integer> supportJavaTypeKey() {
    return Integer.class;
  }

  @Override
  public CellDataTypeEnum supportExcelTypeKey() {
    return CellDataTypeEnum.STRING;
  }

  /**
   * 将excel对象转换为Java对象
   */
  @Override
  public Integer convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
    return StatusEnum.getKey(cellData.getStringValue());
  }

  /**
   * 将 Java 对象转换为 excel 对象
   */
  @Override
  public CellData convertToExcelData(Integer integer, ExcelContentProperty excelContentProperty,
      GlobalConfiguration globalConfiguration) throws Exception {
    return new CellData(StatusEnum.getValue(integer));
  }
}

使用方法一

每个字段都要添加@ExcelProperty(converter = NullConverter.class)代码,如果遇到大量的数据字段去填充处理会增加很多工作量。转换器仅支持需要被处理的数据字段,也就是适用于从数据库查询出来已有的数据,如日期格式或性别字段做转换时才生效

使用方法二

File uploadFile = File.createTempFile("export", ".xlsx");
String templateFilePath = systemUrl + "/template/exportPublishShop.xlsx";
 
ExcelWriterSheetBuilder excelWriterSheetBuilder = 
EasyExcel.write(uploadFile).registerConverter(new TimestampConverter()).withTemplate(templateFilePath).sheet();
 
List<Map<String, String>> productList = 查询数据数据
 
// productList 如果数据量很大一定要做分页查询,避免占用内存过大
excelWriterSheetBuilder.doFill(productList);

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐