1.引入 hutool-poi WrapExcelUtils

This commit is contained in:
Wang Chen Chen
2023-11-30 14:33:51 +08:00
parent d6b0160513
commit 6d6e4dd8d6
6 changed files with 211 additions and 334 deletions

View File

@@ -26,9 +26,14 @@
<artifactId>spring-boot-starter-json</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-poi</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<artifactId>poi-ooxml</artifactId>
<version>${apache-poi.version}</version>
</dependency>

View File

@@ -1,327 +0,0 @@
package com.xaaef.molly.common.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* <p>
* Excel 工具类
* </p>
*
* @author WangChenChen
* @version 1.2
* @date 2023/11/23 14:50
*/
@Slf4j
public class ExcelUtils {
/**
* 默认分页数据
*/
private static final int PAGE_SIZE = 10000;
/**
* 最大分页数据
*/
private static final int MAX_PAGE_SIZE = 60000;
/**
* 泛型 分页导出 为 Excel
*
* @param fileName 文件名称
* @param pageSize 每页多少条数据Excel每个Sheet最大极限6w条数据
* @param dataList 数据列表
* @return ByteArrayOutputStream
*/
public static <T> ByteArrayOutputStream genPageExport(Integer pageSize, List<T> dataList) {
if (pageSize == null || pageSize < 1) {
pageSize = PAGE_SIZE;
}
if (pageSize > MAX_PAGE_SIZE) {
pageSize = MAX_PAGE_SIZE;
}
int finalPageSize = pageSize;
//创建一个Excel文件
return getWorkbook((workbook) -> {
if (!dataList.isEmpty()) {
// 获取设备的总数量
int total = dataList.size();
if (total > finalPageSize) {
var pageNum = new AtomicInteger(1);
toPageIndexRange(total, finalPageSize, (fromIndex, toIndex) -> {
var rangeData = dataList.subList(fromIndex, toIndex);
genPageExport(workbook, String.format("第 %d 页", pageNum.get()), rangeData);
pageNum.getAndIncrement();
});
} else {
genPageExport(workbook, String.format("第 %d 页", 1), dataList);
}
}
});
}
/**
* 泛型 分页导出 为 Excel 。每页默认 1w 条数据
*
* @param fileName 文件名称
* @param dataList 数据列表
* @return ByteArrayOutputStream
*/
public static <T> ByteArrayOutputStream genPageExport(List<T> dataList) {
return genPageExport(PAGE_SIZE, dataList);
}
/**
* 泛型 分页导出 为 Excel 。每页默认 1w 条数据
*
* @param fileName 文件名称
* @param dataList 数据列表
* @return ResponseEntity<ByteArrayResource>
*/
public static <T> ResponseEntity<ByteArrayResource> genPageExport(String fileName, List<T> dataList) {
return genPageExport(fileName, PAGE_SIZE, dataList);
}
/**
* 泛型 分页导出 为 Excel
*
* @param fileName 文件名称
* @param pageSize 每页多少条数据Excel每个Sheet最大极限6w条数据
* @param dataList 数据列表
* @return ResponseEntity<ByteArrayResource>
*/
public static <T> ResponseEntity<ByteArrayResource> genPageExport(String fileName, Integer pageSize, List<T> dataList) {
if (StrUtil.isEmpty(fileName)) {
fileName = RandomUtil.randomString(12);
} else {
if (fileName.endsWith(".xlsx")) {
fileName = fileName.replaceFirst(".xlsx", "");
}
}
var os = genPageExport(pageSize, dataList);
var headerValue = String.format("attachment;filename=%s.xlsx", fileNameEncode(fileName));
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, headerValue)
.body(new ByteArrayResource(os.toByteArray()));
}
/**
* 计算分页范围
* 根据总数和分页数量,计算每个分页的 开始索引 和 结束索引
* 假设总共300条数据。每页100条。
* 列如第一页0~99 、第二页100~199 、第三页200~299 以此类推
*/
private static void toPageIndexRange(int total, int pageSize, BiConsumer<Integer, Integer> consumer) {
// 总页数
int totalPage = PageUtil.totalPage(total, pageSize);
// 起始索引
var startIndex = new AtomicInteger(0);
for (int i = 0; i < totalPage; i++) {
int fromIndex = startIndex.get();
var toIndex = (fromIndex + pageSize);
startIndex.set(toIndex);
if (toIndex >= total) {
toIndex = total;
}
consumer.accept(fromIndex, toIndex);
}
}
/**
* 泛型 分页导出
*/
private static <T> void genPageExport(HSSFWorkbook workbook, String sheetName, List<T> rangeData) {
if (rangeData.isEmpty()) {
return;
}
// 反射获取对象属性,和描述名称
var schemaDescriptions = getFieldSchemaDescription(CollectionUtil.getFirst(rangeData));
//创建工作表,指定工作表名称
var sheet1 = workbook.createSheet(sheetName);
sheet1.setDefaultColumnWidth(20);
var titleRow = sheet1.createRow(0);
// 设置标题
setTitleStyle(titleRow, createTitleStyle(workbook), schemaDescriptions);
var cellNum = schemaDescriptions.size();
for (int i = 0; i < rangeData.size(); i++) {
var data = rangeData.get(i);
var row = sheet1.createRow((i + 1));
var fieldsValue = ReflectUtil.getFieldsValue(data);
for (int v = 0; v < cellNum; v++) {
var cell = row.createCell(v);
var val = fieldsValue[v];
setCellValue(cell, val);
}
}
}
/**
* 根据 对象类型 格式化之后设置到单元格内
*/
private static void setCellValue(HSSFCell cell, Object obj) {
if (obj == null) {
cell.setCellType(CellType.BLANK);
return;
}
if (obj instanceof Number) {
cell.setCellType(CellType.NUMERIC);
if (obj instanceof Long o2) {
cell.setCellValue(o2);
} else if (obj instanceof Integer o2) {
cell.setCellValue(o2);
} else if (obj instanceof Short o2) {
cell.setCellValue(o2);
} else if (obj instanceof Float o2) {
cell.setCellValue(o2);
} else if (obj instanceof Double o2) {
cell.setCellValue(o2);
} else if (obj instanceof Byte o2) {
cell.setCellValue(o2);
}
} else {
cell.setCellType(CellType.STRING);
if (obj instanceof String o1) {
cell.setCellValue(o1);
} else if (obj instanceof Boolean o1) {
cell.setCellType(CellType.BOOLEAN);
cell.setCellValue(o1);
} else if (obj instanceof Date o1) {
cell.setCellValue(DateUtil.formatDateTime(o1));
} else if (obj instanceof LocalDate o1) {
cell.setCellValue(o1.format(DatePattern.NORM_DATE_FORMATTER));
} else if (obj instanceof LocalTime o1) {
cell.setCellValue(o1.format(DatePattern.NORM_TIME_FORMATTER));
} else if (obj instanceof LocalDateTime o1) {
cell.setCellValue(o1.format(DatePattern.NORM_DATETIME_FORMATTER));
} else if (obj instanceof ZonedDateTime o1) {
cell.setCellValue(o1.format(DatePattern.NORM_DATETIME_FORMATTER));
} else if (obj instanceof Iterable<?> || obj.getClass().isArray() || obj instanceof Map<?, ?> || obj instanceof Enum<?>) {
cell.setCellValue(JsonUtils.toFormatJson(obj));
} else {
cell.setCellValue(obj.toString());
}
}
}
/**
* 反射获取 对象属性 @Schema注解上的description
*
* @return List<String>
*/
private static List<String> getFieldSchemaDescription(Object obj) {
return Arrays.stream(ReflectUtil.getFields(obj.getClass()))
.map(field -> {
var ann = field.getAnnotation(Schema.class);
return ann == null ? field.getName() : ann.description();
})
.collect(Collectors.toCollection(LinkedList::new));
}
/**
* 获取一个 Workbook
*
* @return ResponseEntity<ByteArrayResource>
*/
public static ByteArrayOutputStream getWorkbook(Consumer<HSSFWorkbook> workbookConsumer) {
//在内存中创建一个Excel文件
var workbook = new HSSFWorkbook();
// 创建一个 消费者
workbookConsumer.accept(workbook);
var os = new ByteArrayOutputStream();
try {
workbook.write(os);
workbook.close();
} catch (IOException e) {
log.error(e.getMessage());
try {
os.write(e.getMessage().getBytes(StandardCharsets.UTF_8));
} catch (IOException ex) {
log.error(e.getMessage());
}
}
return os;
}
/**
* 文件名编码
*/
private static String fileNameEncode(String fileName) {
return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
}
/**
* 创建 标题 样式
*/
private static HSSFCellStyle createTitleStyle(HSSFWorkbook workbook) {
// 标题,颜色
var titleStyle = workbook.createCellStyle();
titleStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中
titleStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
titleStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
titleStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
var font1 = workbook.createFont();
font1.setBold(true); //加粗
font1.setColor(Font.COLOR_NORMAL); //字体颜色
titleStyle.setFont(font1);
return titleStyle;
}
/**
* 设置 标题 样式
*/
private static void setTitleStyle(HSSFRow titleRow, HSSFCellStyle titleStyle, List<String> titleName) {
for (int index = 0; index < titleName.size(); index++) {
var cell = titleRow.createCell(index);
cell.setCellStyle(titleStyle);
cell.setCellValue(titleName.get(index));
cell.setCellType(CellType.STRING);
}
}
}

