feat(core): 新增Map数据写入飞书表格功能- 新增MapWriteBuilder类,支持链式调用写入Map格式数据
- 新增MapTableConfig配置类,用于替代注解配置 - 新增MapDataUtil工具类,提供唯一ID计算和字段映射功能- 在FsHelper中新增writeMap和writeMapBuilder方法 - 在WriteBuilder中增加空字段位置检查逻辑 - 完善字段位置映射处理,避免空指针异常
This commit is contained in:
parent
9500a1df12
commit
f9cff00b2f
@ -5,11 +5,13 @@ import cn.isliu.core.FileData;
|
||||
import cn.isliu.core.FsTableData;
|
||||
import cn.isliu.core.Sheet;
|
||||
import cn.isliu.core.annotation.TableConf;
|
||||
import cn.isliu.core.builder.MapWriteBuilder;
|
||||
import cn.isliu.core.builder.ReadBuilder;
|
||||
import cn.isliu.core.builder.SheetBuilder;
|
||||
import cn.isliu.core.builder.WriteBuilder;
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.client.FsClient;
|
||||
import cn.isliu.core.config.MapTableConfig;
|
||||
import cn.isliu.core.enums.ErrorCode;
|
||||
import cn.isliu.core.enums.FileType;
|
||||
import cn.isliu.core.logging.FsLogger;
|
||||
@ -216,6 +218,11 @@ public class FsHelper {
|
||||
rowNum.set(rowNum.get() + 1);
|
||||
values.forEach((field, fieldValue) -> {
|
||||
String position = titlePostionMap.get(field);
|
||||
|
||||
if (position == null || position.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldValue instanceof FileData) {
|
||||
FileData fileData = (FileData) fieldValue;
|
||||
String fileType = fileData.getFileType();
|
||||
@ -236,6 +243,11 @@ public class FsHelper {
|
||||
int rowCou = rowCount.incrementAndGet();
|
||||
values.forEach((field, fieldValue) -> {
|
||||
String position = titlePostionMap.get(field);
|
||||
|
||||
if (position == null || position.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldValue instanceof FileData) {
|
||||
FileData fileData = (FileData) fieldValue;
|
||||
fileData.setSheetId(sheetId);
|
||||
@ -286,4 +298,74 @@ public class FsHelper {
|
||||
public static <T> WriteBuilder<T> writeBuilder(String sheetId, String spreadsheetToken, List<T> dataList) {
|
||||
return new WriteBuilder<>(sheetId, spreadsheetToken, dataList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Map 数据写入飞书表格
|
||||
*
|
||||
* 直接使用 Map 格式的数据写入飞书表格,无需定义实体类和注解。
|
||||
* 适用于动态字段、临时数据写入等场景。
|
||||
*
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* List<Map<String, Object>> dataList = new ArrayList<>();
|
||||
* Map<String, Object> data = new HashMap<>();
|
||||
* data.put("字段名1", "值1");
|
||||
* data.put("字段名2", "值2");
|
||||
* dataList.add(data);
|
||||
*
|
||||
* MapTableConfig config = MapTableConfig.builder()
|
||||
* .titleRow(2)
|
||||
* .headLine(3)
|
||||
* .addUniKeyName("字段名1")
|
||||
* .enableCover(true)
|
||||
* .build();
|
||||
*
|
||||
* FsHelper.writeMap(sheetId, spreadsheetToken, dataList, config);
|
||||
* </pre>
|
||||
*
|
||||
* @param sheetId 工作表ID
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @param dataList Map数据列表,每个Map的key为字段名,value为字段值
|
||||
* @param config 表格配置,包含标题行、数据起始行、唯一键等配置信息
|
||||
* @return 写入操作结果
|
||||
*/
|
||||
public static Object writeMap(String sheetId, String spreadsheetToken,
|
||||
List<Map<String, Object>> dataList, MapTableConfig config) {
|
||||
return new MapWriteBuilder(sheetId, spreadsheetToken, dataList)
|
||||
.config(config)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Map 数据写入构建器
|
||||
*
|
||||
* 返回一个 Map 数据写入构建器实例,支持链式调用和灵活配置。
|
||||
* 相比直接使用 writeMap 方法,构建器方式提供了更灵活的配置方式。
|
||||
*
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* List<Map<String, Object>> dataList = new ArrayList<>();
|
||||
* Map<String, Object> data = new HashMap<>();
|
||||
* data.put("字段名1", "值1");
|
||||
* data.put("字段名2", "值2");
|
||||
* dataList.add(data);
|
||||
*
|
||||
* FsHelper.writeMapBuilder(sheetId, spreadsheetToken, dataList)
|
||||
* .titleRow(2)
|
||||
* .headLine(3)
|
||||
* .addUniKeyName("字段名1")
|
||||
* .enableCover(true)
|
||||
* .ignoreNotFound(false)
|
||||
* .build();
|
||||
* </pre>
|
||||
*
|
||||
* @param sheetId 工作表ID
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @param dataList 要写入的Map数据列表,每个Map的key为字段名,value为字段值
|
||||
* @return MapWriteBuilder实例,支持链式调用
|
||||
*/
|
||||
public static MapWriteBuilder writeMapBuilder(String sheetId, String spreadsheetToken,
|
||||
List<Map<String, Object>> dataList) {
|
||||
return new MapWriteBuilder(sheetId, spreadsheetToken, dataList);
|
||||
}
|
||||
}
|
||||
390
src/main/java/cn/isliu/core/builder/MapWriteBuilder.java
Normal file
390
src/main/java/cn/isliu/core/builder/MapWriteBuilder.java
Normal file
@ -0,0 +1,390 @@
|
||||
package cn.isliu.core.builder;
|
||||
|
||||
import cn.isliu.core.*;
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.client.FsClient;
|
||||
import cn.isliu.core.config.MapTableConfig;
|
||||
import cn.isliu.core.enums.ErrorCode;
|
||||
import cn.isliu.core.enums.FileType;
|
||||
import cn.isliu.core.logging.FsLogger;
|
||||
import cn.isliu.core.service.CustomValueService;
|
||||
import cn.isliu.core.utils.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.isliu.core.utils.FsTableUtil.*;
|
||||
|
||||
/**
|
||||
* Map 数据写入构建器
|
||||
*
|
||||
* 提供链式调用方式写入 Map 格式的飞书表格数据
|
||||
*
|
||||
* @author Ls
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapWriteBuilder {
|
||||
|
||||
private final String sheetId;
|
||||
private final String spreadsheetToken;
|
||||
private final List<Map<String, Object>> dataList;
|
||||
private MapTableConfig config;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param sheetId 工作表ID
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @param dataList 要写入的Map数据列表
|
||||
*/
|
||||
public MapWriteBuilder(String sheetId, String spreadsheetToken, List<Map<String, Object>> dataList) {
|
||||
this.sheetId = sheetId;
|
||||
this.spreadsheetToken = spreadsheetToken;
|
||||
this.dataList = dataList;
|
||||
this.config = MapTableConfig.createDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表格配置
|
||||
*
|
||||
* @param config Map表格配置
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder config(MapTableConfig config) {
|
||||
this.config = config;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标题行
|
||||
*
|
||||
* @param titleRow 标题行行号
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder titleRow(int titleRow) {
|
||||
this.config.setTitleRow(titleRow);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据起始行
|
||||
*
|
||||
* @param headLine 数据起始行号
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder headLine(int headLine) {
|
||||
this.config.setHeadLine(headLine);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置唯一键字段
|
||||
*
|
||||
* @param uniKeyNames 唯一键字段名集合
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder uniKeyNames(Set<String> uniKeyNames) {
|
||||
this.config.setUniKeyNames(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加唯一键字段
|
||||
*
|
||||
* @param uniKeyName 唯一键字段名
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder addUniKeyName(String uniKeyName) {
|
||||
this.config.addUniKeyName(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否覆盖已存在数据
|
||||
*
|
||||
* @param enableCover 是否覆盖
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder enableCover(boolean enableCover) {
|
||||
this.config.setEnableCover(enableCover);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否忽略未找到的数据
|
||||
*
|
||||
* @param ignoreNotFound 是否忽略
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder ignoreNotFound(boolean ignoreNotFound) {
|
||||
this.config.setIgnoreNotFound(ignoreNotFound);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据写入
|
||||
*
|
||||
* @return 写入操作结果
|
||||
*/
|
||||
public Object build() {
|
||||
if (dataList.isEmpty()) {
|
||||
FsLogger.warn("【Map写入】数据列表为空,跳过写入操作");
|
||||
return null;
|
||||
}
|
||||
|
||||
FeishuClient client = FsClient.getInstance().getClient();
|
||||
Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
|
||||
|
||||
// 读取表格数据以获取字段位置映射和现有数据
|
||||
Map<String, String> titlePostionMap = readFieldsPositionMap(sheet, client);
|
||||
config.setFieldsPositionMap(titlePostionMap);
|
||||
|
||||
// 读取现有数据用于匹配和更新
|
||||
Map<String, Integer> currTableRowMap = readExistingData(sheet, client, titlePostionMap);
|
||||
|
||||
// 计算下一个可用行号
|
||||
int nextAvailableRow = calculateNextAvailableRow(currTableRowMap, config.getHeadLine());
|
||||
|
||||
// 初始化批量插入对象
|
||||
CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder =
|
||||
CustomValueService.ValueRequest.batchPutValues();
|
||||
|
||||
List<FileData> fileDataList = new ArrayList<>();
|
||||
AtomicInteger rowCount = new AtomicInteger(nextAvailableRow);
|
||||
|
||||
// 处理每条数据
|
||||
for (Map<String, Object> data : dataList) {
|
||||
String uniqueId = MapDataUtil.calculateUniqueId(data, config);
|
||||
|
||||
AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
|
||||
|
||||
if (uniqueId != null && rowNum.get() != null) {
|
||||
// 更新现有行
|
||||
rowNum.set(rowNum.get() + 1);
|
||||
processDataRow(data, titlePostionMap, rowNum.get(), resultValuesBuilder,
|
||||
fileDataList, config.isEnableCover());
|
||||
} else if (!config.isIgnoreNotFound()) {
|
||||
// 插入新行
|
||||
int newRow = rowCount.incrementAndGet();
|
||||
processDataRow(data, titlePostionMap, newRow, resultValuesBuilder,
|
||||
fileDataList, config.isEnableCover());
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要扩展行数
|
||||
ensureSufficientRows(sheet, rowCount.get(), client);
|
||||
|
||||
// 上传文件
|
||||
uploadFiles(fileDataList, client);
|
||||
|
||||
// 批量写入数据
|
||||
return batchWriteValues(resultValuesBuilder, client);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取字段位置映射
|
||||
*/
|
||||
private Map<String, String> readFieldsPositionMap(Sheet sheet, FeishuClient client) {
|
||||
int titleRow = config.getTitleRow();
|
||||
int colCount = sheet.getGridProperties().getColumnCount();
|
||||
|
||||
// 读取标题行数据
|
||||
ValuesBatch valuesBatch = FsApiUtil.getSheetData(
|
||||
sheet.getSheetId(), spreadsheetToken,
|
||||
"A" + titleRow,
|
||||
getColumnName(colCount - 1) + titleRow,
|
||||
client
|
||||
);
|
||||
|
||||
Map<String, String> fieldsPositionMap = new HashMap<>();
|
||||
|
||||
if (valuesBatch != null && valuesBatch.getValueRanges() != null) {
|
||||
for (ValueRange valueRange : valuesBatch.getValueRanges()) {
|
||||
if (valueRange.getValues() != null && !valueRange.getValues().isEmpty()) {
|
||||
List<Object> titleRowValues = valueRange.getValues().get(0);
|
||||
for (int i = 0; i < titleRowValues.size(); i++) {
|
||||
Object value = titleRowValues.get(i);
|
||||
if (value != null) {
|
||||
String fieldName = value.toString();
|
||||
String columnPosition = getColumnName(i);
|
||||
fieldsPositionMap.put(fieldName, columnPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fieldsPositionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取现有数据
|
||||
*/
|
||||
private Map<String, Integer> readExistingData(Sheet sheet, FeishuClient client, Map<String, String> titlePostionMap) {
|
||||
int headLine = config.getHeadLine();
|
||||
int titleRow = config.getTitleRow();
|
||||
int totalRow = sheet.getGridProperties().getRowCount();
|
||||
int colCount = sheet.getGridProperties().getColumnCount();
|
||||
int startOffset = 1;
|
||||
|
||||
// 批量读取数据
|
||||
int rowCountPerBatch = Math.min(totalRow, 100);
|
||||
int actualRows = Math.max(0, totalRow - startOffset);
|
||||
int batchCount = (actualRows + rowCountPerBatch - 1) / rowCountPerBatch;
|
||||
|
||||
List<List<Object>> values = new LinkedList<>();
|
||||
for (int i = 0; i < batchCount; i++) {
|
||||
int startRowIndex = startOffset + i * rowCountPerBatch;
|
||||
int endRowIndex = Math.min(startRowIndex + rowCountPerBatch - 1, totalRow - 1);
|
||||
|
||||
ValuesBatch valuesBatch = FsApiUtil.getSheetData(
|
||||
sheet.getSheetId(), spreadsheetToken,
|
||||
"A" + startRowIndex,
|
||||
getColumnName(colCount - 1) + endRowIndex,
|
||||
client
|
||||
);
|
||||
|
||||
if (valuesBatch != null && valuesBatch.getValueRanges() != null) {
|
||||
for (ValueRange valueRange : valuesBatch.getValueRanges()) {
|
||||
if (valueRange.getValues() != null) {
|
||||
values.addAll(valueRange.getValues());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表格数据
|
||||
TableData tableData = processSheetData(sheet, values);
|
||||
List<FsTableData> dataList = getFsTableData(tableData, new ArrayList<>());
|
||||
|
||||
// 获取标题映射
|
||||
Map<String, String> titleMap = new HashMap<>();
|
||||
dataList.stream()
|
||||
.filter(d -> d.getRow() == (titleRow - 1))
|
||||
.findFirst()
|
||||
.ifPresent(d -> {
|
||||
Map<String, String> map = (Map<String, String>) d.getData();
|
||||
titleMap.putAll(map);
|
||||
});
|
||||
|
||||
// 转换为带字段名的数据,并计算唯一ID
|
||||
return dataList.stream()
|
||||
.filter(fsTableData -> fsTableData.getRow() >= headLine)
|
||||
.map(item -> {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
Map<String, Object> map = (Map<String, Object>) item.getData();
|
||||
|
||||
map.forEach((k, v) -> {
|
||||
String title = titleMap.get(k);
|
||||
if (title != null) {
|
||||
resultMap.put(title, v);
|
||||
}
|
||||
});
|
||||
|
||||
String uniqueId = MapDataUtil.calculateUniqueId(resultMap, config);
|
||||
item.setUniqueId(uniqueId);
|
||||
item.setFieldsPositionMap(titlePostionMap);
|
||||
return item;
|
||||
})
|
||||
.filter(item -> item.getUniqueId() != null)
|
||||
.collect(Collectors.toMap(FsTableData::getUniqueId, FsTableData::getRow, (v1, v2) -> v1));
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算下一个可用行号
|
||||
*/
|
||||
private int calculateNextAvailableRow(Map<String, Integer> currTableRowMap, int headLine) {
|
||||
if (currTableRowMap.isEmpty()) {
|
||||
return headLine;
|
||||
}
|
||||
|
||||
return currTableRowMap.values().stream()
|
||||
.max(Integer::compareTo)
|
||||
.map(maxRow -> maxRow + 1)
|
||||
.orElse(headLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单行数据
|
||||
*/
|
||||
private void processDataRow(Map<String, Object> data, Map<String, String> titlePostionMap,
|
||||
int rowNum, CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder,
|
||||
List<FileData> fileDataList, boolean enableCover) {
|
||||
data.forEach((field, fieldValue) -> {
|
||||
String position = titlePostionMap.get(field);
|
||||
|
||||
if (position == null || position.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 处理文件数据
|
||||
if (fieldValue instanceof FileData) {
|
||||
FileData fileData = (FileData) fieldValue;
|
||||
String fileType = fileData.getFileType();
|
||||
if (fileType.equals(FileType.IMAGE.getType())) {
|
||||
fileData.setSheetId(sheetId);
|
||||
fileData.setSpreadsheetToken(spreadsheetToken);
|
||||
fileData.setPosition(position + rowNum);
|
||||
fileDataList.add(fileData);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加到批量写入
|
||||
if (enableCover || fieldValue != null) {
|
||||
resultValuesBuilder.addRange(sheetId, position + rowNum, position + rowNum)
|
||||
.addRow(GenerateUtil.getRowData(fieldValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保行数足够
|
||||
*/
|
||||
private void ensureSufficientRows(Sheet sheet, int requiredRows, FeishuClient client) {
|
||||
int rowTotal = sheet.getGridProperties().getRowCount();
|
||||
if (requiredRows >= rowTotal) {
|
||||
FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", requiredRows - rowTotal, client);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
private void uploadFiles(List<FileData> fileDataList, FeishuClient client) {
|
||||
fileDataList.forEach(fileData -> {
|
||||
try {
|
||||
FsApiUtil.imageUpload(
|
||||
fileData.getImageData(),
|
||||
fileData.getFileName(),
|
||||
fileData.getPosition(),
|
||||
fileData.getSheetId(),
|
||||
fileData.getSpreadsheetToken(),
|
||||
client
|
||||
);
|
||||
} catch (Exception e) {
|
||||
FsLogger.error(ErrorCode.BUSINESS_LOGIC_ERROR,
|
||||
"【飞书表格】Map写入-文件上传异常! " + fileData.getFileUrl());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量写入数据
|
||||
*/
|
||||
private Object batchWriteValues(CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder,
|
||||
FeishuClient client) {
|
||||
CustomValueService.ValueRequest build = resultValuesBuilder.build();
|
||||
CustomValueService.ValueBatchUpdatePutRequest batchPutValues = build.getBatchPutValues();
|
||||
List<CustomValueService.ValueRangeItem> valueRanges = batchPutValues.getValueRanges();
|
||||
|
||||
if (valueRanges != null && !valueRanges.isEmpty()) {
|
||||
return FsApiUtil.batchPutValues(sheetId, spreadsheetToken, build, client);
|
||||
}
|
||||
|
||||
FsLogger.warn("【Map写入】没有数据需要写入");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -198,6 +198,10 @@ public class WriteBuilder<T> {
|
||||
values.forEach((field, fieldValue) -> {
|
||||
String position = finalTitlePostionMap.get(field);
|
||||
|
||||
if (position == null || position.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldValue instanceof FileData) {
|
||||
FileData fileData = (FileData) fieldValue;
|
||||
String fileType = fileData.getFileType();
|
||||
@ -217,8 +221,12 @@ public class WriteBuilder<T> {
|
||||
int rowCou = rowCount.incrementAndGet();
|
||||
Map<String, String> finalTitlePostionMap1 = titlePostionMap;
|
||||
values.forEach((field, fieldValue) -> {
|
||||
|
||||
String position = finalTitlePostionMap1.get(field);
|
||||
|
||||
if (position == null || position.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fieldValue instanceof FileData) {
|
||||
FileData fileData = (FileData) fieldValue;
|
||||
fileData.setSheetId(sheetId);
|
||||
|
||||
290
src/main/java/cn/isliu/core/config/MapTableConfig.java
Normal file
290
src/main/java/cn/isliu/core/config/MapTableConfig.java
Normal file
@ -0,0 +1,290 @@
|
||||
package cn.isliu.core.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Map方式表格配置类
|
||||
*
|
||||
* 用于替代注解配置,支持使用Map方式操作飞书表格
|
||||
*
|
||||
* @author Ls
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapTableConfig {
|
||||
|
||||
/**
|
||||
* 标题行行号(从1开始)
|
||||
*/
|
||||
private int titleRow = 1;
|
||||
|
||||
/**
|
||||
* 数据起始行行号(从1开始)
|
||||
*/
|
||||
private int headLine = 1;
|
||||
|
||||
/**
|
||||
* 唯一键字段名列表
|
||||
*/
|
||||
private Set<String> uniKeyNames = new HashSet<>();
|
||||
|
||||
/**
|
||||
* 是否覆盖已存在数据
|
||||
*/
|
||||
private boolean enableCover = false;
|
||||
|
||||
/**
|
||||
* 是否忽略未找到的数据
|
||||
*/
|
||||
private boolean ignoreNotFound = false;
|
||||
|
||||
/**
|
||||
* 字段位置映射 (字段名 -> 列位置,如 "添加SPU" -> "A")
|
||||
*/
|
||||
private Map<String, String> fieldsPositionMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 获取标题行行号
|
||||
*
|
||||
* @return 标题行行号
|
||||
*/
|
||||
public int getTitleRow() {
|
||||
return titleRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标题行行号
|
||||
*
|
||||
* @param titleRow 标题行行号
|
||||
* @return MapTableConfig实例,支持链式调用
|
||||
*/
|
||||
public MapTableConfig setTitleRow(int titleRow) {
|
||||
this.titleRow = titleRow;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据起始行行号
|
||||
*
|
||||
* @return 数据起始行行号
|
||||
*/
|
||||
public int getHeadLine() {
|
||||
return headLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据起始行行号
|
||||
*
|
||||
* @param headLine 数据起始行行号
|
||||
* @return MapTableConfig实例,支持链式调用
|
||||
*/
|
||||
public MapTableConfig setHeadLine(int headLine) {
|
||||
this.headLine = headLine;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取唯一键字段名集合
|
||||
*
|
||||
* @return 唯一键字段名集合
|
||||
*/
|
||||
public Set<String> getUniKeyNames() {
|
||||
return uniKeyNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置唯一键字段名集合
|
||||
*
|
||||
* @param uniKeyNames 唯一键字段名集合
|
||||
* @return MapTableConfig实例,支持链式调用
|
||||
*/
|
||||
public MapTableConfig setUniKeyNames(Set<String> uniKeyNames) {
|
||||
this.uniKeyNames = uniKeyNames;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加唯一键字段名
|
||||
*
|
||||
* @param uniKeyName 唯一键字段名
|
||||
* @return MapTableConfig实例,支持链式调用
|
||||
*/
|
||||
public MapTableConfig addUniKeyName(String uniKeyName) {
|
||||
this.uniKeyNames.add(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否覆盖已存在数据
|
||||
*
|
||||
* @return true表示覆盖,false表示不覆盖
|
||||
*/
|
||||
public boolean isEnableCover() {
|
||||
return enableCover;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否覆盖已存在数据
|
||||
*
|
||||
* @param enableCover true表示覆盖,false表示不覆盖
|
||||
* @return MapTableConfig实例,支持链式调用
|
||||
*/
|
||||
public MapTableConfig setEnableCover(boolean enableCover) {
|
||||
this.enableCover = enableCover;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否忽略未找到的数据
|
||||
*
|
||||
* @return true表示忽略,false表示不忽略
|
||||
*/
|
||||
public boolean isIgnoreNotFound() {
|
||||
return ignoreNotFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否忽略未找到的数据
|
||||
*
|
||||
* @param ignoreNotFound true表示忽略,false表示不忽略
|
||||
* @return MapTableConfig实例,支持链式调用
|
||||
*/
|
||||
public MapTableConfig setIgnoreNotFound(boolean ignoreNotFound) {
|
||||
this.ignoreNotFound = ignoreNotFound;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段位置映射
|
||||
*
|
||||
* @return 字段位置映射
|
||||
*/
|
||||
public Map<String, String> getFieldsPositionMap() {
|
||||
return fieldsPositionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段位置映射
|
||||
*
|
||||
* @param fieldsPositionMap 字段位置映射
|
||||
* @return MapTableConfig实例,支持链式调用
|
||||
*/
|
||||
public MapTableConfig setFieldsPositionMap(Map<String, String> fieldsPositionMap) {
|
||||
this.fieldsPositionMap = fieldsPositionMap;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建默认配置
|
||||
*
|
||||
* @return 默认配置实例
|
||||
*/
|
||||
public static MapTableConfig createDefault() {
|
||||
return new MapTableConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建配置构建器
|
||||
*
|
||||
* @return 配置构建器实例
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置构建器
|
||||
*/
|
||||
public static class Builder {
|
||||
private final MapTableConfig config = new MapTableConfig();
|
||||
|
||||
/**
|
||||
* 设置标题行行号
|
||||
*
|
||||
* @param titleRow 标题行行号
|
||||
* @return Builder实例
|
||||
*/
|
||||
public Builder titleRow(int titleRow) {
|
||||
config.titleRow = titleRow;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据起始行行号
|
||||
*
|
||||
* @param headLine 数据起始行行号
|
||||
* @return Builder实例
|
||||
*/
|
||||
public Builder headLine(int headLine) {
|
||||
config.headLine = headLine;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置唯一键字段名集合
|
||||
*
|
||||
* @param uniKeyNames 唯一键字段名集合
|
||||
* @return Builder实例
|
||||
*/
|
||||
public Builder uniKeyNames(Set<String> uniKeyNames) {
|
||||
config.uniKeyNames = new HashSet<>(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加唯一键字段名
|
||||
*
|
||||
* @param uniKeyName 唯一键字段名
|
||||
* @return Builder实例
|
||||
*/
|
||||
public Builder addUniKeyName(String uniKeyName) {
|
||||
config.uniKeyNames.add(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否覆盖已存在数据
|
||||
*
|
||||
* @param enableCover true表示覆盖,false表示不覆盖
|
||||
* @return Builder实例
|
||||
*/
|
||||
public Builder enableCover(boolean enableCover) {
|
||||
config.enableCover = enableCover;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否忽略未找到的数据
|
||||
*
|
||||
* @param ignoreNotFound true表示忽略,false表示不忽略
|
||||
* @return Builder实例
|
||||
*/
|
||||
public Builder ignoreNotFound(boolean ignoreNotFound) {
|
||||
config.ignoreNotFound = ignoreNotFound;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段位置映射
|
||||
*
|
||||
* @param fieldsPositionMap 字段位置映射
|
||||
* @return Builder实例
|
||||
*/
|
||||
public Builder fieldsPositionMap(Map<String, String> fieldsPositionMap) {
|
||||
config.fieldsPositionMap = new HashMap<>(fieldsPositionMap);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建配置对象
|
||||
*
|
||||
* @return MapTableConfig实例
|
||||
*/
|
||||
public MapTableConfig build() {
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
134
src/main/java/cn/isliu/core/utils/MapDataUtil.java
Normal file
134
src/main/java/cn/isliu/core/utils/MapDataUtil.java
Normal file
@ -0,0 +1,134 @@
|
||||
package cn.isliu.core.utils;
|
||||
|
||||
import cn.isliu.core.config.MapTableConfig;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Map 数据处理工具类
|
||||
*
|
||||
* 提供Map格式数据的处理方法,包括唯一ID计算、字段位置映射等
|
||||
*
|
||||
* @author Ls
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapDataUtil {
|
||||
|
||||
/**
|
||||
* 计算 Map 数据的唯一ID
|
||||
*
|
||||
* 根据配置的唯一键字段计算数据的唯一标识。
|
||||
* 如果没有指定唯一键,则使用所有字段计算。
|
||||
*
|
||||
* @param data Map数据
|
||||
* @param config 表格配置
|
||||
* @return 唯一ID(SHA256哈希值)
|
||||
*/
|
||||
public static String calculateUniqueId(Map<String, Object> data, MapTableConfig config) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Set<String> uniKeyNames = config.getUniKeyNames();
|
||||
|
||||
if (uniKeyNames == null || uniKeyNames.isEmpty()) {
|
||||
// 如果没有指定唯一键,使用所有字段计算
|
||||
String jsonStr = StringUtil.mapToJson(data);
|
||||
return StringUtil.getSHA256(jsonStr);
|
||||
}
|
||||
|
||||
// 使用指定的唯一键字段计算
|
||||
List<Object> uniKeyValues = new ArrayList<>();
|
||||
for (String key : uniKeyNames) {
|
||||
if (data.containsKey(key)) {
|
||||
uniKeyValues.add(data.get(key));
|
||||
}
|
||||
}
|
||||
|
||||
if (uniKeyValues.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String jsonStr = StringUtil.listToJson(uniKeyValues);
|
||||
return StringUtil.getSHA256(jsonStr);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从标题行数据构建字段位置映射
|
||||
*
|
||||
* 将标题行的列位置与字段名称进行映射,用于后续数据写入时定位单元格位置。
|
||||
*
|
||||
* @param titleRowData 标题行数据,key为列位置(如"A"、"B"),value为字段名称
|
||||
* @return 字段位置映射,key为字段名称,value为列位置
|
||||
*/
|
||||
public static Map<String, String> buildFieldsPositionMap(Map<String, String> titleRowData) {
|
||||
if (titleRowData == null || titleRowData.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<String, String> fieldsPositionMap = new HashMap<>();
|
||||
titleRowData.forEach((position, fieldName) -> {
|
||||
if (fieldName != null && !fieldName.isEmpty()) {
|
||||
fieldsPositionMap.put(fieldName, position);
|
||||
}
|
||||
});
|
||||
return fieldsPositionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证数据字段是否与表格字段匹配
|
||||
*
|
||||
* 检查数据中的字段是否在表格的字段位置映射中存在。
|
||||
*
|
||||
* @param data 数据Map
|
||||
* @param fieldsPositionMap 字段位置映射
|
||||
* @return 未匹配的字段列表
|
||||
*/
|
||||
public static List<String> validateDataFields(Map<String, Object> data, Map<String, String> fieldsPositionMap) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
if (fieldsPositionMap == null || fieldsPositionMap.isEmpty()) {
|
||||
return new ArrayList<>(data.keySet());
|
||||
}
|
||||
|
||||
List<String> unmatchedFields = new ArrayList<>();
|
||||
for (String fieldName : data.keySet()) {
|
||||
if (!fieldsPositionMap.containsKey(fieldName)) {
|
||||
unmatchedFields.add(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
return unmatchedFields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤数据字段
|
||||
*
|
||||
* 只保留在字段位置映射中存在的字段。
|
||||
*
|
||||
* @param data 原始数据Map
|
||||
* @param fieldsPositionMap 字段位置映射
|
||||
* @return 过滤后的数据Map
|
||||
*/
|
||||
public static Map<String, Object> filterDataFields(Map<String, Object> data, Map<String, String> fieldsPositionMap) {
|
||||
if (data == null || data.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
if (fieldsPositionMap == null || fieldsPositionMap.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<String, Object> filteredData = new HashMap<>();
|
||||
data.forEach((fieldName, fieldValue) -> {
|
||||
if (fieldsPositionMap.containsKey(fieldName)) {
|
||||
filteredData.put(fieldName, fieldValue);
|
||||
}
|
||||
});
|
||||
|
||||
return filteredData;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user