Excel多級數據結構導入導出工具

Excel多級數據結構導入導出工具

這是一個功能強大的Excel導入導出工具庫,專門用于處理復雜的多級嵌套數據結構。通過自定義注解配置,可以輕松實現Java對象與Excel文件之間的雙向轉換。

核心功能特性

1. 多級數據結構支持

  • 嵌套對象處理: 支持任意層級的對象嵌套(如:集團→公司→部門→團隊→員工)
  • 自動合并單元格: 上級數據自動合并對應的行數,數據清晰層次分明
  • 智能填充: 合并單元格區域自動填充相同的上級數據
  • 層級識別: 自動識別和處理不同層級的同名字段,避免數據覆蓋

2. 強大的注解系統

  • @ExcelColumn: Excel列配置注解,控制字段與Excel列的映射關系
  • @ExcelObject: 對象關系注解,定義嵌套對象和集合的處理方式
  • @ExcelConvert: 數據轉換注解,實現字段值的自定義轉換邏輯

3. 靈活的數據轉換

  • 內置轉換器: 性別、狀態、布爾值等常用數據類型轉換
  • 自定義轉換器: 支持實現自定義的雙向數據轉換邏輯
  • 日期格式化: 支持多種日期格式的導入導出
  • 數據驗證: 支持必填字段驗證和數據類型檢查

注解詳細說明

1. @ExcelColumn 注解

用于標識Java字段與Excel列的映射關系。

@ExcelColumn(name = "列名稱",           // Excel表頭顯示的列名index = 0,               // 列的位置索引(可選)width = 20,              // 列寬度required = true,         // 是否必填isKey = true,            // 是否為關鍵字段(用于數據關聯)dateFormat = "yyyy-MM-dd", // 日期格式(僅日期字段)defaultValue = "默認值"    // 默認值(可選)
)

參數說明

  • name: Excel列標題,導出時作為表頭,導入時用于匹配列
  • index: 列的順序位置,影響Excel中的列順序
  • width: Excel列寬度(字符數)
  • required: 導入時是否驗證該字段必填
  • isKey: 標識關鍵字段,用于多級數據的層級關聯
  • dateFormat: 日期字段的格式化模式
  • defaultValue: 字段為空時的默認值

2. @ExcelObject 注解

用于定義嵌套對象和集合的處理方式。

@ExcelObject(isCollection = true,           // 是否為集合類型elementType = Company.class,   // 集合元素類型order = 1                      // 處理順序
)
private List<Company> companies;

參數說明

  • isCollection: 是否為集合(List、Set等)
  • elementType: 集合中元素的具體類型
  • order: 處理順序,決定在Excel中的層級位置

多級結構示例

@Data
public class School {@ExcelColumn(name = "學校名稱", isKey = true)private String schoolName;@ExcelObject(isCollection = true, elementType = ClassInfo.class, order = 1)private List<ClassInfo> classes;  // 一級嵌套:班級列表
}@Data  
public class ClassInfo {@ExcelColumn(name = "班級名稱", isKey = true)private String className;@ExcelObject(isCollection = true, elementType = Student.class, order = 2)private List<Student> students;  // 二級嵌套:學生列表
}@Data
public class Student {@ExcelColumn(name = "學生姓名")private String studentName;@ExcelColumn(name = "年齡")private Integer age;
}

3. @ExcelConvert 注解

用于實現字段值的自定義轉換。

@ExcelColumn(name = "性別")
@ExcelConvert(converter = GenderConverter.class)
private Integer gender;  // 存儲:1-男,2-女  顯示:男/女

內置轉換器

  • GenderConverter: 性別轉換(1?男,2?女)
  • StatusConverter: 狀態轉換(0?禁用,1?啟用)
  • BooleanConverter: 布爾轉換(true?是,false?否)

使用示例

1. 單層數據結構

@Data
public class Employee {@ExcelColumn(name = "員工編號", required = true, width = 15)private String empCode;@ExcelColumn(name = "姓名", required = true, width = 20)private String name;@ExcelColumn(name = "性別", width = 10)@ExcelConvert(converter = GenderConverter.class)private Integer gender;@ExcelColumn(name = "入職日期", dateFormat = "yyyy-MM-dd", width = 15)private Date joinDate;
}

2. 多級數據結構

@Data
public class Company {@ExcelColumn(name = "公司名稱", isKey = true, width = 25)private String companyName;@ExcelColumn(name = "公司地址", width = 40)private String address;// 部門列表 - 第一層嵌套@ExcelObject(isCollection = true, elementType = Department.class, order = 1)private List<Department> departments;
}@Data
public class Department {@ExcelColumn(name = "部門名稱", isKey = true, width = 20)private String deptName;@ExcelColumn(name = "部門經理", width = 15)private String manager;// 員工列表 - 第二層嵌套@ExcelObject(isCollection = true, elementType = Employee.class, order = 2)private List<Employee> employees;
}

3. 導入導出代碼

// 導出Excel
@GetMapping("/export")
public void exportData(HttpServletResponse response) {List<Company> companies = dataService.getCompanies();ExcelUtils.exportExcel(companies, Company.class, "公司組織架構", response);
}// 導入Excel
@PostMapping("/import")
public List<Company> importData(@RequestParam("file") MultipartFile file) {return ExcelUtils.importExcel(file.getInputStream(), Company.class);
}

Excel效果展示

在這里插入圖片描述

數據導入原理

1. 表頭映射

  • 根據@ExcelColumnname屬性匹配Excel表頭
  • 支持不同層級的同名字段自動識別
  • 按層級優先級進行字段映射

2. 數據填充

  • 合并單元格區域內的空值自動填充上級數據
  • 根據isKey字段進行數據分組和層級關聯
  • 自動構建嵌套對象結構

3. 類型轉換

  • 根據@ExcelConvert注解進行數據轉換
  • 支持日期格式化和自定義轉換邏輯

API接口示例

核心方法

// 導出Excel
public static void exportExcel(List<T> dataList, Class<T> clazz, String fileName, HttpServletResponse response)// 導入Excel  
public static List<T> importExcel(InputStream inputStream, Class<T> clazz)

Controller使用示例

@RestController
public class ExcelController {// 導出數據到Excel@GetMapping("/export/{entityType}")public void exportData(@PathVariable String entityType, HttpServletResponse response) {List<?> dataList = dataService.getData(entityType);Class<?> clazz = getEntityClass(entityType);ExcelUtils.exportExcel(dataList, clazz, entityType + "數據導出", response);}// 從Excel導入數據@PostMapping("/import/{entityType}")public List<?> importData(@PathVariable String entityType,@RequestParam("file") MultipartFile file) {Class<?> clazz = getEntityClass(entityType);return ExcelUtils.importExcel(file.getInputStream(), clazz);}
}