View File

@@ -0,0 +1,197 @@
package com.xaaef.molly.common.util;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
/**
* <p>
* Excel 工具类
* </p>
*
* @author WangChenChen
* @version 1.2
* @date 2023/11/23 14:50
*/
@Slf4j
public class WrapExcelUtils {
/**
* 默认分页数据
*/
private static final int PAGE_SIZE = 10000;
/**
* 最大分页数据
*/
private static final int MAX_PAGE_SIZE = 60000;
/**
* 泛型 分页导出 为 Excel 。每页默认 1w 条数据
*
* @param fileName 文件名称
* @param dataList 数据列表
* @return ByteArrayOutputStream
*/
public static <T> ByteArrayOutputStream genPageWrite(List<T> dataList) {
return genPageWrite(PAGE_SIZE, dataList);
}
/**
* 泛型 分页导出 为 Excel 。每页默认 1w 条数据
*
* @param fileName 文件名称
* @param dataList 数据列表
* @return ResponseEntity<ByteArrayResource>
*/
public static <T> ResponseEntity<ByteArrayResource> genPageWrite(String fileName, List<T> dataList) {
return genPageWrite(fileName, PAGE_SIZE, dataList);
}
/**
* 泛型 分页导出 为 Excel
*
* @param fileName 文件名称
* @param pageSize 每页多少条数据Excel每个Sheet最大极限6w条数据
* @param dataList 数据列表
* @return ResponseEntity<ByteArrayResource>
*/
public static <T> ResponseEntity<ByteArrayResource> genPageWrite(String fileName, Integer pageSize, List<T> dataList) {
if (StrUtil.isEmpty(fileName)) {
fileName = RandomUtil.randomString(12);
} else {
if (fileName.endsWith(".xlsx")) {
fileName = fileName.replaceFirst(".xlsx", "");
}
}
var os = genPageWrite(pageSize, dataList);
var headerValue = String.format("attachment;filename=%s.xlsx", fileNameEncode(fileName));
return ResponseEntity.ok()
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.header(HttpHeaders.CONTENT_DISPOSITION, headerValue)
.body(new ByteArrayResource(os.toByteArray()));
}
/**
* 泛型 分页导出 为 Excel
*
* @param fileName 文件名称
* @param pageSize 每页多少条数据Excel每个Sheet最大极限6w条数据
* @param dataList 数据列表
* @return ByteArrayOutputStream
*/
public static <T> ByteArrayOutputStream genPageWrite(Integer pageSize, List<T> dataList) {
if (pageSize == null || pageSize < 1) {
pageSize = PAGE_SIZE;
}
if (pageSize > MAX_PAGE_SIZE) {
pageSize = MAX_PAGE_SIZE;
}
var bigWriter = ExcelUtil.getBigWriter();
if (!dataList.isEmpty()) {
// 获取对象字段的别名
Map<String, String> fieldSchemaDescription = getFieldSchemaDescription(dataList.getFirst());
bigWriter.setOnlyAlias(true).setHeaderAlias(fieldSchemaDescription);
// 获取设备的总数量
int total = dataList.size();
if (total > pageSize) {
// 删除 第一个 Sheet
bigWriter.getWorkbook().removeSheetAt(0);
var pageNum = new AtomicInteger(1);
// 分页写入
toPageIndexRange(total, pageSize, (fromIndex, toIndex) -> {
var rangeData = dataList.subList(fromIndex, toIndex);
var sheetName = String.format("第 %d 页", pageNum.get());
bigWriter.setSheet(sheetName).write(rangeData);
pageNum.getAndIncrement();
});
} else {
bigWriter.write(dataList);
}
}
var result = new ByteArrayOutputStream();
bigWriter.flush(result, true);
return result;
}
/**
* 计算分页范围
* 根据总数和分页数量,计算每个分页的 开始索引 和 结束索引
* 假设总共300条数据。每页100条。
* 列如第一页0~99 、第二页100~199 、第三页200~299 以此类推
*/
private static void toPageIndexRange(int total, int pageSize, BiConsumer<Integer, Integer> consumer) {
// 总页数
int totalPage = PageUtil.totalPage(total, pageSize);
// 起始索引
var startIndex = new AtomicInteger(0);
for (int i = 0; i < totalPage; i++) {
int fromIndex = startIndex.get();
var toIndex = (fromIndex + pageSize);
startIndex.set(toIndex);
if (toIndex >= total) {
toIndex = total;
}
consumer.accept(fromIndex, toIndex);
}
}
/**
* 反射获取 对象属性 @Schema注解上的description
* <p>
* key : 属性名称
* value : cn.hutool.core.annotation.Alias.value
* 或者 io.swagger.v3.oas.annotations.media.Schema.description
*
* @return Map<String, String>
*/
private static Map<String, String> getFieldSchemaDescription(Object obj) {
return Arrays.stream(ReflectUtil.getFields(obj.getClass()))
.collect(Collectors.toMap(Field::getName, field -> {
var ann1 = field.getAnnotation(cn.hutool.core.annotation.Alias.class);
if (ann1 != null) {
return ann1.value();
}
var ann2 = field.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class);
if (ann2 != null) {
return ann2.description();
}
return field.getName();
}, (k1, k2) -> k1, LinkedHashMap::new));
}
/**
* 文件名编码
*/
private static String fileNameEncode(String fileName) {
return URLEncoder.encode(fileName, StandardCharsets.UTF_8).replaceAll("\\+", "%20");
}
}

