Java easyExcel實(shí)現(xiàn)導(dǎo)入多sheet的Excel
1.官網(wǎng)
2.Excel樣式

3.代碼
@Slf4j
public class DynamicImportListener implements ReadListener<Map<Integer, String>> {
/**
* 從哪一行開始讀數(shù)據(jù)
*/
private final int headRowNumber;
/**
* 公司信息列
*/
private final int companyInfoNumber;
/**
* 數(shù)據(jù)列
*/
private final int headNumber;
private final Map<Integer, Map<Integer, String>> rawRowsMap = new HashMap<>();
private final List<CellExtra> extraMergeInfoList = new ArrayList<>();
private final Map<Integer, String> headerMap = new LinkedHashMap<>();
private final NavigableMap<Integer, String> companyInfoMap = new TreeMap<>();
public DynamicImportListener(int headRowNumber, int companyInfoNumber, int headNumber) {
this.headRowNumber = headRowNumber;
this.companyInfoNumber = companyInfoNumber;
this.headNumber = headNumber;
}
@Override
public void invoke(Map<Integer, String> rowMap, AnalysisContext context) {
int rowIndex = context.readRowHolder().getRowIndex();
if (rowIndex == companyInfoNumber) {
String company = rowMap.get(0);
if (StrUtil.isNotBlank(company)) {
companyInfoMap.put(rowIndex, company.trim());
}
} else if (rowIndex == headNumber) {
for (Map.Entry<Integer, String> e : rowMap.entrySet()) {
String v = e.getValue();
if (StrUtil.isNotBlank(v)) {
headerMap.put(e.getKey(), v.trim());
}
}
}else {
rawRowsMap.put(rowIndex, rowMap);
}
}
@Override
public void extra(CellExtra extra, AnalysisContext context) {
if (extra.getType() == CellExtraTypeEnum.MERGE
&& extra.getFirstRowIndex() >= headRowNumber - 1) {
extraMergeInfoList.add(extra);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.info("讀取完畢:數(shù)據(jù)總行 {},表頭列 {},合并單元格 {} 條",
rawRowsMap.size(), headerMap.size(), extraMergeInfoList.size());
}
/**
* 獲取首條公司信息
*/
public String getCompanyInfo() {
return companyInfoMap.isEmpty() ? null : companyInfoMap.firstEntry().getValue();
}
/**
* 獲取所有合并單元格元數(shù)據(jù)
*/
public List<CellExtra> getMergedRegions() {
return Collections.unmodifiableList(extraMergeInfoList);
}
/**
* 回填合并單元格數(shù)據(jù)
*/
public void fillMergedCells() {
if (extraMergeInfoList.isEmpty()) return;
for (CellExtra extra : extraMergeInfoList) {
int r1 = extra.getFirstRowIndex();
int r2 = extra.getLastRowIndex();
int c1 = extra.getFirstColumnIndex();
String init = rawRowsMap.get(r1).get(c1);
for (int rr = r1; rr <= r2; rr++) {
Map<Integer, String> row = rawRowsMap.get(rr);
if (row == null) continue;
for (int cc = c1; cc <= extra.getLastColumnIndex(); cc++) {
row.put(cc, init);
}
}
}
}
/**
* 構(gòu)建 VO 列表,固定字段 + extraFields
*/
public <T> List<T> buildVoList(Class<T> voClass) {
List<T> result = new ArrayList<>();
int headerIdx = headNumber;
int maxRow = rawRowsMap.keySet().stream().max(Integer::compareTo).orElse(headerIdx);
for (int idx = headerIdx + 1; idx <= maxRow; idx++) {
Map<Integer, String> rowMap = rawRowsMap.get(idx);
if (rowMap == null) continue;
try {
T vo = voClass.getDeclaredConstructor().newInstance();
// 填充列
for (Map.Entry<Integer, String> head : headerMap.entrySet()) {
String headerName = head.getValue();
String cellVal = rowMap.get(head.getKey());
String value = (cellVal == null) ? "" : cellVal.trim();
boolean matched = false;
for (Field f : voClass.getDeclaredFields()) {
ExcelProperty prop = f.getAnnotation(ExcelProperty.class);
if (prop != null && Arrays.asList(prop.value()).contains(headerName)) {
f.setAccessible(true);
f.set(vo, convertType(f.getType(), value));
matched = true;
break;
}
}
if (!matched) {
Method m = voClass.getMethod("getExtraFields");
@SuppressWarnings("unchecked")
Map<String, String> extra = (Map<String, String>) m.invoke(vo);
extra.put(headerName, value);
}
}
result.add(vo);
} catch (Exception e) {
log.error("行 {} 構(gòu)建 VO 失敗: {}", idx + 1, e.getMessage());
}
}
return result;
}
/**
* @param targetType 目標(biāo)類型
* @param text 文本
* @return java.lang.Object
* @description 數(shù)據(jù)類型轉(zhuǎn)換
* @author zhaohuaqing
* @date 2025/6/26 11:42
*/
private Object convertType(Class<?> targetType, String text) {
if (text == null) {
return null;
}
String trimmed = text.trim();
// 字符串
if (targetType == String.class) {
return trimmed;
}
// 原生數(shù)字類型
if (targetType == Integer.class || targetType == int.class) {
return Integer.valueOf(trimmed);
}
if (targetType == Long.class || targetType == long.class) {
return Long.valueOf(trimmed);
}
if (targetType == Double.class || targetType == double.class) {
return Double.valueOf(trimmed);
}
if (targetType == Float.class || targetType == float.class) {
return Float.valueOf(trimmed);
}
if (targetType == Short.class || targetType == short.class) {
return Short.valueOf(trimmed);
}
if (targetType == Byte.class || targetType == byte.class) {
return Byte.valueOf(trimmed);
}
// BigDecimal
if (targetType == BigDecimal.class) {
return new BigDecimal(trimmed);
}
// 布爾
if (targetType == Boolean.class || targetType == boolean.class) {
// 支持 "true"/"false",也支持 "1"/"0"
if ("1".equals(trimmed) || "0".equals(trimmed)) {
return "1".equals(trimmed);
}
return Boolean.valueOf(trimmed);
}
// Java 8 日期時(shí)間
if (targetType == LocalDate.class) {
// 默認(rèn) ISO 格式,或自定義
return LocalDate.parse(trimmed, DateTimeFormatter.ISO_LOCAL_DATE);
}
if (targetType == LocalTime.class) {
return LocalTime.parse(trimmed, DateTimeFormatter.ISO_LOCAL_TIME);
}
if (targetType == LocalDateTime.class) {
return LocalDateTime.parse(trimmed, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
if (targetType == OffsetDateTime.class) {
return OffsetDateTime.parse(trimmed, DateTimeFormatter.ISO_OFFSET_DATE_TIME);
}
if (targetType == ZonedDateTime.class) {
return ZonedDateTime.parse(trimmed, DateTimeFormatter.ISO_ZONED_DATE_TIME);
}
// 舊版 java.util.Date
if (targetType == java.util.Date.class) {
try {
// 你可以根據(jù) Excel 導(dǎo)出格式,調(diào)整 SimpleDateFormat
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(trimmed);
} catch (ParseException e) {
throw new RuntimeException("日期解析失敗: " + trimmed, e);
}
}
return trimmed;
}
}
如何使用
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 設(shè)置 chain = false,避免excel導(dǎo)入有問題
public class SopExtractMdsaVO {
@ExcelProperty(value = "一級(jí)xxx")
private String firstLevel;
@ExcelProperty(value = "二級(jí)xxx")
private String secondLevel;
@ExcelProperty(value = "三級(jí)xxx")
private String thirdLevel;
@ExcelProperty(value = "xxx")
private String formula;
@ExcelProperty(value = "xxx")
private String factor;
@ExcelProperty(value = "xxx")
private String referenceValue;
@ExcelProperty(value = "xxx")
private String element;
@ExcelProperty(value = "xxx")
private String scheme;
@ExcelProperty(value = "超鏈接1")
private String hyperlink1;
@ExcelProperty(value = "超鏈接2")
private String hyperlink2;
@ExcelProperty(value = "超鏈接3")
private String hyperlink3;
/**
* 動(dòng)態(tài)列:所有未在 VO 明確定義的列
*/
private Map<String, String> extraFields = new LinkedHashMap<>();
/**
* 管控方案IDs
*/
private String controlPlanIds;
/**
* 輸出物IDs
*/
private String outputMaterialIds;
/**
* @return 非空的 hyperlink 列表
*/
public List<String> nonBlankHyperlinks() {
List<String> list = new ArrayList<>(10);
if (StrUtil.isNotBlank(hyperlink1)) list.add(hyperlink1);
if (StrUtil.isNotBlank(hyperlink2)) list.add(hyperlink2);
if (StrUtil.isNotBlank(hyperlink3)) list.add(hyperlink3);
return list;
}
}
InputStream inputStream = file.getInputStream();
int headRowNumber = 0; // 表頭在 Excel 的第 2 行(從 1 開始計(jì))
DynamicImportListener listener = new DynamicImportListener(headRowNumber, 0, 1);
// 1) 讀數(shù)據(jù)、收集表頭 & 合并單元格 & 公司信息
EasyExcel.read(inputStream, listener)
.extraRead(CellExtraTypeEnum.MERGE)
.sheet("TEST")
.headRowNumber(headRowNumber)
.doRead();
// 2) 外部拿公司信息
String company = listener.getCompanyInfo();
// 3) 回填合并單元格
listener.fillMergedCells();
// 4) 構(gòu)建 VO 列表(包含固定字段 + extraFields)
List<SopExtractMdsaVO> rows = listener.buildVoList(SopExtractMdsaVO.class);
到此這篇關(guān)于Java easyExcel實(shí)現(xiàn)導(dǎo)入多sheet的Excel的文章就介紹到這了,更多相關(guān)Java easyExcel導(dǎo)入Excel內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java JDBC連接數(shù)據(jù)庫常見操作總結(jié)
這篇文章主要介紹了Java JDBC連接數(shù)據(jù)庫常見操作,結(jié)合實(shí)例形式總結(jié)分析了java基于jdbc連接mysql、Oracle數(shù)據(jù)庫及連接池相關(guān)操作技巧,需要的朋友可以參考下2019-03-03
java常用工具類 XML工具類、數(shù)據(jù)驗(yàn)證工具類
這篇文章主要為大家詳細(xì)介紹了java常用工具類,包括XML工具類、數(shù)據(jù)驗(yàn)證工具類,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05
MybatisPlus?QueryWrapper常用方法實(shí)例
MyBatis-Plus(opens new window)是一個(gè)MyBatis(opens new window)的增強(qiáng)工具,在 MyBatis的基礎(chǔ)上只做增強(qiáng)不做改變,為簡化開發(fā)、提高效率而生,下面這篇文章主要給大家介紹了關(guān)于MybatisPlus?QueryWrapper常用方法的相關(guān)資料,需要的朋友可以參考下2022-04-04
java使用servlet實(shí)現(xiàn)驗(yàn)證碼
這篇文章主要介紹了java使用servlet實(shí)現(xiàn)驗(yàn)證碼,簡單實(shí)用,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
Springboot跨域CORS處理實(shí)現(xiàn)原理
這篇文章主要介紹了Springboot跨域CORS處理實(shí)現(xiàn)原理,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04
淺談JSP與Servlet傳值及對(duì)比(總結(jié))
下面小編就為大家?guī)硪黄獪\談JSP與Servlet傳值及對(duì)比(總結(jié))。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05
java搭建一個(gè)Socket服務(wù)器響應(yīng)多用戶訪問
本篇文章主要介紹了java搭建一個(gè)Socket服務(wù)器響應(yīng)多用戶訪問,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-02-02
淺談SpringMVC HandlerInterceptor詭異問題排查
這篇文章主要介紹了淺談SpringMVC HandlerInterceptor詭異問題排查,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2019-05-05
SpringCloud之注冊中心之Nacos負(fù)載均衡詳解
Nacos提供多種負(fù)載均衡策略,包括權(quán)重、同機(jī)房、同地域、同環(huán)境等,服務(wù)下線和權(quán)重配置可以通過Nacos管理界面進(jìn)行,同時(shí),Nacos使用Raft算法選舉Leader節(jié)點(diǎn),若IP地址改變可能會(huì)影響Leader選舉,配置同集群優(yōu)先訪問可以提高訪問速度,通過配置集群名稱和負(fù)載均衡策略2025-03-03