自定義轉換器詳解

轉換器接口

public interface ExcelConverter<T, E> {// 導出時:將Java對象字段值轉換為Excel顯示值E convertToExcel(T value, String[] params);// 導入時:將Excel單元格值轉換為Java對象字段值  T convertFromExcel(E value, String[] params);
}

內置轉換器實現

1. StatusConverter(狀態轉換器)
public class StatusConverter implements ExcelConverter<Integer, String> {@Overridepublic String convertToExcel(Integer value, String[] params) {if (value == null) return "";return value == 1 ? "啟用" : "禁用";}@Override  public Integer convertFromExcel(String value, String[] params) {if (StringUtils.isBlank(value)) return null;return "啟用".equals(value) ? 1 : 0;}
}
2. GenderConverter(性別轉換器)
public class GenderConverter implements ExcelConverter<Integer, String> {@Overridepublic String convertToExcel(Integer value, String[] params) {if (value == null) return "";return value == 1 ? "男" : "女";}@Overridepublic Integer convertFromExcel(String value, String[] params) {if (StringUtils.isBlank(value)) return null;return "男".equals(value) ? 1 : 2;}
}

自定義轉換器示例

等級轉換器
public class LevelConverter implements ExcelConverter<Integer, String> {@Overridepublic String convertToExcel(Integer value, String[] params) {if (value == null) return "";switch (value) {case 1: return "初級";case 2: return "中級"; case 3: return "高級";case 4: return "專家";default: return "未知";}}@Overridepublic Integer convertFromExcel(String value, String[] params) {if (StringUtils.isBlank(value)) return null;switch (value) {case "初級": return 1;case "中級": return 2;case "高級": return 3;case "專家": return 4;default: return 0;}}
}
帶參數的轉換器
public class ScoreConverter implements ExcelConverter<Double, String> {@Overridepublic String convertToExcel(Double value, String[] params) {if (value == null) return "";// params[0] 可以傳遞精度參數int precision = params.length > 0 ? Integer.parseInt(params[0]) : 2;String format = "%." + precision + "f";if (value >= 90) return String.format(format + " (優秀)", value);if (value >= 80) return String.format(format + " (良好)", value);  if (value >= 70) return String.format(format + " (中等)", value);if (value >= 60) return String.format(format + " (及格)", value);return String.format(format + " (不及格)", value);}@Overridepublic Double convertFromExcel(String value, String[] params) {if (StringUtils.isBlank(value)) return null;// 提取數字部分 "85.5 (良好)" -> 85.5String numStr = value.replaceAll("[^\\d.]", "");return Double.parseDouble(numStr);}
}// 使用時傳遞參數
@ExcelColumn(name = "考試成績")
@ExcelConvert(converter = ScoreConverter.class, params = {"1"}) // 1位小數
private Double score;

高級特性

1. 同名字段處理

當不同層級存在同名字段時,系統會自動按層級優先級進行映射:

// 學校實體
public class School {@ExcelColumn(name = "地址")  // L0_addressprivate String address;
}// 學生實體  
public class Student {@ExcelColumn(name = "地址")  // L2_address  private String address;
}

系統會為每個字段生成唯一標識(如 L0_addressL2_address),避免數據覆蓋。

2. 智能數據填充

對于合并單元格,系統會智能填充空值:

// 導入時,如果某行的學校名稱為空,會自動填充同一合并區域內的有效值
Row1: [學校A] [班級1] [學生1]  
Row2: [    ] [     ] [學生2]  // 空值會被填充為"學校A"和"班級1"
Row3: [    ] [     ] [學生3]

3. 數據驗證與錯誤處理

@ExcelColumn(name = "年齡", required = true)
private Integer age;// 導入時會驗證:
// 1. 必填字段不能為空
// 2. 數據類型必須匹配  
// 3. 轉換器處理異常時記錄警告但不中斷處理

最佳實踐

1. 實體類設計建議

@Data
public class OptimalEntity {// 1. 關鍵字段使用 isKey = true@ExcelColumn(name = "編號", isKey = true, required = true)private String code;// 2. 合理設置列寬@ExcelColumn(name = "詳細描述", width = 50)  private String description;// 3. 日期字段指定格式@ExcelColumn(name = "創建時間", dateFormat = "yyyy-MM-dd HH:mm:ss")private Date createTime;// 4. 使用轉換器處理枚舉或狀態@ExcelColumn(name = "狀態")@ExcelConvert(converter = StatusConverter.class)private Integer status;// 5. 嵌套集合按order排序@ExcelObject(isCollection = true, elementType = SubEntity.class, order = 1)private List<SubEntity> children;
}

2. 性能優化建議

  • 大數據量: 對于超過10萬行的數據,建議分批處理
  • 內存控制: 導入時設置合適的JVM內存參數
  • 字段簡化: 非必要字段不加@ExcelColumn注解

運行項目

環境要求

  • Java: 17+
  • Maven: 3.6+
  • Spring Boot: 3.x

技術規范

支持的數據類型

  • ? 基礎類型: String, Integer, Long, Double, Boolean
  • ? 日期類型: Date, LocalDate, LocalDateTime
  • ? 集合類型: List, Set(配合@ExcelObject使用)
  • ? 自定義對象: 支持任意嵌套深度

Excel格式要求

  • 文件格式: .xlsx (Excel 2007+)
  • 表頭位置: 第一行
  • 數據位置: 從第二行開始
  • 編碼: UTF-8(支持中文)
  • 單元格類型: 自動識別文本、數字、日期

限制說明

