feat(core): 支持表格数据 Upsert 模式写入
- 在 MapTableConfig 和 MapSheetConfig 中新增 upsert 配置项,控制是否启用 Upsert 模式 - MapWriteBuilder 新增 upsert 方法用于设置 Upsert 模式开关- 实现根据 upsert 配置决定是否读取现有数据进行匹配更新或直接追加写入 - 添加分组字段支持,允许按指定字段对数据进行分组处理 -优化数据写入逻辑,区分 Upsert 模式和纯追加模式的数据读取与行号计算 - 增强异常处理和日志记录,提升系统稳定性与可维护性
This commit is contained in:
parent
3b0b8712a8
commit
7ad1060adf
@ -65,4 +65,15 @@ public @interface TableConf {
|
||||
* @return 背景颜色
|
||||
*/
|
||||
String headBackColor() default "#cccccc";
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用 Upsert 模式
|
||||
*
|
||||
* true(默认):根据唯一键匹配,存在则更新,不存在则追加
|
||||
* false:不匹配唯一键,所有数据直接追加到表格末尾
|
||||
*
|
||||
* @return 是否启用 Upsert 模式
|
||||
*/
|
||||
boolean upsert() default true;
|
||||
}
|
||||
@ -31,11 +31,11 @@ import java.util.stream.Collectors;
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapSheetBuilder {
|
||||
|
||||
|
||||
private final String sheetName;
|
||||
private final String spreadsheetToken;
|
||||
private MapSheetConfig config;
|
||||
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
@ -47,7 +47,7 @@ public class MapSheetBuilder {
|
||||
this.spreadsheetToken = spreadsheetToken;
|
||||
this.config = MapSheetConfig.createDefault();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置表格配置
|
||||
*
|
||||
@ -58,7 +58,7 @@ public class MapSheetBuilder {
|
||||
this.config = config;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置标题行
|
||||
*
|
||||
@ -69,7 +69,7 @@ public class MapSheetBuilder {
|
||||
this.config.setTitleRow(titleRow);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置数据起始行
|
||||
*
|
||||
@ -80,7 +80,7 @@ public class MapSheetBuilder {
|
||||
this.config.setHeadLine(headLine);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置唯一键字段
|
||||
*
|
||||
@ -91,7 +91,7 @@ public class MapSheetBuilder {
|
||||
this.config.setUniKeyNames(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加唯一键字段
|
||||
*
|
||||
@ -102,7 +102,7 @@ public class MapSheetBuilder {
|
||||
this.config.addUniKeyName(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置字段列表
|
||||
*
|
||||
@ -113,7 +113,7 @@ public class MapSheetBuilder {
|
||||
this.config.setFields(new ArrayList<>(fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加单个字段
|
||||
*
|
||||
@ -124,7 +124,7 @@ public class MapSheetBuilder {
|
||||
this.config.addField(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量添加字段
|
||||
*
|
||||
@ -135,7 +135,7 @@ public class MapSheetBuilder {
|
||||
this.config.addFields(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量添加字段(可变参数)
|
||||
*
|
||||
@ -146,7 +146,7 @@ public class MapSheetBuilder {
|
||||
this.config.addFields(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置表头样式
|
||||
*
|
||||
@ -159,7 +159,7 @@ public class MapSheetBuilder {
|
||||
this.config.setHeadBackColor(backColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否为纯文本格式
|
||||
*
|
||||
@ -170,7 +170,7 @@ public class MapSheetBuilder {
|
||||
this.config.setText(isText);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否启用字段描述
|
||||
*
|
||||
@ -181,7 +181,7 @@ public class MapSheetBuilder {
|
||||
this.config.setEnableDesc(enableDesc);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置分组字段
|
||||
*
|
||||
@ -192,7 +192,7 @@ public class MapSheetBuilder {
|
||||
this.config.setGroupFields(Arrays.asList(groupFields));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加自定义属性
|
||||
*
|
||||
@ -204,7 +204,7 @@ public class MapSheetBuilder {
|
||||
this.config.addCustomProperty(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建表格并返回工作表ID
|
||||
*
|
||||
@ -215,7 +215,7 @@ public class MapSheetBuilder {
|
||||
if (config.getFields().isEmpty()) {
|
||||
throw new IllegalArgumentException("字段定义列表不能为空");
|
||||
}
|
||||
|
||||
|
||||
// 判断是否为分组表格
|
||||
if (config.getGroupFields() != null && !config.getGroupFields().isEmpty()) {
|
||||
return buildGroupSheet();
|
||||
@ -223,145 +223,145 @@ public class MapSheetBuilder {
|
||||
return buildNormalSheet();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建普通表格
|
||||
*/
|
||||
private String buildNormalSheet() {
|
||||
// 转换字段定义为 FieldProperty
|
||||
Map<String, FieldProperty> fieldsMap = convertToFieldsMap(config.getFields());
|
||||
|
||||
|
||||
// 生成表头
|
||||
List<String> headers = config.getFields().stream()
|
||||
.sorted(Comparator.comparingInt(MapFieldDefinition::getOrder))
|
||||
.map(MapFieldDefinition::getFieldName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
.sorted(Comparator.comparingInt(MapFieldDefinition::getOrder))
|
||||
.map(MapFieldDefinition::getFieldName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 创建 TableConf
|
||||
TableConf tableConf = createTableConf();
|
||||
|
||||
|
||||
// 创建飞书客户端
|
||||
FeishuClient client = FsClient.getInstance().getClient();
|
||||
|
||||
|
||||
// 1、创建sheet
|
||||
String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
|
||||
|
||||
|
||||
// 2、添加表头数据
|
||||
Map<String, String> fieldDescriptions = buildFieldDescriptions();
|
||||
FsApiUtil.putValues(spreadsheetToken,
|
||||
FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf, fieldDescriptions),
|
||||
client);
|
||||
|
||||
FsApiUtil.putValues(spreadsheetToken,
|
||||
FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf, fieldDescriptions),
|
||||
client);
|
||||
|
||||
// 3、设置单元格为文本格式
|
||||
if (config.isText()) {
|
||||
String column = FsTableUtil.getColumnNameByNuNumber(headers.size());
|
||||
FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
|
||||
}
|
||||
|
||||
|
||||
// 4、设置表格样式
|
||||
FsApiUtil.setTableStyle(
|
||||
FsTableUtil.getDefaultTableStyle(sheetId, fieldsMap, tableConf),
|
||||
client, spreadsheetToken);
|
||||
|
||||
FsTableUtil.getDefaultTableStyle(sheetId, fieldsMap, tableConf),
|
||||
client, spreadsheetToken);
|
||||
|
||||
// 5、合并单元格
|
||||
List<CustomCellService.CellRequest> mergeCell = FsTableUtil.getMergeCell(sheetId, fieldsMap);
|
||||
if (!mergeCell.isEmpty()) {
|
||||
mergeCell.forEach(cell -> FsApiUtil.mergeCells(cell, client, spreadsheetToken));
|
||||
}
|
||||
|
||||
|
||||
// 6、设置表格下拉
|
||||
try {
|
||||
// 准备自定义属性,包含字段的 options 配置
|
||||
Map<String, Object> customProps = prepareCustomProperties(fieldsMap);
|
||||
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId,
|
||||
config.isEnableDesc(), customProps);
|
||||
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId,
|
||||
config.isEnableDesc(), customProps);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(MapSheetBuilder.class.getName()).log(Level.SEVERE,
|
||||
"【Map表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
|
||||
"【Map表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
return sheetId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建分组表格
|
||||
*/
|
||||
private String buildGroupSheet() {
|
||||
// 转换字段定义为 FieldProperty
|
||||
Map<String, FieldProperty> fieldsMap = convertToFieldsMap(config.getFields());
|
||||
|
||||
|
||||
// 生成表头
|
||||
List<String> headers = config.getFields().stream()
|
||||
.sorted(Comparator.comparingInt(MapFieldDefinition::getOrder))
|
||||
.map(MapFieldDefinition::getFieldName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
.sorted(Comparator.comparingInt(MapFieldDefinition::getOrder))
|
||||
.map(MapFieldDefinition::getFieldName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 创建 TableConf
|
||||
TableConf tableConf = createTableConf();
|
||||
|
||||
|
||||
// 创建飞书客户端
|
||||
FeishuClient client = FsClient.getInstance().getClient();
|
||||
|
||||
|
||||
// 1、创建sheet
|
||||
String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
|
||||
|
||||
|
||||
// 2、添加表头数据(分组模式)
|
||||
List<String> groupFieldList = config.getGroupFields();
|
||||
List<String> headerList = FsTableUtil.getGroupHeaders(groupFieldList, headers);
|
||||
Map<String, String> fieldDescriptions = buildFieldDescriptions();
|
||||
FsApiUtil.putValues(spreadsheetToken,
|
||||
FsTableUtil.getHeadTemplateBuilder(sheetId, headers, headerList, fieldsMap,
|
||||
tableConf, fieldDescriptions, groupFieldList),
|
||||
client);
|
||||
|
||||
FsTableUtil.getHeadTemplateBuilder(sheetId, headers, headerList, fieldsMap,
|
||||
tableConf, fieldDescriptions, groupFieldList),
|
||||
client);
|
||||
|
||||
// 3、设置单元格为文本格式
|
||||
if (config.isText()) {
|
||||
String column = FsTableUtil.getColumnNameByNuNumber(headerList.size());
|
||||
FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
|
||||
}
|
||||
|
||||
|
||||
// 4、设置表格样式(分组模式)
|
||||
Map<String, String[]> positions = FsTableUtil.calculateGroupPositions(headers, groupFieldList);
|
||||
positions.forEach((key, value) ->
|
||||
FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, value, tableConf),
|
||||
client, spreadsheetToken));
|
||||
|
||||
positions.forEach((key, value) ->
|
||||
FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, value, tableConf),
|
||||
client, spreadsheetToken));
|
||||
|
||||
// 5、合并单元格
|
||||
List<CustomCellService.CellRequest> mergeCell = FsTableUtil.getMergeCell(sheetId, positions.values());
|
||||
if (!mergeCell.isEmpty()) {
|
||||
mergeCell.forEach(cell -> FsApiUtil.mergeCells(cell, client, spreadsheetToken));
|
||||
}
|
||||
|
||||
|
||||
// 6、设置表格下拉
|
||||
try {
|
||||
String[] headerWithColumnIdentifiers = FsTableUtil.generateHeaderWithColumnIdentifiers(headers, groupFieldList);
|
||||
// 准备自定义属性,包含字段的 options 配置
|
||||
Map<String, Object> customProps = prepareCustomProperties(fieldsMap);
|
||||
FsTableUtil.setTableOptions(spreadsheetToken, headerWithColumnIdentifiers, fieldsMap,
|
||||
sheetId, config.isEnableDesc(), customProps);
|
||||
FsTableUtil.setTableOptions(spreadsheetToken, headerWithColumnIdentifiers, fieldsMap,
|
||||
sheetId, config.isEnableDesc(), customProps);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(MapSheetBuilder.class.getName()).log(Level.SEVERE,
|
||||
"【Map表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
|
||||
"【Map表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
|
||||
}
|
||||
|
||||
|
||||
return sheetId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将 MapFieldDefinition 列表转换为 FieldProperty Map
|
||||
*/
|
||||
private Map<String, FieldProperty> convertToFieldsMap(List<MapFieldDefinition> fields) {
|
||||
Map<String, FieldProperty> fieldsMap = new LinkedHashMap<>();
|
||||
|
||||
|
||||
for (MapFieldDefinition field : fields) {
|
||||
TableProperty tableProperty = createTableProperty(field);
|
||||
FieldProperty fieldProperty = new FieldProperty(field.getFieldName(), tableProperty);
|
||||
fieldsMap.put(field.getFieldName(), fieldProperty);
|
||||
}
|
||||
|
||||
|
||||
return fieldsMap;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据 MapFieldDefinition 创建 TableProperty 注解实例
|
||||
*/
|
||||
@ -371,67 +371,67 @@ public class MapSheetBuilder {
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return TableProperty.class;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] value() {
|
||||
return new String[]{field.getFieldName()};
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String desc() {
|
||||
return field.getDescription() != null ? field.getDescription() : "";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String field() {
|
||||
return field.getFieldName();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return field.getOrder();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public TypeEnum type() {
|
||||
return field.getType() != null ? field.getType() : TypeEnum.TEXT;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<? extends BaseEnum> enumClass() {
|
||||
// 优先级1:如果配置了 enumClass,直接返回
|
||||
if (field.getEnumClass() != null && field.getEnumClass() != BaseEnum.class) {
|
||||
return field.getEnumClass();
|
||||
}
|
||||
|
||||
|
||||
// 优先级2:如果没有配置 enumClass 但配置了 options,创建动态枚举类
|
||||
// 注意:这里返回 BaseEnum.class,实际的 options 通过 optionsClass 处理
|
||||
return BaseEnum.class;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<? extends FieldValueProcess> fieldFormatClass() {
|
||||
return FieldValueProcess.class;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<? extends OptionsValueProcess> optionsClass() {
|
||||
// 优先级1:如果配置了 optionsClass,直接返回
|
||||
if (field.getOptionsClass() != null && field.getOptionsClass() != OptionsValueProcess.class) {
|
||||
return field.getOptionsClass();
|
||||
}
|
||||
|
||||
|
||||
// 优先级2:如果配置了 options 但没有 optionsClass,创建动态的处理类
|
||||
if (field.getOptions() != null && !field.getOptions().isEmpty()) {
|
||||
return MapOptionsUtil.createDynamicOptionsClass(field.getOptions());
|
||||
}
|
||||
|
||||
|
||||
// 优先级3:返回默认值
|
||||
return OptionsValueProcess.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建 TableConf 注解实例
|
||||
*/
|
||||
@ -441,50 +441,56 @@ public class MapSheetBuilder {
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return TableConf.class;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] uniKeys() {
|
||||
Set<String> uniKeyNames = config.getUniKeyNames();
|
||||
return uniKeyNames != null ? uniKeyNames.toArray(new String[0]) : new String[0];
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int headLine() {
|
||||
return config.getHeadLine();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int titleRow() {
|
||||
return config.getTitleRow();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean enableCover() {
|
||||
return config.isEnableCover();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isText() {
|
||||
return config.isText();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean enableDesc() {
|
||||
return config.isEnableDesc();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String headFontColor() {
|
||||
return config.getHeadFontColor();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String headBackColor() {
|
||||
return config.getHeadBackColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean upsert() {
|
||||
// MapSheetConfig 继承自 MapTableConfig,支持 upsert 配置
|
||||
return config.isUpsert();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建字段描述映射
|
||||
*/
|
||||
@ -497,20 +503,20 @@ public class MapSheetBuilder {
|
||||
}
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 准备自定义属性
|
||||
*
|
||||
*
|
||||
* 将字段配置的 options 放入 customProperties,供 DynamicOptionsProcess 使用
|
||||
*/
|
||||
private Map<String, Object> prepareCustomProperties(Map<String, FieldProperty> fieldsMap) {
|
||||
Map<String, Object> customProps = new HashMap<>();
|
||||
|
||||
|
||||
// 复制原有的自定义属性
|
||||
if (config.getCustomProperties() != null) {
|
||||
customProps.putAll(config.getCustomProperties());
|
||||
}
|
||||
|
||||
|
||||
// 为每个配置了 options 的字段添加选项到 customProperties
|
||||
for (MapFieldDefinition field : config.getFields()) {
|
||||
if (field.getOptions() != null && !field.getOptions().isEmpty()) {
|
||||
@ -518,8 +524,7 @@ public class MapSheetBuilder {
|
||||
customProps.put("_dynamicOptions_" + field.getFieldName(), field.getOptions());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return customProps;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package cn.isliu.core.builder;
|
||||
|
||||
import cn.isliu.core.*;
|
||||
import cn.isliu.core.annotation.TableConf;
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.client.FsClient;
|
||||
import cn.isliu.core.config.MapTableConfig;
|
||||
@ -9,6 +10,7 @@ import cn.isliu.core.enums.FileType;
|
||||
import cn.isliu.core.logging.FsLogger;
|
||||
import cn.isliu.core.service.CustomValueService;
|
||||
import cn.isliu.core.utils.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@ -26,12 +28,13 @@ import static cn.isliu.core.utils.FsTableUtil.*;
|
||||
* @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;
|
||||
|
||||
private String groupField;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
@ -45,7 +48,7 @@ public class MapWriteBuilder {
|
||||
this.dataList = dataList;
|
||||
this.config = MapTableConfig.createDefault();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置表格配置
|
||||
*
|
||||
@ -56,7 +59,7 @@ public class MapWriteBuilder {
|
||||
this.config = config;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置标题行
|
||||
*
|
||||
@ -67,7 +70,7 @@ public class MapWriteBuilder {
|
||||
this.config.setTitleRow(titleRow);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置数据起始行
|
||||
*
|
||||
@ -78,7 +81,7 @@ public class MapWriteBuilder {
|
||||
this.config.setHeadLine(headLine);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置唯一键字段
|
||||
*
|
||||
@ -89,7 +92,7 @@ public class MapWriteBuilder {
|
||||
this.config.setUniKeyNames(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加唯一键字段
|
||||
*
|
||||
@ -100,7 +103,7 @@ public class MapWriteBuilder {
|
||||
this.config.addUniKeyName(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否覆盖已存在数据
|
||||
*
|
||||
@ -111,7 +114,7 @@ public class MapWriteBuilder {
|
||||
this.config.setEnableCover(enableCover);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否忽略未找到的数据
|
||||
*
|
||||
@ -122,7 +125,35 @@ public class MapWriteBuilder {
|
||||
this.config.setIgnoreNotFound(ignoreNotFound);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否启用 Upsert 模式
|
||||
*
|
||||
* true(默认):根据唯一键匹配,存在则更新,不存在则追加
|
||||
* false:不匹配唯一键,所有数据直接追加到表格末尾
|
||||
*
|
||||
* @param upsert true 为 Upsert 模式,false 为纯追加模式
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder upsert(boolean upsert) {
|
||||
this.config.setUpsert(upsert);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分组字段
|
||||
*
|
||||
* 配置分组字段,用于处理数据行分组。
|
||||
* 当数据行存在分组字段时,将按照分组字段进行分组,并分别处理每个分组。
|
||||
*
|
||||
* @param groupField 分组字段名称
|
||||
* @return MapWriteBuilder实例
|
||||
*/
|
||||
public MapWriteBuilder groupField(String groupField) {
|
||||
this.groupField = groupField;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据写入
|
||||
*
|
||||
@ -133,73 +164,156 @@ public class MapWriteBuilder {
|
||||
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());
|
||||
|
||||
|
||||
// 根据 upsert 配置决定是否需要读取现有数据用于匹配
|
||||
Map<String, Integer> currTableRowMap = new HashMap<>();
|
||||
int nextAvailableRow = config.getHeadLine();
|
||||
int headLine = config.getHeadLine();
|
||||
int titleRow = config.getTitleRow();
|
||||
List<FsTableData> fsTableDataList;
|
||||
|
||||
if (config.isUpsert()) {
|
||||
// Upsert 模式:读取现有数据用于匹配和更新
|
||||
fsTableDataList = readExistingData(sheet, client, groupField);
|
||||
|
||||
if (!fsTableDataList.isEmpty()) {
|
||||
Map<String, String> fieldsPositionMap = fsTableDataList.get(0).getFieldsPositionMap();
|
||||
if (fieldsPositionMap != null) {
|
||||
titlePostionMap = fieldsPositionMap;
|
||||
}
|
||||
}
|
||||
|
||||
currTableRowMap = getCurrTableRowMap(fsTableDataList, titleRow, titlePostionMap, headLine);
|
||||
|
||||
nextAvailableRow = calculateNextAvailableRow(currTableRowMap, config.getHeadLine());
|
||||
} else {
|
||||
// 纯追加模式:只需要读取现有数据获取最大行号
|
||||
fsTableDataList = readMaxRowNumber(sheet, client, groupField);
|
||||
|
||||
if (!fsTableDataList.isEmpty()) {
|
||||
Map<String, String> fieldsPositionMap = fsTableDataList.get(0).getFieldsPositionMap();
|
||||
if (fieldsPositionMap != null) {
|
||||
titlePostionMap = fieldsPositionMap;
|
||||
}
|
||||
}
|
||||
|
||||
// 找到数据行中的最大行号
|
||||
int maxRow = fsTableDataList.stream()
|
||||
.filter(fsTableData -> fsTableData.getRow() >= headLine)
|
||||
.mapToInt(FsTableData::getRow)
|
||||
.max()
|
||||
.orElse(headLine - 1);
|
||||
|
||||
nextAvailableRow = maxRow + 1;
|
||||
}
|
||||
|
||||
// 初始化批量插入对象
|
||||
CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder =
|
||||
CustomValueService.ValueRequest.batchPutValues();
|
||||
|
||||
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()) {
|
||||
// 插入新行
|
||||
|
||||
if (config.isUpsert()) {
|
||||
// Upsert 模式:计算 uniqueId 并匹配更新或追加
|
||||
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());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 纯追加模式:不计算 uniqueId,所有数据直接追加到表格末尾
|
||||
for (Map<String, Object> data : dataList) {
|
||||
int newRow = rowCount.incrementAndGet();
|
||||
processDataRow(data, titlePostionMap, newRow, resultValuesBuilder,
|
||||
fileDataList, config.isEnableCover());
|
||||
processDataRow(data, titlePostionMap, newRow, resultValuesBuilder,
|
||||
fileDataList, config.isEnableCover());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 检查是否需要扩展行数
|
||||
ensureSufficientRows(sheet, rowCount.get(), client);
|
||||
|
||||
|
||||
// 上传文件
|
||||
uploadFiles(fileDataList, client);
|
||||
|
||||
|
||||
// 批量写入数据
|
||||
return batchWriteValues(resultValuesBuilder, client);
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
private Map<String, Integer> getCurrTableRowMap(List<FsTableData> fsTableDataList, int titleRow,
|
||||
Map<String, String> titlePostionMap, int headLine) {
|
||||
Map<String, Integer> currTableRowMap;
|
||||
// 获取标题映射
|
||||
Map<String, String> titleMap = new HashMap<>();
|
||||
fsTableDataList.stream()
|
||||
.filter(d -> d.getRow() == (titleRow - 1))
|
||||
.findFirst()
|
||||
.ifPresent(d -> {
|
||||
Map<String, String> map = (Map<String, String>) d.getData();
|
||||
titleMap.putAll(map);
|
||||
});
|
||||
|
||||
// 转换为带字段名的数据,并计算唯一ID
|
||||
currTableRowMap = fsTableDataList.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));
|
||||
return currTableRowMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取字段位置映射
|
||||
*/
|
||||
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
|
||||
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()) {
|
||||
@ -215,37 +329,37 @@ public class MapWriteBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return fieldsPositionMap;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取现有数据
|
||||
*
|
||||
* @param groupField 分组字段名称,如果为null则读取全部数据
|
||||
*/
|
||||
private Map<String, Integer> readExistingData(Sheet sheet, FeishuClient client, Map<String, String> titlePostionMap) {
|
||||
int headLine = config.getHeadLine();
|
||||
int titleRow = config.getTitleRow();
|
||||
private List<FsTableData> readExistingData(Sheet sheet, FeishuClient client, String groupField) {
|
||||
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
|
||||
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) {
|
||||
@ -254,44 +368,28 @@ public class MapWriteBuilder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 处理表格数据
|
||||
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));
|
||||
|
||||
// 根据是否有分组字段,选择不同的处理方式
|
||||
List<FsTableData> dataList;
|
||||
if (groupField == null || groupField.isEmpty()) {
|
||||
// 无分组:读取全部数据
|
||||
dataList = getFsTableData(tableData, new ArrayList<>());
|
||||
} else {
|
||||
// 有分组:需要重新调用完整的分组读取方法
|
||||
// 创建临时的 TableConf 用于分组读取
|
||||
TableConf tempTableConf = createTempTableConf();
|
||||
Map<String, List<FsTableData>> groupDataMap = FsTableUtil.getGroupFsTableData(
|
||||
sheet, spreadsheetToken, tempTableConf, new ArrayList<>(), new HashMap<>()
|
||||
);
|
||||
dataList = groupDataMap.getOrDefault(groupField, new ArrayList<>());
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算下一个可用行号
|
||||
*/
|
||||
@ -299,26 +397,142 @@ public class MapWriteBuilder {
|
||||
if (currTableRowMap.isEmpty()) {
|
||||
return headLine;
|
||||
}
|
||||
|
||||
|
||||
return currTableRowMap.values().stream()
|
||||
.max(Integer::compareTo)
|
||||
.map(maxRow -> maxRow + 1)
|
||||
.orElse(headLine);
|
||||
.max(Integer::compareTo)
|
||||
.map(maxRow -> maxRow + 1)
|
||||
.orElse(headLine);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 读取表格最大行号(用于纯追加模式)
|
||||
*
|
||||
* 只读取数据获取最大行号,不计算 uniqueId 和构建映射表
|
||||
*
|
||||
* @param groupField 分组字段名称,如果为null则读取全部数据
|
||||
*/
|
||||
private List<FsTableData> readMaxRowNumber(Sheet sheet, FeishuClient client, String groupField) {
|
||||
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;
|
||||
if (groupField == null || groupField.isEmpty()) {
|
||||
// 无分组:读取全部数据
|
||||
dataList = getFsTableData(tableData, new ArrayList<>());
|
||||
} else {
|
||||
// 有分组:需要重新调用完整的分组读取方法
|
||||
// 创建临时的 TableConf 用于分组读取
|
||||
TableConf tempTableConf = createTempTableConf();
|
||||
Map<String, List<FsTableData>> groupDataMap = FsTableUtil.getGroupFsTableData(
|
||||
sheet, spreadsheetToken, tempTableConf, new ArrayList<>(), new HashMap<>()
|
||||
);
|
||||
dataList = groupDataMap.getOrDefault(groupField, new ArrayList<>());
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建临时的 TableConf 对象(用于分组读取)
|
||||
*/
|
||||
private TableConf createTempTableConf() {
|
||||
return new TableConf() {
|
||||
@Override
|
||||
public Class<? extends java.lang.annotation.Annotation> annotationType() {
|
||||
return TableConf.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] uniKeys() {
|
||||
return config.getUniKeyNames().toArray(new String[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int headLine() {
|
||||
return config.getHeadLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int titleRow() {
|
||||
return config.getTitleRow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableCover() {
|
||||
return config.isEnableCover();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isText() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableDesc() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String headFontColor() {
|
||||
return "#ffffff";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String headBackColor() {
|
||||
return "#000000";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean upsert() {
|
||||
return config.isUpsert();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单行数据
|
||||
*/
|
||||
private void processDataRow(Map<String, Object> data, Map<String, String> titlePostionMap,
|
||||
int rowNum, CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder,
|
||||
List<FileData> fileDataList, boolean enableCover) {
|
||||
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;
|
||||
@ -330,15 +544,15 @@ public class MapWriteBuilder {
|
||||
fileDataList.add(fileData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 添加到批量写入
|
||||
if (enableCover || fieldValue != null) {
|
||||
if (enableCover || (fieldValue != null && !(fieldValue instanceof FileData))) {
|
||||
resultValuesBuilder.addRange(sheetId, position + rowNum, position + rowNum)
|
||||
.addRow(GenerateUtil.getRowData(fieldValue));
|
||||
.addRow(GenerateUtil.getRowData(fieldValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 确保行数足够
|
||||
*/
|
||||
@ -348,7 +562,7 @@ public class MapWriteBuilder {
|
||||
FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", Math.abs(requiredRows - rowTotal), client);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
@ -356,20 +570,20 @@ public class MapWriteBuilder {
|
||||
fileDataList.forEach(fileData -> {
|
||||
try {
|
||||
FsApiUtil.imageUpload(
|
||||
fileData.getImageData(),
|
||||
fileData.getFileName(),
|
||||
fileData.getPosition(),
|
||||
fileData.getSheetId(),
|
||||
fileData.getSpreadsheetToken(),
|
||||
client
|
||||
fileData.getImageData(),
|
||||
fileData.getFileName(),
|
||||
fileData.getPosition(),
|
||||
fileData.getSheetId(),
|
||||
fileData.getSpreadsheetToken(),
|
||||
client
|
||||
);
|
||||
} catch (Exception e) {
|
||||
FsLogger.error(ErrorCode.BUSINESS_LOGIC_ERROR,
|
||||
"【飞书表格】Map写入-文件上传异常! " + fileData.getFileUrl());
|
||||
FsLogger.error(ErrorCode.BUSINESS_LOGIC_ERROR,
|
||||
"【飞书表格】Map写入-文件上传异常! " + fileData.getFileUrl());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量写入数据
|
||||
*/
|
||||
@ -378,11 +592,11 @@ public class MapWriteBuilder {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ public class WriteBuilder<T> {
|
||||
private Class<?> clazz;
|
||||
private boolean ignoreNotFound;
|
||||
private String groupField;
|
||||
private Boolean upsert;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
@ -106,6 +107,22 @@ public class WriteBuilder<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否启用 Upsert 模式
|
||||
*
|
||||
* 此方法设置的值会覆盖 @TableConf 注解中的配置。
|
||||
*
|
||||
* true(默认):根据唯一键匹配,存在则更新,不存在则追加
|
||||
* false:不匹配唯一键,所有数据直接追加到表格末尾
|
||||
*
|
||||
* @param upsert true 为 Upsert 模式,false 为纯追加模式
|
||||
* @return WriteBuilder实例,支持链式调用
|
||||
*/
|
||||
public WriteBuilder<T> upsert(boolean upsert) {
|
||||
this.upsert = upsert;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据写入并返回操作结果
|
||||
*
|
||||
@ -133,6 +150,10 @@ public class WriteBuilder<T> {
|
||||
Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
|
||||
|
||||
TableConf tableConf = aClass != null ? PropertyUtil.getTableConf(aClass) : PropertyUtil.getTableConf(sourceClass);
|
||||
|
||||
// 确定最终的 upsert 值:Builder 方法参数优先,否则使用注解配置
|
||||
boolean finalUpsert = (this.upsert != null) ? this.upsert : tableConf.upsert();
|
||||
|
||||
Map<String, String> titlePostionMap = FsTableUtil.getTitlePostionMap(sheet, spreadsheetToken, tableConf);
|
||||
Set<String> keys = titlePostionMap.keySet();
|
||||
|
||||
@ -161,12 +182,18 @@ public class WriteBuilder<T> {
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Integer> currTableRowMap = fsTableDataList.stream()
|
||||
.filter(fsTableData -> fsTableData.getRow() >= tableConf.headLine())
|
||||
.collect(Collectors.toMap(
|
||||
FsTableData::getUniqueId,
|
||||
FsTableData::getRow,
|
||||
(existing, replacement) -> existing));
|
||||
// 根据 finalUpsert 决定是否构建映射表
|
||||
Map<String, Integer> currTableRowMap = new HashMap<>();
|
||||
if (finalUpsert) {
|
||||
// Upsert 模式:构建 uniqueId 到行号的映射表
|
||||
currTableRowMap = fsTableDataList.stream()
|
||||
.filter(fsTableData -> fsTableData.getRow() >= tableConf.headLine())
|
||||
.collect(Collectors.toMap(
|
||||
FsTableData::getUniqueId,
|
||||
FsTableData::getRow,
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
}
|
||||
|
||||
final Integer[] row = {tableConf.headLine()};
|
||||
fsTableDataList.forEach(fsTableData -> {
|
||||
@ -182,54 +209,76 @@ public class WriteBuilder<T> {
|
||||
|
||||
AtomicInteger rowCount = new AtomicInteger(row[0]);
|
||||
|
||||
for (T data : dataList) {
|
||||
Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
|
||||
if (finalUpsert) {
|
||||
// Upsert 模式:计算 uniqueId 并匹配更新或追加
|
||||
for (T data : dataList) {
|
||||
Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
|
||||
|
||||
// 计算唯一标识:如果data类型与aClass相同,使用忽略字段逻辑;否则直接从data获取uniqueId
|
||||
String uniqueId;
|
||||
if (data.getClass().equals(aClass)) {
|
||||
// 类型相同,使用忽略字段逻辑计算唯一标识
|
||||
uniqueId = calculateUniqueIdWithIgnoreFields(data, processedIgnoreFields, tableConf);
|
||||
} else {
|
||||
uniqueId = GenerateUtil.getUniqueId(data, tableConf);
|
||||
// 计算唯一标识:如果data类型与aClass相同,使用忽略字段逻辑;否则直接从data获取uniqueId
|
||||
String uniqueId;
|
||||
if (data.getClass().equals(aClass)) {
|
||||
// 类型相同,使用忽略字段逻辑计算唯一标识
|
||||
uniqueId = calculateUniqueIdWithIgnoreFields(data, processedIgnoreFields, tableConf);
|
||||
} else {
|
||||
uniqueId = GenerateUtil.getUniqueId(data, tableConf);
|
||||
}
|
||||
|
||||
AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
|
||||
if (uniqueId != null && rowNum.get() != null) {
|
||||
// 找到匹配的行 → 更新
|
||||
rowNum.set(rowNum.get() + 1);
|
||||
Map<String, String> finalTitlePostionMap = titlePostionMap;
|
||||
values.forEach((field, fieldValue) -> {
|
||||
String position = finalTitlePostionMap.get(field);
|
||||
|
||||
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.get());
|
||||
fileDataList.add(fileData);
|
||||
}
|
||||
}
|
||||
if (tableConf.enableCover() || fieldValue != null) {
|
||||
resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
|
||||
.addRow(GenerateUtil.getRowData(fieldValue));
|
||||
}
|
||||
});
|
||||
} else if (!ignoreNotFound) {
|
||||
// 未找到 && ignoreNotFound = false → 追加
|
||||
int rowCou = rowCount.incrementAndGet();
|
||||
Map<String, String> finalTitlePostionMap1 = titlePostionMap;
|
||||
values.forEach((field, fieldValue) -> {
|
||||
|
||||
String position = finalTitlePostionMap1.get(field);
|
||||
if (fieldValue instanceof FileData) {
|
||||
FileData fileData = (FileData) fieldValue;
|
||||
fileData.setSheetId(sheetId);
|
||||
fileData.setSpreadsheetToken(spreadsheetToken);
|
||||
fileData.setPosition(position + rowCou);
|
||||
fileDataList.add(fileData);
|
||||
}
|
||||
|
||||
if (tableConf.enableCover() || fieldValue != null) {
|
||||
resultValuesBuilder.addRange(sheetId, position + rowCou, position + rowCou)
|
||||
.addRow(GenerateUtil.getRowData(fieldValue));
|
||||
}
|
||||
});
|
||||
}
|
||||
// else: 未找到 && ignoreNotFound = true → 跳过该数据
|
||||
}
|
||||
} else {
|
||||
// 纯追加模式:不计算 uniqueId,所有数据直接追加到表格末尾
|
||||
for (T data : dataList) {
|
||||
Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
|
||||
|
||||
AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
|
||||
if (uniqueId != null && rowNum.get() != null) {
|
||||
rowNum.set(rowNum.get() + 1);
|
||||
int rowCou = rowCount.incrementAndGet();
|
||||
Map<String, String> finalTitlePostionMap = titlePostionMap;
|
||||
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();
|
||||
if (fileType.equals(FileType.IMAGE.getType())) {
|
||||
fileData.setSheetId(sheetId);
|
||||
fileData.setSpreadsheetToken(spreadsheetToken);
|
||||
fileData.setPosition(position + rowNum.get());
|
||||
fileDataList.add(fileData);
|
||||
}
|
||||
}
|
||||
if (tableConf.enableCover() || fieldValue != null) {
|
||||
resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
|
||||
.addRow(GenerateUtil.getRowData(fieldValue));
|
||||
}
|
||||
});
|
||||
} else if (!ignoreNotFound) {
|
||||
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);
|
||||
|
||||
@ -12,27 +12,27 @@ import java.util.*;
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapSheetConfig extends MapTableConfig {
|
||||
|
||||
|
||||
/**
|
||||
* 字段定义列表
|
||||
*/
|
||||
private List<MapFieldDefinition> fields = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* 表头字体颜色(十六进制,如 #ffffff)
|
||||
*/
|
||||
private String headFontColor = "#ffffff";
|
||||
|
||||
|
||||
/**
|
||||
* 表头背景颜色(十六进制,如 #000000)
|
||||
*/
|
||||
private String headBackColor = "#000000";
|
||||
|
||||
|
||||
/**
|
||||
* 是否将单元格设置为纯文本格式
|
||||
*/
|
||||
private boolean isText = false;
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用字段描述行
|
||||
*/
|
||||
@ -98,12 +98,12 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
* 分组字段列表(用于创建分组表格)
|
||||
*/
|
||||
private List<String> groupFields = new ArrayList<>();
|
||||
|
||||
|
||||
/**
|
||||
* 自定义属性映射(用于传递额外配置)
|
||||
*/
|
||||
private Map<String, Object> customProperties = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* 创建默认配置
|
||||
*
|
||||
@ -112,7 +112,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
public static MapSheetConfig createDefault() {
|
||||
return new MapSheetConfig();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建表格配置构建器
|
||||
*
|
||||
@ -121,7 +121,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
public static SheetBuilder sheetBuilder() {
|
||||
return new SheetBuilder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加单个字段
|
||||
*
|
||||
@ -132,7 +132,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
this.fields.add(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量添加字段
|
||||
*
|
||||
@ -143,7 +143,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
this.fields.addAll(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量添加字段(可变参数)
|
||||
*
|
||||
@ -154,7 +154,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
this.fields.addAll(Arrays.asList(fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加分组字段
|
||||
*
|
||||
@ -165,7 +165,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
this.groupFields.add(groupField);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加自定义属性
|
||||
*
|
||||
@ -195,7 +195,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
*/
|
||||
public static class SheetBuilder {
|
||||
private final MapSheetConfig config = new MapSheetConfig();
|
||||
|
||||
|
||||
/**
|
||||
* 设置标题行行号
|
||||
*
|
||||
@ -206,7 +206,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.setTitleRow(titleRow);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置数据起始行行号
|
||||
*
|
||||
@ -217,7 +217,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.setHeadLine(headLine);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置唯一键字段名集合
|
||||
*
|
||||
@ -228,7 +228,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.setUniKeyNames(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加唯一键字段名
|
||||
*
|
||||
@ -239,7 +239,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.addUniKeyName(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否覆盖已存在数据
|
||||
*
|
||||
@ -250,7 +250,23 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.setEnableCover(enableCover);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否启用 Upsert 模式
|
||||
*
|
||||
* true(默认):根据唯一键匹配,存在则更新,不存在则追加
|
||||
* false:不匹配唯一键,所有数据直接追加到表格末尾
|
||||
*
|
||||
* @param upsert true 为 Upsert 模式,false 为纯追加模式
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder upsert(boolean upsert) {
|
||||
config.setUpsert(upsert);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置字段定义列表
|
||||
*
|
||||
@ -261,7 +277,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.fields = new ArrayList<>(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加单个字段
|
||||
*
|
||||
@ -272,7 +288,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.fields.add(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量添加字段
|
||||
*
|
||||
@ -283,7 +299,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.fields.addAll(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 批量添加字段(可变参数)
|
||||
*
|
||||
@ -294,7 +310,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.fields.addAll(Arrays.asList(fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置表头字体颜色
|
||||
*
|
||||
@ -305,7 +321,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.headFontColor = headFontColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置表头背景颜色
|
||||
*
|
||||
@ -316,7 +332,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.headBackColor = headBackColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置表头样式
|
||||
*
|
||||
@ -329,7 +345,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.headBackColor = backColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否将单元格设置为纯文本
|
||||
*
|
||||
@ -340,7 +356,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.isText = isText;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否启用字段描述行
|
||||
*
|
||||
@ -351,7 +367,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.enableDesc = enableDesc;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置分组字段列表
|
||||
*
|
||||
@ -362,7 +378,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.groupFields = new ArrayList<>(groupFields);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置分组字段(可变参数)
|
||||
*
|
||||
@ -373,7 +389,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.groupFields = Arrays.asList(groupFields);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加分组字段
|
||||
*
|
||||
@ -384,7 +400,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.groupFields.add(groupField);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置自定义属性映射
|
||||
*
|
||||
@ -395,7 +411,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.customProperties = new HashMap<>(customProperties);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加自定义属性
|
||||
*
|
||||
@ -407,7 +423,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
config.customProperties.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建配置对象
|
||||
*
|
||||
@ -418,7 +434,7 @@ public class MapSheetConfig extends MapTableConfig {
|
||||
if (config.fields.isEmpty()) {
|
||||
throw new IllegalArgumentException("字段定义列表不能为空");
|
||||
}
|
||||
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
@ -14,37 +14,44 @@ import java.util.Set;
|
||||
* @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;
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用 Upsert 模式
|
||||
* true(默认):根据唯一键匹配,存在则更新,不存在则追加
|
||||
* false:不匹配唯一键,所有数据直接追加到表格末尾
|
||||
*/
|
||||
private boolean upsert = true;
|
||||
|
||||
/**
|
||||
* 字段位置映射 (字段名 -> 列位置,如 "添加SPU" -> "A")
|
||||
*/
|
||||
private Map<String, String> fieldsPositionMap = new HashMap<>();
|
||||
|
||||
|
||||
/**
|
||||
* 获取标题行行号
|
||||
*
|
||||
@ -53,7 +60,7 @@ public class MapTableConfig {
|
||||
public int getTitleRow() {
|
||||
return titleRow;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置标题行行号
|
||||
*
|
||||
@ -64,7 +71,7 @@ public class MapTableConfig {
|
||||
this.titleRow = titleRow;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取数据起始行行号
|
||||
*
|
||||
@ -73,7 +80,7 @@ public class MapTableConfig {
|
||||
public int getHeadLine() {
|
||||
return headLine;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置数据起始行行号
|
||||
*
|
||||
@ -84,7 +91,7 @@ public class MapTableConfig {
|
||||
this.headLine = headLine;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取唯一键字段名集合
|
||||
*
|
||||
@ -93,7 +100,7 @@ public class MapTableConfig {
|
||||
public Set<String> getUniKeyNames() {
|
||||
return uniKeyNames;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置唯一键字段名集合
|
||||
*
|
||||
@ -104,7 +111,7 @@ public class MapTableConfig {
|
||||
this.uniKeyNames = uniKeyNames;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加唯一键字段名
|
||||
*
|
||||
@ -115,7 +122,7 @@ public class MapTableConfig {
|
||||
this.uniKeyNames.add(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否覆盖已存在数据
|
||||
*
|
||||
@ -124,7 +131,7 @@ public class MapTableConfig {
|
||||
public boolean isEnableCover() {
|
||||
return enableCover;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否覆盖已存在数据
|
||||
*
|
||||
@ -135,7 +142,7 @@ public class MapTableConfig {
|
||||
this.enableCover = enableCover;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否忽略未找到的数据
|
||||
*
|
||||
@ -144,7 +151,7 @@ public class MapTableConfig {
|
||||
public boolean isIgnoreNotFound() {
|
||||
return ignoreNotFound;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否忽略未找到的数据
|
||||
*
|
||||
@ -155,7 +162,31 @@ public class MapTableConfig {
|
||||
this.ignoreNotFound = ignoreNotFound;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 是否启用 Upsert 模式
|
||||
*
|
||||
* @return true 为 Upsert 模式,false 为纯追加模式
|
||||
*/
|
||||
public boolean isUpsert() {
|
||||
return upsert;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否启用 Upsert 模式
|
||||
*
|
||||
* true(默认):根据唯一键匹配,存在则更新,不存在则追加
|
||||
* false:不匹配唯一键,所有数据直接追加到表格末尾
|
||||
*
|
||||
* @param upsert true 为 Upsert 模式,false 为纯追加模式
|
||||
* @return MapTableConfig实例,支持链式调用
|
||||
*/
|
||||
public MapTableConfig setUpsert(boolean upsert) {
|
||||
this.upsert = upsert;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段位置映射
|
||||
*
|
||||
@ -164,7 +195,7 @@ public class MapTableConfig {
|
||||
public Map<String, String> getFieldsPositionMap() {
|
||||
return fieldsPositionMap;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置字段位置映射
|
||||
*
|
||||
@ -175,7 +206,7 @@ public class MapTableConfig {
|
||||
this.fieldsPositionMap = fieldsPositionMap;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建默认配置
|
||||
*
|
||||
@ -184,7 +215,7 @@ public class MapTableConfig {
|
||||
public static MapTableConfig createDefault() {
|
||||
return new MapTableConfig();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建配置构建器
|
||||
*
|
||||
@ -193,13 +224,13 @@ public class MapTableConfig {
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 配置构建器
|
||||
*/
|
||||
public static class Builder {
|
||||
private final MapTableConfig config = new MapTableConfig();
|
||||
|
||||
|
||||
/**
|
||||
* 设置标题行行号
|
||||
*
|
||||
@ -210,7 +241,7 @@ public class MapTableConfig {
|
||||
config.titleRow = titleRow;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置数据起始行行号
|
||||
*
|
||||
@ -221,7 +252,7 @@ public class MapTableConfig {
|
||||
config.headLine = headLine;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置唯一键字段名集合
|
||||
*
|
||||
@ -232,7 +263,7 @@ public class MapTableConfig {
|
||||
config.uniKeyNames = new HashSet<>(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 添加唯一键字段名
|
||||
*
|
||||
@ -243,7 +274,7 @@ public class MapTableConfig {
|
||||
config.uniKeyNames.add(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否覆盖已存在数据
|
||||
*
|
||||
@ -254,7 +285,7 @@ public class MapTableConfig {
|
||||
config.enableCover = enableCover;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否忽略未找到的数据
|
||||
*
|
||||
@ -265,7 +296,21 @@ public class MapTableConfig {
|
||||
config.ignoreNotFound = ignoreNotFound;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置是否启用 Upsert 模式
|
||||
*
|
||||
* true(默认):根据唯一键匹配,存在则更新,不存在则追加
|
||||
* false:不匹配唯一键,所有数据直接追加到表格末尾
|
||||
*
|
||||
* @param upsert true 为 Upsert 模式,false 为纯追加模式
|
||||
* @return Builder实例
|
||||
*/
|
||||
public Builder upsert(boolean upsert) {
|
||||
config.upsert = upsert;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段位置映射
|
||||
*
|
||||
@ -276,7 +321,7 @@ public class MapTableConfig {
|
||||
config.fieldsPositionMap = new HashMap<>(fieldsPositionMap);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建配置对象
|
||||
*
|
||||
|
||||
@ -484,6 +484,12 @@ public class PropertyUtil {
|
||||
public String headBackColor() {
|
||||
return "#cccccc";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean upsert() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
return tableConf;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user