add easyExcel

This commit is contained in:
pancm
2023-06-08 11:14:41 +08:00
parent 62c4c0b957
commit f02d2cc6f4
6 changed files with 737 additions and 8 deletions

View File

@@ -0,0 +1,22 @@
package com.pancm.excel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
/**
* @author pancm
* @Title: pancm_project
* @Description:
* @Version:1.0.0
* @Since:jdk1.8
* @date 2023/3/23
*/
@Data
@EqualsAndHashCode
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}

View File

@@ -0,0 +1,208 @@
package com.pancm.excel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.util.CollectionUtils;
import com.alibaba.excel.write.merge.AbstractMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.google.common.collect.Lists;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author pancm
* @Title: pancm_project
* @Description: 多行合并
* 合并单元格
* @Version:1.0.0
* @Since:jdk1.8
* @date 2023/3/23
*/
public class EasyExcelMergeTest {
public static void main(String[] args) {
writeExcel();
writeExcel01();
writeExcel02();
writeExcel03();
}
private static String getPath(String s) {
return System.getProperty("user.dir") + "/" + s+"_"+System.currentTimeMillis() + ".xlsx";
}
private static List<DemoData> data1() {
List<DemoData> list = Lists.newArrayList();
for (int i = 0; i < 3; i++) {
DemoData data = new DemoData();
data.setString("字符串" + 1);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
for (int i = 0; i < 3; i++) {
DemoData data = new DemoData();
data.setString("字符串" + 2);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
for (int i = 0; i < 4; i++) {
DemoData data = new DemoData();
data.setString("字符串" + 3);
data.setDate(new Date());
data.setDoubleData(0.57);
list.add(data);
}
return list;
}
// 自定义合并策略 该类继承了AbstractMergeStrategy抽象合并策略需要重写merge()方法
public static class CustomMergeStrategy extends AbstractMergeStrategy {
/**
* 分组,每几行合并一次
*/
private List<Integer> exportFieldGroupCountList;
/**
* 目标合并列index
*/
private Integer targetColumnIndex;
// 需要开始合并单元格的首行index
private Integer rowIndex;
// exportDataList为待合并目标列的值
public CustomMergeStrategy(List<String> exportDataList, Integer targetColumnIndex) {
this.exportFieldGroupCountList = getGroupCountList(exportDataList);
this.targetColumnIndex = targetColumnIndex;
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, Integer relativeRowIndex) {
if (null == rowIndex) {
rowIndex = cell.getRowIndex();
}
// 仅从首行以及目标列的单元格开始合并,忽略其他
if (cell.getRowIndex() == rowIndex && cell.getColumnIndex() == targetColumnIndex) {
mergeGroupColumn(sheet);
}
}
private void mergeGroupColumn(Sheet sheet) {
int rowCount = rowIndex;
for (Integer count : exportFieldGroupCountList) {
if (count == 1) {
rowCount += count;
continue;
}
// 合并单元格
CellRangeAddress cellRangeAddress = new CellRangeAddress(rowCount, rowCount + count - 1, targetColumnIndex, targetColumnIndex);
sheet.addMergedRegionUnsafe(cellRangeAddress);
rowCount += count;
}
}
// 该方法将目标列根据值是否相同连续可合并,存储可合并的行数
private List<Integer> getGroupCountList(List<String> exportDataList) {
if (CollectionUtils.isEmpty(exportDataList)) {
return new ArrayList<>();
}
List<Integer> groupCountList = new ArrayList<>();
int count = 1;
for (int i = 1; i < exportDataList.size(); i++) {
if (exportDataList.get(i).equals(exportDataList.get(i - 1))) {
count++;
} else {
groupCountList.add(count);
count = 1;
}
}
// 处理完最后一条后
groupCountList.add(count);
return groupCountList;
}
}
// 单列多行合并
public static void writeExcel() {
String fileName = getPath("单列多行");
ExcelWriter excelWriter = EasyExcel.write(fileName).excelType(ExcelTypeEnum.XLSX).build();
List<DemoData> demoDataList = data1();
// 写sheet的时候注册相应的自定义合并单元格策略
WriteSheet writeSheet = EasyExcel.writerSheet("模板1").head(DemoData.class)
.registerWriteHandler(new CustomMergeStrategy(demoDataList.stream().map(DemoData::getString).collect(Collectors.toList()), 0))
.build();
excelWriter.write(demoDataList, writeSheet);
excelWriter.finish();
}
//多列多行合并
public static void writeExcel01() {
String fileName = getPath("多行多列");
ExcelWriter excelWriter = EasyExcel.write(fileName).excelType(ExcelTypeEnum.XLSX).build();
List<DemoData> demoDataList = data1();
WriteSheet writeSheet = EasyExcel.writerSheet("模板1").head(DemoData.class)
.registerWriteHandler(new CustomMergeStrategy(demoDataList.stream().map(DemoData::getString).collect(Collectors.toList()), 0))
.registerWriteHandler(new CustomMergeStrategy(demoDataList.stream().map(o -> o.getDoubleData().toString()).collect(Collectors.toList()), 2))
.build();
excelWriter.write(demoDataList, writeSheet);
excelWriter.finish();
}
//多sheet
public static void writeExcel02() {
String fileName = getPath("多sheet");
ExcelWriter excelWriter = EasyExcel.write(fileName).excelType(ExcelTypeEnum.XLSX).build();
List<DemoData> demoDataList = data1();
WriteSheet writeSheet = EasyExcel.writerSheet("模板1").head(DemoData.class)
.registerWriteHandler(new CustomMergeStrategy(demoDataList.stream().map(DemoData::getString).collect(Collectors.toList()), 0))
.registerWriteHandler(new CustomMergeStrategy(demoDataList.stream().map(o -> o.getDoubleData().toString()).collect(Collectors.toList()), 2))
.build();
excelWriter.write(demoDataList, writeSheet);
WriteSheet writeSheet1 = EasyExcel.writerSheet("模板2").head(DemoData.class).build();
excelWriter.write(data1(), writeSheet1);
excelWriter.finish();
}
//多表
public static void writeExcel03() {
String fileName = getPath("多表");
ExcelWriter excelWriter = EasyExcel.write(fileName).excelType(ExcelTypeEnum.XLSX).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();
List<DemoData> demoDataList = data1();
// 需要表头设置为trueWriteTable一些属性会继承自WriteSheet
WriteTable writeTable = EasyExcel.writerTable(1).head(DemoData.class).needHead(Boolean.TRUE)
.registerWriteHandler(new CustomMergeStrategy(demoDataList.stream().map(DemoData::getString).collect(Collectors.toList()), 0))
.registerWriteHandler(new CustomMergeStrategy(demoDataList.stream().map(o -> o.getDoubleData().toString()).collect(Collectors.toList()), 2))
.build();
excelWriter.write(demoDataList, writeSheet, writeTable);
WriteTable writeTable1 = EasyExcel.writerTable(2).head(DemoData.class).needHead(Boolean.TRUE).build();
excelWriter.write(data1(), writeSheet, writeTable1);
excelWriter.finish();
}
}

View File

@@ -1,15 +1,288 @@
package com.pancm.excel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson.JSON;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* @author pancm
* @Title: pancm_project
* @Description:
* 参考: https://www.yuque.com/easyexcel/doc
* @Description: 参考: https://www.yuque.com/easyexcel/doc
* @Version:1.0.0
* @Since:jdk1.8
* @date 2021/1/26
*/
@Slf4j
public class EasyExcelTest {
/**
* 最简单的读
* <p>
* 1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>
* 2. 由于默认一行行的读取excel所以需要创建excel一行一行的回调监听器参照{@link DemoDataListener}
* <p>
* 3. 直接读即可
*/
@Test
public void simpleRead() {
// 写法1JDK8+ ,不用额外写一个DemoDataListener
// since: 3.0.0-beta1
String fileName = "/home" + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读然后读取第一个sheet 文件流会自动关闭
// 这里每次会读取3000条数据 然后返回过来 直接调用使用数据就行
// EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {
// for (DemoData demoData : dataList) {
// log.info("读取到一条数据{}", JSON.toJSONString(demoData));
// }
// })).sheet().doRead();
// 写法2
// 匿名内部类 不用额外写一个DemoDataListener
fileName = "/home" + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {
/**
* 单次缓存的数据量
*/
public static final int BATCH_COUNT = 100;
/**
*临时存储
*/
private List<DemoData> cachedDataList = new ArrayList<>();
/**
* All listeners receive this method when any one Listener does an error report. If an exception is thrown here, the
* entire read will terminate.
*
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
}
/**
* When analysis one head row trigger invoke function.
*
* @param headMap
* @param context
*/
@Override
public void invokeHead(Map<Integer, CellData> headMap, AnalysisContext context) {
}
@Override
public void invoke(DemoData data, AnalysisContext context) {
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
cachedDataList = new ArrayList<>();
}
}
/**
* The current method is called when extra information is returned
*
* @param extra extra information
* @param context
*/
@Override
public void extra(CellExtra extra, AnalysisContext context) {
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
}
/**
* Verify that there is another piece of data.You can stop the read by returning false
*
* @param context
* @return
*/
@Override
public boolean hasNext(AnalysisContext context) {
return false;
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
log.info("存储数据库成功!");
}
}).sheet().doRead();
// 有个很重要的点 DemoDataListener 不能被spring管理要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法3
fileName = "/home" + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
// 写法4
fileName = "/home" + "demo" + File.separator + "demo.xlsx";
// 一个文件一个reader
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
// 构建一个sheet 这里可以指定名字或者no
ReadSheet readSheet = EasyExcel.readSheet(0).build();
// 读取一个sheet
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}
@Data
@EqualsAndHashCode
class DemoData {
private String string;
private Date date;
private Double doubleData;
}
class DemoDataListener implements ReadListener<DemoData> {
/**
* 每隔5条存储数据库实际使用中可以100条然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
private List<DemoData> cachedDataList = new ArrayList<>();
/**
* 假设这个是一个DAO当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
// private DemoDAO demoDAO;
//
// public DemoDataListener() {
// // 这里是demo所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
// demoDAO = new DemoDAO();
// }
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
// public DemoDataListener(DemoDAO demoDAO) {
// this.demoDAO = demoDAO;
// }
/**
* All listeners receive this method when any one Listener does an error report. If an exception is thrown here, the
* entire read will terminate.
*
* @param exception
* @param context
* @throws Exception
*/
@Override
public void onException(Exception exception, AnalysisContext context) throws Exception {
}
/**
* When analysis one head row trigger invoke function.
*
* @param headMap
* @param context
*/
@Override
public void invokeHead(Map<Integer, CellData> headMap, AnalysisContext context) {
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
cachedDataList.add(data);
// 达到BATCH_COUNT了需要去存储一次数据库防止数据几万条数据在内存容易OOM
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
// cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* The current method is called when extra information is returned
*
* @param extra extra information
* @param context
*/
@Override
public void extra(CellExtra extra, AnalysisContext context) {
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* Verify that there is another piece of data.You can stop the read by returning false
*
* @param context
* @return
*/
@Override
public boolean hasNext(AnalysisContext context) {
return false;
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", cachedDataList.size());
// demoDAO.save(cachedDataList);
log.info("存储数据库成功!");
}
}
}

View File

@@ -0,0 +1,110 @@
package com.pancm.excel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Serializable;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class EasyExcelUtils {
public static void main(String[] args) {
String[] headMap = { "项目名称", "楼栋名称", "单元名称", "楼层名称", "房间名称", "业主/租户姓名", "房间状态", "房间功能","认证人数","测试" };
String[] dataStrMap={"hName","bName","uName","fName","pName","cName","pState","pFunction","pNum"};
NoModelWriteData d = new NoModelWriteData();
d.setFileName("认证统计");
d.setHeadMap(headMap);
d.setDataStrMap(dataStrMap);
List<JSONObject> listDatas = new ArrayList<>();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hName","项目1");
jsonObject.put("bName","二楼");
jsonObject.put("aa","测试");
d.setDataList(listDatas);
EasyExcelUtils easyExcelUtils = new EasyExcelUtils();
// easyExcelUtils.jsonWrite(d, response);
}
//不创建对象的导出
public void jsonWrite(NoModelWriteData data, HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题请直接用浏览器或者用postman
try {
// response.setContentType("application/vnd.ms-excel");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode(data.getFileName(), "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
// 这里需要设置不关闭流
EasyExcel.write(response.getOutputStream()).head(head(data.getHeadMap())).sheet(data.getFileName()).doWrite(dataList(data.getDataList(), data.getDataStrMap()));
} catch (Exception e) {
// 重置response
response.reset();
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
Map<String, String> map = new HashMap<String, String>();
map.put("status", "failure");
map.put("message", "下载文件失败" + e.getMessage());
response.getWriter().println(JSON.toJSONString(map));
}
}
//创建对象的导出
public <T> void simpleWrite(SimpleWriteData data,Class<T> clazz, HttpServletResponse response) throws IOException {
// 这里注意 有同学反应使用swagger 会导致各种问题请直接用浏览器或者用postman
// response.setContentType("application/vnd.ms-excel");
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
String fileName = URLEncoder.encode(data.getFileName(), "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), clazz).sheet(data.getFileName()).doWrite(data.getDataList());
}
//设置表头
private List<List<String>> head(String[] headMap) {
List<List<String>> list = new ArrayList<List<String>>();
for (String head : headMap) {
List<String> headList = new ArrayList<String>();
headList.add(head);
list.add(headList);
}
return list;
}
//设置导出的数据内容
private List<List<Object>> dataList(List<JSONObject> dataList, String[] dataStrMap) {
List<List<Object>> list = new ArrayList<List<Object>>();
for (JSONObject map : dataList) {
List<Object> data = new ArrayList<Object>();
for (int i = 0; i < dataStrMap.length; i++) {
data.add(map.get(dataStrMap[i]));
}
list.add(data);
}
return list;
}
}
@Data
class NoModelWriteData implements Serializable {
private String fileName;//文件名
private String[] headMap;//表头数组
private String[] dataStrMap;//对应数据字段数组
private List<JSONObject> dataList;//数据集合
}
@Data
class SimpleWriteData implements Serializable {
private String fileName;//文件名
private List<?> dataList;//数据列表
}

View File

@@ -0,0 +1,115 @@
package com.pancm.excel;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author pancm
* @Title: pancm_project
* @Description: excel写模块测试
* @Version:1.0.0
* @Since:jdk1.8
* @date 2022/3/2
*/
@Slf4j
public class EasyExcelWriteTest {
public static void main(String[] args) {
List<JSONObject> jsonObjectList = new ArrayList<>();
JSONObject jsonObject = new JSONObject();
jsonObject.put("t1",1);
jsonObject.put("t2","2");
JSONObject jsonObject2 = new JSONObject();
jsonObject2.put("t1",11);
jsonObject2.put("t2","22");
jsonObjectList.add(jsonObject);
jsonObjectList.add(jsonObject2);
String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写然后写到第一个sheet名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName).sheet("模板").doWrite(jsonObjectList);
}
public void excludeOrIncludeWrite() {
// 注意 simpleWrite在数据量不大的情况下可以使用5000以内具体也要看实际情况数据量大参照 重复多次写入
// 写法1 JDK8+
// since: 3.0.0-beta1
String fileName = "/home"+ "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写然后写到第一个sheet名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
// EasyExcel.write(fileName, DemoData.class)
// .sheet("模板")
// .doWrite(
// () -> {
// // 分页查询数据
// return data();
// });
// 写法2
fileName = "/home"+ "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写然后写到第一个sheet名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
// 写法3
fileName = "/home"+ "/simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
private List<DemoData> data() {
List<DemoData> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
@Data
@EqualsAndHashCode
class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
}

View File

@@ -1,8 +1,9 @@
/**
* @Title: package-info
* @Description: excel相关包
* @Version:1.0.0
* @author pancm
* @date 2019年2月28日
*/
* @Title: pancm_project
* @Description: excel的工具类
* @Version:1.0.0
* @Since:jdk1.8
* @author pancm
* @date 2021/1/26
*/
package com.pancm.excel;