From a40f354f33596a49e5d5d86212c1305d43daf35d Mon Sep 17 00:00:00 2001 From: smallchill Date: Mon, 30 Jun 2025 22:09:58 +0800 Subject: [PATCH] =?UTF-8?q?:tada:=20=E6=96=B0=E5=A2=9Eexcel=E5=B0=81?= =?UTF-8?q?=E8=A3=85=EF=BC=8C=E5=B0=86easyexcel=E5=88=87=E6=8D=A2=E4=B8=BA?= =?UTF-8?q?fastexcel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blade-starter-excel/pom.xml | 47 +++++ .../core/excel/listener/DataListener.java | 50 +++++ .../core/excel/listener/ImportListener.java | 71 +++++++ .../core/excel/support/ExcelException.java | 36 ++++ .../core/excel/support/ExcelImporter.java | 34 +++ .../core/excel/util/ExcelUtil.java | 199 ++++++++++++++++++ pom.xml | 20 +- 7 files changed, 451 insertions(+), 6 deletions(-) create mode 100644 blade-starter-excel/pom.xml create mode 100644 blade-starter-excel/src/main/java/org/springblade/core/excel/listener/DataListener.java create mode 100644 blade-starter-excel/src/main/java/org/springblade/core/excel/listener/ImportListener.java create mode 100644 blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelException.java create mode 100644 blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelImporter.java create mode 100644 blade-starter-excel/src/main/java/org/springblade/core/excel/util/ExcelUtil.java diff --git a/blade-starter-excel/pom.xml b/blade-starter-excel/pom.xml new file mode 100644 index 0000000..ca5d519 --- /dev/null +++ b/blade-starter-excel/pom.xml @@ -0,0 +1,47 @@ + + + 4.0.0 + + org.springblade + blade-tool + ${revision} + + + blade-starter-excel + ${project.artifactId} + jar + + + org.springblade.blade.starter.excel + + + + + org.springblade + blade-core-launch + + + cn.idev.excel + fastexcel + + + org.apache.poi + poi + + + org.apache.poi + poi-ooxml + + + org.apache.poi + poi-scratchpad + + + org.apache.poi + ooxml-schemas + + + + diff --git a/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/DataListener.java b/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/DataListener.java new file mode 100644 index 0000000..697269c --- /dev/null +++ b/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/DataListener.java @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 org.springblade.core.excel.listener; + +import cn.idev.excel.context.AnalysisContext; +import cn.idev.excel.event.AnalysisEventListener; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Excel监听器 + * + * @author Chill + */ +@Getter +@EqualsAndHashCode(callSuper = true) +public class DataListener extends AnalysisEventListener { + + /** + * 缓存的数据列表 + */ + private final List dataList = new ArrayList<>(); + + @Override + public void invoke(T data, AnalysisContext analysisContext) { + dataList.add(data); + } + + @Override + public void doAfterAllAnalysed(AnalysisContext analysisContext) { + + } + +} diff --git a/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/ImportListener.java b/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/ImportListener.java new file mode 100644 index 0000000..3eedf0d --- /dev/null +++ b/blade-starter-excel/src/main/java/org/springblade/core/excel/listener/ImportListener.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 org.springblade.core.excel.listener; + +import cn.idev.excel.context.AnalysisContext; +import cn.idev.excel.event.AnalysisEventListener; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.RequiredArgsConstructor; +import org.springblade.core.excel.support.ExcelImporter; + +import java.util.ArrayList; +import java.util.List; + +/** + * Excel监听器 + * + * @author Chill + */ +@Data +@RequiredArgsConstructor +@EqualsAndHashCode(callSuper = true) +public class ImportListener extends AnalysisEventListener { + + /** + * 默认每隔3000条存储数据库 + */ + private int batchCount = 3000; + /** + * 缓存的数据列表 + */ + private List list = new ArrayList<>(); + /** + * 数据导入类 + */ + private final ExcelImporter importer; + + @Override + public void invoke(T data, AnalysisContext analysisContext) { + list.add(data); + // 达到BATCH_COUNT,则调用importer方法入库,防止数据几万条数据在内存,容易OOM + if (list.size() >= batchCount) { + // 调用importer方法 + importer.save(list); + // 存储完成清理list + list.clear(); + } + } + + @Override + public void doAfterAllAnalysed(AnalysisContext analysisContext) { + // 调用importer方法 + importer.save(list); + // 存储完成清理list + list.clear(); + } + +} diff --git a/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelException.java b/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelException.java new file mode 100644 index 0000000..11e492c --- /dev/null +++ b/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelException.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 org.springblade.core.excel.support; + +import java.io.Serial; + +/** + * Excel异常处理类 + * + * @author Chill + */ +public class ExcelException extends RuntimeException { + @Serial + private static final long serialVersionUID = 1L; + + /** + * 默认构造函数 + * @param message 异常信息 + */ + public ExcelException(String message) { + super(message); + } +} diff --git a/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelImporter.java b/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelImporter.java new file mode 100644 index 0000000..7ae014d --- /dev/null +++ b/blade-starter-excel/src/main/java/org/springblade/core/excel/support/ExcelImporter.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 org.springblade.core.excel.support; + +import java.util.List; + +/** + * Excel统一导入接口 + * + * @author Chill + */ +public interface ExcelImporter { + + /** + * 导入数据逻辑 + * + * @param data 数据集合 + */ + void save(List data); + +} diff --git a/blade-starter-excel/src/main/java/org/springblade/core/excel/util/ExcelUtil.java b/blade-starter-excel/src/main/java/org/springblade/core/excel/util/ExcelUtil.java new file mode 100644 index 0000000..acd86bf --- /dev/null +++ b/blade-starter-excel/src/main/java/org/springblade/core/excel/util/ExcelUtil.java @@ -0,0 +1,199 @@ +/** + * Copyright (c) 2018-2099, Chill Zhuang 庄骞 (bladejava@qq.com). + *

+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.gnu.org/licenses/lgpl.html + *

+ * 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 org.springblade.core.excel.util; + + +import cn.idev.excel.FastExcel; +import cn.idev.excel.read.builder.ExcelReaderBuilder; +import cn.idev.excel.read.listener.ReadListener; +import cn.idev.excel.util.DateUtils; +import cn.idev.excel.write.builder.ExcelWriterBuilder; +import cn.idev.excel.write.handler.WriteHandler; +import jakarta.servlet.http.HttpServletResponse; +import lombok.SneakyThrows; +import org.springblade.core.excel.listener.DataListener; +import org.springblade.core.excel.listener.ImportListener; +import org.springblade.core.excel.support.ExcelException; +import org.springblade.core.excel.support.ExcelImporter; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.List; + +/** + * Excel 工具类 + * + * @author Chill + */ +public class ExcelUtil { + + /** + * 读取 Excel 的所有 sheet 数据。 + * + * @param 数据模型类型 + * @param excel Excel 文件 + * @param clazz 实体类 `Class` 对象 + * @return 读取到的数据列表 + */ + public static List read(MultipartFile excel, Class clazz) { + DataListener dataListener = new DataListener<>(); + ExcelReaderBuilder builder = getReaderBuilder(excel, dataListener, clazz); + if (builder == null) { + return null; + } + builder.doReadAll(); + return dataListener.getDataList(); + } + + /** + * 读取 Excel 指定 sheet 的数据。 + * + * @param 数据模型类型 + * @param excel Excel 文件 + * @param sheetNo sheet 序号(从 0 开始) + * @param clazz 实体类 `Class` 对象 + * @return 读取到的数据列表 + */ + public static List read(MultipartFile excel, int sheetNo, Class clazz) { + return read(excel, sheetNo, 1, clazz); + } + + /** + * 读取 Excel 指定 sheet 的数据。 + * + * @param 数据模型类型 + * @param excel Excel 文件 + * @param sheetNo sheet 序号(从 0 开始) + * @param headRowNumber 表头占用的行数 + * @param clazz 实体类 `Class` 对象 + * @return 读取到的数据列表 + */ + public static List read(MultipartFile excel, int sheetNo, int headRowNumber, Class clazz) { + DataListener dataListener = new DataListener<>(); + ExcelReaderBuilder builder = getReaderBuilder(excel, dataListener, clazz); + if (builder == null) { + return null; + } + builder.sheet(sheetNo).headRowNumber(headRowNumber).doRead(); + return dataListener.getDataList(); + } + + /** + * 读取数据并使用指定的导入器进行数据导入。 + * + * @param 数据模型类型 + * @param excel Excel 文件 + * @param importer 导入逻辑处理器 + * @param clazz 实体类 `Class` 对象 + */ + public static void save(MultipartFile excel, ExcelImporter importer, Class clazz) { + ImportListener importListener = new ImportListener<>(importer); + ExcelReaderBuilder builder = getReaderBuilder(excel, importListener, clazz); + if (builder != null) { + builder.doReadAll(); + } + } + + /** + * 导出 Excel。 + *

+ * 默认使用当前时间戳作为文件名,"导出数据" 作为 sheet 名。 + * + * @param 数据模型类型 + * @param response `HttpServletResponse` 对象 + * @param dataList 要导出的数据列表 + * @param clazz 实体类 `Class` 对象 + */ + @SneakyThrows + public static void export(HttpServletResponse response, List dataList, Class clazz) { + export(response, DateUtils.format(new Date(), DateUtils.DATE_FORMAT_14), "导出数据", dataList, clazz); + } + + /** + * 导出 Excel。 + * + * @param 数据模型类型 + * @param response `HttpServletResponse` 对象 + * @param fileName 文件名(不含扩展名) + * @param sheetName sheet 名称 + * @param dataList 要导出的数据列表 + * @param clazz 实体类 `Class` 对象 + */ + @SneakyThrows + public static void export(HttpServletResponse response, String fileName, String sheetName, List dataList, Class clazz) { + export(response, fileName, sheetName, dataList, null, clazz); + } + + /** + * 导出 Excel。 + * + * @param 数据模型类型 + * @param response `HttpServletResponse` 对象 + * @param fileName 文件名(不含扩展名) + * @param sheetName sheet 名称 + * @param dataList 要导出的数据列表 + * @param writeHandler 自定义 `WriteHandler` 处理器 + * @param clazz 实体类 `Class` 对象 + */ + @SneakyThrows + public static void export(HttpServletResponse response, String fileName, String sheetName, List dataList, WriteHandler writeHandler, Class clazz) { + response.setContentType("application/vnd.ms-excel"); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8); + response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx"); + + ExcelWriterBuilder write = FastExcel.write(response.getOutputStream(), clazz); + if (writeHandler != null) { + write.registerWriteHandler(writeHandler); + } + write.sheet(sheetName).doWrite(dataList); + } + + /** + * 获取 Excel 读取构建器 `ExcelReaderBuilder`。 + * + * @param 数据模型类型 + * @param excel Excel 文件 + * @param readListener Excel 读取监听器 + * @param clazz 实体类 `Class` 对象 + * @return `ExcelReaderBuilder` 构建器实例,若发生 `IOException` 则返回 `null` + * @throws ExcelException 如果文件为空或文件类型不正确 + */ + public static ExcelReaderBuilder getReaderBuilder(MultipartFile excel, ReadListener readListener, Class clazz) { + String filename = excel.getOriginalFilename(); + if (!StringUtils.hasText(filename)) { + throw new ExcelException("请上传文件!"); + } + if ((!StringUtils.endsWithIgnoreCase(filename, ".xls") && !StringUtils.endsWithIgnoreCase(filename, ".xlsx"))) { + throw new ExcelException("请上传正确的excel文件!"); + } + InputStream inputStream; + try { + inputStream = new BufferedInputStream(excel.getInputStream()); + return FastExcel.read(inputStream, clazz, readListener); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + +} diff --git a/pom.xml b/pom.xml index a8df3d8..39ede1a 100644 --- a/pom.xml +++ b/pom.xml @@ -71,6 +71,7 @@ 3.0.1 1.2.0 5.4.1 + 1.4 @@ -84,6 +85,7 @@ blade-starter-api-crypto blade-starter-datascope blade-starter-develop + blade-starter-excel blade-starter-loadbalancer blade-starter-log blade-starter-mybatis @@ -166,6 +168,11 @@ blade-starter-develop ${revision} + + org.springblade + blade-starter-excel + ${revision} + org.springblade blade-core-secure @@ -433,12 +440,6 @@ easy-captcha 1.6.2 - - - com.alibaba - easyexcel - 4.0.3 - cn.idev.excel @@ -465,6 +466,11 @@ poi-scratchpad ${poi.version} + + org.apache.poi + ooxml-schemas + ${poi.ooxl.version} + org.bouncycastle @@ -687,6 +693,8 @@ org.apache.maven.plugins maven-javadoc-plugin 3.11.2 + + package