refactor(common-excel): 重构 Excel 导入导出构建器

- 删除 ExcelUtil,统一由 ExcelBuilder 承载导入、导出、模板导出、ZIP 导出和自定义写出能力
  - 新增 ExcelBuilder.read(...) 导入构建器,支持 validate、failFast、listener、sheet、密码、空行、表头行、trim/strip、限制读取行数等配置
  - 扩展导出构建器,支持动态列、sheet 编号、导出密码、表头控制、固定列宽/行高、自定义 Converter 和 WriteHandler
  - 将业务代码中的 ExcelUtil 调用全部替换为 ExcelBuilder 链式调用
  - 同步调整代码生成器 controller 模板,后续生成代码默认使用 ExcelBuilder
  - 将文件名编码、响应头处理、字典表达式转换收敛为内部实现细节
This commit is contained in:
疯狂的狮子Li
2026-05-16 13:56:35 +08:00
parent 475618e960
commit 7b66f0de26
25 changed files with 1109 additions and 724 deletions
@@ -13,10 +13,12 @@ import org.dromara.common.excel.annotation.ExcelDictFormat;
import org.dromara.common.core.service.DictService;
import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.excel.utils.ExcelUtil;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Field;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 字典格式化转换处理
@@ -26,6 +28,8 @@ import java.lang.reflect.Field;
@Slf4j
public class ExcelDictConvert implements Converter<Object> {
private DictService dictService;
@Override
public Class<Object> supportJavaTypeKey() {
return Object.class;
@@ -43,9 +47,9 @@ public class ExcelDictConvert implements Converter<Object> {
String label = cellData.getStringValue();
String value;
if (StringUtils.isBlank(type)) {
value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator());
value = reverseByExp(label, anno.readConverterExp(), anno.separator());
} else {
value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator());
value = getDictService().getDictValue(type, label, anno.separator());
}
return Convert.convert(contentProperty.getField().getType(), value);
}
@@ -60,9 +64,9 @@ public class ExcelDictConvert implements Converter<Object> {
String value = Convert.toStr(object);
String label;
if (StringUtils.isBlank(type)) {
label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator());
label = convertByExp(value, anno.readConverterExp(), anno.separator());
} else {
label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator());
label = getDictService().getDictLabel(type, value, anno.separator());
}
return new WriteCellData<>(label);
}
@@ -70,4 +74,76 @@ public class ExcelDictConvert implements Converter<Object> {
private ExcelDictFormat getAnnotation(Field field) {
return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
}
private DictService getDictService() {
if (dictService == null) {
dictService = SpringUtils.getBean(DictService.class);
}
return dictService;
}
/**
* 解析导出值 0=男,1=女,2=未知。
*/
private static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
Map<String, String> convertSource = parseConverterExp(converterExp);
for (Map.Entry<String, String> item : convertSource.entrySet()) {
if (StringUtils.contains(propertyValue, separator)) {
for (String value : splitPropertyValue(propertyValue, separator)) {
if (item.getKey().equals(value)) {
propertyString.append(item.getValue()).append(separator);
break;
}
}
} else {
if (item.getKey().equals(propertyValue)) {
return item.getValue();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 反向解析值 男=0,女=1,未知=2。
*/
private static String reverseByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
Map<String, String> convertSource = parseConverterExp(converterExp);
for (Map.Entry<String, String> item : convertSource.entrySet()) {
if (StringUtils.contains(propertyValue, separator)) {
for (String value : splitPropertyValue(propertyValue, separator)) {
if (item.getValue().equals(value)) {
propertyString.append(item.getKey()).append(separator);
break;
}
}
} else {
if (item.getValue().equals(propertyValue)) {
return item.getKey();
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
private static Map<String, String> parseConverterExp(String converterExp) {
Map<String, String> result = new LinkedHashMap<>();
if (StringUtils.isBlank(converterExp)) {
return result;
}
for (String item : converterExp.split(StringUtils.SEPARATOR)) {
String[] itemArray = item.split("=", 2);
if (itemArray.length != 2) {
throw new IllegalArgumentException("Excel转换表达式格式错误: " + item);
}
result.put(itemArray[0], itemArray[1]);
}
return result;
}
private static String[] splitPropertyValue(String propertyValue, String separator) {
return propertyValue.split(Pattern.quote(separator));
}
}
@@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class ExcelEnumConvert implements Converter<Object> {
private static final Map<Field, Map<Object, String>> ENUM_MAP_CACHE = new ConcurrentHashMap<>();
private static final Map<Field, Map<Object, Object>> ENUM_REVERSE_MAP_CACHE = new ConcurrentHashMap<>();
private static final Map<Field, Map<String, Object>> ENUM_REVERSE_MAP_CACHE = new ConcurrentHashMap<>();
@Override
public Class<Object> supportJavaTypeKey() {
@@ -55,16 +55,24 @@ public class ExcelEnumConvert implements Converter<Object> {
}
Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
// 从Java输出至Excel是code转text,从Excel转Java应将text与code对调
Map<Object, Object> enumTextToCodeMap = ENUM_REVERSE_MAP_CACHE.computeIfAbsent(
Map<String, Object> enumTextToCodeMap = ENUM_REVERSE_MAP_CACHE.computeIfAbsent(
contentProperty.getField(),
f -> {
Map<Object, Object> reverseMap = new HashMap<>();
enumCodeToTextMap.forEach((key, value) -> reverseMap.put(value, key));
Map<String, Object> reverseMap = new HashMap<>();
enumCodeToTextMap.forEach((key, value) -> {
Object oldValue = reverseMap.put(value, key);
if (ObjectUtil.isNotNull(oldValue)) {
throw new IllegalArgumentException("枚举导入文本值重复: " + value);
}
});
return reverseMap;
}
);
// 应该从text -> code中查找
Object codeValue = enumTextToCodeMap.get(textValue);
Object codeValue = enumTextToCodeMap.get(Convert.toStr(textValue));
if (ObjectUtil.isNull(codeValue)) {
throw new IllegalArgumentException("枚举值不匹配: " + textValue + ",允许值: " + enumTextToCodeMap.keySet());
}
return Convert.convert(contentProperty.getField().getType(), codeValue);
}
@@ -86,6 +94,9 @@ public class ExcelEnumConvert implements Converter<Object> {
for (Enum<?> enumConstant : enumConstants) {
Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
if (ObjectUtil.isNull(codeValue) || ObjectUtil.isNull(textValue)) {
throw new IllegalArgumentException("枚举字段 code/text 不能为空: " + enumConstant.name());
}
enumValueMap.put(codeValue, textValue);
}
return enumValueMap;
@@ -129,11 +129,14 @@ public class CellMergeHandler {
continue;
}
CellMerge cm = field.getAnnotation(CellMerge.class);
int index = cm.index() == -1 ? i : cm.index();
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
int index = cm.index();
if (index == -1) {
index = property != null && property.index() != -1 ? property.index() : i;
}
mergeFields.put(field, FieldColumnIndex.of(index, cm));
if (hasTitle) {
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
if (hasTitle && property != null && property.value().length > 0) {
rowIndex = Math.max(rowIndex, property.value().length);
}
}
@@ -39,13 +39,22 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
/**
* 导入回执
*/
private ExcelResult<T> excelResult;
private final ExcelResult<T> excelResult = new DefaultExcelResult<>();
/**
* 发生异常时是否立即终止读取,默认保持原有快速失败行为
*/
private Boolean failFast = Boolean.TRUE;
public DefaultExcelListener(boolean isValidate) {
this.excelResult = new DefaultExcelResult<>();
this.isValidate = isValidate;
}
public DefaultExcelListener(boolean isValidate, boolean failFast) {
this.isValidate = isValidate;
this.failFast = failFast;
}
/**
* 处理异常
*
@@ -60,7 +69,7 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
Integer rowIndex = excelDataConvertException.getRowIndex();
Integer columnIndex = excelDataConvertException.getColumnIndex();
errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
rowIndex + 1, columnIndex + 1, headMap == null ? "" : headMap.get(columnIndex));
if (log.isDebugEnabled()) {
log.warn(errMsg);
}
@@ -78,7 +87,9 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
log.warn(errMsg, exception);
}
excelResult.getErrorList().add(errMsg);
throw new ExcelAnalysisException(errMsg);
if (failFast) {
throw new ExcelAnalysisException(errMsg);
}
}
@Override
@@ -15,6 +15,7 @@ import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddressList;
import org.apache.poi.ss.util.CellReference;
import org.apache.poi.ss.util.WorkbookUtil;
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
import org.dromara.common.core.exception.ServiceException;
@@ -40,11 +41,8 @@ import java.util.*;
@Slf4j
public class ExcelDownHandler implements SheetWriteHandler {
/**
* Excel表格中的列名英文
* 仅为了解析列英文,禁止修改
*/
private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final int FIRST_DATA_ROW_INDEX = 1;
private static final int LAST_DATA_ROW_INDEX = 1000;
/**
* 单选数据Sheet名
*/
@@ -57,7 +55,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
* 下拉可选项
*/
private final List<DropDownOptions> dropDownOptions;
private final DictService dictService;
private DictService dictService;
/**
* 当前单选进度
*/
@@ -71,7 +69,6 @@ public class ExcelDownHandler implements SheetWriteHandler {
this.dropDownOptions = options;
this.currentOptionsColumnIndex = 0;
this.currentLinkedOptionsSheetIndex = 0;
this.dictService = SpringUtils.getBean(DictService.class);
}
/**
@@ -79,7 +76,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
* 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项
* 如果有且设置了value值,则将其直接置为下拉可选项
* <p>
* 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉
* 2.或者在调用ExcelBuilder时指定了可选项,将依据传入的可选项做下拉
* <p>
* 3.二者并存,注意调用方式
*/
@@ -104,14 +101,20 @@ public class ExcelDownHandler implements SheetWriteHandler {
String converterExp = format.readConverterExp();
if (StringUtils.isNotBlank(dictType)) {
// 如果传递了字典名,则依据字典建立下拉
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
Collection<String> values = Optional.ofNullable(getDictService().getAllDictByDictType(dictType))
.orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
.values();
options = new ArrayList<>(values);
} else if (StringUtils.isNotBlank(converterExp)) {
// 如果指定了确切的值,则直接解析确切的值
List<String> strList = StringUtils.splitList(converterExp, format.separator());
options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]);
List<String> strList = StringUtils.splitList(converterExp, StringUtils.SEPARATOR);
options = StreamUtils.toList(strList, s -> {
String[] itemArray = s.split("=", 2);
if (itemArray.length != 2) {
throw new ServiceException("Excel转换表达式格式错误: {}", s);
}
return itemArray[1];
});
}
} else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
@@ -145,10 +148,10 @@ public class ExcelDownHandler implements SheetWriteHandler {
}
dropDownOptions.forEach(everyOptions -> {
// 如果传递了下拉框选择器参数
if (!everyOptions.getNextOptions().isEmpty()) {
if (CollUtil.isNotEmpty(everyOptions.getNextOptions())) {
// 当二级选项不为空时,使用额外关联表的形式
dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
} else if (everyOptions.getOptions().size() > 10) {
} else if (CollUtil.isNotEmpty(everyOptions.getOptions()) && everyOptions.getOptions().size() > 10) {
// 当一级选项参数个数大于10,使用额外表的形式
dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
} else {
@@ -185,6 +188,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
// 选项数据(使用副本,避免修改调用方的原始数据)
List<String> firstOptions = options.getOptions();
if (CollUtil.isEmpty(firstOptions)) {
return;
}
Map<String, List<String>> secoundOptionsMap = new HashMap<>();
options.getNextOptions().forEach((k, v) -> secoundOptionsMap.put(k, new ArrayList<>(v)));
@@ -210,7 +216,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
linkedOptionsSheetName,
getExcelColumnName(0),
getExcelColumnName(firstOptions.size())
getExcelColumnName(firstOptions.size() - 1)
);
// 设置名称管理器的引用位置
name.setRefersToFormula(firstOptionsFunction);
@@ -242,7 +248,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 数据验证为序列模式,引用到每一个主表中的二级选项位置
// 创建子项的名称管理器,只是为了使得Excel可以识别到数据
String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex());
for (int i = 0; i < 100; i++) {
for (int i = FIRST_DATA_ROW_INDEX; i <= LAST_DATA_ROW_INDEX; i++) {
// 以一级选项对应的主体所在位置创建二级下拉
String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1);
// 二级只能主表每一行的每一列添加二级校验
@@ -348,7 +354,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex,
DataValidationConstraint constraint) {
// 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列
CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex);
CellRangeAddressList addressList = new CellRangeAddressList(FIRST_DATA_ROW_INDEX, LAST_DATA_ROW_INDEX, celIndex, celIndex);
markDataValidationToSheet(helper, sheet, constraint, addressList);
}
@@ -397,17 +403,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
* @return 列index所在得英文名
*/
private String getExcelColumnName(int columnIndex) {
// 26一循环的次数
int columnCircleCount = columnIndex / 26;
// 26一循环内的位置
int thisCircleColumnIndex = columnIndex % 26;
// 26一循环的次数大于0,则视为栏名至少两位
String columnPrefix = columnCircleCount == 0
? StrUtil.EMPTY
: StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
// 从26一循环内取对应的栏位名
String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
// 将二者拼接即为最终的栏位名
return columnPrefix + columnNext;
return CellReference.convertNumToColString(columnIndex);
}
private DictService getDictService() {
if (dictService == null) {
dictService = SpringUtils.getBean(DictService.class);
}
return dictService;
}
}
@@ -16,6 +16,7 @@ import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.dromara.common.excel.annotation.ExcelNotation;
import org.dromara.common.excel.annotation.ExcelRequired;
import org.dromara.common.core.utils.reflect.ReflectUtils;
import java.lang.reflect.Field;
import java.util.HashMap;
@@ -51,6 +52,9 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
}
// 第一行
WriteCellData<?> cellData = context.getFirstCellData();
if (cellData == null) {
return;
}
// 第一个格子
WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
@@ -92,7 +96,7 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
*/
private static Map<String, Short> getRequiredMap(Class<?> clazz) {
Map<String, Short> requiredMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
Field[] fields = ReflectUtils.getFields(clazz);
for (Field field : fields) {
if (!field.isAnnotationPresent(ExcelRequired.class)) {
continue;
@@ -112,7 +116,7 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
*/
private static Map<String, String> getNotationMap(Class<?> clazz) {
Map<String, String> notationMap = new HashMap<>();
Field[] fields = clazz.getDeclaredFields();
Field[] fields = ReflectUtils.getFields(clazz);
for (Field field : fields) {
if (!field.isAnnotationPresent(ExcelNotation.class)) {
continue;
@@ -0,0 +1,895 @@
package org.dromara.common.excel.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ZipUtil;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.fesod.sheet.ExcelWriter;
import org.apache.fesod.sheet.FesodSheet;
import org.apache.fesod.sheet.converters.Converter;
import org.apache.fesod.sheet.read.builder.ExcelReaderBuilder;
import org.apache.fesod.sheet.read.builder.ExcelReaderSheetBuilder;
import org.apache.fesod.sheet.write.builder.ExcelWriterBuilder;
import org.apache.fesod.sheet.write.builder.ExcelWriterSheetBuilder;
import org.apache.fesod.sheet.write.handler.WriteHandler;
import org.apache.fesod.sheet.write.metadata.WriteSheet;
import org.apache.fesod.sheet.write.metadata.fill.FillConfig;
import org.apache.fesod.sheet.write.metadata.fill.FillWrapper;
import org.apache.fesod.sheet.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.apache.fesod.sheet.write.style.column.SimpleColumnWidthStyleStrategy;
import org.apache.fesod.sheet.write.style.row.SimpleRowHeightStyleStrategy;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
import org.dromara.common.excel.core.CellMergeStrategy;
import org.dromara.common.excel.core.DefaultExcelListener;
import org.dromara.common.excel.core.DropDownOptions;
import org.dromara.common.excel.core.ExcelDownHandler;
import org.dromara.common.excel.core.ExcelListener;
import org.dromara.common.excel.core.ExcelResult;
import org.dromara.common.excel.handler.DataWriteHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Excel 导出构造器。
*
* @author Lion Li
*/
public final class ExcelBuilder<T> {
private static final String DEFAULT_SHEET_NAME = "sheet1";
private static final int DEFAULT_ZIP_PAGE_SIZE = 999;
private final List<T> data;
private final Class<T> headType;
private String sheetName = DEFAULT_SHEET_NAME;
private Integer sheetNo;
private boolean merge;
private List<DropDownOptions> options;
private boolean zip;
private int pageSize = DEFAULT_ZIP_PAGE_SIZE;
private String password;
private Boolean needHead;
private Boolean automaticMergeHead;
private Collection<String> includeFields;
private Collection<String> excludeFields;
private Collection<Integer> includeIndexes;
private Collection<Integer> excludeIndexes;
private Boolean orderByIncludeColumn;
private Integer columnWidth;
private Short headRowHeight;
private Short contentRowHeight;
private List<WriteHandler> writeHandlers;
private List<Converter<?>> converters;
private ExcelBuilder(List<T> data, Class<T> headType) {
this.data = data;
this.headType = headType;
}
/**
* 创建导出构造器。
*
* @param data 导出数据
* @param headType 表头类型
* @return 导出构造器
*/
public static <T> ExcelBuilder<T> of(List<T> data, Class<T> headType) {
return new ExcelBuilder<>(data, headType);
}
/**
* 创建自定义写出构造器。
*
* @param headType 表头类型
* @return 导出构造器
*/
public static <T> ExcelBuilder<T> writer(Class<T> headType) {
return new ExcelBuilder<>(null, headType);
}
/**
* 创建模板导出构造器。
*
* @param templatePath 模板路径
* @return 模板导出构造器
*/
public static TemplateBuilder template(String templatePath) {
return new TemplateBuilder(templatePath);
}
/**
* 创建导入读取构造器。
*
* @param is 文件流
* @param clazz 接收实体类
* @return 导入读取构造器
*/
public static <T> ReadBuilder<T> read(InputStream is, Class<T> clazz) {
return new ReadBuilder<>(is, clazz);
}
/**
* 设置工作表名称。
*/
public ExcelBuilder<T> sheetName(String sheetName) {
this.sheetName = StringUtils.blankToDefault(sheetName, DEFAULT_SHEET_NAME);
return this;
}
/**
* 设置工作表编号。
*/
public ExcelBuilder<T> sheetNo(Integer sheetNo) {
this.sheetNo = sheetNo;
return this;
}
/**
* 开启单元格合并。
*/
public ExcelBuilder<T> merge() {
return merge(true);
}
/**
* 设置是否合并单元格。
*/
public ExcelBuilder<T> merge(boolean merge) {
this.merge = merge;
return this;
}
/**
* 设置下拉选项。
*/
public ExcelBuilder<T> options(List<DropDownOptions> options) {
this.options = options;
return this;
}
/**
* 设置导出文件密码。
*/
public ExcelBuilder<T> password(String password) {
this.password = password;
return this;
}
/**
* 设置是否写出表头。
*/
public ExcelBuilder<T> needHead(boolean needHead) {
this.needHead = needHead;
return this;
}
/**
* 设置是否自动合并多级表头。
*/
public ExcelBuilder<T> automaticMergeHead(boolean automaticMergeHead) {
this.automaticMergeHead = automaticMergeHead;
return this;
}
/**
* 仅导出指定字段。
*/
public ExcelBuilder<T> includeFields(Collection<String> includeFields) {
this.includeFields = includeFields;
return this;
}
/**
* 排除指定字段。
*/
public ExcelBuilder<T> excludeFields(Collection<String> excludeFields) {
this.excludeFields = excludeFields;
return this;
}
/**
* 仅导出指定列索引。
*/
public ExcelBuilder<T> includeIndexes(Collection<Integer> includeIndexes) {
this.includeIndexes = includeIndexes;
return this;
}
/**
* 排除指定列索引。
*/
public ExcelBuilder<T> excludeIndexes(Collection<Integer> excludeIndexes) {
this.excludeIndexes = excludeIndexes;
return this;
}
/**
* 设置是否按 include 列顺序导出。
*/
public ExcelBuilder<T> orderByIncludeColumn(boolean orderByIncludeColumn) {
this.orderByIncludeColumn = orderByIncludeColumn;
return this;
}
/**
* 设置固定列宽。
*/
public ExcelBuilder<T> columnWidth(Integer columnWidth) {
if (columnWidth != null && columnWidth <= 0) {
throw new IllegalArgumentException("columnWidth 必须大于 0");
}
this.columnWidth = columnWidth;
return this;
}
/**
* 设置固定行高。
*/
public ExcelBuilder<T> rowHeight(short headRowHeight, short contentRowHeight) {
if (headRowHeight <= 0 || contentRowHeight <= 0) {
throw new IllegalArgumentException("rowHeight 必须大于 0");
}
this.headRowHeight = headRowHeight;
this.contentRowHeight = contentRowHeight;
return this;
}
/**
* 注册自定义写处理器。
*/
public ExcelBuilder<T> registerWriteHandler(WriteHandler writeHandler) {
if (writeHandler == null) {
return this;
}
if (writeHandlers == null) {
writeHandlers = new ArrayList<>();
}
writeHandlers.add(writeHandler);
return this;
}
/**
* 注册自定义转换器。
*/
public ExcelBuilder<T> registerConverter(Converter<?> converter) {
if (converter == null) {
return this;
}
if (converters == null) {
converters = new ArrayList<>();
}
converters.add(converter);
return this;
}
/**
* 开启 ZIP 分页导出。
*/
public ExcelBuilder<T> zip() {
return zip(DEFAULT_ZIP_PAGE_SIZE);
}
/**
* 开启 ZIP 分页导出。
*
* @param pageSize 每个 Excel 文件的数据量
*/
public ExcelBuilder<T> zip(int pageSize) {
if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize 必须大于 0");
}
this.zip = true;
this.pageSize = pageSize;
return this;
}
/**
* 写入 HTTP 响应。
*/
public void toResponse(HttpServletResponse response) {
if (zip) {
exportZipToResponse(response);
return;
}
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
writeSheet(os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 写入输出流。
*/
public void toStream(OutputStream outputStream) {
if (zip) {
throw new UnsupportedOperationException("ZIP导出请使用 toResponse(HttpServletResponse)");
}
writeSheet(outputStream);
}
/**
* 使用自定义写出逻辑写入输出流。
*/
public void toStream(OutputStream outputStream, Consumer<ExcelWriterWrapper<T>> consumer) {
try (ExcelWriter writer = createWriter(outputStream)) {
consumer.accept(ExcelWriterWrapper.of(writer));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 使用自定义写出逻辑写入 HTTP 响应。
*/
public void toResponse(HttpServletResponse response, Consumer<ExcelWriterWrapper<T>> consumer) {
try {
resetResponse(sheetName, response);
toStream(response.getOutputStream(), consumer);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
private void writeSheet(OutputStream outputStream) {
ExcelWriterSheetBuilder builder = createSheetBuilder(createWriterBuilder(outputStream));
if (merge) {
// 合并处理器
builder.registerWriteHandler(new CellMergeStrategy(data, true));
}
builder.doWrite(data);
}
private ExcelWriter createWriter(OutputStream outputStream) {
return createWriterBuilder(outputStream).build();
}
private ExcelWriterBuilder createWriterBuilder(OutputStream outputStream) {
ExcelWriterBuilder builder = FesodSheet.write(outputStream, headType)
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
// 批注必填项处理
.registerWriteHandler(new DataWriteHandler(headType));
if (columnWidth == null) {
// 自动适配
builder.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
} else {
builder.registerWriteHandler(new SimpleColumnWidthStyleStrategy(columnWidth));
}
if (headRowHeight != null || contentRowHeight != null) {
builder.registerWriteHandler(new SimpleRowHeightStyleStrategy(headRowHeight, contentRowHeight));
}
// 添加下拉框操作
builder.registerWriteHandler(new ExcelDownHandler(options));
if (StringUtils.isNotBlank(password)) {
builder.password(password);
}
if (needHead != null) {
builder.needHead(needHead);
}
if (automaticMergeHead != null) {
builder.automaticMergeHead(automaticMergeHead);
}
if (CollUtil.isNotEmpty(includeFields)) {
builder.includeColumnFieldNames(includeFields);
}
if (CollUtil.isNotEmpty(excludeFields)) {
builder.excludeColumnFieldNames(excludeFields);
}
if (CollUtil.isNotEmpty(includeIndexes)) {
builder.includeColumnIndexes(includeIndexes);
}
if (CollUtil.isNotEmpty(excludeIndexes)) {
builder.excludeColumnIndexes(excludeIndexes);
}
if (orderByIncludeColumn != null) {
builder.orderByIncludeColumn(orderByIncludeColumn);
}
if (CollUtil.isNotEmpty(converters)) {
converters.forEach(builder::registerConverter);
}
if (CollUtil.isNotEmpty(writeHandlers)) {
writeHandlers.forEach(builder::registerWriteHandler);
}
return builder;
}
private ExcelWriterSheetBuilder createSheetBuilder(ExcelWriterBuilder builder) {
if (sheetNo != null) {
return builder.sheet(sheetNo, sheetName);
}
return builder.sheet(sheetName);
}
private void exportZipToResponse(HttpServletResponse response) {
if (pageSize <= 0) {
throw new IllegalArgumentException("pageSize 必须大于 0");
}
List<List<T>> pageList = ListUtil.partition(data, pageSize);
if (pageList.size() <= 1) {
zip = false;
toResponse(response);
return;
}
try {
response.setContentType("application/zip");
response.setHeader("Content-Disposition",
"attachment;filename*=UTF-8''" + URLEncoder.encode(sheetName, StandardCharsets.UTF_8) + ".zip");
try (ZipOutputStream zipOut = ZipUtil.getZipOutputStream(response.getOutputStream(), CharsetUtil.CHARSET_UTF_8)) {
for (int i = 0; i < pageList.size(); i++) {
int pageNum = i + 1;
String exportSheetName = sheetName + "_第" + pageNum + "";
byte[] bytes = buildZipEntry(pageList.get(i), exportSheetName);
zipOut.putNextEntry(new ZipEntry(exportSheetName + ".xlsx"));
zipOut.write(bytes);
zipOut.closeEntry();
}
}
} catch (IOException e) {
throw new RuntimeException("导出Zip异常", e);
}
}
private byte[] buildZipEntry(List<T> pageData, String exportSheetName) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
ExcelBuilder<T> builder = ExcelBuilder.of(pageData, headType)
.sheetName(exportSheetName);
copyOptionsTo(builder);
builder.toStream(bos);
return bos.toByteArray();
} catch (Exception e) {
throw new RuntimeException(exportSheetName + "Excel生成失败", e);
}
}
private void copyOptionsTo(ExcelBuilder<T> builder) {
builder.merge = merge;
builder.options = options;
builder.password = password;
builder.needHead = needHead;
builder.automaticMergeHead = automaticMergeHead;
builder.includeFields = includeFields;
builder.excludeFields = excludeFields;
builder.includeIndexes = includeIndexes;
builder.excludeIndexes = excludeIndexes;
builder.orderByIncludeColumn = orderByIncludeColumn;
builder.columnWidth = columnWidth;
builder.headRowHeight = headRowHeight;
builder.contentRowHeight = contentRowHeight;
builder.writeHandlers = writeHandlers;
builder.converters = converters;
}
/**
* 重置响应体。
*/
private static void resetResponse(String filename, HttpServletResponse response) throws UnsupportedEncodingException {
FileUtils.setAttachmentResponseHeader(response, encodingFilename(filename));
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
}
private static String encodingFilename(String filename) {
return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
}
/**
* Excel 导入读取构造器。
*/
public static final class ReadBuilder<T> {
private final InputStream inputStream;
private final Class<T> headType;
private boolean validate = true;
private boolean failFast = true;
private ExcelListener<T> listener;
private Integer sheetNo;
private String sheetName;
private Integer headRowNumber;
private Boolean ignoreEmptyRow;
private String password;
private Boolean autoTrim;
private Boolean autoStrip;
private Integer numRows;
private List<Converter<?>> converters;
private ReadBuilder(InputStream inputStream, Class<T> headType) {
this.inputStream = inputStream;
this.headType = headType;
}
/**
* 设置是否校验导入数据。
*/
public ReadBuilder<T> validate(boolean validate) {
this.validate = validate;
return this;
}
/**
* 设置解析异常时是否立即终止读取。
*/
public ReadBuilder<T> failFast(boolean failFast) {
this.failFast = failFast;
return this;
}
/**
* 设置自定义导入监听器。
*/
public ReadBuilder<T> listener(ExcelListener<T> listener) {
this.listener = listener;
return this;
}
/**
* 设置读取的工作表编号。
*/
public ReadBuilder<T> sheetNo(Integer sheetNo) {
this.sheetNo = sheetNo;
return this;
}
/**
* 设置读取的工作表名称。
*/
public ReadBuilder<T> sheetName(String sheetName) {
this.sheetName = sheetName;
return this;
}
/**
* 设置表头行数。
*/
public ReadBuilder<T> headRowNumber(Integer headRowNumber) {
if (headRowNumber != null && headRowNumber < 0) {
throw new IllegalArgumentException("headRowNumber 不能小于 0");
}
this.headRowNumber = headRowNumber;
return this;
}
/**
* 设置是否忽略空行。
*/
public ReadBuilder<T> ignoreEmptyRow(boolean ignoreEmptyRow) {
this.ignoreEmptyRow = ignoreEmptyRow;
return this;
}
/**
* 设置读取文件密码。
*/
public ReadBuilder<T> password(String password) {
this.password = password;
return this;
}
/**
* 设置是否自动 trim 字符串。
*/
public ReadBuilder<T> autoTrim(boolean autoTrim) {
this.autoTrim = autoTrim;
return this;
}
/**
* 设置是否自动 strip 字符串。
*/
public ReadBuilder<T> autoStrip(boolean autoStrip) {
this.autoStrip = autoStrip;
return this;
}
/**
* 设置最多读取行数。
*/
public ReadBuilder<T> numRows(Integer numRows) {
if (numRows != null && numRows <= 0) {
throw new IllegalArgumentException("numRows 必须大于 0");
}
this.numRows = numRows;
return this;
}
/**
* 注册自定义转换器。
*/
public ReadBuilder<T> registerConverter(Converter<?> converter) {
if (converter == null) {
return this;
}
if (converters == null) {
converters = new ArrayList<>();
}
converters.add(converter);
return this;
}
/**
* 读取Excel并返回对象集合。
*/
public List<T> doReadSync() {
return createSheetBuilder(createReaderBuilder(null)).doReadSync();
}
/**
* 读取Excel并返回解析结果。
*/
public ExcelResult<T> doRead() {
ExcelListener<T> readListener = listener;
if (readListener == null) {
readListener = new DefaultExcelListener<>(validate, failFast);
}
createSheetBuilder(createReaderBuilder(readListener)).doRead();
return readListener.getExcelResult();
}
/**
* 读取所有工作表并返回解析结果。
*/
public ExcelResult<T> doReadAll() {
ExcelListener<T> readListener = listener;
if (readListener == null) {
readListener = new DefaultExcelListener<>(validate, failFast);
}
createReaderBuilder(readListener).doReadAll();
return readListener.getExcelResult();
}
/**
* 读取所有工作表并返回对象集合。
*/
public List<T> doReadAllSync() {
return createReaderBuilder(null).doReadAllSync();
}
private ExcelReaderBuilder createReaderBuilder(ExcelListener<T> readListener) {
ExcelReaderBuilder builder = FesodSheet.read(inputStream)
.head(headType)
.autoCloseStream(false);
if (readListener != null) {
builder.registerReadListener(readListener);
}
if (headRowNumber != null) {
builder.headRowNumber(headRowNumber);
}
if (ignoreEmptyRow != null) {
builder.ignoreEmptyRow(ignoreEmptyRow);
}
if (StringUtils.isNotBlank(password)) {
builder.password(password);
}
if (autoTrim != null) {
builder.autoTrim(autoTrim);
}
if (autoStrip != null) {
builder.autoStrip(autoStrip);
}
if (numRows != null) {
builder.numRows(numRows);
}
if (CollUtil.isNotEmpty(converters)) {
converters.forEach(builder::registerConverter);
}
return builder;
}
private ExcelReaderSheetBuilder createSheetBuilder(ExcelReaderBuilder builder) {
ExcelReaderSheetBuilder sheetBuilder;
if (sheetNo != null && StringUtils.isNotBlank(sheetName)) {
sheetBuilder = builder.sheet(sheetNo, sheetName);
} else if (sheetNo != null) {
sheetBuilder = builder.sheet(sheetNo);
} else if (StringUtils.isNotBlank(sheetName)) {
sheetBuilder = builder.sheet(sheetName);
} else {
sheetBuilder = builder.sheet();
}
if (numRows != null) {
sheetBuilder.numRows(numRows);
}
return sheetBuilder;
}
}
/**
* Excel 模板导出构造器。
*/
public static final class TemplateBuilder {
private final String templatePath;
private String filename = DEFAULT_SHEET_NAME;
private TemplateMode mode;
private Object data;
private TemplateBuilder(String templatePath) {
this.templatePath = templatePath;
}
/**
* 设置下载文件名。
*/
public TemplateBuilder filename(String filename) {
this.filename = StringUtils.blankToDefault(filename, DEFAULT_SHEET_NAME);
return this;
}
/**
* 设置单表多数据模板数据,模板格式为 {.属性}。
*/
public <T> TemplateBuilder data(List<T> data) {
this.mode = TemplateMode.LIST;
this.data = data;
return this;
}
/**
* 设置多表多数据模板数据,模板格式为 {key.属性}。
*/
public TemplateBuilder multiList(Map<String, Object> data) {
this.mode = TemplateMode.MULTI_LIST;
this.data = data;
return this;
}
/**
* 设置多 sheet 模板数据,模板格式为 {key.属性}。
*/
public TemplateBuilder multiSheet(List<Map<String, Object>> data) {
this.mode = TemplateMode.MULTI_SHEET;
this.data = data;
return this;
}
/**
* 写入 HTTP 响应。
*/
public void toResponse(HttpServletResponse response) {
try {
validateData();
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
toStream(os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 写入输出流。
*/
public void toStream(OutputStream outputStream) {
validateData();
ClassPathResource templateResource = new ClassPathResource(templatePath);
try (InputStream templateStream = templateResource.getStream()) {
ExcelWriter excelWriter = FesodSheet.write(outputStream)
.withTemplate(templateStream)
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
try {
fill(excelWriter);
} finally {
excelWriter.finish();
}
} catch (IOException e) {
throw new RuntimeException("读取Excel模板异常", e);
}
}
@SuppressWarnings("unchecked")
private void fill(ExcelWriter excelWriter) {
if (mode == TemplateMode.LIST) {
WriteSheet writeSheet = FesodSheet.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
for (Object item : (List<?>) data) {
excelWriter.fill(item, fillConfig, writeSheet);
}
return;
}
if (mode == TemplateMode.MULTI_LIST) {
WriteSheet writeSheet = FesodSheet.writerSheet().build();
for (Map.Entry<String, Object> map : ((Map<String, Object>) data).entrySet()) {
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
if (map.getValue() instanceof Collection) {
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), fillConfig, writeSheet);
}
}
return;
}
List<Map<String, Object>> sheetData = (List<Map<String, Object>>) data;
for (int i = 0; i < sheetData.size(); i++) {
WriteSheet writeSheet = FesodSheet.writerSheet(i).build();
for (Map.Entry<String, Object> map : sheetData.get(i).entrySet()) {
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
if (map.getValue() instanceof Collection) {
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), writeSheet);
}
}
}
}
private void validateData() {
if (mode == null || data == null) {
throw new IllegalArgumentException("数据为空");
}
if (data instanceof Collection<?> collection && CollUtil.isEmpty(collection)) {
throw new IllegalArgumentException("数据为空");
}
if (data instanceof Map<?, ?> map && CollUtil.isEmpty(map)) {
throw new IllegalArgumentException("数据为空");
}
}
private enum TemplateMode {
LIST,
MULTI_LIST,
MULTI_SHEET
}
}
}
@@ -1,626 +0,0 @@
package org.dromara.common.excel.utils;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ZipUtil;
import jakarta.servlet.ServletOutputStream;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.fesod.sheet.ExcelWriter;
import org.apache.fesod.sheet.FesodSheet;
import org.apache.fesod.sheet.write.builder.ExcelWriterSheetBuilder;
import org.apache.fesod.sheet.write.metadata.WriteSheet;
import org.apache.fesod.sheet.write.metadata.fill.FillConfig;
import org.apache.fesod.sheet.write.metadata.fill.FillWrapper;
import org.apache.fesod.sheet.write.style.column.LongestMatchColumnWidthStyleStrategy;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.excel.convert.ExcelBigNumberConvert;
import org.dromara.common.excel.core.*;
import org.dromara.common.excel.handler.DataWriteHandler;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.function.Consumer;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
/**
* Excel相关处理
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil {
/**
* 读取Excel并返回对象集合
*
* @param is 文件流
* @param clazz 接收实体类
* @return 数据列表
*/
public static <T> List<T> importExcel(InputStream is, Class<T> clazz) {
return FesodSheet.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
}
/**
* 读取Excel并返回解析结果(带默认校验功能)
*
* @param is 文件流
* @param clazz 接收实体类
* @param isValidate 是否开启校验
* @return 解析结果(含成功数据、错误信息)
*/
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate) {
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
FesodSheet.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}
/**
* 读取Excel并返回解析结果(使用自定义监听器)
*
* @param is 文件流
* @param clazz 接收实体类
* @param listener 自定义监听器
* @return 解析结果(含成功数据、错误信息)
*/
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
FesodSheet.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, false, os, null);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 大数据量Excel导出
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
*/
public static <T> void exportExcelZip(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
exportExcelZip(list, sheetName, clazz, response, 999);
}
/**
* 大数据量Excel导出
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param pageSize 每页条数
*/
public static <T> void exportExcelZip(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response, int pageSize) {
// 数据分页
List<List<T>> pageList = ListUtil.partition(list, pageSize);
// 只有一页,直接导出普通Excel
if (pageList.size() <= 1) {
exportSingleExcel(list, sheetName, clazz, response);
return;
}
// 多线程生成所有Excel文件(字节数组)
Map<String, byte[]> excelMap = buildExcelZipData(pageList, sheetName, clazz);
// 写入ZIP并下载
writeExcelZipResponse(sheetName, response, excelMap);
}
/**
* 导出【单文件】Excel
*/
private static <T> void exportSingleExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, false, os, null);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 多线程并行生成多个Excel文件
* 使用虚拟线程,高并发、低资源占用
*
* @return Map<文件名, 文件字节数组>
*/
private static <T> Map<String, byte[]> buildExcelZipData(List<List<T>> pageList, String sheetName, Class<T> clazz) {
// 有序Map,保证文件按页码顺序打包
Map<String, byte[]> excelMap = new LinkedHashMap<>(pageList.size());
List<Future<Map.Entry<String, byte[]>>> futures = new ArrayList<>(pageList.size());
// 使用虚拟线程池执行导出任务
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 1. 提交所有分页导出任务
for (int i = 0; i < pageList.size(); i++) {
int pageNum = i + 1;
List<T> pageData = pageList.get(i);
futures.add(executor.submit(() -> buildExcelZipEntry(pageData, sheetName, clazz, pageNum)));
}
// 2. 获取所有线程执行结果
for (Future<Map.Entry<String, byte[]>> future : futures) {
Map.Entry<String, byte[]> excel = getExcelZipEntry(future);
excelMap.put(excel.getKey(), excel.getValue());
}
}
return excelMap;
}
/**
* 单页Excel生成任务(线程执行单元)
*
* @param pageData 当前页数据
* @param pageNum 当前页码
* @return 文件名 + 文件字节
*/
private static <T> Map.Entry<String, byte[]> buildExcelZipEntry(List<T> pageData, String sheetName, Class<T> clazz, int pageNum) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
String exportSheetName = sheetName + "_第" + pageNum + "";
exportExcel(pageData, exportSheetName, clazz, false, bos, null);
return Map.entry(exportSheetName + ".xlsx", bos.toByteArray());
} catch (Exception e) {
throw new RuntimeException("" + pageNum + "页Excel生成失败", e);
}
}
/**
* 安全获取异步任务结果
* 处理中断异常、执行异常,保证任务稳定
*/
private static Map.Entry<String, byte[]> getExcelZipEntry(Future<Map.Entry<String, byte[]>> future) {
try {
return future.get();
} catch (InterruptedException e) {
// 恢复中断标志
Thread.currentThread().interrupt();
throw new RuntimeException("Excel导出线程被中断", e);
} catch (ExecutionException e) {
throw new RuntimeException("Excel导出失败", e.getCause());
}
}
/**
* 将多个Excel文件打包成ZIP并输出到浏览器下载
*
* @param excelMap 文件名 -> 文件字节
*/
private static void writeExcelZipResponse(String sheetName, HttpServletResponse response, Map<String, byte[]> excelMap) {
try {
// 设置ZIP下载响应头
response.setContentType("application/zip");
response.setHeader("Content-Disposition",
"attachment;filename*=UTF-8''" + URLEncoder.encode(sheetName, StandardCharsets.UTF_8) + ".zip");
// 压缩写入多个Excel文件
try (ZipOutputStream zipOut = ZipUtil.getZipOutputStream(response.getOutputStream(), CharsetUtil.CHARSET_UTF_8)) {
for (Map.Entry<String, byte[]> entry : excelMap.entrySet()) {
zipOut.putNextEntry(new ZipEntry(entry.getKey()));
zipOut.write(entry.getValue());
zipOut.closeEntry();
}
}
} catch (IOException e) {
throw new RuntimeException("导出Zip异常", e);
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param response 响应体
* @param options 级联下拉选
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response, List<DropDownOptions> options) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, false, os, options);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param merge 是否合并单元格
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, merge, os, null);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param merge 是否合并单元格
* @param response 响应体
* @param options 级联下拉选
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge, HttpServletResponse response, List<DropDownOptions> options) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
exportExcel(list, sheetName, clazz, merge, os, options);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param os 输出流
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os) {
exportExcel(list, sheetName, clazz, false, os, null);
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param os 输出流
* @param options 级联下拉选内容
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, OutputStream os, List<DropDownOptions> options) {
exportExcel(list, sheetName, clazz, false, os, options);
}
/**
* 导出excel
*
* @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param merge 是否合并单元格
* @param os 输出流
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, boolean merge,
OutputStream os, List<DropDownOptions> options) {
ExcelWriterSheetBuilder builder = FesodSheet.write(os, clazz)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.registerWriteHandler(new DataWriteHandler(clazz))
.sheet(sheetName);
if (merge) {
// 合并处理器
builder.registerWriteHandler(new CellMergeStrategy(list, true));
}
// 添加下拉框操作
builder.registerWriteHandler(new ExcelDownHandler(options));
builder.doWrite(list);
}
/**
* 导出excel
*
* @param headType 带Excel注解的类型
* @param os 输出流
* @param options Excel下拉可选项
* @param consumer 导出助手消费函数
*/
public static <T> void exportExcel(Class<T> headType, OutputStream os, List<DropDownOptions> options, Consumer<ExcelWriterWrapper<T>> consumer) {
try (ExcelWriter writer = FesodSheet.write(os, headType)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
// 批注必填项处理
.registerWriteHandler(new DataWriteHandler(headType))
// 添加下拉框操作
.registerWriteHandler(new ExcelDownHandler(options))
.build()) {
// 执行消费函数
consumer.accept(ExcelWriterWrapper.of(writer));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 导出excel
*
* @param headType 带Excel注解的类型
* @param os 输出流
* @param consumer 导出助手消费函数
*/
public static <T> void exportExcel(Class<T> headType, OutputStream os, Consumer<ExcelWriterWrapper<T>> consumer) {
exportExcel(headType, os, null, consumer);
}
/**
* 单表多数据模板导出 模板格式为 {.属性}
*
* @param filename 文件名
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param response 响应体
*/
public static <T> void exportTemplate(List<T> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplate(data, templatePath, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 单表多数据模板导出 模板格式为 {.属性}
*
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param os 输出流
*/
public static <T> void exportTemplate(List<T> data, String templatePath, OutputStream os) {
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = FesodSheet.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.registerWriteHandler(new DataWriteHandler(data.getFirst().getClass()))
.build();
try {
WriteSheet writeSheet = FesodSheet.writerSheet().build();
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
// 单表多数据导出 模板格式为 {.属性}
for (T d : data) {
excelWriter.fill(d, fillConfig, writeSheet);
}
} finally {
excelWriter.finish();
}
}
/**
* 多表多数据模板导出 模板格式为 {key.属性}
*
* @param filename 文件名
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param response 响应体
*/
public static void exportTemplateMultiList(Map<String, Object> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplateMultiList(data, templatePath, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 多sheet模板导出 模板格式为 {key.属性}
*
* @param filename 文件名
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param response 响应体
*/
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String filename, String templatePath, HttpServletResponse response) {
try {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("数据为空");
}
resetResponse(filename, response);
ServletOutputStream os = response.getOutputStream();
exportTemplateMultiSheet(data, templatePath, os);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常", e);
}
}
/**
* 多表多数据模板导出 模板格式为 {key.属性}
*
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param os 输出流
*/
public static void exportTemplateMultiList(Map<String, Object> data, String templatePath, OutputStream os) {
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = FesodSheet.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
try {
WriteSheet writeSheet = FesodSheet.writerSheet().build();
for (Map.Entry<String, Object> map : data.entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
if (map.getValue() instanceof Collection) {
// 多表导出必须使用 FillWrapper
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), fillConfig, writeSheet);
}
}
} finally {
excelWriter.finish();
}
}
/**
* 多sheet模板导出 模板格式为 {key.属性}
*
* @param templatePath 模板路径 resource 目录下的路径包括模板文件名
* 例如: excel/temp.xlsx
* 重点: 模板文件必须放置到启动类对应的 resource 目录下
* @param data 模板需要的数据
* @param os 输出流
*/
public static void exportTemplateMultiSheet(List<Map<String, Object>> data, String templatePath, OutputStream os) {
ClassPathResource templateResource = new ClassPathResource(templatePath);
ExcelWriter excelWriter = FesodSheet.write(os)
.withTemplate(templateResource.getStream())
.autoCloseStream(false)
// 大数值自动转换 防止失真
.registerConverter(new ExcelBigNumberConvert())
.build();
try {
for (int i = 0; i < data.size(); i++) {
WriteSheet writeSheet = FesodSheet.writerSheet(i).build();
for (Map.Entry<String, Object> map : data.get(i).entrySet()) {
// 设置列表后续还有数据
FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
if (map.getValue() instanceof Collection) {
// 多表导出必须使用 FillWrapper
excelWriter.fill(new FillWrapper(map.getKey(), (Collection<?>) map.getValue()), fillConfig, writeSheet);
} else {
excelWriter.fill(map.getValue(), writeSheet);
}
}
}
} finally {
excelWriter.finish();
}
}
/**
* 重置响应体
*/
private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
String filename = encodingFilename(sheetName);
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
}
/**
* 解析导出值 0=男,1=女,2=未知
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值
*/
public static String convertByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[0].equals(value)) {
propertyString.append(itemArray[1]).append(separator);
break;
}
}
} else {
if (itemArray[0].equals(propertyValue)) {
return itemArray[1];
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 反向解析值 男=0,女=1,未知=2
*
* @param propertyValue 参数值
* @param converterExp 翻译注解
* @param separator 分隔符
* @return 解析后值
*/
public static String reverseByExp(String propertyValue, String converterExp, String separator) {
StringBuilder propertyString = new StringBuilder();
String[] convertSource = converterExp.split(StringUtils.SEPARATOR);
for (String item : convertSource) {
String[] itemArray = item.split("=");
if (StringUtils.containsAny(propertyValue, separator)) {
for (String value : propertyValue.split(separator)) {
if (itemArray[1].equals(value)) {
propertyString.append(itemArray[0]).append(separator);
break;
}
}
} else {
if (itemArray[1].equals(propertyValue)) {
return itemArray[0];
}
}
}
return StringUtils.stripEnd(propertyString.toString(), separator);
}
/**
* 编码文件名
*/
public static String encodingFilename(String filename) {
return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
}
}
@@ -12,7 +12,7 @@ import org.dromara.common.redis.annotation.RepeatSubmit;
import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.excel.core.ExcelResult;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.demo.domain.TestDemo;
@@ -74,7 +74,9 @@ public class TestDemoController extends BaseController {
@SaCheckPermission("demo:demo:import")
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Void> importData(@RequestPart("file") MultipartFile file) throws Exception {
ExcelResult<TestDemoImportVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true);
ExcelResult<TestDemoImportVo> excelResult = ExcelBuilder.read(file.getInputStream(), TestDemoImportVo.class)
.validate(true)
.doRead();
List<TestDemo> list = MapstructUtils.convert(excelResult.getList(), TestDemo.class);
testDemoService.saveBatch(list);
return R.ok(excelResult.getAnalysis());
@@ -92,7 +94,7 @@ public class TestDemoController extends BaseController {
// for (TestDemoVo vo : list) {
// vo.setId(1234567891234567893L);
// }
ExcelUtil.exportExcel(list, "测试单表", TestDemoVo.class, response);
ExcelBuilder.of(list, TestDemoVo.class).sheetName("测试单表").toResponse(response);
}
/**
@@ -6,7 +6,7 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.dromara.common.excel.core.ExcelResult;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.demo.domain.vo.ExportDemoVo;
import org.dromara.demo.listener.ExportDemoListener;
import org.dromara.demo.service.IExportExcelService;
@@ -14,7 +14,6 @@ import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -48,7 +47,10 @@ public class TestExcelController {
list.add(new TestObj("单列表测试1", "列表测试1", "列表测试2", "列表测试3", "列表测试4"));
list.add(new TestObj("单列表测试2", "列表测试5", "列表测试6", "列表测试7", "列表测试8"));
list.add(new TestObj("单列表测试3", "列表测试9", "列表测试10", "列表测试11", "列表测试12"));
ExcelUtil.exportTemplate(CollUtil.newArrayList(map, list), "单列表.xlsx", "excel/单列表.xlsx", response);
ExcelBuilder.template("excel/单列表.xlsx")
.filename("单列表.xlsx")
.data(CollUtil.newArrayList(map, list))
.toResponse(response);
}
/**
@@ -82,7 +84,10 @@ public class TestExcelController {
multiListMap.put("data2", list2);
multiListMap.put("data3", list3);
multiListMap.put("data4", list4);
ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response);
ExcelBuilder.template("excel/多列表.xlsx")
.filename("多列表.xlsx")
.multiList(multiListMap)
.toResponse(response);
}
/**
@@ -101,7 +106,7 @@ public class TestExcelController {
* @param response /
*/
@GetMapping("/customExport")
public void customExport(HttpServletResponse response) throws IOException {
public void customExport(HttpServletResponse response) {
exportExcelService.customExport(response);
}
@@ -137,7 +142,10 @@ public class TestExcelController {
list.add(sheetMap2);
list.add(sheetMap3);
list.add(sheetMap4);
ExcelUtil.exportTemplateMultiSheet(list, "多sheet列表", "excel/多sheet列表.xlsx", response);
ExcelBuilder.template("excel/多sheet列表.xlsx")
.filename("多sheet列表")
.multiSheet(list)
.toResponse(response);
}
/**
@@ -146,7 +154,9 @@ public class TestExcelController {
@PostMapping(value = "/importWithOptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public List<ExportDemoVo> importWithOptions(@RequestPart("file") MultipartFile file) throws Exception {
// 处理解析结果
ExcelResult<ExportDemoVo> excelResult = ExcelUtil.importExcel(file.getInputStream(), ExportDemoVo.class, new ExportDemoListener());
ExcelResult<ExportDemoVo> excelResult = ExcelBuilder.read(file.getInputStream(), ExportDemoVo.class)
.listener(new ExportDemoListener())
.doRead();
return excelResult.getList();
}
@@ -6,7 +6,7 @@ import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.core.validate.QueryGroup;
import org.dromara.common.web.core.BaseController;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.redis.annotation.RepeatSubmit;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
@@ -55,7 +55,7 @@ public class TestTreeController extends BaseController {
@GetMapping("/export")
public void export(@Validated TestTreeBo bo, HttpServletResponse response) {
List<TestTreeVo> list = testTreeService.queryList(bo);
ExcelUtil.exportExcel(list, "测试树表", TestTreeVo.class, response);
ExcelBuilder.of(list, TestTreeVo.class).sheetName("测试树表").toResponse(response);
}
/**
@@ -2,8 +2,6 @@ package org.dromara.demo.service;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 导出下拉框Excel示例
*
@@ -23,5 +21,5 @@ public interface IExportExcelService {
*
* @param response /
*/
void customExport(HttpServletResponse response) throws IOException;
void customExport(HttpServletResponse response);
}
@@ -8,15 +8,13 @@ import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.excel.core.DropDownOptions;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.excel.utils.ExcelWriterWrapper;
import org.dromara.demo.domain.vo.ExportDemoVo;
import org.dromara.demo.service.IExportExcelService;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -103,7 +101,10 @@ public class ExportExcelServiceImpl implements IExportExcelService {
return everyRowData;
});
ExcelUtil.exportExcel(outList, "下拉框示例", ExportDemoVo.class, response, options);
ExcelBuilder.of(outList, ExportDemoVo.class)
.sheetName("下拉框示例")
.options(options)
.toResponse(response);
}
private String buildOptions(List<DemoCityData> cityDataList, Integer id) {
@@ -240,12 +241,8 @@ public class ExportExcelServiceImpl implements IExportExcelService {
@Override
public void customExport(HttpServletResponse response) throws IOException {
String filename = ExcelUtil.encodingFilename("自定义导出");
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
ExcelUtil.exportExcel(ExportDemoVo.class, response.getOutputStream(), wrapper -> {
public void customExport(HttpServletResponse response) {
ExcelBuilder.writer(ExportDemoVo.class).sheetName("自定义导出").toResponse(response, wrapper -> {
// 创建表格数据,业务中一般通过数据库查询
List<ExportDemoVo> excelDataList = new ArrayList<>();
for (int i = 0; i < 30; i++) {
@@ -19,7 +19,7 @@ import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.log.enums.BusinessType;
#if($enableExport)
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
#end
import ${packageName}.domain.vo.${ClassName}Vo;
import ${packageName}.domain.bo.${ClassName}Bo;
@@ -68,7 +68,7 @@ public class ${ClassName}Controller extends BaseController {
@PostMapping("/export")
public void export(${ClassName}Bo bo, HttpServletResponse response) {
List<${ClassName}Vo> list = ${className}Service.queryList(bo);
ExcelUtil.exportExcel(list, "${functionName}", ${ClassName}Vo.class, response);
ExcelBuilder.of(list, ${ClassName}Vo.class).sheetName("${functionName}").toResponse(response);
}
#end
@@ -7,7 +7,7 @@ import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.CacheNames;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -59,7 +59,7 @@ public class SysLoginInfoController extends BaseController {
@PostMapping("/export")
public void export(SysLoginInfoBo loginInfo, HttpServletResponse response) {
List<SysLoginInfoVo> list = loginInfoService.selectLoginInfoList(loginInfo);
ExcelUtil.exportExcel(list, "登录日志", SysLoginInfoVo.class, response);
ExcelBuilder.of(list, SysLoginInfoVo.class).sheetName("登录日志").toResponse(response);
}
/**
@@ -6,7 +6,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -56,7 +56,7 @@ public class SysOperlogController extends BaseController {
@PostMapping("/export")
public void export(SysOperLogBo operLog, HttpServletResponse response) {
List<SysOperLogVo> list = operLogService.selectOperLogList(operLog);
ExcelUtil.exportExcel(list, "操作日志", SysOperLogVo.class, response);
ExcelBuilder.of(list, SysOperLogVo.class).sheetName("操作日志").toResponse(response);
}
/**
@@ -9,7 +9,7 @@ import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -61,7 +61,7 @@ public class SysClientController extends BaseController {
@PostMapping("/export")
public void export(SysClientBo bo, HttpServletResponse response) {
List<SysClientVo> list = sysClientService.queryList(bo);
ExcelUtil.exportExcel(list, "客户端管理", SysClientVo.class, response);
ExcelBuilder.of(list, SysClientVo.class).sheetName("客户端管理").toResponse(response);
}
/**
@@ -5,7 +5,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -57,7 +57,7 @@ public class SysConfigController extends BaseController {
@PostMapping("/export")
public void export(SysConfigBo config, HttpServletResponse response) {
List<SysConfigVo> list = configService.selectConfigList(config);
ExcelUtil.exportExcel(list, "参数数据", SysConfigVo.class, response);
ExcelBuilder.of(list, SysConfigVo.class).sheetName("参数数据").toResponse(response);
}
/**
@@ -6,7 +6,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -61,7 +61,7 @@ public class SysDictDataController extends BaseController {
@PostMapping("/export")
public void export(SysDictDataBo dictData, HttpServletResponse response) {
List<SysDictDataVo> list = dictDataService.selectDictDataList(dictData);
ExcelUtil.exportExcel(list, "字典数据", SysDictDataVo.class, response);
ExcelBuilder.of(list, SysDictDataVo.class).sheetName("字典数据").toResponse(response);
}
/**
@@ -6,7 +6,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -58,7 +58,7 @@ public class SysDictTypeController extends BaseController {
@PostMapping("/export")
public void export(SysDictTypeBo dictType, HttpServletResponse response) {
List<SysDictTypeVo> list = dictTypeService.selectDictTypeList(dictType);
ExcelUtil.exportExcel(list, "字典类型", SysDictTypeVo.class, response);
ExcelBuilder.of(list, SysDictTypeVo.class).sheetName("字典类型").toResponse(response);
}
/**
@@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor;
import org.dromara.common.core.constant.SystemConstants;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -64,7 +64,7 @@ public class SysPostController extends BaseController {
@PostMapping("/export")
public void export(SysPostBo post, HttpServletResponse response) {
List<SysPostVo> list = postService.selectPostList(post);
ExcelUtil.exportExcel(list, "岗位数据", SysPostVo.class, response);
ExcelBuilder.of(list, SysPostVo.class).sheetName("岗位数据").toResponse(response);
}
/**
@@ -6,7 +6,7 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -66,7 +66,7 @@ public class SysRoleController extends BaseController {
@PostMapping("/export")
public void export(SysRoleBo role, HttpServletResponse response) {
List<SysRoleVo> list = roleService.selectRoleList(role);
ExcelUtil.exportExcel(list, "角色数据", SysRoleVo.class, response);
ExcelBuilder.of(list, SysRoleVo.class).sheetName("角色数据").toResponse(response);
}
/**
@@ -16,7 +16,7 @@ import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.encrypt.annotation.ApiEncrypt;
import org.dromara.common.excel.core.ExcelResult;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -84,7 +84,7 @@ public class SysUserController extends BaseController {
@PostMapping("/export")
public void export(SysUserBo user, HttpServletResponse response) {
List<SysUserExportVo> list = userService.selectUserExportList(user);
ExcelUtil.exportExcel(list, "用户数据", SysUserExportVo.class, response);
ExcelBuilder.of(list, SysUserExportVo.class).sheetName("用户数据").toResponse(response);
}
/**
@@ -97,7 +97,9 @@ public class SysUserController extends BaseController {
@SaCheckPermission("system:user:import")
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public R<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception {
ExcelResult<SysUserImportVo> result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport));
ExcelResult<SysUserImportVo> result = ExcelBuilder.read(file.getInputStream(), SysUserImportVo.class)
.listener(new SysUserImportListener(updateSupport))
.doRead();
return R.ok(result.getAnalysis());
}
@@ -108,7 +110,7 @@ public class SysUserController extends BaseController {
*/
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
ExcelUtil.exportExcel(new ArrayList<>(), "用户数据", SysUserImportVo.class, response);
ExcelBuilder.of(new ArrayList<>(), SysUserImportVo.class).sheetName("用户数据").toResponse(response);
}
/**
@@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.redis.annotation.RepeatSubmit;
@@ -61,7 +61,7 @@ public class FlwCategoryController extends BaseController {
@PostMapping("/export")
public void export(FlowCategoryBo bo, HttpServletResponse response) {
List<FlowCategoryVo> list = flwCategoryService.queryList(bo);
ExcelUtil.exportExcel(list, "流程分类", FlowCategoryVo.class, response);
ExcelBuilder.of(list, FlowCategoryVo.class).sheetName("流程分类").toResponse(response);
}
/**
@@ -9,7 +9,7 @@ import org.dromara.common.core.domain.PageResult;
import org.dromara.common.core.domain.R;
import org.dromara.common.core.validate.AddGroup;
import org.dromara.common.core.validate.EditGroup;
import org.dromara.common.excel.utils.ExcelUtil;
import org.dromara.common.excel.utils.ExcelBuilder;
import org.dromara.common.log.annotation.Log;
import org.dromara.common.log.enums.BusinessType;
import org.dromara.common.mybatis.core.page.PageQuery;
@@ -64,7 +64,7 @@ public class TestLeaveController extends BaseController {
@PostMapping("/export")
public void export(TestLeaveBo bo, HttpServletResponse response) {
List<TestLeaveVo> list = testLeaveService.queryList(bo);
ExcelUtil.exportExcel(list, "请假", TestLeaveVo.class, response);
ExcelBuilder.of(list, TestLeaveVo.class).sheetName("请假").toResponse(response);
}
/**