View File

@@ -10,7 +10,7 @@ import cn.hutool.core.util.RandomUtil;
import com.mysql.cj.jdbc.MysqlDataSource;
import com.xaaef.molly.auth.jwt.JwtSecurityUtils;
import com.xaaef.molly.common.consts.JwtConst;
import com.xaaef.molly.common.util.ExcelUtils;
import com.xaaef.molly.common.util.WrapExcelUtils;
import com.xaaef.molly.common.util.JsonUtils;
import com.xaaef.molly.internal.dto.OperateUserDTO;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -134,6 +134,7 @@ public class NoSpringTests {
System.out.println(formatJson);
}
@Getter
@Setter
@Builder
@@ -173,7 +174,7 @@ public class NoSpringTests {
public void test12() throws IOException {
List<String> source = List.of("A1", "B1", "C3", "D4", "E5", "F6", "S7");
var dataList = new ArrayList<TestExportEntity>();
for (int i = 0; i < 200000; i++) {
for (int i = 0; i < 20000; i++) {
dataList.add(
new TestExportEntity(
(long) (i + 1),
@@ -193,7 +194,7 @@ public class NoSpringTests {
);
}
var start = System.currentTimeMillis();
var entity = ExcelUtils.genPageExport(60000, dataList);
var entity = WrapExcelUtils.genPageWrite(5000, dataList);
var file = Files.createFile(Path.of(String.format("%s.xlsx", RandomUtil.randomString(10)))).toFile();
FileUtil.writeBytes(entity.toByteArray(), file);
var ms = System.currentTimeMillis() - start;
@@ -207,4 +208,5 @@ public class NoSpringTests {
System.out.println(i);
}
}

View File

@@ -2,7 +2,7 @@ package com.xaaef.molly.system.controller;
import com.xaaef.molly.common.domain.Pagination;
import com.xaaef.molly.common.po.SearchPO;
import com.xaaef.molly.common.util.ExcelUtils;
import com.xaaef.molly.common.util.WrapExcelUtils;
import com.xaaef.molly.common.util.JsonResult;
import com.xaaef.molly.system.entity.SysTenant;
import com.xaaef.molly.system.po.CreateTenantPO;
@@ -89,7 +89,7 @@ public class SysTenantController {
@GetMapping("/list/export")
public ResponseEntity<ByteArrayResource> listExport() {
var result = baseService.list();
return ExcelUtils.genPageExport("租户数据", result);
return WrapExcelUtils.genPageWrite("租户数据", result);
}

View File

@@ -45,7 +45,7 @@
<!-- 服务器运行状态查询, 如: CPU、磁盘、内存 -->
<oshi.version>6.4.6</oshi.version>
<!-- Excel文件操作 -->
<apache-poi.version>5.2.4</apache-poi.version>
<apache-poi.version>5.2.5</apache-poi.version>
<!-- 小而美的工具库 -->
<hutool.version>5.8.23</hutool.version>
<!-- 简单、方便的OpenAPI接口文档 -->