  • 單個Excel文件建議不超過 100MB
  • 單次導入數據量建議不超過 10萬行
  • 嵌套層級建議不超過 5層
  • 列數量建議不超過 100列

依賴項

<!-- 核心Excel處理 -->
<dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>5.3.0</version>
</dependency><!-- Spring Boot Web -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>3.5.4</version>
</dependency><!-- 工具類庫 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.14.0</version>
</dependency><!-- 代碼簡化 -->
<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><scope>provided</scope>
</dependency><!-- JSON處理 -->
<dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId>
</dependency>

🎯 總結

這個Excel多級數據結構導入導出工具提供了:

? 強大的注解系統 - 簡單配置即可實現復雜功能
? 多級數據支持 - 處理任意層級的嵌套結構
? 智能數據處理 - 自動合并單元格和數據填充
? 靈活的轉換機制 - 支持自定義數據轉換邏輯
? 完善的錯誤處理 - 友好的異常處理和日志記錄

通過合理使用 @ExcelColumn@ExcelObject@ExcelConvert 注解,您可以輕松實現復雜業務數據的Excel導入導出功能。

源碼

package org.whh.excel.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Excel列注解* 用于標識實體類字段對應的Excel列* * @author whh*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumn {/*** 列名稱*/String name() default "";/*** 列索引(從0開始),如果不指定則按字段聲明順序*/int index() default -1;/*** 列寬度*/int width() default 20;/*** 是否必填*/boolean required() default false;/*** 日期格式(當字段為日期類型時使用)*/String dateFormat() default "yyyy-MM-dd";/*** 數字格式(當字段為數字類型時使用)*/String numberFormat() default "";/*** 默認值*/String defaultValue() default "";/*** 說明*/String description() default "";/*** 是否為關鍵字段,用于多級數據導入時的分組標識* 當多行數據的所有關鍵字段值都相同時,這些行會被歸為同一個對象* * 使用示例:* - 如果只需要按學校編碼分組:在schoolCode字段上設置 isKey = true* - 如果需要按姓名+年齡+性別組合分組:在name、age、gender三個字段上都設置 isKey = true*/boolean isKey() default false;
}
package org.whh.excel.annotation;import org.whh.excel.converter.ExcelConverter;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Excel字段轉換注解* 用于標識字段需要進行值轉換,比如數字轉文字等* * @author whh*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelConvert {/*** 轉換器類*/Class<? extends ExcelConverter<?, ?>> converter();/*** 轉換參數(可選)*/String[] params() default {};/*** 說明*/String description() default "";
}
package org.whh.excel.annotation;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Excel對象注解* 用于標識字段是一個對象或對象集合,需要按多級結構處理* * @author whh*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelObject {/*** 對象類型*/Class<?> type() default Object.class;/*** 是否是集合類型*/boolean isCollection() default false;/*** 集合元素類型(當isCollection為true時使用)*/Class<?> elementType() default Object.class;/*** 層級順序(數字越小層級越高)*/int order() default 0;/*** 說明*/String description() default "";
}
package org.whh.excel.converter;/*** Excel字段轉換器接口* 用于實現字段值的雙向轉換* * @param <T> 實體類字段類型* @param <E> Excel中顯示的類型* @author whh*/
public interface ExcelConverter<T, E> {/*** 導出時的轉換:將實體類字段值轉換為Excel中顯示的值* * @param value 實體類字段值* @param params 轉換參數* @return Excel中顯示的值*/E convertToExcel(T value, String[] params);/*** 導入時的轉換:將Excel中的值轉換為實體類字段值* * @param value Excel中的值* @param params 轉換參數* @return 實體類字段值*/T convertFromExcel(E value, String[] params);
}
package org.whh.excel.util;import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.whh.excel.annotation.ExcelColumn;
import org.whh.excel.annotation.ExcelConvert;
import org.whh.excel.annotation.ExcelObject;
import org.whh.excel.converter.ExcelConverter;import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;/*** 通用Excel導入導出工具類* 基于注解自動處理多級數據結構的導入導出* 支持@ExcelColumn、@ExcelObject、@ExcelConvert注解** @author whh*/
@Slf4j
public class ExcelUtils {/*** 導出數據到Excel(通用方法)*/public static <T> void exportExcel(List<T> data, Class<T> clazz, String fileName, HttpServletResponse response) {try (Workbook workbook = new XSSFWorkbook()) {Sheet sheet = workbook.createSheet();// 解析實體類結構List<FieldInfo> allFields = parseEntityStructure(clazz);// 創建表頭createHeader(sheet, allFields);// 填充數據int currentRow = 1;for (T item : data) {currentRow = fillMultiLevelData(sheet, item, allFields, currentRow);}// 設置響應頭response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));workbook.write(response.getOutputStream());log.info("Excel導出成功");} catch (Exception e) {log.error("Excel導出失敗", e);throw new RuntimeException("Excel導出失敗", e);}}/*** 導入Excel數據(通用方法)*/public static <T> List<T> importExcel(InputStream inputStream, Class<T> clazz) {List<T> result = new ArrayList<>();try (Workbook workbook = WorkbookFactory.create(inputStream)) {Sheet sheet = workbook.getSheetAt(0);// 解析實體類結構List<FieldInfo> allFields = parseEntityStructure(clazz);// 讀取數據并構建對象result = parseMultiLevelData(sheet, clazz, allFields);log.info("Excel導入成功,共導入 {} 條記錄", result.size());} catch (Exception e) {log.error("Excel導入失敗", e);throw new RuntimeException("Excel導入失敗", e);}return result;}/*** 解析實體類結構,獲取所有層級的字段信息*/private static <T> List<FieldInfo> parseEntityStructure(Class<T> clazz) {List<FieldInfo> allFields = new ArrayList<>();collectAllFields(clazz, allFields, 0);// 按層級和索引排序allFields.sort((a, b) -> {if (a.level != b.level) {return Integer.compare(a.level, b.level);}return Integer.compare(a.columnIndex, b.columnIndex);});// 重新分配列索引int currentIndex = 0;for (FieldInfo fieldInfo : allFields) {if (!fieldInfo.isObjectField) {fieldInfo.columnIndex = currentIndex++;}}return allFields;}/*** 遞歸收集所有字段信息*/private static void collectAllFields(Class<?> clazz, List<FieldInfo> allFields, int level) {Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {field.setAccessible(true);ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);ExcelObject excelObject = field.getAnnotation(ExcelObject.class);if (excelColumn != null || excelObject != null) {FieldInfo fieldInfo = new FieldInfo();fieldInfo.field = field;fieldInfo.excelColumn = excelColumn;fieldInfo.excelObject = excelObject;fieldInfo.excelConvert = field.getAnnotation(ExcelConvert.class);fieldInfo.level = level;fieldInfo.isObjectField = (excelObject != null);fieldInfo.isCollectionField = (excelObject != null && excelObject.isCollection());if (excelColumn != null) {fieldInfo.columnIndex = excelColumn.index() != -1 ? excelColumn.index() : Integer.MAX_VALUE;}// 初始化轉換器if (fieldInfo.excelConvert != null) {try {@SuppressWarnings("unchecked")ExcelConverter<Object, Object> converter = (ExcelConverter<Object, Object>)fieldInfo.excelConvert.converter().getDeclaredConstructor().newInstance();fieldInfo.converter = converter;} catch (Exception e) {log.warn("初始化轉換器失敗: {}", fieldInfo.excelConvert.converter().getName());}}allFields.add(fieldInfo);// 如果是對象類型,遞歸收集子字段if (fieldInfo.isObjectField) {Class<?> actualType = getActualType(field);if (actualType != Object.class) {collectAllFields(actualType, allFields, level + 1);}}}}}/*** 獲取字段的實際類型*/private static Class<?> getActualType(Field field) {ExcelObject excelObject = field.getAnnotation(ExcelObject.class);if (excelObject != null) {if (excelObject.isCollection() && excelObject.elementType() != Object.class) {return excelObject.elementType();} else if (!excelObject.isCollection() && excelObject.type() != Object.class) {return excelObject.type();}}Type genericType = field.getGenericType();if (genericType instanceof ParameterizedType) {ParameterizedType paramType = (ParameterizedType) genericType;Type[] actualTypeArguments = paramType.getActualTypeArguments();if (actualTypeArguments.length > 0) {return (Class<?>) actualTypeArguments[0];}}return field.getType();}/*** 創建表頭*/private static void createHeader(Sheet sheet, List<FieldInfo> allFields) {Row headerRow = sheet.createRow(0);for (FieldInfo fieldInfo : allFields) {if (!fieldInfo.isObjectField) {Cell cell = headerRow.createCell(fieldInfo.columnIndex);String headerName = "";if (fieldInfo.excelColumn != null && StringUtils.isNotBlank(fieldInfo.excelColumn.name())) {headerName = fieldInfo.excelColumn.name();} else {headerName = fieldInfo.field.getName();}cell.setCellValue(headerName);// 設置列寬if (fieldInfo.excelColumn != null && fieldInfo.excelColumn.width() > 0) {sheet.setColumnWidth(fieldInfo.columnIndex, fieldInfo.excelColumn.width() * 256);} else {sheet.setColumnWidth(fieldInfo.columnIndex, 20 * 256);}}}}/*** 填充多級數據*/private static int fillMultiLevelData(Sheet sheet, Object rootItem, List<FieldInfo> allFields, int startRow) {try {// 展開為平面數據行List<Map<String, Object>> dataRows = expandToFlatRows(rootItem, allFields);// 填充每行數據for (int i = 0; i < dataRows.size(); i++) {Row row = sheet.createRow(startRow + i);Map<String, Object> rowData = dataRows.get(i);for (FieldInfo fieldInfo : allFields) {if (!fieldInfo.isObjectField) {Cell cell = row.createCell(fieldInfo.columnIndex);// 使用層級特定的唯一鍵獲取值String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();Object value = rowData.get(uniqueKey);String cellValue = convertToExcelValue(value, fieldInfo);cell.setCellValue(cellValue);}}}// 創建合并單元格if (dataRows.size() > 1) {createMergeRegions(sheet, rootItem, allFields, startRow, dataRows.size());}return startRow + dataRows.size();} catch (Exception e) {log.error("填充多級數據失敗", e);return startRow + 1;}}/*** 展開對象為平面數據行*/private static List<Map<String, Object>> expandToFlatRows(Object item, List<FieldInfo> allFields) {List<Map<String, Object>> result = new ArrayList<>();try {expandObjectToRows(item, new HashMap<>(), allFields, result);} catch (Exception e) {log.warn("展開對象失敗", e);}return result.isEmpty() ? Arrays.asList(new HashMap<>()) : result;}/*** 遞歸展開對象到行數據*/private static void expandObjectToRows(Object item, Map<String, Object> parentData,List<FieldInfo> allFields, List<Map<String, Object>> result) throws Exception {Map<String, Object> currentData = new HashMap<>(parentData);List<Object> childItems = new ArrayList<>();// 收集當前層級的字段值for (FieldInfo fieldInfo : allFields) {if (fieldInfo.field.getDeclaringClass().isAssignableFrom(item.getClass())) {Object value = fieldInfo.field.get(item);if (fieldInfo.isCollectionField && value instanceof Collection) {childItems.addAll((Collection<?>) value);} else if (!fieldInfo.isObjectField) {// 使用層級+字段名作為唯一鍵,避免不同層級的同名字段相互覆蓋String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();currentData.put(uniqueKey, value);  // 使用唯一鍵存儲,避免覆蓋}}}// 如果有子對象,遞歸處理if (!childItems.isEmpty()) {for (Object childItem : childItems) {expandObjectToRows(childItem, currentData, allFields, result);}} else {result.add(currentData);}}/*** 創建合并單元格(支持任意多級數據結構)*/private static void createMergeRegions(Sheet sheet, Object rootItem, List<FieldInfo> allFields,int startRow, int totalRows) {try {// 構建層級結構樹HierarchyNode rootNode = buildHierarchyTree(rootItem, allFields, startRow);// 遞歸創建所有層級的合并區域createMergeRegionsRecursively(sheet, rootNode, allFields);} catch (Exception e) {log.warn("創建合并單元格失敗", e);}}/*** 構建層級結構樹(支持任意層級)*/private static HierarchyNode buildHierarchyTree(Object rootItem, List<FieldInfo> allFields, int startRow) {HierarchyNode rootNode = new HierarchyNode();rootNode.item = rootItem;rootNode.level = 0;rootNode.startRow = startRow;// 遞歸構建層級樹buildHierarchyTreeRecursively(rootNode, allFields);return rootNode;}/*** 遞歸構建層級結構樹*/private static void buildHierarchyTreeRecursively(HierarchyNode node, List<FieldInfo> allFields) {try {List<Object> childItems = new ArrayList<>();// 找到當前對象的子對象集合for (FieldInfo fieldInfo : allFields) {if (fieldInfo.field.getDeclaringClass().isAssignableFrom(node.item.getClass())&& fieldInfo.isCollectionField) {Object value = fieldInfo.field.get(node.item);if (value instanceof Collection) {childItems.addAll((Collection<?>) value);}break; // 每個對象只處理一個子集合}}// 為每個子對象創建節點int currentRow = node.startRow;for (Object childItem : childItems) {HierarchyNode childNode = new HierarchyNode();childNode.item = childItem;childNode.level = node.level + 1;childNode.startRow = currentRow;childNode.parent = node;node.children.add(childNode);// 遞歸構建子節點buildHierarchyTreeRecursively(childNode, allFields);// 計算當前子節點占用的行數childNode.rowCount = calculateNodeRowCount(childNode);currentRow += childNode.rowCount;}// 如果沒有子節點,則為葉子節點,占用1行if (childItems.isEmpty()) {node.rowCount = 1;} else {// 有子節點時,行數為所有子節點行數之和node.rowCount = node.children.stream().mapToInt(child -> child.rowCount).sum();}} catch (Exception e) {log.warn("構建層級樹失敗", e);node.rowCount = 1;}}/*** 計算節點占用的行數*/private static int calculateNodeRowCount(HierarchyNode node) {if (node.children.isEmpty()) {return 1;}return node.children.stream().mapToInt(child -> child.rowCount).sum();}/*** 遞歸創建所有層級的合并區域*/private static void createMergeRegionsRecursively(Sheet sheet, HierarchyNode node, List<FieldInfo> allFields) {try {// 為當前層級的字段創建合并區域if (node.rowCount > 1) {List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> !f.isObjectField && f.level == node.level).toList();for (FieldInfo fieldInfo : currentLevelFields) {CellRangeAddress mergeRegion = new CellRangeAddress(node.startRow, node.startRow + node.rowCount - 1,fieldInfo.columnIndex, fieldInfo.columnIndex);sheet.addMergedRegion(mergeRegion);// 設置居中樣式setCellStyle(sheet, node.startRow, fieldInfo.columnIndex);}}// 遞歸處理子節點for (HierarchyNode child : node.children) {createMergeRegionsRecursively(sheet, child, allFields);}} catch (Exception e) {log.warn("遞歸創建合并區域失敗", e);}}/*** 層級節點類(支持任意層級的數據結構)*/private static class HierarchyNode {Object item;              // 當前對象int level;               // 層級深度 (0=根層級)int startRow;            // 起始行號int rowCount;            // 占用行數HierarchyNode parent;    // 父節點List<HierarchyNode> children = new ArrayList<>();  // 子節點列表}/*** 設置單元格樣式*/private static void setCellStyle(Sheet sheet, int row, int col) {try {Row sheetRow = sheet.getRow(row);if (sheetRow != null) {Cell cell = sheetRow.getCell(col);if (cell != null) {CellStyle style = sheet.getWorkbook().createCellStyle();style.setAlignment(HorizontalAlignment.CENTER);style.setVerticalAlignment(VerticalAlignment.CENTER);cell.setCellStyle(style);}}} catch (Exception e) {log.warn("設置單元格樣式失敗", e);}}/*** 轉換值為Excel顯示格式*/private static String convertToExcelValue(Object value, FieldInfo fieldInfo) {if (value == null) {return fieldInfo.excelColumn != null ? fieldInfo.excelColumn.defaultValue() : "";}// 使用轉換器if (fieldInfo.converter != null) {try {Object converted = fieldInfo.converter.convertToExcel(value, fieldInfo.excelConvert.params());return String.valueOf(converted);} catch (Exception e) {log.warn("字段轉換失敗: {}", fieldInfo.field.getName());}}// 日期格式化if (value instanceof Date && fieldInfo.excelColumn != null &&StringUtils.isNotBlank(fieldInfo.excelColumn.dateFormat())) {SimpleDateFormat sdf = new SimpleDateFormat(fieldInfo.excelColumn.dateFormat());return sdf.format((Date) value);}return String.valueOf(value);}/*** 解析Excel數據為對象列表*/private static <T> List<T> parseMultiLevelData(Sheet sheet, Class<T> clazz, List<FieldInfo> allFields) {List<T> result = new ArrayList<>();try {if (sheet.getPhysicalNumberOfRows() <= 1) {log.warn("Excel文件沒有數據行");return result;}// 讀取表頭Row headerRow = sheet.getRow(0);Map<String, Integer> headerMap = parseHeader(headerRow, allFields);// 讀取所有數據行List<Map<String, Object>> rowDataList = readAllRows(sheet, headerMap, allFields);if (rowDataList.isEmpty()) {log.warn("沒有讀取到任何數據");return result;}// 構建多級對象結構result = buildMultiLevelObjects(rowDataList, clazz, allFields);} catch (Exception e) {log.error("解析Excel數據失敗", e);throw new RuntimeException("解析Excel數據失敗", e);}return result;}/*** 解析Excel表頭*/private static Map<String, Integer> parseHeader(Row headerRow, List<FieldInfo> allFields) {Map<String, Integer> headerMap = new HashMap<>();if (headerRow != null) {// 按層級排序字段,確保低層級的字段優先映射List<FieldInfo> sortedFields = allFields.stream().filter(f -> !f.isObjectField && f.excelColumn != null).sorted((a, b) -> Integer.compare(a.level, b.level)).toList();// 記錄每個列名已經被哪些層級使用過Map<String, Set<Integer>> usedLevels = new HashMap<>();for (int i = 0; i < headerRow.getPhysicalNumberOfCells(); i++) {Cell cell = headerRow.getCell(i);if (cell != null) {String headerName = cell.getStringCellValue();// 按層級順序找到第一個未映射的匹配字段for (FieldInfo fieldInfo : sortedFields) {if (headerName.equals(fieldInfo.excelColumn.name())) {String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();// 檢查這個字段是否已經被映射過if (!headerMap.containsKey(uniqueKey)) {// 記錄此列名在此層級的使用usedLevels.computeIfAbsent(headerName, k -> new HashSet<>()).add(fieldInfo.level);headerMap.put(uniqueKey, i);break;}}}}}}log.debug("解析表頭完成,映射關系: {}", headerMap);return headerMap;}/*** 讀取所有數據行*/private static List<Map<String, Object>> readAllRows(Sheet sheet, Map<String, Integer> headerMap, List<FieldInfo> allFields) {List<Map<String, Object>> rowDataList = new ArrayList<>();for (int i = 1; i <= sheet.getLastRowNum(); i++) {Row row = sheet.getRow(i);if (row == null) continue;Map<String, Object> rowData = new HashMap<>();boolean hasData = false;// 讀取每個字段的值for (FieldInfo fieldInfo : allFields) {if (!fieldInfo.isObjectField && fieldInfo.excelColumn != null) {String fieldName = fieldInfo.field.getName();// 使用層級特定的唯一鍵查找列索引String uniqueKey = "L" + fieldInfo.level + "_" + fieldName;Integer colIndex = headerMap.get(uniqueKey);if (colIndex != null) {Cell cell = row.getCell(colIndex);Object value = getCellValue(cell, fieldInfo);// 使用層級特定的唯一鍵存儲,避免同名字段覆蓋rowData.put(uniqueKey, value);if (value != null && !value.toString().trim().isEmpty()) {hasData = true;}}}}if (hasData) {rowData.put("_rowIndex", i);rowDataList.add(rowData);}}// 填補合并單元格的空值問題fillMergedCellValues(rowDataList, allFields);log.debug("讀取到 {} 行數據", rowDataList.size());return rowDataList;}/*** 填補合并單元格的空值問題* 嚴格按層級和子對象組隔離,只在同層級同對象組內填補空值*/private static void fillMergedCellValues(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields) {if (rowDataList.isEmpty()) return;// 按層級分組字段Map<Integer, List<String>> levelFields = new HashMap<>();for (FieldInfo fieldInfo : allFields) {if (!fieldInfo.isObjectField && fieldInfo.excelColumn != null) {String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();levelFields.computeIfAbsent(fieldInfo.level, k -> new ArrayList<>()).add(uniqueKey);}}// 找到最大層級(葉子節點層級)int maxLevel = levelFields.keySet().stream().mapToInt(Integer::intValue).max().orElse(0);// 按層級逐級處理,嚴格隔離for (int currentLevel = 0; currentLevel < maxLevel; currentLevel++) {List<String> currentLevelFieldKeys = levelFields.get(currentLevel);if (currentLevelFieldKeys == null || currentLevelFieldKeys.isEmpty()) continue;// 獲取當前層級的關鍵字段List<String> currentLevelKeyFields = getKeyFieldNames(allFields, currentLevel);// 按當前層級的對象分組Map<String, List<Map<String, Object>>> currentLevelGroups = new LinkedHashMap<>();for (Map<String, Object> rowData : rowDataList) {String levelKey = buildObjectKey(rowData, currentLevelKeyFields, currentLevel);currentLevelGroups.computeIfAbsent(levelKey, k -> new ArrayList<>()).add(rowData);}// 為當前層級的每個對象組分別填補空值for (Map.Entry<String, List<Map<String, Object>>> levelGroup : currentLevelGroups.entrySet()) {String levelKey = levelGroup.getKey();List<Map<String, Object>> levelRowData = levelGroup.getValue();for (String fieldKey : currentLevelFieldKeys) {// 檢查當前層級對象組內是否有空值需要填補boolean hasEmptyValue = false;Object masterValue = null;// 先找到第一個非空值作為主值for (Map<String, Object> rowData : levelRowData) {Object currentValue = rowData.get(fieldKey);if (currentValue != null && !currentValue.toString().trim().isEmpty()) {if (masterValue == null) {masterValue = currentValue;}} else {hasEmptyValue = true;}}// 只有同時滿足"有主值"和"有空值"的條件才進行填充if (masterValue != null && hasEmptyValue) {int filledCount = 0;for (Map<String, Object> rowData : levelRowData) {Object currentValue = rowData.get(fieldKey);if (currentValue == null || currentValue.toString().trim().isEmpty()) {rowData.put(fieldKey, masterValue);filledCount++;}}}}}}}/*** 獲取單元格值,支持合并單元格*/private static Object getCellValue(Cell cell, FieldInfo fieldInfo) {if (cell == null) return null;try {// 檢查是否為合并單元格Sheet sheet = cell.getSheet();int rowIndex = cell.getRowIndex();int colIndex = cell.getColumnIndex();// 查找合并區域for (CellRangeAddress range : sheet.getMergedRegions()) {if (range.isInRange(rowIndex, colIndex)) {// 如果是合并單元格,獲取合并區域的第一個單元格的值Row firstRow = sheet.getRow(range.getFirstRow());if (firstRow != null) {Cell firstCell = firstRow.getCell(range.getFirstColumn());if (firstCell != null && firstCell != cell) {return getCellValue(firstCell, fieldInfo);}}}}// 普通單元格處理switch (cell.getCellType()) {case STRING:String stringValue = cell.getStringCellValue();return convertCellValue(stringValue, fieldInfo);case NUMERIC:double numericValue = cell.getNumericCellValue();// 如果字段類型是Date,嘗試轉換為日期if (fieldInfo.field.getType() == Date.class) {return cell.getDateCellValue();} else if (fieldInfo.field.getType() == Integer.class || fieldInfo.field.getType() == int.class) {return (int) numericValue;}return numericValue;case BOOLEAN:return cell.getBooleanCellValue();default:return null;}} catch (Exception e) {log.warn("讀取單元格值失敗: {}", e.getMessage());return null;}}/*** 轉換單元格值*/private static Object convertCellValue(String value, FieldInfo fieldInfo) {if (value == null || value.trim().isEmpty()) return null;Class<?> fieldType = fieldInfo.field.getType();try {// 如果有自定義轉換器,先嘗試轉換if (fieldInfo.converter != null) {return fieldInfo.converter.convertFromExcel(value, new String[0]);}// 根據字段類型轉換if (fieldType == String.class) {return value;} else if (fieldType == Integer.class || fieldType == int.class) {return Integer.parseInt(value);} else if (fieldType == Long.class || fieldType == long.class) {return Long.parseLong(value);} else if (fieldType == Double.class || fieldType == double.class) {return Double.parseDouble(value);} else if (fieldType == Boolean.class || fieldType == boolean.class) {return Boolean.parseBoolean(value);} else if (fieldType == Date.class) {// 嘗試解析日期SimpleDateFormat[] formats = {new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"),new SimpleDateFormat("yyyy-MM-dd"),new SimpleDateFormat("yyyy/MM/dd")};for (SimpleDateFormat format : formats) {try {return format.parse(value);} catch (Exception ignored) {}}}return value;} catch (Exception e) {log.warn("轉換單元格值失敗: {} -> {}", value, fieldType.getSimpleName());return value;}}/*** 構建多級對象結構*/private static <T> List<T> buildMultiLevelObjects(List<Map<String, Object>> rowDataList, Class<T> clazz, List<FieldInfo> allFields) {List<T> result = new ArrayList<>();try {// 找到最大層級深度int maxLevel = allFields.stream().filter(f -> !f.isObjectField).mapToInt(f -> f.level).max().orElse(0);log.debug("最大層級深度: {}", maxLevel);// 按層級分組數據Map<String, List<Map<String, Object>>> groupedData = groupDataByHierarchy(rowDataList, allFields);// 構建根對象for (Map.Entry<String, List<Map<String, Object>>> entry : groupedData.entrySet()) {T rootObject = buildSingleObject(entry.getValue(), clazz, allFields, 0);if (rootObject != null) {result.add(rootObject);}}} catch (Exception e) {log.error("構建多級對象失敗", e);}return result;}/*** 按層級分組數據(支持任意級別)*/private static Map<String, List<Map<String, Object>>> groupDataByHierarchy(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields) {Map<String, List<Map<String, Object>>> groupedData = new LinkedHashMap<>();// 獲取根層級的關鍵字段組合List<String> rootKeyFieldNames = getKeyFieldNames(allFields, 0);for (Map<String, Object> rowData : rowDataList) {// 構建根對象的唯一標識String key = buildObjectKey(rowData, rootKeyFieldNames, 0);groupedData.computeIfAbsent(key, k -> new ArrayList<>()).add(rowData);}return groupedData;}/*** 獲取指定層級的關鍵字段名稱列表*/private static List<String> getKeyFieldNames(List<FieldInfo> allFields, int level) {List<String> keyFieldNames = new ArrayList<>();// 獲取當前層級的所有字段List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> !f.isObjectField && f.level == level).toList();// 收集所有標記為isKey=true的字段for (FieldInfo field : currentLevelFields) {ExcelColumn column = field.excelColumn;if (column != null && column.isKey()) {keyFieldNames.add(field.field.getName());}}// 如果沒有找到任何關鍵字段,使用第一個字段作為默認關鍵字段if (keyFieldNames.isEmpty() && !currentLevelFields.isEmpty()) {keyFieldNames.add(currentLevelFields.get(0).field.getName());}return keyFieldNames;}/*** 構建對象的唯一標識鍵*/private static String buildObjectKey(Map<String, Object> rowData, List<String> keyFieldNames, int level) {StringBuilder key = new StringBuilder();for (String fieldName : keyFieldNames) {// 使用層級特定的唯一鍵獲取值String uniqueKey = "L" + level + "_" + fieldName;Object value = rowData.get(uniqueKey);key.append(value != null ? value.toString() : "null").append("|");}return key.toString();}/*** 構建單個對象*/private static <T> T buildSingleObject(List<Map<String, Object>> rowDataList, Class<T> clazz, List<FieldInfo> allFields, int level) {try {T object = clazz.getDeclaredConstructor().newInstance();// 設置當前層級的字段值Map<String, Object> representativeRow = findRepresentativeRow(rowDataList, allFields, level);List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> !f.isObjectField && f.level == level).toList();for (FieldInfo fieldInfo : currentLevelFields) {// 使用層級特定的唯一鍵,避免同名字段覆蓋問題String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();Object value = representativeRow.get(uniqueKey);if (value != null) {fieldInfo.field.set(object, value);}}List<FieldInfo> childCollectionFields = allFields.stream().filter(f -> f.isCollectionField && f.level == level).toList();for (FieldInfo collectionField : childCollectionFields) {Class<?> elementType = getActualType(collectionField.field);// 按子對象分組Map<String, List<Map<String, Object>>> childGroups = groupChildData(rowDataList, allFields, level + 1);List<Object> childObjects = new ArrayList<>();for (List<Map<String, Object>> childRowData : childGroups.values()) {Object childObject = buildSingleObject(childRowData, elementType, allFields, level + 1);if (childObject != null) {childObjects.add(childObject);}}collectionField.field.set(object, childObjects);}return object;} catch (Exception e) {log.error("構建對象失敗: {}", e.getMessage());return null;}}/*** 找到代表性的行數據(用于獲取當前層級的字段值)* 對于每個字段,優先選擇有值的行數據*/private static Map<String, Object> findRepresentativeRow(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields, int level) {if (rowDataList.isEmpty()) {return new HashMap<>();}if (rowDataList.size() == 1) {return rowDataList.get(0);}// 獲取當前層級的所有字段List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> !f.isObjectField && f.level == level).toList();// 創建一個組合的代表性行數據Map<String, Object> representativeRow = new HashMap<>(rowDataList.get(0));// 對于每個字段,找到第一個非空值for (FieldInfo fieldInfo : currentLevelFields) {String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();Object currentValue = representativeRow.get(uniqueKey);// 如果當前值是空的,嘗試從其他行中找到非空值if (currentValue == null || currentValue.toString().trim().isEmpty()) {for (Map<String, Object> rowData : rowDataList) {Object value = rowData.get(uniqueKey);if (value != null && !value.toString().trim().isEmpty()) {representativeRow.put(uniqueKey, value);break;}}}}return representativeRow;}/*** 分組子數據(支持任意級別)*/private static Map<String, List<Map<String, Object>>> groupChildData(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields, int childLevel) {Map<String, List<Map<String, Object>>> childGroups = new LinkedHashMap<>();// 獲取子層級的關鍵字段組合List<String> childKeyFieldNames = getKeyFieldNames(allFields, childLevel);for (Map<String, Object> rowData : rowDataList) {// 構建子對象的唯一標識String key = buildObjectKey(rowData, childKeyFieldNames, childLevel);childGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(rowData);}return childGroups;}/*** 字段信息類*/private static class FieldInfo {Field field;ExcelColumn excelColumn;ExcelObject excelObject;ExcelConvert excelConvert;ExcelConverter<Object, Object> converter;int level;int columnIndex;boolean isObjectField;boolean isCollectionField;}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/918854.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/918854.shtml
英文地址,請注明出處:http://en.pswp.cn/news/918854.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于UniApp的新大陸物聯網平臺溫濕度檢測系統開發方案

新大陸物聯網平臺對接要點 認證方式&#xff1a; 使用AccessToken進行API認證 Token存儲在本地緩存中 數據格式&#xff1a; 溫度數據單位&#xff1a;攝氏度(C) 濕度數據單位&#xff1a;百分比(%) 時間格式&#xff1a;ISO 8601或時間戳 設備狀態&#xff1a; online:…

Git、JSON、MQTT

GIT簡介&#xff1a;Git是什么&#xff1f;Git是目前世界上最先進的分布式版本控制系統作用&#xff1a;版本控制&#xff08;版本的備份--->版本的回溯和前進&#xff09;多人協作優勢&#xff1a;SVN(集中式)劣勢&#xff1a;過度依賴服務器和網絡&#xff0c;容災性差Git…

yolo目標檢測技術之yolov11項目實戰(三)

yolo目標檢測技術之yolov11項目實戰&#xff08;三&#xff09; 文章目錄yolo目標檢測技術之yolov11項目實戰&#xff08;三&#xff09;一、 基于 YOLO11 的火焰與煙霧檢測系統&#xff08;實戰代碼&#xff09;項目目標環境搭建創建虛擬環境安裝依賴1.1 數據集準備1. 下載地址…

CF思維小訓練(二)

清晰的繽紛的都可以 臟兮兮的甜的也都有轉機 不想太小心 錯過第一百零一場美麗 CF思維小訓練&#xff08;二&#xff09; 書接上回CF思維小訓練-CSDN博客 雖然代碼很短&#xff0c;都是每一道題的背后都思維滿滿&#xff1b; 目錄CF思維小訓練&#xff08;二&#xff09;Arbo…

分布式鎖:從理論到實戰的深度指南

1. 分布式鎖是啥&#xff1f;為什么它比單機鎖更“硬核”&#xff1f;分布式鎖&#xff0c;聽起來高大上&#xff0c;其實核心問題很簡單&#xff1a;在多個機器、進程或服務同時搶奪資源時&#xff0c;怎么保證不打架&#xff1f; 想象一下&#xff0c;你在雙十一搶購限量款球…

基于UniApp的智能在線客服系統前端設計與實現

了解更多&#xff0c;搜索“程序員老狼”一、引言在當今數字化時代&#xff0c;客戶服務已成為企業競爭力的重要組成部分。本文將詳細介紹一款基于UniApp框架開發的跨平臺智能客服系統前端實現方案&#xff0c;該系統不僅具備傳統客服功能&#xff0c;還融入了現代即時通訊和人…

react與vue的對比,來實現標簽內部類似v-for循環,v-if等功能

前言&#xff1a;在vue中我們提供了很多標簽方法&#xff0c;比如用的比較多的v-for循環內容&#xff0c;v-if/v-show等判斷&#xff0c;可以直接寫在標簽中&#xff0c;大大提高了我們的開發效率&#xff0c;那么在react中有沒有類似的方法呢&#xff1f;我們這里來說一說。re…

PCB工藝-四層板制作流程(簡單了解下)

一&#xff09;流程&#xff1a;四層板的內層芯板&#xff0c;是由一張雙面覆銅板PP*2銅箔*2覆銅板蝕刻好線路&#xff0c;就是我們的芯板了PP全名叫半固化片&#xff0c;主體是玻璃纖維布環氧樹脂&#xff0c;是絕緣介質銅箔片&#xff0c;是單獨一張銅箔&#xff0c;很薄&…

無人機三維路徑規劃

文章目錄 1、引言 2、背景知識 3、核心算法 4、挑戰與優化 5、初始效果 6、需要改進地方 7、水平方向優化路線 8、垂直方向優化路線 9、與經過路線相交的網格都繪制出來 1、引言 介紹三維路徑規劃的定義和重要性:在無人機、機器人導航、虛擬現實等領域的應用。 概述文章目標和…

Spring-解決項目依賴異常問題

一.檢查項目的Maven路徑是否正確在確保新項目中的依賴在自己的電腦中已經存在的情況下&#xff1a;可以檢查項目的Maven路徑是否正確在拿到一個新項目時&#xff0c;要檢查這個項目的Maven路徑是自己電腦上設置好的Maven路徑嗎&#xff1f;如果不是&#xff0c;項目依賴會出問題…

系統設計——DDD領域模型驅動實踐

摘要本文主要介紹了DDD&#xff08;領域驅動設計&#xff09;在系統設計中的實踐應用&#xff0c;包括其在編碼規范、分層架構設計等方面的具體要求和建議。重點強調了應用層的命名規范&#xff0c;如避免使用模糊的Handler、Processor等命名&#xff0c;推薦使用動詞加業務動作…

開源衛星軟件平臺LibreCube技術深度解析

LibreCube技術深度解析&#xff1a;開源衛星軟件平臺的完整指南 LibreCube是一個專為CubeSat設計的模塊化開源衛星軟件平臺&#xff0c;它通過整合姿態控制、通信管理和任務調度等核心功能&#xff0c;為立方星開發者提供了完整的解決方案。本文將全面剖析LibreCube的技術架構…

React(四):事件總線、setState的細節、PureComponent、ref

React(四) 一、事件總線 二、關于setState的原理 1. setState的三種使用方式 (1)基本使用 (2)傳入一個回調 (3)第一個參數是對象,第二個參數是回調 2. 為什么setState要設置成異步 (1)提升性能,減少render次數 (2)避免state和props數據不同步 3. 獲取異步修改完數…

CPUcores-【硬核優化】CPU增強解鎖全部內核!可優化游戲性能、提升幀數!啟用CPU全內核+超線程,以更高優先級運行游戲!支持各種游戲和應用優化~

軟件介紹&#xff08;文末獲取&#xff09;CPUCores&#xff1a;游戲性能優化利器?這款工具&#xff0c;專為優化提升中低配電腦的幀數而生。其獨創的CPU資源調度技術&#xff0c;能讓老舊硬件煥發新生核心技術原理?采用「內核級隔離」方案&#xff0c;通過&#xff1a;系統進…

HQA-Attack: Toward High Quality Black-Box Hard-Label Adversarial Attack on Text

文本對抗性攻擊分為白盒攻擊和黑盒攻擊&#xff0c;其中黑盒攻擊更貼近現實&#xff0c;又可分為軟標簽和硬標簽設置&#xff0c;。這些名詞分別是什么意思 在文本對抗性攻擊中&#xff0c;“白盒攻擊”“黑盒攻擊”以及黑盒攻擊下的“軟標簽”“硬標簽”設置&#xff0c;核心差…

PyCharm性能優化與大型項目管理指南

1. PyCharm性能深度調優 1.1 內存與JVM配置優化 PyCharm基于JVM運行,合理配置JVM參數可顯著提升性能: # 自定義VM選項文件位置 # Windows: %USERPROFILE%\AppData\Roaming\JetBrains\<product><version>\pycharm64.exe.vmoptions # macOS: ~/Library/Applicat…

基于Java飛算AI的Spring Boot聊天室系統全流程實戰

在當今數字化時代&#xff0c;實時通訊已成為現代應用不可或缺的核心功能。從社交平臺到企業協作&#xff0c;從在線客服到游戲互動&#xff0c;實時聊天功能正以前所未有的速度滲透到各行各業。然而&#xff0c;開發一個功能完善的聊天室系統絕非易事——傳統開發模式下&#…

在 Conda 環境下編譯 C++ 程序時報錯:version `GLIBCXX_3.4.30‘ not found

報錯信息如下 ERROR:/root/SVF/llvm-16.0.4.obj/bin/clang: /opt/miniconda3/envs/py38/lib/libstdc.so.6: version GLIBCXX_3.4.30 not found (required by /root/SVF/llvm-16.0.4.obj/bin/clang)根據錯誤信息&#xff0c;問題是由于 Conda 環境中的libstdc.so.6缺少GLIBCXX_3…

vue+flask基于Apriori算法規則的求職推薦系統

文章結尾部分有CSDN官方提供的學長 聯系方式名片 文章結尾部分有CSDN官方提供的學長 聯系方式名片 關注B站&#xff0c;有好處&#xff01;編號&#xff1a;F069 基于Apriori關聯規則職位相似度的推薦算法進行職位推薦 基于決策樹、隨機森林的預測薪資 vueflaskmysql爬蟲 設計求…

機器學習第九課之DBSCAN算法

目錄 簡介 一、dbscan相關概念 二、dbscan的API 三、案例分析 1. 導入所需庫 2. 數據讀取與預處理 3. 數據準備 4. DBSCAN 參數調優 5. 確定最佳參數并應用 總結 簡介 本次我們將聚焦于一款極具特色的聚類算法 ——DBSCAN。相較于 K-means 等需要預先指定簇數量的算法…