From 90b3ad820eea45d021835197eac5bc3ad55b12ed Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 13 十月 2026 11:31:17 +0800
Subject: [PATCH] feat(finance): 新增财务核算模块及 ERP 统计接口
---
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 177 insertions(+), 3 deletions(-)
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
index f4f17ec..5a9b24d 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
@@ -1,7 +1,13 @@
package cn.iocoder.yudao.framework.excel.core.util;
+import cn.idev.excel.ExcelWriter;
import cn.idev.excel.FastExcelFactory;
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.idev.excel.context.AnalysisContext;
import cn.idev.excel.converters.longconverter.LongStringConverter;
+import cn.idev.excel.enums.CellExtraTypeEnum;
+import cn.idev.excel.event.AnalysisEventListener;
+import cn.idev.excel.metadata.CellExtra;
import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
import cn.iocoder.yudao.framework.excel.core.handler.ColumnWidthMatchStyleStrategy;
import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
@@ -10,12 +16,18 @@
import java.io.IOException;
import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* Excel 宸ュ叿绫�
*
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
*/
public class ExcelUtils {
@@ -33,12 +45,22 @@
public static <T> void write(HttpServletResponse response, String filename, String sheetName,
Class<T> head, List<T> data) throws IOException {
// 杈撳嚭 Excel
- FastExcelFactory.write(response.getOutputStream(), head)
+ ExcelWriter excelWriter = FastExcelFactory.write(response.getOutputStream(), head)
.autoCloseStream(false) // 涓嶈鑷姩鍏抽棴锛屼氦缁� Servlet 鑷繁澶勭悊
.registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 鍩轰簬 column 闀垮害锛岃嚜鍔ㄩ�傞厤銆傛渶澶� 255 瀹藉害
.registerWriteHandler(new SelectSheetWriteHandler(head)) // 鍩轰簬鍥哄畾 sheet 瀹炵幇涓嬫媺妗�
.registerConverter(new LongStringConverter()) // 閬垮厤 Long 绫诲瀷涓㈠け绮惧害
- .sheet(sheetName).doWrite(data);
+ .build();
+ try {
+ excelWriter.write(data, FastExcelFactory.writerSheet(sheetName).build());
+ } finally {
+ // finish() 灏� workbook 鍐欏叆鍒� response 杈撳嚭娴�
+ excelWriter.finish();
+ // close() 瑙﹀彂 dispose() 娓呯悊涓存椂鏂囦欢锛�
+ // POI SXSSFWorkbook.dispose() 鍙兘杈撳嚭 WARN "Failed to dispose sheet"锛�
+ // 杩欐槸 POI 宸茬煡闂锛堜复鏃舵枃浠舵竻鐞嗘椂 BufferedWriter 宸插叧闂級锛屼笉褰卞搷瀵煎嚭缁撴灉
+ excelWriter.close();
+ }
// 璁剧疆 header 鍜� contentType銆傚啓鍦ㄦ渶鍚庣殑鍘熷洜鏄紝閬垮厤鎶ラ敊鏃讹紝鍝嶅簲 contentType 宸茬粡琚慨鏀逛簡
response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
@@ -53,4 +75,156 @@
}
}
+ /**
+ * 璇诲彇 Excel锛屽苟鎶娿�岀旱鍚戝悎骞跺崟鍏冩牸銆嶇殑鍊煎~鍏呭埌鍚堝苟鍖哄煙鍐呯殑姣忎竴琛岋紝鍗虫妸鍚堝苟鍗曞厓鏍兼媺骞虫垚鏅�氳〃鏍笺��
+ *
+ * 渚嬪 A2:A5 鍚堝苟銆佸�间负 100 鏃讹紝璇诲彇缁撴灉涓 2~5 琛岀殑璇ュ垪鍧囦负 100銆�
+ * 鍥犱负 Excel 鍙湪鍚堝苟鍖哄煙宸︿笂瑙掍繚瀛樺�硷紝鐩存帴璇诲彇鏃跺叾浣欒涓虹┖锛屼細瀵艰嚧銆屽悓涓�寮犲崟鐨勫琛屾槑缁嗐�嶆棤娉曡瘑鍒��
+ * 娉ㄦ剰锛氬彧澶勭悊鍗曞垪鐨勭旱鍚戝悎骞讹紱琛ㄥご琛岀殑鍚堝苟銆佽法鍒楃殑妯悜鍚堝苟涓嶅鐞嗐��
+ *
+ * @param file 鏂囦欢
+ * @param head Excel head 澶�
+ * @return 鏁版嵁鍒楄〃
+ */
+ public static <T> List<T> readFlat(MultipartFile file, Class<T> head) throws IOException {
+ MergedCellFillReadListener<T> listener = new MergedCellFillReadListener<>(head);
+ // 鍙傝�� https://t.zsxq.com/zM77F 甯栧瓙锛屽鍔� try 澶勭悊锛屽吋瀹� windows 鍦烘櫙
+ try (InputStream inputStream = file.getInputStream()) {
+ FastExcelFactory.read(inputStream, head, listener)
+ .extraRead(CellExtraTypeEnum.MERGE) // 棰濆璇诲彇鍚堝苟鍗曞厓鏍间俊鎭�
+ .autoCloseStream(false) // 涓嶈鑷姩鍏抽棴锛屼氦缁� Servlet 鑷繁澶勭悊
+ .doReadAllSync();
+ }
+ listener.fillMergedCell();
+ return listener.getDataList();
+ }
+
+ /**
+ * 鍚堝苟鍗曞厓鏍煎~鍏呯殑璇诲彇鐩戝惉鍣細璇诲彇杩囩▼涓敹闆嗚〃澶淬�佹暟鎹涓庡悎骞跺尯鍩燂紝璇诲彇瀹屾垚鍚庢妸鍚堝苟鍗曞厓鏍肩殑鍊煎~鍏呭埌鍚堝苟鍖哄煙鍐呯殑姣忎竴琛�
+ */
+ private static class MergedCellFillReadListener<T> extends AnalysisEventListener<T> {
+
+ /**
+ * 琛ㄥご锛歬ey 涓� sheet 缂栧彿锛寁alue 涓恒�屽垪鍙� -> 琛ㄥご鏂囨湰銆�
+ */
+ private final Map<Integer, Map<Integer, String>> sheetHeaderMap = new HashMap<>();
+ /**
+ * 鏁版嵁琛岋細key 涓恒�宻heet 缂栧彿 - 琛屽彿銆嶏紝value 涓烘暟鎹
+ */
+ private final Map<String, T> rowDataMap = new HashMap<>();
+ /**
+ * 鍚堝苟鍖哄煙锛歬ey 涓� sheet 缂栧彿
+ */
+ private final Map<Integer, List<CellExtra>> sheetMergedCellMap = new HashMap<>();
+ /**
+ * 銆岃〃澶存枃鏈� -> 瀛楁銆嶏紝鐢ㄤ簬鎶婂悎骞跺崟鍏冩牸鐨勫�煎~鍏呭洖瀛楁
+ */
+ private final Map<String, Field> headerFieldMap = new HashMap<>();
+ /**
+ * 閲嶅鍑虹幇鐨勮〃澶存枃鏈紝鏃犳硶纭畾濉厖鍒板摢涓瓧娈碉紝蹇界暐
+ */
+ private final Set<String> ambiguousHeaderSet = new HashSet<>();
+ /**
+ * 鏁版嵁鍒楄〃锛屽嵆璇诲彇缁撴灉
+ */
+ private final List<T> dataList = new ArrayList<>();
+
+ private MergedCellFillReadListener(Class<T> head) {
+ for (Field field : head.getDeclaredFields()) {
+ ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
+ if (excelProperty == null || excelProperty.value().length == 0
+ || excelProperty.value()[0].trim().isEmpty()) {
+ continue;
+ }
+ String header = excelProperty.value()[0].trim();
+ if (headerFieldMap.containsKey(header)) {
+ ambiguousHeaderSet.add(header);
+ continue;
+ }
+ field.setAccessible(true);
+ headerFieldMap.put(header, field);
+ }
+ }
+
+ @Override
+ public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
+ sheetHeaderMap.put(context.readSheetHolder().getSheetNo(), headMap);
+ }
+
+ @Override
+ public void invoke(T data, AnalysisContext context) {
+ dataList.add(data);
+ rowDataMap.put(buildRowKey(context.readSheetHolder().getSheetNo(),
+ context.readRowHolder().getRowIndex()), data);
+ }
+
+ @Override
+ public void extra(CellExtra extra, AnalysisContext context) {
+ if (extra.getType() == CellExtraTypeEnum.MERGE) {
+ sheetMergedCellMap.computeIfAbsent(context.readSheetHolder().getSheetNo(), k -> new ArrayList<>())
+ .add(extra);
+ }
+ }
+
+ @Override
+ public void doAfterAllAnalysed(AnalysisContext context) {
+ }
+
+ /**
+ * 鎶婂悎骞跺崟鍏冩牸鐨勫�硷紝濉厖鍒板悎骞跺尯鍩熷唴鐨勬瘡涓�琛�
+ */
+ public void fillMergedCell() {
+ sheetMergedCellMap.forEach((sheetNo, mergedCellList) -> {
+ Map<Integer, String> headerMap = sheetHeaderMap.get(sheetNo);
+ if (headerMap == null) {
+ return;
+ }
+ mergedCellList.forEach(extra -> fillMergedCell(sheetNo, headerMap, extra));
+ });
+ }
+
+ private void fillMergedCell(Integer sheetNo, Map<Integer, String> headerMap, CellExtra extra) {
+ // 鍙鐞嗗崟鍒楃殑绾靛悜鍚堝苟锛涜法鍒楃殑妯悜鍚堝苟锛堝琛ㄥご鍚堝苟锛変笉澶勭悊
+ if (extra.getFirstColumnIndex() == null || !extra.getFirstColumnIndex().equals(extra.getLastColumnIndex())) {
+ return;
+ }
+ // 琛ㄥご琛岀殑鍚堝苟涓嶅鐞�
+ if (extra.getFirstRowIndex() == null || extra.getFirstRowIndex() < 1) {
+ return;
+ }
+ String header = headerMap.get(extra.getFirstColumnIndex());
+ Field field = header == null ? null : headerFieldMap.get(header);
+ if (field == null || ambiguousHeaderSet.contains(header)) {
+ return;
+ }
+ T sourceData = rowDataMap.get(buildRowKey(sheetNo, extra.getFirstRowIndex()));
+ if (sourceData == null) {
+ return;
+ }
+ try {
+ Object value = field.get(sourceData);
+ if (value == null) {
+ return;
+ }
+ for (int rowIndex = extra.getFirstRowIndex() + 1; rowIndex <= extra.getLastRowIndex(); rowIndex++) {
+ T targetData = rowDataMap.get(buildRowKey(sheetNo, rowIndex));
+ if (targetData != null) {
+ field.set(targetData, value);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ // 瀛楁宸� setAccessible锛岀悊璁轰笂涓嶄細鍙戠敓锛涘嵆浣垮彂鐢熶篃蹇界暐璇ュ悎骞跺崟鍏冩牸锛屼笉褰卞搷鍏跺畠鏁版嵁
+ }
+ }
+
+ private String buildRowKey(Integer sheetNo, Integer rowIndex) {
+ return sheetNo + "-" + rowIndex;
+ }
+
+ public List<T> getDataList() {
+ return dataList;
+ }
+
+ }
+
}
--
Gitblit v1.9.3