diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java index 5df7efb73..19d883429 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelDictConvert.java @@ -13,10 +13,12 @@ import org.dromara.common.excel.annotation.ExcelDictFormat; import org.dromara.common.core.service.DictService; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.StringUtils; -import org.dromara.common.excel.utils.ExcelUtil; import lombok.extern.slf4j.Slf4j; import java.lang.reflect.Field; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Pattern; /** * 字典格式化转换处理 @@ -26,6 +28,8 @@ import java.lang.reflect.Field; @Slf4j public class ExcelDictConvert implements Converter { + private DictService dictService; + @Override public Class supportJavaTypeKey() { return Object.class; @@ -43,9 +47,9 @@ public class ExcelDictConvert implements Converter { String label = cellData.getStringValue(); String value; if (StringUtils.isBlank(type)) { - value = ExcelUtil.reverseByExp(label, anno.readConverterExp(), anno.separator()); + value = reverseByExp(label, anno.readConverterExp(), anno.separator()); } else { - value = SpringUtils.getBean(DictService.class).getDictValue(type, label, anno.separator()); + value = getDictService().getDictValue(type, label, anno.separator()); } return Convert.convert(contentProperty.getField().getType(), value); } @@ -60,9 +64,9 @@ public class ExcelDictConvert implements Converter { String value = Convert.toStr(object); String label; if (StringUtils.isBlank(type)) { - label = ExcelUtil.convertByExp(value, anno.readConverterExp(), anno.separator()); + label = convertByExp(value, anno.readConverterExp(), anno.separator()); } else { - label = SpringUtils.getBean(DictService.class).getDictLabel(type, value, anno.separator()); + label = getDictService().getDictLabel(type, value, anno.separator()); } return new WriteCellData<>(label); } @@ -70,4 +74,76 @@ public class ExcelDictConvert implements Converter { private ExcelDictFormat getAnnotation(Field field) { return AnnotationUtil.getAnnotation(field, ExcelDictFormat.class); } + + private DictService getDictService() { + if (dictService == null) { + dictService = SpringUtils.getBean(DictService.class); + } + return dictService; + } + + /** + * 解析导出值 0=男,1=女,2=未知。 + */ + private static String convertByExp(String propertyValue, String converterExp, String separator) { + StringBuilder propertyString = new StringBuilder(); + Map convertSource = parseConverterExp(converterExp); + for (Map.Entry 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 convertSource = parseConverterExp(converterExp); + for (Map.Entry 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 parseConverterExp(String converterExp) { + Map 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)); + } } diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java index 877218d90..7c6c787cf 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/convert/ExcelEnumConvert.java @@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap; public class ExcelEnumConvert implements Converter { private static final Map> ENUM_MAP_CACHE = new ConcurrentHashMap<>(); - private static final Map> ENUM_REVERSE_MAP_CACHE = new ConcurrentHashMap<>(); + private static final Map> ENUM_REVERSE_MAP_CACHE = new ConcurrentHashMap<>(); @Override public Class supportJavaTypeKey() { @@ -55,16 +55,24 @@ public class ExcelEnumConvert implements Converter { } Map enumCodeToTextMap = beforeConvert(contentProperty); // 从Java输出至Excel是code转text,从Excel转Java应将text与code对调 - Map enumTextToCodeMap = ENUM_REVERSE_MAP_CACHE.computeIfAbsent( + Map enumTextToCodeMap = ENUM_REVERSE_MAP_CACHE.computeIfAbsent( contentProperty.getField(), f -> { - Map reverseMap = new HashMap<>(); - enumCodeToTextMap.forEach((key, value) -> reverseMap.put(value, key)); + Map reverseMap = new HashMap<>(); + enumCodeToTextMap.forEach((key, value) -> { + Object oldValue = reverseMap.put(value, key); + if (ObjectUtil.isNotNull(oldValue)) { + throw new IllegalArgumentException("枚举导入文本值重复: " + value); + } + }); return reverseMap; } ); // 应该从text -> code中查找 - Object codeValue = enumTextToCodeMap.get(textValue); + Object codeValue = enumTextToCodeMap.get(Convert.toStr(textValue)); + if (ObjectUtil.isNull(codeValue)) { + throw new IllegalArgumentException("枚举值不匹配: " + textValue + ",允许值: " + enumTextToCodeMap.keySet()); + } return Convert.convert(contentProperty.getField().getType(), codeValue); } @@ -86,6 +94,9 @@ public class ExcelEnumConvert implements Converter { for (Enum enumConstant : enumConstants) { Object codeValue = ReflectUtils.invokeGetter(enumConstant, anno.codeField()); String textValue = ReflectUtils.invokeGetter(enumConstant, anno.textField()); + if (ObjectUtil.isNull(codeValue) || ObjectUtil.isNull(textValue)) { + throw new IllegalArgumentException("枚举字段 code/text 不能为空: " + enumConstant.name()); + } enumValueMap.put(codeValue, textValue); } return enumValueMap; diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java index cc2232fd0..a8dca7b2f 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/CellMergeHandler.java @@ -129,11 +129,14 @@ public class CellMergeHandler { continue; } CellMerge cm = field.getAnnotation(CellMerge.class); - int index = cm.index() == -1 ? i : cm.index(); + ExcelProperty property = field.getAnnotation(ExcelProperty.class); + int index = cm.index(); + if (index == -1) { + index = property != null && property.index() != -1 ? property.index() : i; + } mergeFields.put(field, FieldColumnIndex.of(index, cm)); - if (hasTitle) { - ExcelProperty property = field.getAnnotation(ExcelProperty.class); + if (hasTitle && property != null && property.value().length > 0) { rowIndex = Math.max(rowIndex, property.value().length); } } diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java index 513c556a5..ac5bf599d 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/DefaultExcelListener.java @@ -39,13 +39,22 @@ public class DefaultExcelListener extends AnalysisEventListener implements /** * 导入回执 */ - private ExcelResult excelResult; + private final ExcelResult excelResult = new DefaultExcelResult<>(); + + /** + * 发生异常时是否立即终止读取,默认保持原有快速失败行为 + */ + private Boolean failFast = Boolean.TRUE; public DefaultExcelListener(boolean isValidate) { - this.excelResult = new DefaultExcelResult<>(); this.isValidate = isValidate; } + public DefaultExcelListener(boolean isValidate, boolean failFast) { + this.isValidate = isValidate; + this.failFast = failFast; + } + /** * 处理异常 * @@ -60,7 +69,7 @@ public class DefaultExcelListener extends AnalysisEventListener implements Integer rowIndex = excelDataConvertException.getRowIndex(); Integer columnIndex = excelDataConvertException.getColumnIndex(); errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常
", - rowIndex + 1, columnIndex + 1, headMap.get(columnIndex)); + rowIndex + 1, columnIndex + 1, headMap == null ? "" : headMap.get(columnIndex)); if (log.isDebugEnabled()) { log.warn(errMsg); } @@ -78,7 +87,9 @@ public class DefaultExcelListener extends AnalysisEventListener implements log.warn(errMsg, exception); } excelResult.getErrorList().add(errMsg); - throw new ExcelAnalysisException(errMsg); + if (failFast) { + throw new ExcelAnalysisException(errMsg); + } } @Override diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java index 36d4d0de6..e16bc49f7 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/core/ExcelDownHandler.java @@ -15,6 +15,7 @@ import org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder; import lombok.extern.slf4j.Slf4j; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.WorkbookUtil; import org.apache.poi.xssf.usermodel.XSSFDataValidation; import org.dromara.common.core.exception.ServiceException; @@ -40,11 +41,8 @@ import java.util.*; @Slf4j public class ExcelDownHandler implements SheetWriteHandler { - /** - * Excel表格中的列名英文 - * 仅为了解析列英文,禁止修改 - */ - private static final String EXCEL_COLUMN_NAME = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + private static final int FIRST_DATA_ROW_INDEX = 1; + private static final int LAST_DATA_ROW_INDEX = 1000; /** * 单选数据Sheet名 */ @@ -57,7 +55,7 @@ public class ExcelDownHandler implements SheetWriteHandler { * 下拉可选项 */ private final List dropDownOptions; - private final DictService dictService; + private DictService dictService; /** * 当前单选进度 */ @@ -71,7 +69,6 @@ public class ExcelDownHandler implements SheetWriteHandler { this.dropDownOptions = options; this.currentOptionsColumnIndex = 0; this.currentLinkedOptionsSheetIndex = 0; - this.dictService = SpringUtils.getBean(DictService.class); } /** @@ -79,7 +76,7 @@ public class ExcelDownHandler implements SheetWriteHandler { * 1.通过解析传入的@ExcelProperty同级是否标注有@DropDown选项 * 如果有且设置了value值,则将其直接置为下拉可选项 *

- * 2.或者在调用ExcelUtil时指定了可选项,将依据传入的可选项做下拉 + * 2.或者在调用ExcelBuilder时指定了可选项,将依据传入的可选项做下拉 *

* 3.二者并存,注意调用方式 */ @@ -104,14 +101,20 @@ public class ExcelDownHandler implements SheetWriteHandler { String converterExp = format.readConverterExp(); if (StringUtils.isNotBlank(dictType)) { // 如果传递了字典名,则依据字典建立下拉 - Collection values = Optional.ofNullable(dictService.getAllDictByDictType(dictType)) + Collection values = Optional.ofNullable(getDictService().getAllDictByDictType(dictType)) .orElseThrow(() -> new ServiceException("字典 {} 不存在", dictType)) .values(); options = new ArrayList<>(values); } else if (StringUtils.isNotBlank(converterExp)) { // 如果指定了确切的值,则直接解析确切的值 - List strList = StringUtils.splitList(converterExp, format.separator()); - options = StreamUtils.toList(strList, s -> StringUtils.split(s, "=")[1]); + List strList = StringUtils.splitList(converterExp, StringUtils.SEPARATOR); + options = StreamUtils.toList(strList, s -> { + String[] itemArray = s.split("=", 2); + if (itemArray.length != 2) { + throw new ServiceException("Excel转换表达式格式错误: {}", s); + } + return itemArray[1]; + }); } } else if (field.isAnnotationPresent(ExcelEnumFormat.class)) { // 否则如果指定了@ExcelEnumFormat,则使用枚举的逻辑 @@ -145,10 +148,10 @@ public class ExcelDownHandler implements SheetWriteHandler { } dropDownOptions.forEach(everyOptions -> { // 如果传递了下拉框选择器参数 - if (!everyOptions.getNextOptions().isEmpty()) { + if (CollUtil.isNotEmpty(everyOptions.getNextOptions())) { // 当二级选项不为空时,使用额外关联表的形式 dropDownLinkedOptions(helper, workbook, sheet, everyOptions); - } else if (everyOptions.getOptions().size() > 10) { + } else if (CollUtil.isNotEmpty(everyOptions.getOptions()) && everyOptions.getOptions().size() > 10) { // 当一级选项参数个数大于10,使用额外表的形式 dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions()); } else { @@ -185,6 +188,9 @@ public class ExcelDownHandler implements SheetWriteHandler { workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true); // 选项数据(使用副本,避免修改调用方的原始数据) List firstOptions = options.getOptions(); + if (CollUtil.isEmpty(firstOptions)) { + return; + } Map> secoundOptionsMap = new HashMap<>(); options.getNextOptions().forEach((k, v) -> secoundOptionsMap.put(k, new ArrayList<>(v))); @@ -210,7 +216,7 @@ public class ExcelDownHandler implements SheetWriteHandler { String firstOptionsFunction = String.format("%s!$%s$1:$%s$1", linkedOptionsSheetName, getExcelColumnName(0), - getExcelColumnName(firstOptions.size()) + getExcelColumnName(firstOptions.size() - 1) ); // 设置名称管理器的引用位置 name.setRefersToFormula(firstOptionsFunction); @@ -242,7 +248,7 @@ public class ExcelDownHandler implements SheetWriteHandler { // 数据验证为序列模式,引用到每一个主表中的二级选项位置 // 创建子项的名称管理器,只是为了使得Excel可以识别到数据 String mainSheetFirstOptionsColumnName = getExcelColumnName(options.getIndex()); - for (int i = 0; i < 100; i++) { + for (int i = FIRST_DATA_ROW_INDEX; i <= LAST_DATA_ROW_INDEX; i++) { // 以一级选项对应的主体所在位置创建二级下拉 String secondOptionsFunction = String.format("=INDIRECT(%s%d)", mainSheetFirstOptionsColumnName, i + 1); // 二级只能主表每一行的每一列添加二级校验 @@ -348,7 +354,7 @@ public class ExcelDownHandler implements SheetWriteHandler { private void markOptionsToSheet(DataValidationHelper helper, Sheet sheet, Integer celIndex, DataValidationConstraint constraint) { // 设置数据有效性加载在哪个单元格上,四个参数分别是:起始行、终止行、起始列、终止列 - CellRangeAddressList addressList = new CellRangeAddressList(1, 1000, celIndex, celIndex); + CellRangeAddressList addressList = new CellRangeAddressList(FIRST_DATA_ROW_INDEX, LAST_DATA_ROW_INDEX, celIndex, celIndex); markDataValidationToSheet(helper, sheet, constraint, addressList); } @@ -397,17 +403,13 @@ public class ExcelDownHandler implements SheetWriteHandler { * @return 列index所在得英文名 */ private String getExcelColumnName(int columnIndex) { - // 26一循环的次数 - int columnCircleCount = columnIndex / 26; - // 26一循环内的位置 - int thisCircleColumnIndex = columnIndex % 26; - // 26一循环的次数大于0,则视为栏名至少两位 - String columnPrefix = columnCircleCount == 0 - ? StrUtil.EMPTY - : StrUtil.subWithLength(EXCEL_COLUMN_NAME, columnCircleCount - 1, 1); - // 从26一循环内取对应的栏位名 - String columnNext = StrUtil.subWithLength(EXCEL_COLUMN_NAME, thisCircleColumnIndex, 1); - // 将二者拼接即为最终的栏位名 - return columnPrefix + columnNext; + return CellReference.convertNumToColString(columnIndex); + } + + private DictService getDictService() { + if (dictService == null) { + dictService = SpringUtils.getBean(DictService.class); + } + return dictService; } } diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java index 62a03ca8b..541801764 100644 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/handler/DataWriteHandler.java @@ -16,6 +16,7 @@ import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.XSSFRichTextString; import org.dromara.common.excel.annotation.ExcelNotation; import org.dromara.common.excel.annotation.ExcelRequired; +import org.dromara.common.core.utils.reflect.ReflectUtils; import java.lang.reflect.Field; import java.util.HashMap; @@ -51,6 +52,9 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler { } // 第一行 WriteCellData cellData = context.getFirstCellData(); + if (cellData == null) { + return; + } // 第一个格子 WriteCellStyle writeCellStyle = cellData.getOrCreateStyle(); @@ -92,7 +96,7 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler { */ private static Map getRequiredMap(Class clazz) { Map requiredMap = new HashMap<>(); - Field[] fields = clazz.getDeclaredFields(); + Field[] fields = ReflectUtils.getFields(clazz); for (Field field : fields) { if (!field.isAnnotationPresent(ExcelRequired.class)) { continue; @@ -112,7 +116,7 @@ public class DataWriteHandler implements SheetWriteHandler, CellWriteHandler { */ private static Map getNotationMap(Class clazz) { Map notationMap = new HashMap<>(); - Field[] fields = clazz.getDeclaredFields(); + Field[] fields = ReflectUtils.getFields(clazz); for (Field field : fields) { if (!field.isAnnotationPresent(ExcelNotation.class)) { continue; diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelBuilder.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelBuilder.java new file mode 100644 index 000000000..6b87fee20 --- /dev/null +++ b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelBuilder.java @@ -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 { + + private static final String DEFAULT_SHEET_NAME = "sheet1"; + + private static final int DEFAULT_ZIP_PAGE_SIZE = 999; + + private final List data; + + private final Class headType; + + private String sheetName = DEFAULT_SHEET_NAME; + + private Integer sheetNo; + + private boolean merge; + + private List options; + + private boolean zip; + + private int pageSize = DEFAULT_ZIP_PAGE_SIZE; + + private String password; + + private Boolean needHead; + + private Boolean automaticMergeHead; + + private Collection includeFields; + + private Collection excludeFields; + + private Collection includeIndexes; + + private Collection excludeIndexes; + + private Boolean orderByIncludeColumn; + + private Integer columnWidth; + + private Short headRowHeight; + + private Short contentRowHeight; + + private List writeHandlers; + + private List> converters; + + private ExcelBuilder(List data, Class headType) { + this.data = data; + this.headType = headType; + } + + /** + * 创建导出构造器。 + * + * @param data 导出数据 + * @param headType 表头类型 + * @return 导出构造器 + */ + public static ExcelBuilder of(List data, Class headType) { + return new ExcelBuilder<>(data, headType); + } + + /** + * 创建自定义写出构造器。 + * + * @param headType 表头类型 + * @return 导出构造器 + */ + public static ExcelBuilder writer(Class 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 ReadBuilder read(InputStream is, Class clazz) { + return new ReadBuilder<>(is, clazz); + } + + /** + * 设置工作表名称。 + */ + public ExcelBuilder sheetName(String sheetName) { + this.sheetName = StringUtils.blankToDefault(sheetName, DEFAULT_SHEET_NAME); + return this; + } + + /** + * 设置工作表编号。 + */ + public ExcelBuilder sheetNo(Integer sheetNo) { + this.sheetNo = sheetNo; + return this; + } + + /** + * 开启单元格合并。 + */ + public ExcelBuilder merge() { + return merge(true); + } + + /** + * 设置是否合并单元格。 + */ + public ExcelBuilder merge(boolean merge) { + this.merge = merge; + return this; + } + + /** + * 设置下拉选项。 + */ + public ExcelBuilder options(List options) { + this.options = options; + return this; + } + + /** + * 设置导出文件密码。 + */ + public ExcelBuilder password(String password) { + this.password = password; + return this; + } + + /** + * 设置是否写出表头。 + */ + public ExcelBuilder needHead(boolean needHead) { + this.needHead = needHead; + return this; + } + + /** + * 设置是否自动合并多级表头。 + */ + public ExcelBuilder automaticMergeHead(boolean automaticMergeHead) { + this.automaticMergeHead = automaticMergeHead; + return this; + } + + /** + * 仅导出指定字段。 + */ + public ExcelBuilder includeFields(Collection includeFields) { + this.includeFields = includeFields; + return this; + } + + /** + * 排除指定字段。 + */ + public ExcelBuilder excludeFields(Collection excludeFields) { + this.excludeFields = excludeFields; + return this; + } + + /** + * 仅导出指定列索引。 + */ + public ExcelBuilder includeIndexes(Collection includeIndexes) { + this.includeIndexes = includeIndexes; + return this; + } + + /** + * 排除指定列索引。 + */ + public ExcelBuilder excludeIndexes(Collection excludeIndexes) { + this.excludeIndexes = excludeIndexes; + return this; + } + + /** + * 设置是否按 include 列顺序导出。 + */ + public ExcelBuilder orderByIncludeColumn(boolean orderByIncludeColumn) { + this.orderByIncludeColumn = orderByIncludeColumn; + return this; + } + + /** + * 设置固定列宽。 + */ + public ExcelBuilder columnWidth(Integer columnWidth) { + if (columnWidth != null && columnWidth <= 0) { + throw new IllegalArgumentException("columnWidth 必须大于 0"); + } + this.columnWidth = columnWidth; + return this; + } + + /** + * 设置固定行高。 + */ + public ExcelBuilder 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 registerWriteHandler(WriteHandler writeHandler) { + if (writeHandler == null) { + return this; + } + if (writeHandlers == null) { + writeHandlers = new ArrayList<>(); + } + writeHandlers.add(writeHandler); + return this; + } + + /** + * 注册自定义转换器。 + */ + public ExcelBuilder registerConverter(Converter converter) { + if (converter == null) { + return this; + } + if (converters == null) { + converters = new ArrayList<>(); + } + converters.add(converter); + return this; + } + + /** + * 开启 ZIP 分页导出。 + */ + public ExcelBuilder zip() { + return zip(DEFAULT_ZIP_PAGE_SIZE); + } + + /** + * 开启 ZIP 分页导出。 + * + * @param pageSize 每个 Excel 文件的数据量 + */ + public ExcelBuilder 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> 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> 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> 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 pageData, String exportSheetName) { + try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { + ExcelBuilder 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 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 { + + private final InputStream inputStream; + + private final Class headType; + + private boolean validate = true; + + private boolean failFast = true; + + private ExcelListener 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> converters; + + private ReadBuilder(InputStream inputStream, Class headType) { + this.inputStream = inputStream; + this.headType = headType; + } + + /** + * 设置是否校验导入数据。 + */ + public ReadBuilder validate(boolean validate) { + this.validate = validate; + return this; + } + + /** + * 设置解析异常时是否立即终止读取。 + */ + public ReadBuilder failFast(boolean failFast) { + this.failFast = failFast; + return this; + } + + /** + * 设置自定义导入监听器。 + */ + public ReadBuilder listener(ExcelListener listener) { + this.listener = listener; + return this; + } + + /** + * 设置读取的工作表编号。 + */ + public ReadBuilder sheetNo(Integer sheetNo) { + this.sheetNo = sheetNo; + return this; + } + + /** + * 设置读取的工作表名称。 + */ + public ReadBuilder sheetName(String sheetName) { + this.sheetName = sheetName; + return this; + } + + /** + * 设置表头行数。 + */ + public ReadBuilder headRowNumber(Integer headRowNumber) { + if (headRowNumber != null && headRowNumber < 0) { + throw new IllegalArgumentException("headRowNumber 不能小于 0"); + } + this.headRowNumber = headRowNumber; + return this; + } + + /** + * 设置是否忽略空行。 + */ + public ReadBuilder ignoreEmptyRow(boolean ignoreEmptyRow) { + this.ignoreEmptyRow = ignoreEmptyRow; + return this; + } + + /** + * 设置读取文件密码。 + */ + public ReadBuilder password(String password) { + this.password = password; + return this; + } + + /** + * 设置是否自动 trim 字符串。 + */ + public ReadBuilder autoTrim(boolean autoTrim) { + this.autoTrim = autoTrim; + return this; + } + + /** + * 设置是否自动 strip 字符串。 + */ + public ReadBuilder autoStrip(boolean autoStrip) { + this.autoStrip = autoStrip; + return this; + } + + /** + * 设置最多读取行数。 + */ + public ReadBuilder numRows(Integer numRows) { + if (numRows != null && numRows <= 0) { + throw new IllegalArgumentException("numRows 必须大于 0"); + } + this.numRows = numRows; + return this; + } + + /** + * 注册自定义转换器。 + */ + public ReadBuilder registerConverter(Converter converter) { + if (converter == null) { + return this; + } + if (converters == null) { + converters = new ArrayList<>(); + } + converters.add(converter); + return this; + } + + /** + * 读取Excel并返回对象集合。 + */ + public List doReadSync() { + return createSheetBuilder(createReaderBuilder(null)).doReadSync(); + } + + /** + * 读取Excel并返回解析结果。 + */ + public ExcelResult doRead() { + ExcelListener readListener = listener; + if (readListener == null) { + readListener = new DefaultExcelListener<>(validate, failFast); + } + createSheetBuilder(createReaderBuilder(readListener)).doRead(); + return readListener.getExcelResult(); + } + + /** + * 读取所有工作表并返回解析结果。 + */ + public ExcelResult doReadAll() { + ExcelListener readListener = listener; + if (readListener == null) { + readListener = new DefaultExcelListener<>(validate, failFast); + } + createReaderBuilder(readListener).doReadAll(); + return readListener.getExcelResult(); + } + + /** + * 读取所有工作表并返回对象集合。 + */ + public List doReadAllSync() { + return createReaderBuilder(null).doReadAllSync(); + } + + private ExcelReaderBuilder createReaderBuilder(ExcelListener 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 TemplateBuilder data(List data) { + this.mode = TemplateMode.LIST; + this.data = data; + return this; + } + + /** + * 设置多表多数据模板数据,模板格式为 {key.属性}。 + */ + public TemplateBuilder multiList(Map data) { + this.mode = TemplateMode.MULTI_LIST; + this.data = data; + return this; + } + + /** + * 设置多 sheet 模板数据,模板格式为 {key.属性}。 + */ + public TemplateBuilder multiSheet(List> 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 map : ((Map) 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> sheetData = (List>) data; + for (int i = 0; i < sheetData.size(); i++) { + WriteSheet writeSheet = FesodSheet.writerSheet(i).build(); + for (Map.Entry 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 + } + } +} diff --git a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java b/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java deleted file mode 100644 index 2f586a3d5..000000000 --- a/ruoyi-common/ruoyi-common-excel/src/main/java/org/dromara/common/excel/utils/ExcelUtil.java +++ /dev/null @@ -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 List importExcel(InputStream is, Class clazz) { - return FesodSheet.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync(); - } - - /** - * 读取Excel并返回解析结果(带默认校验功能) - * - * @param is 文件流 - * @param clazz 接收实体类 - * @param isValidate 是否开启校验 - * @return 解析结果(含成功数据、错误信息) - */ - public static ExcelResult importExcel(InputStream is, Class clazz, boolean isValidate) { - DefaultExcelListener listener = new DefaultExcelListener<>(isValidate); - FesodSheet.read(is, clazz, listener).sheet().doRead(); - return listener.getExcelResult(); - } - - /** - * 读取Excel并返回解析结果(使用自定义监听器) - * - * @param is 文件流 - * @param clazz 接收实体类 - * @param listener 自定义监听器 - * @return 解析结果(含成功数据、错误信息) - */ - public static ExcelResult importExcel(InputStream is, Class clazz, ExcelListener listener) { - FesodSheet.read(is, clazz, listener).sheet().doRead(); - return listener.getExcelResult(); - } - - /** - * 导出excel - * - * @param list 导出数据集合 - * @param sheetName 工作表的名称 - * @param clazz 实体类 - * @param response 响应体 - */ - public static void exportExcel(List list, String sheetName, Class 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 void exportExcelZip(List list, String sheetName, Class clazz, HttpServletResponse response) { - exportExcelZip(list, sheetName, clazz, response, 999); - } - - /** - * 大数据量Excel导出 - * - * @param list 导出数据集合 - * @param sheetName 工作表的名称 - * @param clazz 实体类 - * @param pageSize 每页条数 - */ - public static void exportExcelZip(List list, String sheetName, Class clazz, HttpServletResponse response, int pageSize) { - // 数据分页 - List> pageList = ListUtil.partition(list, pageSize); - // 只有一页,直接导出普通Excel - if (pageList.size() <= 1) { - exportSingleExcel(list, sheetName, clazz, response); - return; - } - // 多线程生成所有Excel文件(字节数组) - Map excelMap = buildExcelZipData(pageList, sheetName, clazz); - // 写入ZIP并下载 - writeExcelZipResponse(sheetName, response, excelMap); - } - - /** - * 导出【单文件】Excel - */ - private static void exportSingleExcel(List list, String sheetName, Class 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 Map buildExcelZipData(List> pageList, String sheetName, Class clazz) { - // 有序Map,保证文件按页码顺序打包 - Map excelMap = new LinkedHashMap<>(pageList.size()); - List>> futures = new ArrayList<>(pageList.size()); - - // 使用虚拟线程池执行导出任务 - try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) { - // 1. 提交所有分页导出任务 - for (int i = 0; i < pageList.size(); i++) { - int pageNum = i + 1; - List pageData = pageList.get(i); - futures.add(executor.submit(() -> buildExcelZipEntry(pageData, sheetName, clazz, pageNum))); - } - // 2. 获取所有线程执行结果 - for (Future> future : futures) { - Map.Entry excel = getExcelZipEntry(future); - excelMap.put(excel.getKey(), excel.getValue()); - } - } - return excelMap; - } - - /** - * 单页Excel生成任务(线程执行单元) - * - * @param pageData 当前页数据 - * @param pageNum 当前页码 - * @return 文件名 + 文件字节 - */ - private static Map.Entry buildExcelZipEntry(List pageData, String sheetName, Class 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 getExcelZipEntry(Future> 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 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 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 void exportExcel(List list, String sheetName, Class clazz, HttpServletResponse response, List 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 void exportExcel(List list, String sheetName, Class 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 void exportExcel(List list, String sheetName, Class clazz, boolean merge, HttpServletResponse response, List 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 void exportExcel(List list, String sheetName, Class clazz, OutputStream os) { - exportExcel(list, sheetName, clazz, false, os, null); - } - - /** - * 导出excel - * - * @param list 导出数据集合 - * @param sheetName 工作表的名称 - * @param clazz 实体类 - * @param os 输出流 - * @param options 级联下拉选内容 - */ - public static void exportExcel(List list, String sheetName, Class clazz, OutputStream os, List options) { - exportExcel(list, sheetName, clazz, false, os, options); - } - - /** - * 导出excel - * - * @param list 导出数据集合 - * @param sheetName 工作表的名称 - * @param clazz 实体类 - * @param merge 是否合并单元格 - * @param os 输出流 - */ - public static void exportExcel(List list, String sheetName, Class clazz, boolean merge, - OutputStream os, List 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 void exportExcel(Class headType, OutputStream os, List options, Consumer> 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 void exportExcel(Class headType, OutputStream os, Consumer> consumer) { - exportExcel(headType, os, null, consumer); - } - - /** - * 单表多数据模板导出 模板格式为 {.属性} - * - * @param filename 文件名 - * @param templatePath 模板路径 resource 目录下的路径包括模板文件名 - * 例如: excel/temp.xlsx - * 重点: 模板文件必须放置到启动类对应的 resource 目录下 - * @param data 模板需要的数据 - * @param response 响应体 - */ - public static void exportTemplate(List 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 void exportTemplate(List 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 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> 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 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 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> 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 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"; - } - -} diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestDemoController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestDemoController.java index b91c4f80c..cb1ba9680 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestDemoController.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestDemoController.java @@ -12,7 +12,7 @@ import org.dromara.common.redis.annotation.RepeatSubmit; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.core.domain.PageResult; import org.dromara.common.excel.core.ExcelResult; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.demo.domain.TestDemo; @@ -74,7 +74,9 @@ public class TestDemoController extends BaseController { @SaCheckPermission("demo:demo:import") @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R importData(@RequestPart("file") MultipartFile file) throws Exception { - ExcelResult excelResult = ExcelUtil.importExcel(file.getInputStream(), TestDemoImportVo.class, true); + ExcelResult excelResult = ExcelBuilder.read(file.getInputStream(), TestDemoImportVo.class) + .validate(true) + .doRead(); List list = MapstructUtils.convert(excelResult.getList(), TestDemo.class); testDemoService.saveBatch(list); return R.ok(excelResult.getAnalysis()); @@ -92,7 +94,7 @@ public class TestDemoController extends BaseController { // for (TestDemoVo vo : list) { // vo.setId(1234567891234567893L); // } - ExcelUtil.exportExcel(list, "测试单表", TestDemoVo.class, response); + ExcelBuilder.of(list, TestDemoVo.class).sheetName("测试单表").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java index 8458dc8ba..f727938fc 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestExcelController.java @@ -6,7 +6,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import lombok.RequiredArgsConstructor; import org.dromara.common.excel.core.ExcelResult; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.demo.domain.vo.ExportDemoVo; import org.dromara.demo.listener.ExportDemoListener; import org.dromara.demo.service.IExportExcelService; @@ -14,7 +14,6 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -48,7 +47,10 @@ public class TestExcelController { list.add(new TestObj("单列表测试1", "列表测试1", "列表测试2", "列表测试3", "列表测试4")); list.add(new TestObj("单列表测试2", "列表测试5", "列表测试6", "列表测试7", "列表测试8")); list.add(new TestObj("单列表测试3", "列表测试9", "列表测试10", "列表测试11", "列表测试12")); - ExcelUtil.exportTemplate(CollUtil.newArrayList(map, list), "单列表.xlsx", "excel/单列表.xlsx", response); + ExcelBuilder.template("excel/单列表.xlsx") + .filename("单列表.xlsx") + .data(CollUtil.newArrayList(map, list)) + .toResponse(response); } /** @@ -82,7 +84,10 @@ public class TestExcelController { multiListMap.put("data2", list2); multiListMap.put("data3", list3); multiListMap.put("data4", list4); - ExcelUtil.exportTemplateMultiList(multiListMap, "多列表.xlsx", "excel/多列表.xlsx", response); + ExcelBuilder.template("excel/多列表.xlsx") + .filename("多列表.xlsx") + .multiList(multiListMap) + .toResponse(response); } /** @@ -101,7 +106,7 @@ public class TestExcelController { * @param response / */ @GetMapping("/customExport") - public void customExport(HttpServletResponse response) throws IOException { + public void customExport(HttpServletResponse response) { exportExcelService.customExport(response); } @@ -137,7 +142,10 @@ public class TestExcelController { list.add(sheetMap2); list.add(sheetMap3); list.add(sheetMap4); - ExcelUtil.exportTemplateMultiSheet(list, "多sheet列表", "excel/多sheet列表.xlsx", response); + ExcelBuilder.template("excel/多sheet列表.xlsx") + .filename("多sheet列表") + .multiSheet(list) + .toResponse(response); } /** @@ -146,7 +154,9 @@ public class TestExcelController { @PostMapping(value = "/importWithOptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public List importWithOptions(@RequestPart("file") MultipartFile file) throws Exception { // 处理解析结果 - ExcelResult excelResult = ExcelUtil.importExcel(file.getInputStream(), ExportDemoVo.class, new ExportDemoListener()); + ExcelResult excelResult = ExcelBuilder.read(file.getInputStream(), ExportDemoVo.class) + .listener(new ExportDemoListener()) + .doRead(); return excelResult.getList(); } diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestTreeController.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestTreeController.java index 1677abce0..4e61d482c 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestTreeController.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/controller/TestTreeController.java @@ -6,7 +6,7 @@ import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; import org.dromara.common.core.validate.QueryGroup; import org.dromara.common.web.core.BaseController; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.redis.annotation.RepeatSubmit; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; @@ -55,7 +55,7 @@ public class TestTreeController extends BaseController { @GetMapping("/export") public void export(@Validated TestTreeBo bo, HttpServletResponse response) { List list = testTreeService.queryList(bo); - ExcelUtil.exportExcel(list, "测试树表", TestTreeVo.class, response); + ExcelBuilder.of(list, TestTreeVo.class).sheetName("测试树表").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java index ad2392b97..896ad0a1f 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/IExportExcelService.java @@ -2,8 +2,6 @@ package org.dromara.demo.service; import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; - /** * 导出下拉框Excel示例 * @@ -23,5 +21,5 @@ public interface IExportExcelService { * * @param response / */ - void customExport(HttpServletResponse response) throws IOException; + void customExport(HttpServletResponse response); } diff --git a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java index 7163530ed..d8adfd889 100644 --- a/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java +++ b/ruoyi-modules/ruoyi-demo/src/main/java/org/dromara/demo/service/impl/ExportExcelServiceImpl.java @@ -8,15 +8,13 @@ import lombok.Data; import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.utils.StreamUtils; -import org.dromara.common.core.utils.file.FileUtils; import org.dromara.common.excel.core.DropDownOptions; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.excel.utils.ExcelWriterWrapper; import org.dromara.demo.domain.vo.ExportDemoVo; import org.dromara.demo.service.IExportExcelService; import org.springframework.stereotype.Service; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -103,7 +101,10 @@ public class ExportExcelServiceImpl implements IExportExcelService { return everyRowData; }); - ExcelUtil.exportExcel(outList, "下拉框示例", ExportDemoVo.class, response, options); + ExcelBuilder.of(outList, ExportDemoVo.class) + .sheetName("下拉框示例") + .options(options) + .toResponse(response); } private String buildOptions(List cityDataList, Integer id) { @@ -240,12 +241,8 @@ public class ExportExcelServiceImpl implements IExportExcelService { @Override - public void customExport(HttpServletResponse response) throws IOException { - String filename = ExcelUtil.encodingFilename("自定义导出"); - FileUtils.setAttachmentResponseHeader(response, filename); - response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8"); - - ExcelUtil.exportExcel(ExportDemoVo.class, response.getOutputStream(), wrapper -> { + public void customExport(HttpServletResponse response) { + ExcelBuilder.writer(ExportDemoVo.class).sheetName("自定义导出").toResponse(response, wrapper -> { // 创建表格数据,业务中一般通过数据库查询 List excelDataList = new ArrayList<>(); for (int i = 0; i < 30; i++) { diff --git a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm index 36c54ac03..3ac280c67 100644 --- a/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm +++ b/ruoyi-modules/ruoyi-gen/src/main/resources/vm/java/controller.java.vm @@ -19,7 +19,7 @@ import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; import org.dromara.common.log.enums.BusinessType; #if($enableExport) -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; #end import ${packageName}.domain.vo.${ClassName}Vo; import ${packageName}.domain.bo.${ClassName}Bo; @@ -68,7 +68,7 @@ public class ${ClassName}Controller extends BaseController { @PostMapping("/export") public void export(${ClassName}Bo bo, HttpServletResponse response) { List<${ClassName}Vo> list = ${className}Service.queryList(bo); - ExcelUtil.exportExcel(list, "${functionName}", ${ClassName}Vo.class, response); + ExcelBuilder.of(list, ${ClassName}Vo.class).sheetName("${functionName}").toResponse(response); } #end diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLoginInfoController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLoginInfoController.java index a61e5e607..336b0bec5 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLoginInfoController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysLoginInfoController.java @@ -7,7 +7,7 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.CacheNames; import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -59,7 +59,7 @@ public class SysLoginInfoController extends BaseController { @PostMapping("/export") public void export(SysLoginInfoBo loginInfo, HttpServletResponse response) { List list = loginInfoService.selectLoginInfoList(loginInfo); - ExcelUtil.exportExcel(list, "登录日志", SysLoginInfoVo.class, response); + ExcelBuilder.of(list, SysLoginInfoVo.class).sheetName("登录日志").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java index dc8fd7fdb..8b5974bfd 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/monitor/SysOperlogController.java @@ -6,7 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -56,7 +56,7 @@ public class SysOperlogController extends BaseController { @PostMapping("/export") public void export(SysOperLogBo operLog, HttpServletResponse response) { List list = operLogService.selectOperLogList(operLog); - ExcelUtil.exportExcel(list, "操作日志", SysOperLogVo.class, response); + ExcelBuilder.of(list, SysOperLogVo.class).sheetName("操作日志").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java index 7bef26648..4be7e7abd 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysClientController.java @@ -9,7 +9,7 @@ import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -61,7 +61,7 @@ public class SysClientController extends BaseController { @PostMapping("/export") public void export(SysClientBo bo, HttpServletResponse response) { List list = sysClientService.queryList(bo); - ExcelUtil.exportExcel(list, "客户端管理", SysClientVo.class, response); + ExcelBuilder.of(list, SysClientVo.class).sheetName("客户端管理").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java index 8c6210fa7..12d2a7e09 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysConfigController.java @@ -5,7 +5,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -57,7 +57,7 @@ public class SysConfigController extends BaseController { @PostMapping("/export") public void export(SysConfigBo config, HttpServletResponse response) { List list = configService.selectConfigList(config); - ExcelUtil.exportExcel(list, "参数数据", SysConfigVo.class, response); + ExcelBuilder.of(list, SysConfigVo.class).sheetName("参数数据").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java index e316477a3..131c1e2e9 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictDataController.java @@ -6,7 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -61,7 +61,7 @@ public class SysDictDataController extends BaseController { @PostMapping("/export") public void export(SysDictDataBo dictData, HttpServletResponse response) { List list = dictDataService.selectDictDataList(dictData); - ExcelUtil.exportExcel(list, "字典数据", SysDictDataVo.class, response); + ExcelBuilder.of(list, SysDictDataVo.class).sheetName("字典数据").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java index dea026e9f..b18d0ad4d 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysDictTypeController.java @@ -6,7 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -58,7 +58,7 @@ public class SysDictTypeController extends BaseController { @PostMapping("/export") public void export(SysDictTypeBo dictType, HttpServletResponse response) { List list = dictTypeService.selectDictTypeList(dictType); - ExcelUtil.exportExcel(list, "字典类型", SysDictTypeVo.class, response); + ExcelBuilder.of(list, SysDictTypeVo.class).sheetName("字典类型").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java index 6ab322ae6..2df12437b 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysPostController.java @@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.constant.SystemConstants; import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -64,7 +64,7 @@ public class SysPostController extends BaseController { @PostMapping("/export") public void export(SysPostBo post, HttpServletResponse response) { List list = postService.selectPostList(post); - ExcelUtil.exportExcel(list, "岗位数据", SysPostVo.class, response); + ExcelBuilder.of(list, SysPostVo.class).sheetName("岗位数据").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java index 3762006d9..7885fa91e 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysRoleController.java @@ -6,7 +6,7 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -66,7 +66,7 @@ public class SysRoleController extends BaseController { @PostMapping("/export") public void export(SysRoleBo role, HttpServletResponse response) { List list = roleService.selectRoleList(role); - ExcelUtil.exportExcel(list, "角色数据", SysRoleVo.class, response); + ExcelBuilder.of(list, SysRoleVo.class).sheetName("角色数据").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java index e0c741c32..e55390194 100644 --- a/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java +++ b/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/controller/system/SysUserController.java @@ -16,7 +16,7 @@ import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.encrypt.annotation.ApiEncrypt; import org.dromara.common.excel.core.ExcelResult; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -84,7 +84,7 @@ public class SysUserController extends BaseController { @PostMapping("/export") public void export(SysUserBo user, HttpServletResponse response) { List list = userService.selectUserExportList(user); - ExcelUtil.exportExcel(list, "用户数据", SysUserExportVo.class, response); + ExcelBuilder.of(list, SysUserExportVo.class).sheetName("用户数据").toResponse(response); } /** @@ -97,7 +97,9 @@ public class SysUserController extends BaseController { @SaCheckPermission("system:user:import") @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public R importData(@RequestPart("file") MultipartFile file, boolean updateSupport) throws Exception { - ExcelResult result = ExcelUtil.importExcel(file.getInputStream(), SysUserImportVo.class, new SysUserImportListener(updateSupport)); + ExcelResult result = ExcelBuilder.read(file.getInputStream(), SysUserImportVo.class) + .listener(new SysUserImportListener(updateSupport)) + .doRead(); return R.ok(result.getAnalysis()); } @@ -108,7 +110,7 @@ public class SysUserController extends BaseController { */ @PostMapping("/importTemplate") public void importTemplate(HttpServletResponse response) { - ExcelUtil.exportExcel(new ArrayList<>(), "用户数据", SysUserImportVo.class, response); + ExcelBuilder.of(new ArrayList<>(), SysUserImportVo.class).sheetName("用户数据").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java index ad1387a32..4c04e0c3e 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/FlwCategoryController.java @@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor; import org.dromara.common.core.domain.R; import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.redis.annotation.RepeatSubmit; @@ -61,7 +61,7 @@ public class FlwCategoryController extends BaseController { @PostMapping("/export") public void export(FlowCategoryBo bo, HttpServletResponse response) { List list = flwCategoryService.queryList(bo); - ExcelUtil.exportExcel(list, "流程分类", FlowCategoryVo.class, response); + ExcelBuilder.of(list, FlowCategoryVo.class).sheetName("流程分类").toResponse(response); } /** diff --git a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java index d285d232c..3b6cb6a23 100644 --- a/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java +++ b/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/controller/TestLeaveController.java @@ -9,7 +9,7 @@ import org.dromara.common.core.domain.PageResult; import org.dromara.common.core.domain.R; import org.dromara.common.core.validate.AddGroup; import org.dromara.common.core.validate.EditGroup; -import org.dromara.common.excel.utils.ExcelUtil; +import org.dromara.common.excel.utils.ExcelBuilder; import org.dromara.common.log.annotation.Log; import org.dromara.common.log.enums.BusinessType; import org.dromara.common.mybatis.core.page.PageQuery; @@ -64,7 +64,7 @@ public class TestLeaveController extends BaseController { @PostMapping("/export") public void export(TestLeaveBo bo, HttpServletResponse response) { List list = testLeaveService.queryList(bo); - ExcelUtil.exportExcel(list, "请假", TestLeaveVo.class, response); + ExcelBuilder.of(list, TestLeaveVo.class).sheetName("请假").toResponse(response); } /**