mirror of
https://github.com/dromara/RuoYi-Vue-Plus.git
synced 2026-06-27 16:14:31 +00:00
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:
+81
-5
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
+16
-5
@@ -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;
|
||||
|
||||
+6
-3
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+15
-4
@@ -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
|
||||
|
||||
+30
-28
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -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;
|
||||
|
||||
+895
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
-626
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
+5
-3
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+17
-7
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-3
@@ -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);
|
||||
}
|
||||
|
||||
+7
-10
@@ -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
|
||||
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+6
-4
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user