diff --git a/example/pom.xml b/example/pom.xml
index 9e32182d..e9d0fc86 100644
--- a/example/pom.xml
+++ b/example/pom.xml
@@ -59,7 +59,7 @@
com.github.liaochong
myexcel
- 4.3.0.RC5
+ 4.3.3
org.apache.logging.log4j
diff --git a/pom.xml b/pom.xml
index 2776b106..1d486d69 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
com.github.liaochong
myexcel
- 4.3.2
+ 4.3.3
jar
myexcel
@@ -22,8 +22,8 @@
UTF-8
UTF-8
1.8
- 5.2.3
- 1.16.1
+ 5.2.4
+ 1.16.2
1.18.22
3.15.4.RELEASE
2.3
@@ -32,6 +32,7 @@
3.1.1.RELEASE
5.0.3
4.0.1
+ 5.0.0
5.8.2
3.9.4
1.10.0
@@ -111,6 +112,12 @@
${javax.servlet-api.version}
provided
+
+ jakarta.servlet
+ jakarta.servlet-api
+ ${jakarta.servlet-api.version}
+ provided
+
org.junit.jupiter
junit-jupiter-api
diff --git a/src/main/java/com/github/liaochong/myexcel/core/AbstractSimpleExcelBuilder.java b/src/main/java/com/github/liaochong/myexcel/core/AbstractSimpleExcelBuilder.java
index d8c7a434..ce99e189 100644
--- a/src/main/java/com/github/liaochong/myexcel/core/AbstractSimpleExcelBuilder.java
+++ b/src/main/java/com/github/liaochong/myexcel/core/AbstractSimpleExcelBuilder.java
@@ -495,10 +495,12 @@ protected List getPreElectionFields(ClassFieldContainer classFi
} else {
if (configuration.excludeParent) {
preElectionFields = classFieldContainer.getDeclaredFields().stream()
- .filter(fieldDefinition -> fieldDefinition.getField().isAnnotationPresent(ExcelColumn.class))
+ .filter(fieldDefinition ->
+ fieldDefinition.getField().isAnnotationPresent(ExcelColumn.class)
+ || fieldDefinition.getField().isAnnotationPresent(MultiColumn.class))
.collect(Collectors.toList());
} else {
- preElectionFields = classFieldContainer.getFieldsByAnnotation(ExcelColumn.class);
+ preElectionFields = classFieldContainer.getFieldsByAnnotation(ExcelColumn.class, MultiColumn.class);
}
}
if (configuration.ignoreStaticFields) {
@@ -506,9 +508,36 @@ protected List getPreElectionFields(ClassFieldContainer classFi
.filter(fieldDefinition -> !Modifier.isStatic(fieldDefinition.getField().getModifiers()))
.collect(Collectors.toList());
}
+ this.extractedMultiFieldDefinitions(preElectionFields);
return preElectionFields;
}
+ /**
+ * 提取嵌套的MultiColumn中导出列到顶层
+ *
+ * @param preElectionFields 预选字段
+ */
+ private void extractedMultiFieldDefinitions(List preElectionFields) {
+ List multiFieldDefinitions = preElectionFields.stream()
+ .filter(preElectionField -> preElectionField.getField().isAnnotationPresent(MultiColumn.class) && preElectionField.getField().getType() != List.class)
+ .flatMap(preElectionField ->
+ // 这种方式会导致缺失第一个父字段,需要补全
+ ReflectUtil.getWriteFieldDefinitionsOfExcelColumn(preElectionField.getField().getType()).stream()
+ .peek(fieldDefinition -> {
+ if (fieldDefinition.getParentFields() == null || fieldDefinition.getParentFields().isEmpty()) {
+ fieldDefinition.setParentFields(Collections.singletonList(preElectionField.getField()));
+ } else {
+ fieldDefinition.getParentFields().add(0, preElectionField.getField());
+ }
+ })
+ ).collect(Collectors.toList());
+ if (multiFieldDefinitions.isEmpty()) {
+ return;
+ }
+ preElectionFields.removeIf(preElectionField -> preElectionField.getField().isAnnotationPresent(MultiColumn.class) && preElectionField.getField().getType() != List.class);
+ preElectionFields.addAll(multiFieldDefinitions);
+ }
+
/**
* 展示字段order与标题title长度一致性自适应
*/
diff --git a/src/main/java/com/github/liaochong/myexcel/core/converter/WriteConverterContext.java b/src/main/java/com/github/liaochong/myexcel/core/converter/WriteConverterContext.java
index 271c93af..e27704bc 100644
--- a/src/main/java/com/github/liaochong/myexcel/core/converter/WriteConverterContext.java
+++ b/src/main/java/com/github/liaochong/myexcel/core/converter/WriteConverterContext.java
@@ -75,7 +75,7 @@ public static synchronized void registering(WriteConverter... writeConverters) {
}
public static Pair extends Class, Object> convert(FieldDefinition fieldDefinition, Object object, ConvertContext convertContext) {
- Object result = ReflectUtil.getFieldValue(object, fieldDefinition);
+ Object result = getFieldVal(fieldDefinition, object);
if (result == null) {
return Constants.NULL_PAIR;
}
@@ -83,6 +83,30 @@ public static Pair extends Class, Object> convert(FieldDefinition fieldDefinit
return writeConverter.convert(fieldDefinition.getField(), fieldDefinition.getField().getType(), result, convertContext);
}
+ private static Object getFieldVal(FieldDefinition fieldDefinition, Object object) {
+ Object result;
+ if (fieldDefinition.getParentFields() == null
+ || fieldDefinition.getParentFields().isEmpty()) {
+ result = ReflectUtil.getFieldValue(object, fieldDefinition);
+ } else {
+ Object prevObj;
+ try {
+ prevObj = object;
+ for (int i = 0, size = fieldDefinition.getParentFields().size(); i < size; i++) {
+ Field parentField = fieldDefinition.getParentFields().get(i);
+ prevObj = parentField.get(prevObj);
+ }
+ if (prevObj == null) {
+ return null;
+ }
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ result = ReflectUtil.getFieldValue(prevObj, fieldDefinition);
+ }
+ return result;
+ }
+
public static WriteConverter getWriteConverter(Field field, Class> fieldType, Object result, ConvertContext convertContext, List> writeConverterContainer) {
WriteConverter writeConverter = convertContext.isConvertCsv ? CSV_CONVERTER_CACHE.get(Pair.of(field, fieldType)) : EXCEL_CONVERTER_CACHE.get(Pair.of(field, fieldType));
if (writeConverter != null) {
diff --git a/src/main/java/com/github/liaochong/myexcel/utils/AttachmentV2ExportUtil.java b/src/main/java/com/github/liaochong/myexcel/utils/AttachmentV2ExportUtil.java
new file mode 100644
index 00000000..bd9f80fb
--- /dev/null
+++ b/src/main/java/com/github/liaochong/myexcel/utils/AttachmentV2ExportUtil.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.github.liaochong.myexcel.utils;
+
+import com.github.liaochong.myexcel.core.constant.Constants;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.commons.codec.CharEncoding;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.opc.PackageAccess;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionMode;
+import org.apache.poi.poifs.crypt.Encryptor;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.streaming.SXSSFWorkbook;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+
+/**
+ * 附件导出工具类
+ *
+ * @author liaochong
+ * @version 1.0
+ */
+public final class AttachmentV2ExportUtil {
+
+ /**
+ * 导出
+ *
+ * @param workbook workbook
+ * @param fileName file name,suffix is not required,and it is not recommended to carry a suffix
+ * @param response HttpServletResponse
+ */
+ public static void export(Workbook workbook, String fileName, HttpServletResponse response) {
+ try {
+ String suffix = Constants.XLSX;
+ if (workbook instanceof HSSFWorkbook) {
+ if (fileName.endsWith(suffix)) {
+ fileName = fileName.substring(0, fileName.length() - 1);
+ }
+ suffix = Constants.XLS;
+ response.setContentType("application/vnd.ms-excel");
+ } else {
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ }
+ if (!fileName.endsWith(suffix)) {
+ fileName += suffix;
+ }
+ setAttachmentConfig(fileName, response);
+ workbook.write(response.getOutputStream());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ clear(workbook);
+ }
+ }
+
+ /**
+ * 加密导出
+ *
+ * @param workbook workbook
+ * @param fileName fileName
+ * @param response response
+ * @param password password
+ */
+ public static void encryptExport(final Workbook workbook, String fileName, HttpServletResponse response, final String password) {
+ if (workbook instanceof HSSFWorkbook) {
+ throw new IllegalArgumentException("Document encryption for.xls is not supported");
+ }
+ Path path = null;
+ try {
+ String suffix = Constants.XLSX;
+ path = TempFileOperator.createTempFile("encrypt_temp", suffix);
+ workbook.write(Files.newOutputStream(path));
+
+ final POIFSFileSystem fs = new POIFSFileSystem();
+ final EncryptionInfo info = new EncryptionInfo(EncryptionMode.standard);
+ final Encryptor enc = info.getEncryptor();
+ enc.confirmPassword(password);
+
+ try (OPCPackage opc = OPCPackage.open(path.toFile(), PackageAccess.READ_WRITE);
+ OutputStream os = enc.getDataStream(fs)) {
+ opc.save(os);
+ }
+ if (!fileName.endsWith(suffix)) {
+ fileName += suffix;
+ }
+ response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ setAttachmentConfig(fileName, response);
+ fs.writeFilesystem(response.getOutputStream());
+ } catch (IOException | InvalidFormatException | GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ } finally {
+ clear(workbook);
+ TempFileOperator.deleteTempFile(path);
+ }
+ }
+
+ private static void clear(Workbook workbook) {
+ if (workbook instanceof SXSSFWorkbook) {
+ ((SXSSFWorkbook) workbook).dispose();
+ }
+ try {
+ workbook.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * 一般文件导出接口
+ *
+ * @param path 文件
+ * @param fileName 导出后文件名称
+ * @param response 响应流
+ */
+ public static void export(Path path, String fileName, HttpServletResponse response) {
+ try {
+ response.setContentType("application/octet-stream");
+ setAttachmentConfig(fileName, response);
+ response.getOutputStream().write(Files.readAllBytes(path));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ TempFileOperator.deleteTempFile(path);
+ }
+ }
+
+ private static void setAttachmentConfig(String fileName, HttpServletResponse response) throws UnsupportedEncodingException {
+ response.setCharacterEncoding(CharEncoding.UTF_8);
+ response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, CharEncoding.UTF_8).replace("+", "%20"));
+ }
+}
diff --git a/src/main/java/com/github/liaochong/myexcel/utils/ReflectUtil.java b/src/main/java/com/github/liaochong/myexcel/utils/ReflectUtil.java
index db3b679c..2812d21c 100644
--- a/src/main/java/com/github/liaochong/myexcel/utils/ReflectUtil.java
+++ b/src/main/java/com/github/liaochong/myexcel/utils/ReflectUtil.java
@@ -28,6 +28,7 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
@@ -60,6 +61,56 @@ public static ClassFieldContainer getAllFieldsOfClass(Class> clazz) {
return container;
}
+ public static List getWriteFieldDefinitionsOfExcelColumn(Class> dataType) {
+ if (dataType == Map.class) {
+ return Collections.emptyList();
+ }
+ List fieldDefinitions = new ArrayList<>();
+ getWriteFieldDefinition(dataType, fieldDefinitions, null, 0);
+ return fieldDefinitions;
+ }
+
+ private static void getWriteFieldDefinition(Class> dataType, List fieldDefinitions, List parentFields, int level) {
+ ClassFieldContainer classFieldContainer = ReflectUtil.getAllFieldsOfClass(dataType);
+ List fields = classFieldContainer.getFieldsByAnnotation(ExcelColumn.class, MultiColumn.class);
+ if (level == 0 && fields.isEmpty()) {
+ // If no field contains an ExcelColumn annotation, all fields are read in the default order
+ List allFields = classFieldContainer.getFields();
+ for (int i = 0, size = allFields.size(); i < size; i++) {
+ fieldDefinitions.add(allFields.get(i));
+ }
+ } else {
+ List topParentFields = new LinkedList<>();
+ if (parentFields != null) {
+ topParentFields.addAll(parentFields);
+ }
+ for (FieldDefinition fieldDefinition : fields) {
+ if (level == 0) {
+ parentFields = new LinkedList<>();
+ }
+ Field field = fieldDefinition.getField();
+ if (field.isAnnotationPresent(MultiColumn.class)) {
+ MultiColumn multiColumn = field.getAnnotation(MultiColumn.class);
+ List childrenParentFields = new LinkedList<>(topParentFields);
+ childrenParentFields.add(field);
+ if (ReadConverterContext.support(multiColumn.classType())) {
+ field.setAccessible(true);
+ fieldDefinition = new FieldDefinition(field);
+ fieldDefinition.setParentFields(parentFields.isEmpty() ? Collections.emptyList() : parentFields);
+ fieldDefinitions.add(fieldDefinition);
+ } else {
+ getWriteFieldDefinition(multiColumn.classType(), fieldDefinitions, childrenParentFields, level + 1);
+ }
+ } else {
+ field.setAccessible(true);
+ fieldDefinition = new FieldDefinition(field);
+ fieldDefinition.setParentFields(parentFields.isEmpty() ? Collections.emptyList() : parentFields);
+ fieldDefinitions.add(fieldDefinition);
+ }
+ }
+ }
+ }
+
public static Map getFieldDefinitionMapOfExcelColumn(Class> dataType) {
if (dataType == Map.class) {
return Collections.emptyMap();
diff --git a/src/test/java/com/github/liaochong/myexcel/core/DefaultExcelReaderTest.java b/src/test/java/com/github/liaochong/myexcel/core/DefaultExcelReaderTest.java
index de7b9809..5f7cfcb6 100644
--- a/src/test/java/com/github/liaochong/myexcel/core/DefaultExcelReaderTest.java
+++ b/src/test/java/com/github/liaochong/myexcel/core/DefaultExcelReaderTest.java
@@ -34,7 +34,7 @@ public void readXlsxPicture() throws Exception {
URL htmlToExcelEampleURL = this.getClass().getResource("/picture.xlsx");
Path path = Paths.get(htmlToExcelEampleURL.toURI());
List