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.service.DictService;
|
||||||
import org.dromara.common.core.utils.SpringUtils;
|
import org.dromara.common.core.utils.SpringUtils;
|
||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.excel.utils.ExcelUtil;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
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
|
@Slf4j
|
||||||
public class ExcelDictConvert implements Converter<Object> {
|
public class ExcelDictConvert implements Converter<Object> {
|
||||||
|
|
||||||
|
private DictService dictService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<Object> supportJavaTypeKey() {
|
public Class<Object> supportJavaTypeKey() {
|
||||||
return Object.class;
|
return Object.class;
|
||||||
@@ -43,9 +47,9 @@ public class ExcelDictConvert implements Converter<Object> {
|
|||||||
String label = cellData.getStringValue();
|
String label = cellData.getStringValue();
|
||||||
String value;
|
String value;
|
||||||
if (StringUtils.isBlank(type)) {
|
if (StringUtils.isBlank(type)) {
|
||||||
value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator());
|
value = reverseByExp(label, anno.readConverterExp(), anno.separator());
|
||||||
} else {
|
} 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);
|
return Convert.convert(contentProperty.getField().getType(), value);
|
||||||
}
|
}
|
||||||
@@ -60,9 +64,9 @@ public class ExcelDictConvert implements Converter<Object> {
|
|||||||
String value = Convert.toStr(object);
|
String value = Convert.toStr(object);
|
||||||
String label;
|
String label;
|
||||||
if (StringUtils.isBlank(type)) {
|
if (StringUtils.isBlank(type)) {
|
||||||
label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator());
|
label = convertByExp(value, anno.readConverterExp(), anno.separator());
|
||||||
} else {
|
} else {
|
||||||
label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator());
|
label = getDictService().getDictLabel(type, value, anno.separator());
|
||||||
}
|
}
|
||||||
return new WriteCellData<>(label);
|
return new WriteCellData<>(label);
|
||||||
}
|
}
|
||||||
@@ -70,4 +74,76 @@ public class ExcelDictConvert implements Converter<Object> {
|
|||||||
private ExcelDictFormat getAnnotation(Field field) {
|
private ExcelDictFormat getAnnotation(Field field) {
|
||||||
return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class);
|
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> {
|
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, 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
|
@Override
|
||||||
public Class<Object> supportJavaTypeKey() {
|
public Class<Object> supportJavaTypeKey() {
|
||||||
@@ -55,16 +55,24 @@ public class ExcelEnumConvert implements Converter<Object> {
|
|||||||
}
|
}
|
||||||
Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
|
Map<Object, String> enumCodeToTextMap = beforeConvert(contentProperty);
|
||||||
// 从Java输出至Excel是code转text,从Excel转Java应将text与code对调
|
// 从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(),
|
contentProperty.getField(),
|
||||||
f -> {
|
f -> {
|
||||||
Map<Object, Object> reverseMap = new HashMap<>();
|
Map<String, Object> reverseMap = new HashMap<>();
|
||||||
enumCodeToTextMap.forEach((key, value) -> reverseMap.put(value, key));
|
enumCodeToTextMap.forEach((key, value) -> {
|
||||||
|
Object oldValue = reverseMap.put(value, key);
|
||||||
|
if (ObjectUtil.isNotNull(oldValue)) {
|
||||||
|
throw new IllegalArgumentException("枚举导入文本值重复: " + value);
|
||||||
|
}
|
||||||
|
});
|
||||||
return reverseMap;
|
return reverseMap;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// 应该从text -> code中查找
|
// 应该从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);
|
return Convert.convert(contentProperty.getField().getType(), codeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +94,9 @@ public class ExcelEnumConvert implements Converter<Object> {
|
|||||||
for (Enum<?> enumConstant : enumConstants) {
|
for (Enum<?> enumConstant : enumConstants) {
|
||||||
Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
|
Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField());
|
||||||
String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField());
|
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);
|
enumValueMap.put(codeValue, textValue);
|
||||||
}
|
}
|
||||||
return enumValueMap;
|
return enumValueMap;
|
||||||
|
|||||||
+6
-3
@@ -129,11 +129,14 @@ public class CellMergeHandler {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
CellMerge cm = field.getAnnotation(CellMerge.class);
|
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));
|
mergeFields.put(field, FieldColumnIndex.of(index, cm));
|
||||||
|
|
||||||
if (hasTitle) {
|
if (hasTitle && property != null && property.value().length > 0) {
|
||||||
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
|
|
||||||
rowIndex = Math.max(rowIndex, property.value().length);
|
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) {
|
public DefaultExcelListener(boolean isValidate) {
|
||||||
this.excelResult = new DefaultExcelResult<>();
|
|
||||||
this.isValidate = isValidate;
|
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 rowIndex = excelDataConvertException.getRowIndex();
|
||||||
Integer columnIndex = excelDataConvertException.getColumnIndex();
|
Integer columnIndex = excelDataConvertException.getColumnIndex();
|
||||||
errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
|
errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
|
||||||
rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
|
rowIndex + 1, columnIndex + 1, headMap == null ? "" : headMap.get(columnIndex));
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.warn(errMsg);
|
log.warn(errMsg);
|
||||||
}
|
}
|
||||||
@@ -78,7 +87,9 @@ public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements
|
|||||||
log.warn(errMsg, exception);
|
log.warn(errMsg, exception);
|
||||||
}
|
}
|
||||||
excelResult.getErrorList().add(errMsg);
|
excelResult.getErrorList().add(errMsg);
|
||||||
throw new ExcelAnalysisException(errMsg);
|
if (failFast) {
|
||||||
|
throw new ExcelAnalysisException(errMsg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+30
-28
@@ -15,6 +15,7 @@ import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.ss.util.CellRangeAddressList;
|
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.ss.util.WorkbookUtil;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
|
import org.apache.poi.xssf.usermodel.XSSFDataValidation;
|
||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
@@ -40,11 +41,8 @@ import java.util.*;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
public class ExcelDownHandler implements SheetWriteHandler {
|
public class ExcelDownHandler implements SheetWriteHandler {
|
||||||
|
|
||||||
/**
|
private static final int FIRST_DATA_ROW_INDEX = 1;
|
||||||
* Excel表格中的列名英文
|
private static final int LAST_DATA_ROW_INDEX = 1000;
|
||||||
* 仅为了解析列英文,禁止修改
|
|
||||||
*/
|
|
||||||
private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
|
||||||
/**
|
/**
|
||||||
* 单选数据Sheet名
|
* 单选数据Sheet名
|
||||||
*/
|
*/
|
||||||
@@ -57,7 +55,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
* 下拉可选项
|
* 下拉可选项
|
||||||
*/
|
*/
|
||||||
private final List<DropDownOptions> dropDownOptions;
|
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.dropDownOptions = options;
|
||||||
this.currentOptionsColumnIndex = 0;
|
this.currentOptionsColumnIndex = 0;
|
||||||
this.currentLinkedOptionsSheetIndex = 0;
|
this.currentLinkedOptionsSheetIndex = 0;
|
||||||
this.dictService = SpringUtils.getBean(DictService.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,7 +76,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
* 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项
|
* 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项
|
||||||
* 如果有且设置了value值,则将其直接置为下拉可选项
|
* 如果有且设置了value值,则将其直接置为下拉可选项
|
||||||
* <p>
|
* <p>
|
||||||
* 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉
|
* 2.或者在调用ExcelBuilder时指定了可选项,将依据传入的可选项做下拉
|
||||||
* <p>
|
* <p>
|
||||||
* 3.二者并存,注意调用方式
|
* 3.二者并存,注意调用方式
|
||||||
*/
|
*/
|
||||||
@@ -104,14 +101,20 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
String converterExp = format.readConverterExp();
|
String converterExp = format.readConverterExp();
|
||||||
if (StringUtils.isNotBlank(dictType)) {
|
if (StringUtils.isNotBlank(dictType)) {
|
||||||
// 如果传递了字典名,则依据字典建立下拉
|
// 如果传递了字典名,则依据字典建立下拉
|
||||||
Collection<String> values = Optional.ofNullable(dictService.getAllDictByDictType(dictType))
|
Collection<String> values = Optional.ofNullable(getDictService().getAllDictByDictType(dictType))
|
||||||
.orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
|
.orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType))
|
||||||
.values();
|
.values();
|
||||||
options = new ArrayList<>(values);
|
options = new ArrayList<>(values);
|
||||||
} else if (StringUtils.isNotBlank(converterExp)) {
|
} else if (StringUtils.isNotBlank(converterExp)) {
|
||||||
// 如果指定了确切的值,则直接解析确切的值
|
// 如果指定了确切的值,则直接解析确切的值
|
||||||
List<String> strList = StringUtils.splitList(converterExp, format.separator());
|
List<String> strList = StringUtils.splitList(converterExp, StringUtils.SEPARATOR);
|
||||||
options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]);
|
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)) {
|
} else if (field.isAnnotationPresent(ExcelEnumFormat.class)) {
|
||||||
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
|
// 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑
|
||||||
@@ -145,10 +148,10 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
}
|
}
|
||||||
dropDownOptions.forEach(everyOptions -> {
|
dropDownOptions.forEach(everyOptions -> {
|
||||||
// 如果传递了下拉框选择器参数
|
// 如果传递了下拉框选择器参数
|
||||||
if (!everyOptions.getNextOptions().isEmpty()) {
|
if (CollUtil.isNotEmpty(everyOptions.getNextOptions())) {
|
||||||
// 当二级选项不为空时,使用额外关联表的形式
|
// 当二级选项不为空时,使用额外关联表的形式
|
||||||
dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
|
dropDownLinkedOptions(helper, workbook, sheet, everyOptions);
|
||||||
} else if (everyOptions.getOptions().size() > 10) {
|
} else if (CollUtil.isNotEmpty(everyOptions.getOptions()) && everyOptions.getOptions().size() > 10) {
|
||||||
// 当一级选项参数个数大于10,使用额外表的形式
|
// 当一级选项参数个数大于10,使用额外表的形式
|
||||||
dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
|
dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
|
||||||
} else {
|
} else {
|
||||||
@@ -185,6 +188,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
|
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
|
||||||
// 选项数据(使用副本,避免修改调用方的原始数据)
|
// 选项数据(使用副本,避免修改调用方的原始数据)
|
||||||
List<String> firstOptions = options.getOptions();
|
List<String> firstOptions = options.getOptions();
|
||||||
|
if (CollUtil.isEmpty(firstOptions)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
Map<String, List<String>> secoundOptionsMap = new HashMap<>();
|
Map<String, List<String>> secoundOptionsMap = new HashMap<>();
|
||||||
options.getNextOptions().forEach((k, v) -> secoundOptionsMap.put(k, new ArrayList<>(v)));
|
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",
|
String firstOptionsFunction = String.format("%s!$%s$1:$%s$1",
|
||||||
linkedOptionsSheetName,
|
linkedOptionsSheetName,
|
||||||
getExcelColumnName(0),
|
getExcelColumnName(0),
|
||||||
getExcelColumnName(firstOptions.size())
|
getExcelColumnName(firstOptions.size() - 1)
|
||||||
);
|
);
|
||||||
// 设置名称管理器的引用位置
|
// 设置名称管理器的引用位置
|
||||||
name.setRefersToFormula(firstOptionsFunction);
|
name.setRefersToFormula(firstOptionsFunction);
|
||||||
@@ -242,7 +248,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
// 数据验证为序列模式,引用到每一个主表中的二级选项位置
|
// 数据验证为序列模式,引用到每一个主表中的二级选项位置
|
||||||
// 创建子项的名称管理器,只是为了使得Excel可以识别到数据
|
// 创建子项的名称管理器,只是为了使得Excel可以识别到数据
|
||||||
String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex());
|
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);
|
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,
|
private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex,
|
||||||
DataValidationConstraint constraint) {
|
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);
|
markDataValidationToSheet(helper, sheet, constraint, addressList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -397,17 +403,13 @@ public class ExcelDownHandler implements SheetWriteHandler {
|
|||||||
* @return 列index所在得英文名
|
* @return 列index所在得英文名
|
||||||
*/
|
*/
|
||||||
private String getExcelColumnName(int columnIndex) {
|
private String getExcelColumnName(int columnIndex) {
|
||||||
// 26一循环的次数
|
return CellReference.convertNumToColString(columnIndex);
|
||||||
int columnCircleCount = columnIndex / 26;
|
}
|
||||||
// 26一循环内的位置
|
|
||||||
int thisCircleColumnIndex = columnIndex % 26;
|
private DictService getDictService() {
|
||||||
// 26一循环的次数大于0,则视为栏名至少两位
|
if (dictService == null) {
|
||||||
String columnPrefix = columnCircleCount == 0
|
dictService = SpringUtils.getBean(DictService.class);
|
||||||
? StrUtil.EMPTY
|
}
|
||||||
: StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1);
|
return dictService;
|
||||||
// 从26一循环内取对应的栏位名
|
|
||||||
String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1);
|
|
||||||
// 将二者拼接即为最终的栏位名
|
|
||||||
return columnPrefix + columnNext;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -16,6 +16,7 @@ import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
|
|||||||
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
|
||||||
import org.dromara.common.excel.annotation.ExcelNotation;
|
import org.dromara.common.excel.annotation.ExcelNotation;
|
||||||
import org.dromara.common.excel.annotation.ExcelRequired;
|
import org.dromara.common.excel.annotation.ExcelRequired;
|
||||||
|
import org.dromara.common.core.utils.reflect.ReflectUtils;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@@ -51,6 +52,9 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
|
|||||||
}
|
}
|
||||||
// 第一行
|
// 第一行
|
||||||
WriteCellData<?> cellData = context.getFirstCellData();
|
WriteCellData<?> cellData = context.getFirstCellData();
|
||||||
|
if (cellData == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// 第一个格子
|
// 第一个格子
|
||||||
WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
|
WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();
|
||||||
|
|
||||||
@@ -92,7 +96,7 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
|
|||||||
*/
|
*/
|
||||||
private static Map<String, Short> getRequiredMap(Class<?> clazz) {
|
private static Map<String, Short> getRequiredMap(Class<?> clazz) {
|
||||||
Map<String, Short> requiredMap = new HashMap<>();
|
Map<String, Short> requiredMap = new HashMap<>();
|
||||||
Field[] fields = clazz.getDeclaredFields();
|
Field[] fields = ReflectUtils.getFields(clazz);
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
if (!field.isAnnotationPresent(ExcelRequired.class)) {
|
if (!field.isAnnotationPresent(ExcelRequired.class)) {
|
||||||
continue;
|
continue;
|
||||||
@@ -112,7 +116,7 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler {
|
|||||||
*/
|
*/
|
||||||
private static Map<String, String> getNotationMap(Class<?> clazz) {
|
private static Map<String, String> getNotationMap(Class<?> clazz) {
|
||||||
Map<String, String> notationMap = new HashMap<>();
|
Map<String, String> notationMap = new HashMap<>();
|
||||||
Field[] fields = clazz.getDeclaredFields();
|
Field[] fields = ReflectUtils.getFields(clazz);
|
||||||
for (Field field : fields) {
|
for (Field field : fields) {
|
||||||
if (!field.isAnnotationPresent(ExcelNotation.class)) {
|
if (!field.isAnnotationPresent(ExcelNotation.class)) {
|
||||||
continue;
|
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.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
import org.dromara.common.core.domain.PageResult;
|
||||||
import org.dromara.common.excel.core.ExcelResult;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.demo.domain.TestDemo;
|
import org.dromara.demo.domain.TestDemo;
|
||||||
@@ -74,7 +74,9 @@ public class TestDemoController extends BaseController {
|
|||||||
@SaCheckPermission("demo:demo:import")
|
@SaCheckPermission("demo:demo:import")
|
||||||
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public R<Void> importData(@RequestPart("file") MultipartFile file) throws Exception {
|
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);
|
List<TestDemo> list = MapstructUtils.convert(excelResult.getList(), TestDemo.class);
|
||||||
testDemoService.saveBatch(list);
|
testDemoService.saveBatch(list);
|
||||||
return R.ok(excelResult.getAnalysis());
|
return R.ok(excelResult.getAnalysis());
|
||||||
@@ -92,7 +94,7 @@ public class TestDemoController extends BaseController {
|
|||||||
// for (TestDemoVo vo : list) {
|
// for (TestDemoVo vo : list) {
|
||||||
// vo.setId(1234567891234567893L);
|
// 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.Data;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.excel.core.ExcelResult;
|
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.domain.vo.ExportDemoVo;
|
||||||
import org.dromara.demo.listener.ExportDemoListener;
|
import org.dromara.demo.listener.ExportDemoListener;
|
||||||
import org.dromara.demo.service.IExportExcelService;
|
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.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -48,7 +47,10 @@ public class TestExcelController {
|
|||||||
list.add(new TestObj("单列表测试1", "列表测试1", "列表测试2", "列表测试3", "列表测试4"));
|
list.add(new TestObj("单列表测试1", "列表测试1", "列表测试2", "列表测试3", "列表测试4"));
|
||||||
list.add(new TestObj("单列表测试2", "列表测试5", "列表测试6", "列表测试7", "列表测试8"));
|
list.add(new TestObj("单列表测试2", "列表测试5", "列表测试6", "列表测试7", "列表测试8"));
|
||||||
list.add(new TestObj("单列表测试3", "列表测试9", "列表测试10", "列表测试11", "列表测试12"));
|
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("data2", list2);
|
||||||
multiListMap.put("data3", list3);
|
multiListMap.put("data3", list3);
|
||||||
multiListMap.put("data4", list4);
|
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 /
|
* @param response /
|
||||||
*/
|
*/
|
||||||
@GetMapping("/customExport")
|
@GetMapping("/customExport")
|
||||||
public void customExport(HttpServletResponse response) throws IOException {
|
public void customExport(HttpServletResponse response) {
|
||||||
exportExcelService.customExport(response);
|
exportExcelService.customExport(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +142,10 @@ public class TestExcelController {
|
|||||||
list.add(sheetMap2);
|
list.add(sheetMap2);
|
||||||
list.add(sheetMap3);
|
list.add(sheetMap3);
|
||||||
list.add(sheetMap4);
|
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)
|
@PostMapping(value = "/importWithOptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public List<ExportDemoVo> importWithOptions(@RequestPart("file") MultipartFile file) throws Exception {
|
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();
|
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.EditGroup;
|
||||||
import org.dromara.common.core.validate.QueryGroup;
|
import org.dromara.common.core.validate.QueryGroup;
|
||||||
import org.dromara.common.web.core.BaseController;
|
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.redis.annotation.RepeatSubmit;
|
||||||
import org.dromara.common.log.annotation.Log;
|
import org.dromara.common.log.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
@@ -55,7 +55,7 @@ public class TestTreeController extends BaseController {
|
|||||||
@GetMapping("/export")
|
@GetMapping("/export")
|
||||||
public void export(@Validated TestTreeBo bo, HttpServletResponse response) {
|
public void export(@Validated TestTreeBo bo, HttpServletResponse response) {
|
||||||
List<TestTreeVo> list = testTreeService.queryList(bo);
|
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 jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导出下拉框Excel示例
|
* 导出下拉框Excel示例
|
||||||
*
|
*
|
||||||
@@ -23,5 +21,5 @@ public interface IExportExcelService {
|
|||||||
*
|
*
|
||||||
* @param response /
|
* @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 lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.constant.SystemConstants;
|
import org.dromara.common.core.constant.SystemConstants;
|
||||||
import org.dromara.common.core.utils.StreamUtils;
|
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.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.common.excel.utils.ExcelWriterWrapper;
|
||||||
import org.dromara.demo.domain.vo.ExportDemoVo;
|
import org.dromara.demo.domain.vo.ExportDemoVo;
|
||||||
import org.dromara.demo.service.IExportExcelService;
|
import org.dromara.demo.service.IExportExcelService;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -103,7 +101,10 @@ public class ExportExcelServiceImpl implements IExportExcelService {
|
|||||||
return everyRowData;
|
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) {
|
private String buildOptions(List<DemoCityData> cityDataList, Integer id) {
|
||||||
@@ -240,12 +241,8 @@ public class ExportExcelServiceImpl implements IExportExcelService {
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void customExport(HttpServletResponse response) throws IOException {
|
public void customExport(HttpServletResponse response) {
|
||||||
String filename = ExcelUtil.encodingFilename("自定义导出");
|
ExcelBuilder.writer(ExportDemoVo.class).sheetName("自定义导出").toResponse(response, wrapper -> {
|
||||||
FileUtils.setAttachmentResponseHeader(response, filename);
|
|
||||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
|
|
||||||
|
|
||||||
ExcelUtil.exportExcel(ExportDemoVo.class, response.getOutputStream(), wrapper -> {
|
|
||||||
// 创建表格数据,业务中一般通过数据库查询
|
// 创建表格数据,业务中一般通过数据库查询
|
||||||
List<ExportDemoVo> excelDataList = new ArrayList<>();
|
List<ExportDemoVo> excelDataList = new ArrayList<>();
|
||||||
for (int i = 0; i < 30; i++) {
|
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.core.validate.EditGroup;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
#if($enableExport)
|
#if($enableExport)
|
||||||
import org.dromara.common.excel.utils.ExcelUtil;
|
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||||
#end
|
#end
|
||||||
import ${packageName}.domain.vo.${ClassName}Vo;
|
import ${packageName}.domain.vo.${ClassName}Vo;
|
||||||
import ${packageName}.domain.bo.${ClassName}Bo;
|
import ${packageName}.domain.bo.${ClassName}Bo;
|
||||||
@@ -68,7 +68,7 @@ public class ${ClassName}Controller extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(${ClassName}Bo bo, HttpServletResponse response) {
|
public void export(${ClassName}Bo bo, HttpServletResponse response) {
|
||||||
List<${ClassName}Vo> list = ${className}Service.queryList(bo);
|
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
|
#end
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -7,7 +7,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.dromara.common.core.constant.CacheNames;
|
import org.dromara.common.core.constant.CacheNames;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
import org.dromara.common.core.domain.PageResult;
|
||||||
import org.dromara.common.core.domain.R;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -59,7 +59,7 @@ public class SysLoginInfoController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysLoginInfoBo loginInfo, HttpServletResponse response) {
|
public void export(SysLoginInfoBo loginInfo, HttpServletResponse response) {
|
||||||
List<SysLoginInfoVo> list = loginInfoService.selectLoginInfoList(loginInfo);
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
import org.dromara.common.core.domain.PageResult;
|
||||||
import org.dromara.common.core.domain.R;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -56,7 +56,7 @@ public class SysOperlogController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysOperLogBo operLog, HttpServletResponse response) {
|
public void export(SysOperLogBo operLog, HttpServletResponse response) {
|
||||||
List<SysOperLogVo> list = operLogService.selectOperLogList(operLog);
|
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.domain.R;
|
||||||
import org.dromara.common.core.validate.AddGroup;
|
import org.dromara.common.core.validate.AddGroup;
|
||||||
import org.dromara.common.core.validate.EditGroup;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -61,7 +61,7 @@ public class SysClientController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysClientBo bo, HttpServletResponse response) {
|
public void export(SysClientBo bo, HttpServletResponse response) {
|
||||||
List<SysClientVo> list = sysClientService.queryList(bo);
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
import org.dromara.common.core.domain.PageResult;
|
||||||
import org.dromara.common.core.domain.R;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -57,7 +57,7 @@ public class SysConfigController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysConfigBo config, HttpServletResponse response) {
|
public void export(SysConfigBo config, HttpServletResponse response) {
|
||||||
List<SysConfigVo> list = configService.selectConfigList(config);
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
import org.dromara.common.core.domain.PageResult;
|
||||||
import org.dromara.common.core.domain.R;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -61,7 +61,7 @@ public class SysDictDataController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysDictDataBo dictData, HttpServletResponse response) {
|
public void export(SysDictDataBo dictData, HttpServletResponse response) {
|
||||||
List<SysDictDataVo> list = dictDataService.selectDictDataList(dictData);
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
import org.dromara.common.core.domain.PageResult;
|
||||||
import org.dromara.common.core.domain.R;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -58,7 +58,7 @@ public class SysDictTypeController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysDictTypeBo dictType, HttpServletResponse response) {
|
public void export(SysDictTypeBo dictType, HttpServletResponse response) {
|
||||||
List<SysDictTypeVo> list = dictTypeService.selectDictTypeList(dictType);
|
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.constant.SystemConstants;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
import org.dromara.common.core.domain.PageResult;
|
||||||
import org.dromara.common.core.domain.R;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -64,7 +64,7 @@ public class SysPostController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysPostBo post, HttpServletResponse response) {
|
public void export(SysPostBo post, HttpServletResponse response) {
|
||||||
List<SysPostVo> list = postService.selectPostList(post);
|
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 lombok.RequiredArgsConstructor;
|
||||||
import org.dromara.common.core.domain.PageResult;
|
import org.dromara.common.core.domain.PageResult;
|
||||||
import org.dromara.common.core.domain.R;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -66,7 +66,7 @@ public class SysRoleController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysRoleBo role, HttpServletResponse response) {
|
public void export(SysRoleBo role, HttpServletResponse response) {
|
||||||
List<SysRoleVo> list = roleService.selectRoleList(role);
|
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.core.utils.StringUtils;
|
||||||
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
import org.dromara.common.encrypt.annotation.ApiEncrypt;
|
||||||
import org.dromara.common.excel.core.ExcelResult;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -84,7 +84,7 @@ public class SysUserController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(SysUserBo user, HttpServletResponse response) {
|
public void export(SysUserBo user, HttpServletResponse response) {
|
||||||
List<SysUserExportVo> list = userService.selectUserExportList(user);
|
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")
|
@SaCheckPermission("system:user:import")
|
||||||
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public R<Void> importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception {
|
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());
|
return R.ok(result.getAnalysis());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,7 +110,7 @@ public class SysUserController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@PostMapping("/importTemplate")
|
@PostMapping("/importTemplate")
|
||||||
public void importTemplate(HttpServletResponse response) {
|
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.domain.R;
|
||||||
import org.dromara.common.core.validate.AddGroup;
|
import org.dromara.common.core.validate.AddGroup;
|
||||||
import org.dromara.common.core.validate.EditGroup;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.redis.annotation.RepeatSubmit;
|
import org.dromara.common.redis.annotation.RepeatSubmit;
|
||||||
@@ -61,7 +61,7 @@ public class FlwCategoryController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(FlowCategoryBo bo, HttpServletResponse response) {
|
public void export(FlowCategoryBo bo, HttpServletResponse response) {
|
||||||
List<FlowCategoryVo> list = flwCategoryService.queryList(bo);
|
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.domain.R;
|
||||||
import org.dromara.common.core.validate.AddGroup;
|
import org.dromara.common.core.validate.AddGroup;
|
||||||
import org.dromara.common.core.validate.EditGroup;
|
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.annotation.Log;
|
||||||
import org.dromara.common.log.enums.BusinessType;
|
import org.dromara.common.log.enums.BusinessType;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
@@ -64,7 +64,7 @@ public class TestLeaveController extends BaseController {
|
|||||||
@PostMapping("/export")
|
@PostMapping("/export")
|
||||||
public void export(TestLeaveBo bo, HttpServletResponse response) {
|
public void export(TestLeaveBo bo, HttpServletResponse response) {
|
||||||
List<TestLeaveVo> list = testLeaveService.queryList(bo);
|
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