feat(feishu): 增强飞书表格客户端功能和数据处理能力- 添加自定义服务支持,包括表格、行列、单元格、数据值等扩展服务
-优化客户端管理器,实现线程安全和客户端实例缓存 - 改进表格数据读取逻辑,支持字段映射和唯一标识生成 - 增强表格工具类功能,添加分组数据处理和表头模板构建 - 完善表格构建器,支持字段描述和下拉选项设置 -优化HTTP客户端配置,增加调用超时时间 - 修复数据处理中的空值判断逻辑- 添加表格样式设置和单元格合并功能
This commit is contained in:
		
							parent
							
								
									3c31130cbe
								
							
						
					
					
						commit
						59d5d937cc
					
				@ -118,7 +118,7 @@ public class FsHelper {
 | 
				
			|||||||
        TableConf tableConf = PropertyUtil.getTableConf(clazz);
 | 
					        TableConf tableConf = PropertyUtil.getTableConf(clazz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Map<String, FieldProperty> fieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
 | 
					        Map<String, FieldProperty> fieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
 | 
				
			||||||
        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf);
 | 
					        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf, fieldsMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
 | 
					        List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -179,10 +179,10 @@ public class FsHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        FeishuClient client = FsClient.getInstance().getClient();
 | 
					        FeishuClient client = FsClient.getInstance().getClient();
 | 
				
			||||||
        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
					        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
				
			||||||
        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf);
 | 
					        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf, fieldsMap);
 | 
				
			||||||
        Map<String, Integer> currTableRowMap = fsTableDataList.stream().collect(Collectors.toMap(FsTableData::getUniqueId, FsTableData::getRow));
 | 
					        Map<String, Integer> currTableRowMap = fsTableDataList.stream().collect(Collectors.toMap(FsTableData::getUniqueId, FsTableData::getRow));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final Integer[] row = {0};
 | 
					        final Integer[] row = {tableConf.headLine()};
 | 
				
			||||||
        fsTableDataList.forEach(fsTableData -> {
 | 
					        fsTableDataList.forEach(fsTableData -> {
 | 
				
			||||||
            if (fsTableData.getRow() > row[0]) {
 | 
					            if (fsTableData.getRow() > row[0]) {
 | 
				
			||||||
                row[0] = fsTableData.getRow();
 | 
					                row[0] = fsTableData.getRow();
 | 
				
			||||||
@ -190,32 +190,32 @@ public class FsHelper {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Map<String, String> titlePostionMap = FsTableUtil.getTitlePostionMap(sheet, spreadsheetToken, tableConf);
 | 
					        Map<String, String> titlePostionMap = FsTableUtil.getTitlePostionMap(sheet, spreadsheetToken, tableConf);
 | 
				
			||||||
 | 
					        Set<String> keys = titlePostionMap.keySet();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Map<String, String> fieldMap = new HashMap<>();
 | 
					        Map<String, String> fieldMap = new HashMap<>();
 | 
				
			||||||
        fieldsMap.forEach((field, fieldProperty) -> fieldMap.put(field, fieldProperty.getField()));
 | 
					        fieldsMap.forEach((field, fieldProperty) -> {
 | 
				
			||||||
 | 
					            if (keys.contains(field)) {
 | 
				
			||||||
 | 
					                fieldMap.put(field, fieldProperty.getField());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 初始化批量插入对象
 | 
					        // 初始化批量插入对象
 | 
				
			||||||
        CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder = CustomValueService.ValueRequest.batchPutValues();
 | 
					        CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder = CustomValueService.ValueRequest.batchPutValues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        List<FileData> fileDataList = new ArrayList<>();
 | 
					        List<FileData> fileDataList = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        AtomicInteger rowCount = new AtomicInteger(row[0] + 1);
 | 
					        AtomicInteger rowCount = new AtomicInteger(row[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (T data : dataList) {
 | 
					        for (T data : dataList) {
 | 
				
			||||||
            Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
 | 
					            Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            String uniqueId =  GenerateUtil.getUniqueId(data);
 | 
					            String uniqueId =  GenerateUtil.getUniqueId(data, tableConf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
 | 
					            AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
 | 
				
			||||||
            if (uniqueId != null && rowNum.get() != null) {
 | 
					            if (uniqueId != null && rowNum.get() != null) {
 | 
				
			||||||
                rowNum.set(rowNum.get() + 1);
 | 
					                rowNum.set(rowNum.get() + 1);
 | 
				
			||||||
                values.forEach((field, fieldValue) -> {
 | 
					                values.forEach((field, fieldValue) -> {
 | 
				
			||||||
                    if (!tableConf.enableCover() && fieldValue == null) {
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    String position = titlePostionMap.get(field);
 | 
					                    String position = titlePostionMap.get(field);
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if (fieldValue instanceof FileData) {
 | 
					                    if (fieldValue instanceof FileData) {
 | 
				
			||||||
                        FileData fileData = (FileData) fieldValue;
 | 
					                        FileData fileData = (FileData) fieldValue;
 | 
				
			||||||
                        String fileType = fileData.getFileType();
 | 
					                        String fileType = fileData.getFileType();
 | 
				
			||||||
@ -226,16 +226,15 @@ public class FsHelper {
 | 
				
			|||||||
                            fileDataList.add(fileData);
 | 
					                            fileDataList.add(fileData);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (tableConf.enableCover() || fieldValue != null) {
 | 
				
			||||||
                        resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
 | 
					                        resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
 | 
				
			||||||
                                .addRow(GenerateUtil.getRowData(fieldValue));
 | 
					                                .addRow(GenerateUtil.getRowData(fieldValue));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                int rowCou = rowCount.incrementAndGet();
 | 
					                int rowCou = rowCount.incrementAndGet();
 | 
				
			||||||
                values.forEach((field, fieldValue) -> {
 | 
					                values.forEach((field, fieldValue) -> {
 | 
				
			||||||
                    if (!tableConf.enableCover() && fieldValue == null) {
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    String position = titlePostionMap.get(field);
 | 
					                    String position = titlePostionMap.get(field);
 | 
				
			||||||
                    if (fieldValue instanceof FileData) {
 | 
					                    if (fieldValue instanceof FileData) {
 | 
				
			||||||
                        FileData fileData = (FileData) fieldValue;
 | 
					                        FileData fileData = (FileData) fieldValue;
 | 
				
			||||||
@ -244,15 +243,18 @@ public class FsHelper {
 | 
				
			|||||||
                        fileData.setPosition(position + rowCou);
 | 
					                        fileData.setPosition(position + rowCou);
 | 
				
			||||||
                        fileDataList.add(fileData);
 | 
					                        fileDataList.add(fileData);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (tableConf.enableCover() || fieldValue != null) {
 | 
				
			||||||
                        resultValuesBuilder.addRange(sheetId, position + rowCou, position + rowCou)
 | 
					                        resultValuesBuilder.addRange(sheetId, position + rowCou, position + rowCou)
 | 
				
			||||||
                                .addRow(GenerateUtil.getRowData(fieldValue));
 | 
					                                .addRow(GenerateUtil.getRowData(fieldValue));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int rowTotal = sheet.getGridProperties().getRowCount();
 | 
					        int rowTotal = sheet.getGridProperties().getRowCount();
 | 
				
			||||||
        int rowNum = rowCount.get();
 | 
					        int rowNum = rowCount.get();
 | 
				
			||||||
        if (rowNum > rowTotal) {
 | 
					        if (rowNum >= rowTotal) {
 | 
				
			||||||
            FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", rowTotal - rowNum, client);
 | 
					            FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", rowTotal - rowNum, client);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
package cn.isliu.core;
 | 
					package cn.isliu.core;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.Objects;
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class FsTableData {
 | 
					public class FsTableData {
 | 
				
			||||||
@ -7,6 +8,7 @@ public class FsTableData {
 | 
				
			|||||||
    private Integer row;
 | 
					    private Integer row;
 | 
				
			||||||
    private String uniqueId;
 | 
					    private String uniqueId;
 | 
				
			||||||
    private Object data;
 | 
					    private Object data;
 | 
				
			||||||
 | 
					    private Map<String, String> fieldsPositionMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public FsTableData() {
 | 
					    public FsTableData() {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -17,6 +19,13 @@ public class FsTableData {
 | 
				
			|||||||
        this.data = data;
 | 
					        this.data = data;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public FsTableData(Integer row, String uniqueId, Object data, Map<String, String> fieldsPositionMap) {
 | 
				
			||||||
 | 
					        this.row = row;
 | 
				
			||||||
 | 
					        this.uniqueId = uniqueId;
 | 
				
			||||||
 | 
					        this.data = data;
 | 
				
			||||||
 | 
					        this.fieldsPositionMap = fieldsPositionMap;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public Integer getRow() {
 | 
					    public Integer getRow() {
 | 
				
			||||||
        return row;
 | 
					        return row;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -41,16 +50,24 @@ public class FsTableData {
 | 
				
			|||||||
        this.data = data;
 | 
					        this.data = data;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Map<String, String> getFieldsPositionMap() {
 | 
				
			||||||
 | 
					        return fieldsPositionMap;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public void setFieldsPositionMap(Map<String, String> fieldsPositionMap) {
 | 
				
			||||||
 | 
					        this.fieldsPositionMap = fieldsPositionMap;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public boolean equals(Object o) {
 | 
					    public boolean equals(Object o) {
 | 
				
			||||||
        if (o == null || getClass() != o.getClass()) return false;
 | 
					        if (o == null || getClass() != o.getClass()) return false;
 | 
				
			||||||
        FsTableData that = (FsTableData) o;
 | 
					        FsTableData that = (FsTableData) o;
 | 
				
			||||||
        return Objects.equals(row, that.row) && Objects.equals(uniqueId, that.uniqueId) && Objects.equals(data, that.data);
 | 
					        return Objects.equals(row, that.row) && Objects.equals(uniqueId, that.uniqueId) && Objects.equals(data, that.data) && Objects.equals(fieldsPositionMap, that.fieldsPositionMap);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public int hashCode() {
 | 
					    public int hashCode() {
 | 
				
			||||||
        return Objects.hash(row, uniqueId, data);
 | 
					        return Objects.hash(row, uniqueId, data, fieldsPositionMap);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@ -59,6 +76,7 @@ public class FsTableData {
 | 
				
			|||||||
                "row=" + row +
 | 
					                "row=" + row +
 | 
				
			||||||
                ", uniqueId='" + uniqueId + '\'' +
 | 
					                ", uniqueId='" + uniqueId + '\'' +
 | 
				
			||||||
                ", data=" + data +
 | 
					                ", data=" + data +
 | 
				
			||||||
 | 
					                ", fieldsPositionMap=" + fieldsPositionMap +
 | 
				
			||||||
                '}';
 | 
					                '}';
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,14 @@ import java.lang.annotation.*;
 | 
				
			|||||||
@Retention(RetentionPolicy.RUNTIME)
 | 
					@Retention(RetentionPolicy.RUNTIME)
 | 
				
			||||||
@Inherited
 | 
					@Inherited
 | 
				
			||||||
public @interface TableConf {
 | 
					public @interface TableConf {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 表格唯一键
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @return 表格唯一键
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    String[] uniKeys() default {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 表头行数
 | 
					     * 表头行数
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
				
			|||||||
@ -74,7 +74,7 @@ public class ReadBuilder<T> {
 | 
				
			|||||||
        List<String> processedIgnoreFields = processIgnoreFields(fieldsMap);
 | 
					        List<String> processedIgnoreFields = processIgnoreFields(fieldsMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 使用支持忽略字段的方法获取表格数据
 | 
					        // 使用支持忽略字段的方法获取表格数据
 | 
				
			||||||
        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf, processedIgnoreFields);
 | 
					        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf, processedIgnoreFields, fieldsMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
 | 
					        List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -97,6 +97,46 @@ public class ReadBuilder<T> {
 | 
				
			|||||||
        return results;
 | 
					        return results;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public Map<String, List<T>> groupBuild() {
 | 
				
			||||||
 | 
					        Map<String, List<T>> results = new HashMap<>();
 | 
				
			||||||
 | 
					        FeishuClient client = FsClient.getInstance().getClient();
 | 
				
			||||||
 | 
					        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
				
			||||||
 | 
					        TableConf tableConf = PropertyUtil.getTableConf(clazz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Map<String, FieldProperty> fieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 处理忽略字段名称映射
 | 
				
			||||||
 | 
					        List<String> processedIgnoreFields = processIgnoreFields(fieldsMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 使用支持忽略字段的方法获取表格数据
 | 
				
			||||||
 | 
					        Map<String, List<FsTableData>> fsTableDataMap = FsTableUtil.getGroupFsTableData(sheet, spreadsheetToken, tableConf, processedIgnoreFields, fieldsMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fsTableDataMap.forEach((key, fsTableDataList) -> {
 | 
				
			||||||
 | 
					            List<T> groupResults = new ArrayList<>();
 | 
				
			||||||
 | 
					            fsTableDataList.stream().filter(tableData -> tableData.getRow() >= tableConf.headLine()).forEach(tableData -> {
 | 
				
			||||||
 | 
					                Object data = tableData.getData();
 | 
				
			||||||
 | 
					                if (data instanceof HashMap) {
 | 
				
			||||||
 | 
					                    Map<String, Object> rowData = (HashMap<String, Object>) data;
 | 
				
			||||||
 | 
					                    JsonObject jsonObject = JSONUtil.convertMapToJsonObject(rowData);
 | 
				
			||||||
 | 
					                    Map<String, Object> dataMap = ConvertFieldUtil.convertPositionToField(jsonObject, fieldsMap);
 | 
				
			||||||
 | 
					                    T t = GenerateUtil.generateInstance(fieldPathList, clazz, dataMap);
 | 
				
			||||||
 | 
					                    if (t instanceof BaseEntity) {
 | 
				
			||||||
 | 
					                        BaseEntity baseEntity = (BaseEntity) t;
 | 
				
			||||||
 | 
					                        baseEntity.setUniqueId(tableData.getUniqueId());
 | 
				
			||||||
 | 
					                        baseEntity.setRow(tableData.getRow());
 | 
				
			||||||
 | 
					                        baseEntity.setRowData(rowData);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    groupResults.add(t);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            results.put(key, groupResults);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return results;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 处理忽略字段名称映射
 | 
					     * 处理忽略字段名称映射
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
 | 
				
			|||||||
@ -10,10 +10,7 @@ import cn.isliu.core.utils.FsTableUtil;
 | 
				
			|||||||
import cn.isliu.core.utils.PropertyUtil;
 | 
					import cn.isliu.core.utils.PropertyUtil;
 | 
				
			||||||
import cn.isliu.core.utils.StringUtil;
 | 
					import cn.isliu.core.utils.StringUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.Level;
 | 
				
			||||||
import java.util.logging.Logger;
 | 
					import java.util.logging.Logger;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
@ -116,7 +113,9 @@ public class SheetBuilder<T> {
 | 
				
			|||||||
     * @return SheetBuilder实例,支持链式调用
 | 
					     * @return SheetBuilder实例,支持链式调用
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public SheetBuilder<T> fieldDescription(Map<String, String> fieldDescriptions) {
 | 
					    public SheetBuilder<T> fieldDescription(Map<String, String> fieldDescriptions) {
 | 
				
			||||||
 | 
					        if (fieldDescriptions != null && !fieldDescriptions.isEmpty()) {
 | 
				
			||||||
            this.fieldDescriptions.putAll(fieldDescriptions);
 | 
					            this.fieldDescriptions.putAll(fieldDescriptions);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -172,21 +171,21 @@ public class SheetBuilder<T> {
 | 
				
			|||||||
        // 2、添加表头数据
 | 
					        // 2、添加表头数据
 | 
				
			||||||
        FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf, fieldDescriptions), client);
 | 
					        FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf, fieldDescriptions), client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 3、设置表格样式
 | 
					        // 3、设置单元格为文本格式
 | 
				
			||||||
        FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, fieldsMap, tableConf), client, spreadsheetToken);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 4、合并单元格
 | 
					 | 
				
			||||||
        List<CustomCellService.CellRequest> mergeCell = FsTableUtil.getMergeCell(sheetId, fieldsMap);
 | 
					 | 
				
			||||||
        if (!mergeCell.isEmpty()) {
 | 
					 | 
				
			||||||
            mergeCell.forEach(cell -> FsApiUtil.mergeCells(cell, client, spreadsheetToken));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 5、设置单元格为文本格式
 | 
					 | 
				
			||||||
        if (tableConf.isText()) {
 | 
					        if (tableConf.isText()) {
 | 
				
			||||||
            String column = FsTableUtil.getColumnNameByNuNumber(headers.size());
 | 
					            String column = FsTableUtil.getColumnNameByNuNumber(headers.size());
 | 
				
			||||||
            FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
 | 
					            FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 4、设置表格样式
 | 
				
			||||||
 | 
					        FsApiUtil.setTableStyle(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、设置表格下拉
 | 
					        // 6、设置表格下拉
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, tableConf.enableDesc(), customProperties);
 | 
					            FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, tableConf.enableDesc(), customProperties);
 | 
				
			||||||
@ -197,6 +196,57 @@ public class SheetBuilder<T> {
 | 
				
			|||||||
        return sheetId;
 | 
					        return sheetId;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public String groupBuild(String ...groupFields) {
 | 
				
			||||||
 | 
					        // 获取所有字段映射
 | 
				
			||||||
 | 
					        Map<String, FieldProperty> allFieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 根据includeFields过滤字段映射
 | 
				
			||||||
 | 
					        Map<String, FieldProperty> fieldsMap = filterFieldsMap(allFieldsMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 生成表头
 | 
				
			||||||
 | 
					        List<String> headers = PropertyUtil.getHeaders(fieldsMap, includeFields);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 获取表格配置
 | 
				
			||||||
 | 
					        TableConf tableConf = PropertyUtil.getTableConf(clazz);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 创建飞书客户端
 | 
				
			||||||
 | 
					        FeishuClient client = FsClient.getInstance().getClient();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 1、创建sheet
 | 
				
			||||||
 | 
					        String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 2、添加表头数据
 | 
				
			||||||
 | 
					        List<String> groupFieldList = new ArrayList<>(Arrays.asList(groupFields));
 | 
				
			||||||
 | 
					        List<String> headerList = FsTableUtil.getGroupHeaders(groupFieldList, headers);
 | 
				
			||||||
 | 
					        FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers, headerList, fieldsMap, tableConf, fieldDescriptions, groupFieldList), client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 3、设置单元格为文本格式
 | 
				
			||||||
 | 
					        if (tableConf.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));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 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);
 | 
				
			||||||
 | 
					            FsTableUtil.setTableOptions(spreadsheetToken, headerWithColumnIdentifiers, fieldsMap, sheetId, tableConf.enableDesc(), customProperties);
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            Logger.getLogger(SheetBuilder.class.getName()).log(Level.SEVERE,"【表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sheetId;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据包含字段列表过滤字段映射
 | 
					     * 根据包含字段列表过滤字段映射
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -217,7 +267,7 @@ public class SheetBuilder<T> {
 | 
				
			|||||||
                    if (field.isEmpty()) {
 | 
					                    if (field.isEmpty()) {
 | 
				
			||||||
                        return false;
 | 
					                        return false;
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    return includeFields.contains(StringUtil.toUnderscoreCase(field));
 | 
					                    return includeFields.contains(field) || includeFields.contains(StringUtil.toUnderscoreCase(field));
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 | 
					                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -13,10 +13,7 @@ import cn.isliu.core.pojo.FieldProperty;
 | 
				
			|||||||
import cn.isliu.core.service.CustomValueService;
 | 
					import cn.isliu.core.service.CustomValueService;
 | 
				
			||||||
import cn.isliu.core.utils.*;
 | 
					import cn.isliu.core.utils.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.List;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
import java.util.concurrent.atomic.AtomicInteger;
 | 
					import java.util.concurrent.atomic.AtomicInteger;
 | 
				
			||||||
import java.util.concurrent.atomic.AtomicReference;
 | 
					import java.util.concurrent.atomic.AtomicReference;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
@ -34,6 +31,7 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
    private List<String> ignoreUniqueFields;
 | 
					    private List<String> ignoreUniqueFields;
 | 
				
			||||||
    private Class<?> clazz;
 | 
					    private Class<?> clazz;
 | 
				
			||||||
    private boolean ignoreNotFound;
 | 
					    private boolean ignoreNotFound;
 | 
				
			||||||
 | 
					    private String groupField;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 构造函数
 | 
					     * 构造函数
 | 
				
			||||||
@ -48,6 +46,7 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
        this.dataList = dataList;
 | 
					        this.dataList = dataList;
 | 
				
			||||||
        this.clazz = null;
 | 
					        this.clazz = null;
 | 
				
			||||||
        this.ignoreNotFound = false;
 | 
					        this.ignoreNotFound = false;
 | 
				
			||||||
 | 
					        this.groupField = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -93,6 +92,20 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
        return this;
 | 
					        return this;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 设置分组字段
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * 配置分组字段,用于处理数据行分组。
 | 
				
			||||||
 | 
					     * 当数据行存在分组字段时,将按照分组字段进行分组,并分别处理每个分组。
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param groupField 分组字段名称
 | 
				
			||||||
 | 
					     * @return WriteBuilder实例,支持链式调用
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public WriteBuilder<T> groupField(String groupField) {
 | 
				
			||||||
 | 
					        this.groupField = groupField;
 | 
				
			||||||
 | 
					        return this;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 执行数据写入并返回操作结果
 | 
					     * 执行数据写入并返回操作结果
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -107,45 +120,64 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        Class<?> aClass = clazz;
 | 
					        Class<?> aClass = clazz;
 | 
				
			||||||
        Map<String, FieldProperty> fieldsMap;
 | 
					        Map<String, FieldProperty> fieldsMap;
 | 
				
			||||||
        TableConf tableConf = PropertyUtil.getTableConf(aClass);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Map<String, String> fieldMap = new HashMap<>();
 | 
					        Map<String, String> fieldMap = new HashMap<>();
 | 
				
			||||||
        Class<?> sourceClass = dataList.get(0).getClass();
 | 
					        Class<?> sourceClass = dataList.get(0).getClass();
 | 
				
			||||||
        if (aClass.equals(sourceClass)) {
 | 
					        if (aClass != null && aClass.equals(sourceClass)) {
 | 
				
			||||||
            fieldsMap = PropertyUtil.getTablePropertyFieldsMap(aClass);
 | 
					            fieldsMap = PropertyUtil.getTablePropertyFieldsMap(aClass);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            fieldsMap = PropertyUtil.getTablePropertyFieldsMap(sourceClass);
 | 
					            fieldsMap = PropertyUtil.getTablePropertyFieldsMap(sourceClass);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fieldsMap.forEach((field, fieldProperty) -> fieldMap.put(field, fieldProperty.getField()));
 | 
					        FeishuClient client = FsClient.getInstance().getClient();
 | 
				
			||||||
 | 
					        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        TableConf tableConf = aClass != null ? PropertyUtil.getTableConf(aClass) : PropertyUtil.getTableConf(sourceClass);
 | 
				
			||||||
 | 
					        Map<String, String> titlePostionMap = FsTableUtil.getTitlePostionMap(sheet, spreadsheetToken, tableConf);
 | 
				
			||||||
 | 
					        Set<String> keys = titlePostionMap.keySet();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fieldsMap.forEach((field, fieldProperty) -> {
 | 
				
			||||||
 | 
					            if (keys.contains(field)) {
 | 
				
			||||||
 | 
					                fieldMap.put(field, fieldProperty.getField());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 处理忽略字段名称映射
 | 
					        // 处理忽略字段名称映射
 | 
				
			||||||
        List<String> processedIgnoreFields = processIgnoreFields(fieldsMap);
 | 
					        List<String> processedIgnoreFields = processIgnoreFields(fieldsMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        FeishuClient client = FsClient.getInstance().getClient();
 | 
					 | 
				
			||||||
        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // 使用支持忽略字段的方法获取表格数据
 | 
					        // 使用支持忽略字段的方法获取表格数据
 | 
				
			||||||
        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf, processedIgnoreFields);
 | 
					        List<FsTableData> fsTableDataList;
 | 
				
			||||||
        Map<String, Integer> currTableRowMap = fsTableDataList.stream().collect(Collectors.toMap(FsTableData::getUniqueId, FsTableData::getRow));
 | 
					        if (groupField == null) {
 | 
				
			||||||
 | 
					            fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf, processedIgnoreFields, fieldsMap);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Map<String, List<FsTableData>> groupFsTableData = FsTableUtil.getGroupFsTableData(sheet, spreadsheetToken, tableConf, processedIgnoreFields, fieldsMap);
 | 
				
			||||||
 | 
					            fsTableDataList = groupFsTableData.get(groupField);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        final Integer[] row = {0};
 | 
					        if (!fsTableDataList.isEmpty()) {
 | 
				
			||||||
 | 
					            Map<String, String> fieldsPositionMap = fsTableDataList.get(0).getFieldsPositionMap();
 | 
				
			||||||
 | 
					            if (fieldsPositionMap != null) {
 | 
				
			||||||
 | 
					                titlePostionMap = fieldsPositionMap;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Map<String, Integer> currTableRowMap = fsTableDataList.stream()
 | 
				
			||||||
 | 
					                .filter(fsTableData -> fsTableData.getRow() >= tableConf.headLine())
 | 
				
			||||||
 | 
					                .collect(Collectors.toMap(FsTableData::getUniqueId, FsTableData::getRow));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        final Integer[] row = {tableConf.headLine()};
 | 
				
			||||||
        fsTableDataList.forEach(fsTableData -> {
 | 
					        fsTableDataList.forEach(fsTableData -> {
 | 
				
			||||||
            if (fsTableData.getRow() > row[0]) {
 | 
					            if ((fsTableData.getRow() + 1) > row[0]) {
 | 
				
			||||||
                row[0] = fsTableData.getRow();
 | 
					                row[0] = fsTableData.getRow() + 1;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Map<String, String> titlePostionMap = FsTableUtil.getTitlePostionMap(sheet, spreadsheetToken, tableConf);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 初始化批量插入对象
 | 
					        // 初始化批量插入对象
 | 
				
			||||||
        CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder = CustomValueService.ValueRequest.batchPutValues();
 | 
					        CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder = CustomValueService.ValueRequest.batchPutValues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        List<FileData> fileDataList = new ArrayList<>();
 | 
					        List<FileData> fileDataList = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        AtomicInteger rowCount = new AtomicInteger(row[0] + 1);
 | 
					        AtomicInteger rowCount = new AtomicInteger(row[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (T data : dataList) {
 | 
					        for (T data : dataList) {
 | 
				
			||||||
            Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
 | 
					            Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
 | 
				
			||||||
@ -154,20 +186,17 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
            String uniqueId;
 | 
					            String uniqueId;
 | 
				
			||||||
            if (data.getClass().equals(aClass)) {
 | 
					            if (data.getClass().equals(aClass)) {
 | 
				
			||||||
                // 类型相同,使用忽略字段逻辑计算唯一标识
 | 
					                // 类型相同,使用忽略字段逻辑计算唯一标识
 | 
				
			||||||
                uniqueId = calculateUniqueIdWithIgnoreFields(data, processedIgnoreFields, aClass);
 | 
					                uniqueId = calculateUniqueIdWithIgnoreFields(data, processedIgnoreFields, tableConf);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                uniqueId = GenerateUtil.getUniqueId(data);
 | 
					                uniqueId = GenerateUtil.getUniqueId(data, tableConf);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
 | 
					            AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
 | 
				
			||||||
            if (uniqueId != null && rowNum.get() != null) {
 | 
					            if (uniqueId != null && rowNum.get() != null) {
 | 
				
			||||||
                rowNum.set(rowNum.get() + 1);
 | 
					                rowNum.set(rowNum.get() + 1);
 | 
				
			||||||
 | 
					                Map<String, String> finalTitlePostionMap = titlePostionMap;
 | 
				
			||||||
                values.forEach((field, fieldValue) -> {
 | 
					                values.forEach((field, fieldValue) -> {
 | 
				
			||||||
                    if (!tableConf.enableCover() && fieldValue == null) {
 | 
					                    String position = finalTitlePostionMap.get(field);
 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    String position = titlePostionMap.get(field);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    if (fieldValue instanceof FileData) {
 | 
					                    if (fieldValue instanceof FileData) {
 | 
				
			||||||
                        FileData fileData = (FileData) fieldValue;
 | 
					                        FileData fileData = (FileData) fieldValue;
 | 
				
			||||||
@ -179,17 +208,17 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
                            fileDataList.add(fileData);
 | 
					                            fileDataList.add(fileData);
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                    if (tableConf.enableCover() || fieldValue != null) {
 | 
				
			||||||
                        resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
 | 
					                        resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
 | 
				
			||||||
                                .addRow(GenerateUtil.getRowData(fieldValue));
 | 
					                                .addRow(GenerateUtil.getRowData(fieldValue));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            } else if (!ignoreNotFound) {
 | 
					            } else if (!ignoreNotFound) {
 | 
				
			||||||
                int rowCou = rowCount.incrementAndGet();
 | 
					                int rowCou = rowCount.incrementAndGet();
 | 
				
			||||||
 | 
					                Map<String, String> finalTitlePostionMap1 = titlePostionMap;
 | 
				
			||||||
                values.forEach((field, fieldValue) -> {
 | 
					                values.forEach((field, fieldValue) -> {
 | 
				
			||||||
                    if (!tableConf.enableCover() && fieldValue == null) {
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    String position = titlePostionMap.get(field);
 | 
					                    String position = finalTitlePostionMap1.get(field);
 | 
				
			||||||
                    if (fieldValue instanceof FileData) {
 | 
					                    if (fieldValue instanceof FileData) {
 | 
				
			||||||
                        FileData fileData = (FileData) fieldValue;
 | 
					                        FileData fileData = (FileData) fieldValue;
 | 
				
			||||||
                        fileData.setSheetId(sheetId);
 | 
					                        fileData.setSheetId(sheetId);
 | 
				
			||||||
@ -197,15 +226,18 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
                        fileData.setPosition(position + rowCou);
 | 
					                        fileData.setPosition(position + rowCou);
 | 
				
			||||||
                        fileDataList.add(fileData);
 | 
					                        fileDataList.add(fileData);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (tableConf.enableCover() || fieldValue != null) {
 | 
				
			||||||
                        resultValuesBuilder.addRange(sheetId, position + rowCou, position + rowCou)
 | 
					                        resultValuesBuilder.addRange(sheetId, position + rowCou, position + rowCou)
 | 
				
			||||||
                                .addRow(GenerateUtil.getRowData(fieldValue));
 | 
					                                .addRow(GenerateUtil.getRowData(fieldValue));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        int rowTotal = sheet.getGridProperties().getRowCount();
 | 
					        int rowTotal = sheet.getGridProperties().getRowCount();
 | 
				
			||||||
        int rowNum = rowCount.get();
 | 
					        int rowNum = rowCount.get();
 | 
				
			||||||
        if (rowNum > rowTotal) {
 | 
					        if (rowNum >= rowTotal) {
 | 
				
			||||||
            FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", rowTotal - rowNum, client);
 | 
					            FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", rowTotal - rowNum, client);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -264,17 +296,17 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
     *
 | 
					     *
 | 
				
			||||||
     * @param data 数据对象
 | 
					     * @param data 数据对象
 | 
				
			||||||
     * @param processedIgnoreFields 处理后的忽略字段列表
 | 
					     * @param processedIgnoreFields 处理后的忽略字段列表
 | 
				
			||||||
     * @param clazz 用于解析注解的实体类
 | 
					     * @param tableConf 用于解析注解的表格配置
 | 
				
			||||||
     * @return 唯一标识
 | 
					     * @return 唯一标识
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private String calculateUniqueIdWithIgnoreFields(T data, List<String> processedIgnoreFields, Class<?> clazz) {
 | 
					    private String calculateUniqueIdWithIgnoreFields(T data, List<String> processedIgnoreFields, TableConf tableConf) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            // 获取所有字段值
 | 
					            // 获取所有字段值
 | 
				
			||||||
            Map<String, Object> allFieldValues = GenerateUtil.getFieldValue(data, new HashMap<>());
 | 
					            Map<String, Object> allFieldValues = GenerateUtil.getFieldValue(data, new HashMap<>());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 如果不需要忽略字段,使用原有逻辑
 | 
					            // 如果不需要忽略字段,使用原有逻辑
 | 
				
			||||||
            if (processedIgnoreFields.isEmpty()) {
 | 
					            if (tableConf.uniKeys().length > 0 || processedIgnoreFields.isEmpty()) {
 | 
				
			||||||
                return GenerateUtil.getUniqueId(data);
 | 
					                return GenerateUtil.getUniqueId(data, tableConf);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // 移除忽略字段后计算唯一标识
 | 
					            // 移除忽略字段后计算唯一标识
 | 
				
			||||||
@ -287,7 +319,7 @@ public class WriteBuilder<T> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            // 如果计算失败,回退到原有逻辑
 | 
					            // 如果计算失败,回退到原有逻辑
 | 
				
			||||||
            return GenerateUtil.getUniqueId(data);
 | 
					            return GenerateUtil.getUniqueId(data, tableConf);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -23,8 +23,14 @@ public class FeishuClient {
 | 
				
			|||||||
    private final String appId;
 | 
					    private final String appId;
 | 
				
			||||||
    private final String appSecret;
 | 
					    private final String appSecret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 服务管理器,用于统一管理自定义服务实例
 | 
					    // 自定义服务,处理官方SDK未覆盖的API
 | 
				
			||||||
    private final ServiceManager<FeishuClient> serviceManager = new ServiceManager<>(this);
 | 
					    private volatile CustomSheetService customSheetService;
 | 
				
			||||||
 | 
					    private volatile CustomDimensionService customDimensionService;
 | 
				
			||||||
 | 
					    private volatile CustomCellService customCellService;
 | 
				
			||||||
 | 
					    private volatile CustomValueService customValueService;
 | 
				
			||||||
 | 
					    private volatile CustomDataValidationService customDataValidationService;
 | 
				
			||||||
 | 
					    private volatile CustomProtectedDimensionService customProtectedDimensionService;
 | 
				
			||||||
 | 
					    private volatile CustomFileService customFileService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private FeishuClient(String appId, String appSecret, Client officialClient, OkHttpClient httpClient) {
 | 
					    private FeishuClient(String appId, String appSecret, Client officialClient, OkHttpClient httpClient) {
 | 
				
			||||||
        this.appId = appId;
 | 
					        this.appId = appId;
 | 
				
			||||||
@ -68,7 +74,14 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展表格服务
 | 
					     * @return 扩展表格服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomSheetService customSheets() {
 | 
					    public CustomSheetService customSheets() {
 | 
				
			||||||
        return serviceManager.getService(CustomSheetService.class, () -> new CustomSheetService(this));
 | 
					        if (customSheetService == null) {
 | 
				
			||||||
 | 
					            synchronized (this) {
 | 
				
			||||||
 | 
					                if (customSheetService == null) {
 | 
				
			||||||
 | 
					                    customSheetService = new CustomSheetService(this);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return customSheetService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -77,7 +90,14 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展行列服务
 | 
					     * @return 扩展行列服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomDimensionService customDimensions() {
 | 
					    public CustomDimensionService customDimensions() {
 | 
				
			||||||
        return serviceManager.getService(CustomDimensionService.class, () -> new CustomDimensionService(this));
 | 
					        if (customDimensionService == null) {
 | 
				
			||||||
 | 
					            synchronized (this) {
 | 
				
			||||||
 | 
					                if (customDimensionService == null) {
 | 
				
			||||||
 | 
					                    customDimensionService = new CustomDimensionService(this);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return customDimensionService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -86,7 +106,14 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展单元格服务
 | 
					     * @return 扩展单元格服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomCellService customCells() {
 | 
					    public CustomCellService customCells() {
 | 
				
			||||||
        return serviceManager.getService(CustomCellService.class, () -> new CustomCellService(this));
 | 
					        if (customCellService == null) {
 | 
				
			||||||
 | 
					            synchronized (this) {
 | 
				
			||||||
 | 
					                if (customCellService == null) {
 | 
				
			||||||
 | 
					                    customCellService = new CustomCellService(this);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return customCellService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -95,7 +122,14 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展数据值服务
 | 
					     * @return 扩展数据值服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomValueService customValues() {
 | 
					    public CustomValueService customValues() {
 | 
				
			||||||
        return serviceManager.getService(CustomValueService.class, () -> new CustomValueService(this));
 | 
					        if (customValueService == null) {
 | 
				
			||||||
 | 
					            synchronized (this) {
 | 
				
			||||||
 | 
					                if (customValueService == null) {
 | 
				
			||||||
 | 
					                    customValueService = new CustomValueService(this);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return customValueService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -104,7 +138,14 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 自定义数据验证服务
 | 
					     * @return 自定义数据验证服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomDataValidationService customDataValidations() {
 | 
					    public CustomDataValidationService customDataValidations() {
 | 
				
			||||||
        return serviceManager.getService(CustomDataValidationService.class, () -> new CustomDataValidationService(this));
 | 
					        if (customDataValidationService == null) {
 | 
				
			||||||
 | 
					            synchronized (this) {
 | 
				
			||||||
 | 
					                if (customDataValidationService == null) {
 | 
				
			||||||
 | 
					                    customDataValidationService = new CustomDataValidationService(this);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return customDataValidationService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -113,7 +154,14 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展保护范围服务
 | 
					     * @return 扩展保护范围服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomProtectedDimensionService customProtectedDimensions() {
 | 
					    public CustomProtectedDimensionService customProtectedDimensions() {
 | 
				
			||||||
        return serviceManager.getService(CustomProtectedDimensionService.class, () -> new CustomProtectedDimensionService(this));
 | 
					        if (customProtectedDimensionService == null) {
 | 
				
			||||||
 | 
					            synchronized (this) {
 | 
				
			||||||
 | 
					                if (customProtectedDimensionService == null) {
 | 
				
			||||||
 | 
					                    customProtectedDimensionService = new CustomProtectedDimensionService(this);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return customProtectedDimensionService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -122,9 +170,15 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展文件服务
 | 
					     * @return 扩展文件服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomFileService customFiles() {
 | 
					    public CustomFileService customFiles() {
 | 
				
			||||||
        return serviceManager.getService(CustomFileService.class, () -> new CustomFileService(this));
 | 
					        if (customFileService == null) {
 | 
				
			||||||
 | 
					            synchronized (this) {
 | 
				
			||||||
 | 
					                if (customFileService == null) {
 | 
				
			||||||
 | 
					                    customFileService = new CustomFileService(this);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return customFileService;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 获取官方客户端
 | 
					     * 获取官方客户端
 | 
				
			||||||
@ -178,7 +232,7 @@ public class FeishuClient {
 | 
				
			|||||||
            // 默认OkHttp配置
 | 
					            // 默认OkHttp配置
 | 
				
			||||||
            this.httpClientBuilder =
 | 
					            this.httpClientBuilder =
 | 
				
			||||||
                    new OkHttpClient.Builder().connectTimeout(10, TimeUnit.MINUTES).readTimeout(10, TimeUnit.MINUTES)
 | 
					                    new OkHttpClient.Builder().connectTimeout(10, TimeUnit.MINUTES).readTimeout(10, TimeUnit.MINUTES)
 | 
				
			||||||
                            .writeTimeout(10, TimeUnit.MINUTES).connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES));
 | 
					                            .writeTimeout(10, TimeUnit.MINUTES).callTimeout(30, TimeUnit.MINUTES);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,8 @@
 | 
				
			|||||||
package cn.isliu.core.client;
 | 
					package cn.isliu.core.client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 线程安全的飞书客户端管理器
 | 
					 * 线程安全的飞书客户端管理器
 | 
				
			||||||
 * 使用ThreadLocal为每个线程维护独立的客户端实例
 | 
					 * 使用ThreadLocal为每个线程维护独立的客户端实例
 | 
				
			||||||
@ -8,6 +11,7 @@ public class FsClient implements AutoCloseable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private static volatile FsClient instance;
 | 
					    private static volatile FsClient instance;
 | 
				
			||||||
    private final ThreadLocal<FeishuClient> clientHolder = new ThreadLocal<>();
 | 
					    private final ThreadLocal<FeishuClient> clientHolder = new ThreadLocal<>();
 | 
				
			||||||
 | 
					    private final Map<String, FeishuClient> clientMap = new ConcurrentHashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 私有构造函数防止外部实例化
 | 
					    // 私有构造函数防止外部实例化
 | 
				
			||||||
    private FsClient() {
 | 
					    private FsClient() {
 | 
				
			||||||
@ -55,11 +59,17 @@ public class FsClient implements AutoCloseable {
 | 
				
			|||||||
        if (appSecret == null || appSecret.trim().isEmpty()) {
 | 
					        if (appSecret == null || appSecret.trim().isEmpty()) {
 | 
				
			||||||
            throw new IllegalArgumentException("appSecret cannot be null or empty");
 | 
					            throw new IllegalArgumentException("appSecret cannot be null or empty");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (clientMap.containsKey(appId + "_" + appSecret)) {
 | 
				
			||||||
 | 
					            FeishuClient feishuClient = clientMap.get(appId + "_" + appSecret);
 | 
				
			||||||
 | 
					            clientHolder.set(feishuClient);
 | 
				
			||||||
 | 
					            return feishuClient;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
            FeishuClient client = FeishuClient.newBuilder(appId, appSecret).build();
 | 
					            FeishuClient client = FeishuClient.newBuilder(appId, appSecret).build();
 | 
				
			||||||
 | 
					            clientMap.put(appId + "_" + appSecret, client);
 | 
				
			||||||
            clientHolder.set(client);
 | 
					            clientHolder.set(client);
 | 
				
			||||||
            return client;
 | 
					            return client;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 设置客户端实例(用于外部已构建的客户端)
 | 
					     * 设置客户端实例(用于外部已构建的客户端)
 | 
				
			||||||
 | 
				
			|||||||
@ -28,7 +28,6 @@ import java.util.stream.Collectors;
 | 
				
			|||||||
 * 包括数据处理、样式设置、选项设置等功能
 | 
					 * 包括数据处理、样式设置、选项设置等功能
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class FsTableUtil {
 | 
					public class FsTableUtil {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 获取飞书表格数据
 | 
					     * 获取飞书表格数据
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -38,8 +37,8 @@ public class FsTableUtil {
 | 
				
			|||||||
     * @param spreadsheetToken 电子表格Token
 | 
					     * @param spreadsheetToken 电子表格Token
 | 
				
			||||||
     * @return 飞书表格数据列表
 | 
					     * @return 飞书表格数据列表
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static List<FsTableData> getFsTableData(Sheet sheet, String spreadsheetToken, TableConf tableConf) {
 | 
					    public static List<FsTableData> getFsTableData(Sheet sheet, String spreadsheetToken, TableConf tableConf, Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
        return getFsTableData(sheet, spreadsheetToken, tableConf, new ArrayList<>());
 | 
					        return getFsTableData(sheet, spreadsheetToken, tableConf, new ArrayList<>(), fieldsMap);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -51,9 +50,129 @@ public class FsTableUtil {
 | 
				
			|||||||
     * @param ignoreUniqueFields 计算唯一标识时忽略的字段列表
 | 
					     * @param ignoreUniqueFields 计算唯一标识时忽略的字段列表
 | 
				
			||||||
     * @return 飞书表格数据列表
 | 
					     * @return 飞书表格数据列表
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static List<FsTableData> getFsTableData(Sheet sheet, String spreadsheetToken, TableConf tableConf, List<String> ignoreUniqueFields) {
 | 
					    public static Map<String, List<FsTableData>> getGroupFsTableData(Sheet sheet, String spreadsheetToken, TableConf tableConf, List<String> ignoreUniqueFields, Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // 计算数据范围
 | 
					        // 计算数据范围
 | 
				
			||||||
 | 
					        List<List<Object>> values = getSourceTableValues(sheet, spreadsheetToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 获取飞书表格数据
 | 
				
			||||||
 | 
					        TableData tableData = processSheetData(sheet, values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String[] uniKeys = tableConf.uniKeys();
 | 
				
			||||||
 | 
					        Set<String> uniKeyNames = getUniKeyNames(fieldsMap, uniKeys);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<FsTableData> dataList = getFsTableData(tableData, ignoreUniqueFields);
 | 
				
			||||||
 | 
					        Map<String, String> titleMap = new HashMap<>();
 | 
				
			||||||
 | 
					        Map<String, List<String>> categoryPositionMap = new HashMap<>();
 | 
				
			||||||
 | 
					        dataList.stream().filter(d -> d.getRow() == (tableConf.titleRow() - 2)).findFirst().ifPresent(d -> {
 | 
				
			||||||
 | 
					            Map<String, String> map = (Map<String, String>) d.getData();
 | 
				
			||||||
 | 
					            map.forEach((k, v) -> {
 | 
				
			||||||
 | 
					                if (v != null && !v.isEmpty()) {
 | 
				
			||||||
 | 
					                    categoryPositionMap.computeIfAbsent(v, k1 -> new ArrayList<>()).add(k);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        dataList.stream().filter(d -> d.getRow() == (tableConf.titleRow() - 1)).findFirst()
 | 
				
			||||||
 | 
					                .ifPresent(d -> {
 | 
				
			||||||
 | 
					                    Map<String, String> map = (Map<String, String>) d.getData();
 | 
				
			||||||
 | 
					                    map.forEach((k, v) -> {
 | 
				
			||||||
 | 
					                        if (v != null && !v.isEmpty()) {
 | 
				
			||||||
 | 
					                            titleMap.put(k + "_" + v, v);
 | 
				
			||||||
 | 
					                        } else {
 | 
				
			||||||
 | 
					                            titleMap.put(k, v);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        List<FsTableData> fsTableDataList = dataList.stream().filter(fsTableData -> fsTableData.getRow() >= (tableConf.headLine() -1)).map(item -> {
 | 
				
			||||||
 | 
					            Map<String, Object> resultMap = new HashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Map<String, Object> map = (Map<String, Object>) item.getData();
 | 
				
			||||||
 | 
					            map.forEach((k, v) -> {
 | 
				
			||||||
 | 
					                titleMap.forEach((k1, v1) -> {
 | 
				
			||||||
 | 
					                    if (k1.startsWith(k)) {
 | 
				
			||||||
 | 
					                        resultMap.put(k1, v);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					            item.setData(resultMap);
 | 
				
			||||||
 | 
					            return item;
 | 
				
			||||||
 | 
					        }).collect(Collectors.toList());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Map<String, List<FsTableData>> dataMap = new HashMap<>();
 | 
				
			||||||
 | 
					        categoryPositionMap.forEach((k1, v1) -> {
 | 
				
			||||||
 | 
					            List<FsTableData> fsList = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            for (FsTableData fsTableData : fsTableDataList) {
 | 
				
			||||||
 | 
					                Map<String, Object> resultMap = new HashMap<>();
 | 
				
			||||||
 | 
					                Map<String, String> fieldsPositionMap = new HashMap<>();
 | 
				
			||||||
 | 
					                Map<String, Object> data = (Map<String, Object>) fsTableData.getData();
 | 
				
			||||||
 | 
					                data.forEach((k, v) -> {
 | 
				
			||||||
 | 
					                    if (k != null) {
 | 
				
			||||||
 | 
					                        String[] split = k.split("_");
 | 
				
			||||||
 | 
					                        if (v1.contains(split[0]) && split.length > 1) {
 | 
				
			||||||
 | 
					                            resultMap.put(split[1], v);
 | 
				
			||||||
 | 
					                            fieldsPositionMap.put(split[1], split[0]);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if(areAllValuesNullOrBlank(resultMap)) {
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                String jsonStr;
 | 
				
			||||||
 | 
					                if (!uniKeyNames.isEmpty()) {
 | 
				
			||||||
 | 
					                    List<Object> uniKeyValues = new ArrayList<>();
 | 
				
			||||||
 | 
					                    for (String key : uniKeyNames) {
 | 
				
			||||||
 | 
					                        if (resultMap.containsKey(key)) {
 | 
				
			||||||
 | 
					                            uniKeyValues.add(resultMap.get(key));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    jsonStr = StringUtil.listToJson(uniKeyValues);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    if (!ignoreUniqueFields.isEmpty()) {
 | 
				
			||||||
 | 
					                        Map<String, Object> clone = new HashMap<>(resultMap);
 | 
				
			||||||
 | 
					                        ignoreUniqueFields.forEach(clone::remove);
 | 
				
			||||||
 | 
					                        jsonStr = StringUtil.mapToJson(clone);
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        jsonStr = StringUtil.mapToJson(resultMap);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                FsTableData fsData = new FsTableData();
 | 
				
			||||||
 | 
					                fsData.setRow(fsTableData.getRow());
 | 
				
			||||||
 | 
					                String uniqueId = StringUtil.getSHA256(jsonStr);
 | 
				
			||||||
 | 
					                fsData.setUniqueId(uniqueId);
 | 
				
			||||||
 | 
					                fsData.setData(resultMap);
 | 
				
			||||||
 | 
					                fsData.setFieldsPositionMap(fieldsPositionMap);
 | 
				
			||||||
 | 
					                fsList.add(fsData);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            dataMap.put(k1, fsList);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return dataMap;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NotNull
 | 
				
			||||||
 | 
					    private static Set<String> getUniKeyNames(Map<String, FieldProperty> fieldsMap, String[] uniKeys) {
 | 
				
			||||||
 | 
					        Set<String> uniKeyNames = new HashSet<>();
 | 
				
			||||||
 | 
					        fieldsMap.forEach((k, v) -> {
 | 
				
			||||||
 | 
					            String field = v.getField();
 | 
				
			||||||
 | 
					            if (field != null && !field.isEmpty()) {
 | 
				
			||||||
 | 
					                if (Arrays.asList(uniKeys).contains(field)) {
 | 
				
			||||||
 | 
					                    uniKeyNames.add(k);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (Arrays.asList(uniKeys).contains(StringUtil.toUnderscoreCase(field))) {
 | 
				
			||||||
 | 
					                    uniKeyNames.add(k);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return uniKeyNames;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @NotNull
 | 
				
			||||||
 | 
					    private static List<List<Object>> getSourceTableValues(Sheet sheet, String spreadsheetToken) {
 | 
				
			||||||
        GridProperties gridProperties = sheet.getGridProperties();
 | 
					        GridProperties gridProperties = sheet.getGridProperties();
 | 
				
			||||||
        int totalRow = gridProperties.getRowCount();
 | 
					        int totalRow = gridProperties.getRowCount();
 | 
				
			||||||
        int rowCount = Math.min(totalRow, 100); // 每次读取的行数
 | 
					        int rowCount = Math.min(totalRow, 100); // 每次读取的行数
 | 
				
			||||||
@ -80,16 +199,40 @@ public class FsTableUtil {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        return values;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取飞书表格数据(支持忽略唯一字段)
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param sheet 工作表对象
 | 
				
			||||||
 | 
					     * @param spreadsheetToken 电子表格Token
 | 
				
			||||||
 | 
					     * @param tableConf 表格配置
 | 
				
			||||||
 | 
					     * @param ignoreUniqueFields 计算唯一标识时忽略的字段列表
 | 
				
			||||||
 | 
					     * @return 飞书表格数据列表
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static List<FsTableData> getFsTableData(Sheet sheet, String spreadsheetToken, TableConf tableConf, List<String> ignoreUniqueFields, Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 计算数据范围
 | 
				
			||||||
 | 
					        List<List<Object>> values = getSourceTableValues(sheet, spreadsheetToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 获取飞书表格数据
 | 
					        // 获取飞书表格数据
 | 
				
			||||||
        TableData tableData = processSheetData(sheet, values);
 | 
					        TableData tableData = processSheetData(sheet, values);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String[] uniKeys = tableConf.uniKeys();
 | 
				
			||||||
 | 
					        Set<String> uniKeyNames = getUniKeyNames(fieldsMap, uniKeys);
 | 
				
			||||||
        List<FsTableData> dataList = getFsTableData(tableData, ignoreUniqueFields);
 | 
					        List<FsTableData> dataList = getFsTableData(tableData, ignoreUniqueFields);
 | 
				
			||||||
        Map<String, String> titleMap = new HashMap<>();
 | 
					        Map<String, String> titleMap = new HashMap<>();
 | 
				
			||||||
 | 
					        Map<String, String> fieldsPositionMap = new HashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        dataList.stream().filter(d -> d.getRow() == (tableConf.titleRow() - 1)).findFirst()
 | 
					        dataList.stream().filter(d -> d.getRow() == (tableConf.titleRow() - 1)).findFirst()
 | 
				
			||||||
                .ifPresent(d -> {
 | 
					                .ifPresent(d -> {
 | 
				
			||||||
                    Map<String, String> map = (Map<String, String>) d.getData();
 | 
					                    Map<String, String> map = (Map<String, String>) d.getData();
 | 
				
			||||||
 | 
					                    map.forEach((k, v) -> {
 | 
				
			||||||
 | 
					                        if (v != null && !v.isEmpty()) {
 | 
				
			||||||
 | 
					                            fieldsPositionMap.put(v, k);
 | 
				
			||||||
 | 
					                        };
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
                    titleMap.putAll(map);
 | 
					                    titleMap.putAll(map);
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
        return dataList.stream().filter(fsTableData -> fsTableData.getRow() >= tableConf.headLine()).map(item -> {
 | 
					        return dataList.stream().filter(fsTableData -> fsTableData.getRow() >= tableConf.headLine()).map(item -> {
 | 
				
			||||||
@ -103,6 +246,24 @@ public class FsTableUtil {
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            item.setData(resultMap);
 | 
					            item.setData(resultMap);
 | 
				
			||||||
 | 
					            item.setFieldsPositionMap(fieldsPositionMap);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String jsonStr = null;
 | 
				
			||||||
 | 
					            if (!uniKeyNames.isEmpty()) {
 | 
				
			||||||
 | 
					                List<Object> uniKeyValues = new ArrayList<>();
 | 
				
			||||||
 | 
					                for (String key : uniKeyNames) {
 | 
				
			||||||
 | 
					                    if (resultMap.containsKey(key)) {
 | 
				
			||||||
 | 
					                        uniKeyValues.add(resultMap.get(key));
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                jsonStr = StringUtil.listToJson(uniKeyValues);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (jsonStr != null) {
 | 
				
			||||||
 | 
					                String uniqueId = StringUtil.getSHA256(jsonStr);
 | 
				
			||||||
 | 
					                item.setUniqueId(uniqueId);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return item;
 | 
					            return item;
 | 
				
			||||||
        }).collect(Collectors.toList());
 | 
					        }).collect(Collectors.toList());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -113,7 +274,7 @@ public class FsTableUtil {
 | 
				
			|||||||
     * @param tableData 表格数据对象
 | 
					     * @param tableData 表格数据对象
 | 
				
			||||||
     * @return 飞书表格数据列表
 | 
					     * @return 飞书表格数据列表
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private static List<FsTableData> getFsTableData(TableData tableData) {
 | 
					    public static List<FsTableData> getFsTableData(TableData tableData) {
 | 
				
			||||||
        return getFsTableData(tableData, new ArrayList<>());
 | 
					        return getFsTableData(tableData, new ArrayList<>());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -124,7 +285,7 @@ public class FsTableUtil {
 | 
				
			|||||||
     * @param ignoreUniqueFields 忽略的唯一字段列表
 | 
					     * @param ignoreUniqueFields 忽略的唯一字段列表
 | 
				
			||||||
     * @return 飞书表格数据列表
 | 
					     * @return 飞书表格数据列表
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    private static List<FsTableData> getFsTableData(TableData tableData, List<String> ignoreUniqueFields) {
 | 
					    public static List<FsTableData> getFsTableData(TableData tableData, List<String> ignoreUniqueFields) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        List<FsTableData> fsTableList = new LinkedList<>();
 | 
					        List<FsTableData> fsTableList = new LinkedList<>();
 | 
				
			||||||
        // 5. 访问补齐后的数据
 | 
					        // 5. 访问补齐后的数据
 | 
				
			||||||
@ -381,6 +542,57 @@ public class FsTableUtil {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static void setTableOptions(String spreadsheetToken, String[] headers, Map<String, FieldProperty> fieldsMap,
 | 
				
			||||||
 | 
					                                       String sheetId, boolean enableDesc, Map<String, Object> customProperties) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int line = getMaxLevel(fieldsMap) + (enableDesc ? 3 : 2);
 | 
				
			||||||
 | 
					        fieldsMap.forEach((field, fieldProperty) -> {
 | 
				
			||||||
 | 
					            TableProperty tableProperty = fieldProperty.getTableProperty();
 | 
				
			||||||
 | 
					            if (tableProperty != null) {
 | 
				
			||||||
 | 
					                List<String> positions = new ArrayList<>();
 | 
				
			||||||
 | 
					                for (String obj : headers) {
 | 
				
			||||||
 | 
					                    if (obj != null && obj.startsWith(field)) {
 | 
				
			||||||
 | 
					                        String[] split = obj.split("_");
 | 
				
			||||||
 | 
					                        if (split.length > 1) {
 | 
				
			||||||
 | 
					                            positions.add(split[1]);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!positions.isEmpty()) {
 | 
				
			||||||
 | 
					                    positions.forEach(position -> {
 | 
				
			||||||
 | 
					                        if (tableProperty.enumClass() != BaseEnum.class) {
 | 
				
			||||||
 | 
					                            FsApiUtil.setOptions(sheetId, FsClient.getInstance().getClient(), spreadsheetToken, tableProperty.type() == TypeEnum.MULTI_SELECT, position + line, position + 200,
 | 
				
			||||||
 | 
					                                    Arrays.stream(tableProperty.enumClass().getEnumConstants()).map(BaseEnum::getDesc).collect(Collectors.toList()));
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if (tableProperty.optionsClass() != OptionsValueProcess.class) {
 | 
				
			||||||
 | 
					                            List<String> result;
 | 
				
			||||||
 | 
					                            Class<? extends OptionsValueProcess> optionsClass = tableProperty.optionsClass();
 | 
				
			||||||
 | 
					                            try {
 | 
				
			||||||
 | 
					                                Map<String, Object> properties = new HashMap<>();
 | 
				
			||||||
 | 
					                                if (customProperties == null) {
 | 
				
			||||||
 | 
					                                    properties.put("_field", fieldProperty);
 | 
				
			||||||
 | 
					                                } else {
 | 
				
			||||||
 | 
					                                    customProperties.put("_field", fieldProperty);
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                                OptionsValueProcess optionsValueProcess = optionsClass.getDeclaredConstructor().newInstance();
 | 
				
			||||||
 | 
					                                result = (List<String>) optionsValueProcess.process(customProperties == null ? properties : customProperties);
 | 
				
			||||||
 | 
					                            } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
 | 
				
			||||||
 | 
					                                throw new RuntimeException(e);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            if (result != null && !result.isEmpty()) {
 | 
				
			||||||
 | 
					                                FsApiUtil.setOptions(sheetId, FsClient.getInstance().getClient(), spreadsheetToken, tableProperty.type() == TypeEnum.MULTI_SELECT, position + line, position + 200,
 | 
				
			||||||
 | 
					                                        result);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void setTableOptions(String spreadsheetToken, List<String> headers, Map<String, FieldProperty> fieldsMap, String sheetId, boolean enableDesc) {
 | 
					    public static void setTableOptions(String spreadsheetToken, List<String> headers, Map<String, FieldProperty> fieldsMap, String sheetId, boolean enableDesc) {
 | 
				
			||||||
        setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, enableDesc, null);
 | 
					        setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, enableDesc, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -390,6 +602,81 @@ public class FsTableUtil {
 | 
				
			|||||||
        return getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf, null);
 | 
					        return getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf, null);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static CustomValueService.ValueRequest getHeadTemplateBuilder(String sheetId, List<String> headers, List<String> headerList, Map<String, FieldProperty> fieldsMap,
 | 
				
			||||||
 | 
					                                                                         TableConf tableConf, Map<String, String> fieldDescriptions, List<String> groupFields) {
 | 
				
			||||||
 | 
					        String position = FsTableUtil.getColumnNameByNuNumber(headerList.size());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        CustomValueService.ValueRequest.BatchPutValuesBuilder batchPutValuesBuilder
 | 
				
			||||||
 | 
					                = CustomValueService.ValueRequest.batchPutValues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int row = tableConf.titleRow();
 | 
				
			||||||
 | 
					        if (tableConf.enableDesc()) {
 | 
				
			||||||
 | 
					            batchPutValuesBuilder.addRange(sheetId + "!A" + (row - 1) + ":" + position + (row + 1));
 | 
				
			||||||
 | 
					            batchPutValuesBuilder.addRow(getGroupArray(headers, headerList.size(), groupFields));
 | 
				
			||||||
 | 
					            batchPutValuesBuilder.addRow(headerList.toArray());
 | 
				
			||||||
 | 
					            batchPutValuesBuilder.addRow(getDescArray(headerList, fieldsMap, fieldDescriptions));
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            batchPutValuesBuilder.addRange(sheetId + "!A" + (row - 1) + ":" + position + row);
 | 
				
			||||||
 | 
					            batchPutValuesBuilder.addRow(getGroupArray(headers, headerList.size(), groupFields));
 | 
				
			||||||
 | 
					            batchPutValuesBuilder.addRow(headerList.toArray());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return batchPutValuesBuilder.build();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static Object[] getGroupArray(List<String> headers, int size, List<String> groupFields) {
 | 
				
			||||||
 | 
					        Object[] groupArray = new Object[size];
 | 
				
			||||||
 | 
					        int index = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (int i = 0; i < groupFields.size(); i++) {
 | 
				
			||||||
 | 
					            if (i > 0) {
 | 
				
			||||||
 | 
					                // 在非第一个groupField前添加null分隔符
 | 
				
			||||||
 | 
					                groupArray[index++] = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            String groupField = groupFields.get(i);
 | 
				
			||||||
 | 
					            // 为当前groupField填充headers长度的数据
 | 
				
			||||||
 | 
					            for (int j = 0; j < headers.size(); j++) {
 | 
				
			||||||
 | 
					                groupArray[index++] = groupField;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return groupArray;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 根据headers和groupFields生成带表格列标识的数据
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param headers 表头列表
 | 
				
			||||||
 | 
					     * @param groupFields 分组字段列表
 | 
				
			||||||
 | 
					     * @return 带表格列标识的数据数组
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static String[] generateHeaderWithColumnIdentifiers(List<String> headers, List<String> groupFields) {
 | 
				
			||||||
 | 
					        // 计算结果数组大小
 | 
				
			||||||
 | 
					        // 每个groupField需要headers.size()个位置,加上(groupFields.size()-1)个null分隔符
 | 
				
			||||||
 | 
					        int size = groupFields.size() * headers.size() + (groupFields.size() - 1);
 | 
				
			||||||
 | 
					        String[] result = new String[size];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        int index = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (int i = 0; i < groupFields.size(); i++) {
 | 
				
			||||||
 | 
					            // 在非第一个groupField前添加null分隔符
 | 
				
			||||||
 | 
					            if (i > 0) {
 | 
				
			||||||
 | 
					                result[index++] = null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 为当前groupField填充headers长度的数据
 | 
				
			||||||
 | 
					            for (int j = 0; j < headers.size(); j++) {
 | 
				
			||||||
 | 
					                // 获取对应的列标识(A, B, C, ...)
 | 
				
			||||||
 | 
					                String columnIdentifier = getColumnName(index);
 | 
				
			||||||
 | 
					                // 拼接header和列标识
 | 
				
			||||||
 | 
					                result[index++] = headers.get(j) + "_" + columnIdentifier;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static CustomValueService.ValueRequest getHeadTemplateBuilder(String sheetId, List<String> headers,
 | 
					    public static CustomValueService.ValueRequest getHeadTemplateBuilder(String sheetId, List<String> headers,
 | 
				
			||||||
                                                                         Map<String, FieldProperty> fieldsMap, TableConf tableConf, Map<String, String> fieldDescriptions) {
 | 
					                                                                         Map<String, FieldProperty> fieldsMap, TableConf tableConf, Map<String, String> fieldDescriptions) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -531,6 +818,53 @@ public class FsTableUtil {
 | 
				
			|||||||
        return styleCellsBatchBuilder;
 | 
					        return styleCellsBatchBuilder;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static CustomCellService.StyleCellsBatchBuilder getDefaultTableStyle(String sheetId, String[] position, TableConf tableConf) {
 | 
				
			||||||
 | 
					        CustomCellService.StyleCellsBatchBuilder styleCellsBatchBuilder = CustomCellService.CellRequest.styleCellsBatch()
 | 
				
			||||||
 | 
					                .addRange(sheetId, position[0] + 1, position[1] + 2)
 | 
				
			||||||
 | 
					                .backColor(tableConf.headBackColor())
 | 
				
			||||||
 | 
					                .foreColor(tableConf.headFontColor());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return styleCellsBatchBuilder;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static Map<String, String[]> calculateGroupPositions(List<String> headers, List<String> groupFields) {
 | 
				
			||||||
 | 
					        Map<String, String[]> positions = new HashMap<>();
 | 
				
			||||||
 | 
					        int index = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (int i = 0; i < groupFields.size(); i++) {
 | 
				
			||||||
 | 
					            String groupField = groupFields.get(i);
 | 
				
			||||||
 | 
					            // 计算开始位置
 | 
				
			||||||
 | 
					            String startPosition = getColumnName(index);
 | 
				
			||||||
 | 
					            // 计算结束位置
 | 
				
			||||||
 | 
					            index += headers.size() - 1;
 | 
				
			||||||
 | 
					            String endPosition = getColumnName(index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            positions.put(groupField, new String[]{startPosition, endPosition});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 如果不是最后一个groupField,跳过null分隔符
 | 
				
			||||||
 | 
					            if (i < groupFields.size() - 1) {
 | 
				
			||||||
 | 
					                index += 2; // 跳过当前末尾位置和null分隔符
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                index += 1; // 只跳过当前末尾位置
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return positions;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static List<String> getGroupHeaders(List<String> groupFieldList, List<String> headers) {
 | 
				
			||||||
 | 
					        List<String> headerList = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        groupFieldList.forEach(groupField -> {
 | 
				
			||||||
 | 
					            if (!headerList.isEmpty()) {
 | 
				
			||||||
 | 
					                headerList.add(null);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            headerList.addAll(headers);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        return headerList;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据层级分组字段属性,并按order排序
 | 
					     * 根据层级分组字段属性,并按order排序
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -688,6 +1022,22 @@ public class FsTableUtil {
 | 
				
			|||||||
        return result;
 | 
					        return result;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //
 | 
				
			||||||
 | 
					    public static List<CustomCellService.CellRequest> getMergeCell(String sheetId, Collection<String[]> positions) {
 | 
				
			||||||
 | 
					        List<CustomCellService.CellRequest> mergeRequests = new ArrayList<>();
 | 
				
			||||||
 | 
					        for (String[] position : positions) {
 | 
				
			||||||
 | 
					            if (position.length == 2) {
 | 
				
			||||||
 | 
					                CustomCellService.CellRequest mergeRequest = CustomCellService.CellRequest.mergeCells()
 | 
				
			||||||
 | 
					                        .sheetId(sheetId)
 | 
				
			||||||
 | 
					                        .startPosition(position[0] + 1)
 | 
				
			||||||
 | 
					                        .endPosition(position[1] + 1)
 | 
				
			||||||
 | 
					                        .build();
 | 
				
			||||||
 | 
					                mergeRequests.add(mergeRequest);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return mergeRequests;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static List<CustomCellService.CellRequest> getMergeCell(String sheetId, Map<String, FieldProperty> fieldsMap) {
 | 
					    public static List<CustomCellService.CellRequest> getMergeCell(String sheetId, Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
        List<CustomCellService.CellRequest> mergeRequests = new ArrayList<>();
 | 
					        List<CustomCellService.CellRequest> mergeRequests = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -937,4 +1287,23 @@ public class FsTableUtil {
 | 
				
			|||||||
    public static String addLineBreaksPer8Chars(String text) {
 | 
					    public static String addLineBreaksPer8Chars(String text) {
 | 
				
			||||||
        return addLineBreaks(text, 8);
 | 
					        return addLineBreaks(text, 8);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static boolean areAllValuesNullOrBlank(Map<String, Object> map) {
 | 
				
			||||||
 | 
					        if (map == null || map.isEmpty()) {
 | 
				
			||||||
 | 
					            return true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (Object value : map.values()) {
 | 
				
			||||||
 | 
					            if (value != null) {
 | 
				
			||||||
 | 
					                if (value instanceof String) {
 | 
				
			||||||
 | 
					                    if (!((String) value).isEmpty()) {
 | 
				
			||||||
 | 
					                        return false;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    return false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,6 +1,7 @@
 | 
				
			|||||||
package cn.isliu.core.utils;
 | 
					package cn.isliu.core.utils;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.isliu.core.FileData;
 | 
					import cn.isliu.core.FileData;
 | 
				
			||||||
 | 
					import cn.isliu.core.annotation.TableConf;
 | 
				
			||||||
import cn.isliu.core.annotation.TableProperty;
 | 
					import cn.isliu.core.annotation.TableProperty;
 | 
				
			||||||
import cn.isliu.core.enums.BaseEnum;
 | 
					import cn.isliu.core.enums.BaseEnum;
 | 
				
			||||||
import cn.isliu.core.enums.FileType;
 | 
					import cn.isliu.core.enums.FileType;
 | 
				
			||||||
@ -22,8 +23,6 @@ import java.util.stream.Collectors;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class GenerateUtil {
 | 
					public class GenerateUtil {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 使用统一的FsLogger替代java.util.logging.Logger
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据配置和数据生成DTO对象(通用版本)
 | 
					     * 根据配置和数据生成DTO对象(通用版本)
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -375,7 +374,7 @@ public class GenerateUtil {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                Object value = getNestedFieldValue(target, fieldPath);
 | 
					                Object value = getNestedFieldValue(target, fieldPath);
 | 
				
			||||||
                if (value != null) {
 | 
					                if (fieldPath.split("\\.").length == 1) {
 | 
				
			||||||
                    result.put(fieldName, value);
 | 
					                    result.put(fieldName, value);
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            } catch (Exception e) {
 | 
					            } catch (Exception e) {
 | 
				
			||||||
@ -481,13 +480,27 @@ public class GenerateUtil {
 | 
				
			|||||||
        return fieldValue;
 | 
					        return fieldValue;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static <T> String getUniqueId(T data) {
 | 
					    public static <T> String getUniqueId(T data, TableConf tableConf) {
 | 
				
			||||||
        String uniqueId = null;
 | 
					        String uniqueId = null;
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            Object uniqueIdObj = GenerateUtil.getNestedFieldValue(data, "uniqueId");
 | 
					            Object uniqueIdObj = GenerateUtil.getNestedFieldValue(data, "uniqueId");
 | 
				
			||||||
            if (uniqueIdObj != null) {
 | 
					            if (uniqueIdObj != null) {
 | 
				
			||||||
                uniqueId = uniqueIdObj.toString();
 | 
					                uniqueId = uniqueIdObj.toString();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            if (uniqueId == null && tableConf.uniKeys().length > 0) {
 | 
				
			||||||
 | 
					                String[] uniKeys = tableConf.uniKeys();
 | 
				
			||||||
 | 
					                List<Object> uniKeyValues = new ArrayList<>();
 | 
				
			||||||
 | 
					                for (String uniKey : uniKeys) {
 | 
				
			||||||
 | 
					                    Object value = GenerateUtil.getNestedFieldValue(data, uniKey);
 | 
				
			||||||
 | 
					                    if (value != null) {
 | 
				
			||||||
 | 
					                        uniKeyValues.add(value);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                if (!uniKeyValues.isEmpty()) {
 | 
				
			||||||
 | 
					                    String jsonStr = StringUtil.listToJson(uniKeyValues);
 | 
				
			||||||
 | 
					                    uniqueId = StringUtil.getSHA256(jsonStr);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            throw new RuntimeException(e);
 | 
					            throw new RuntimeException(e);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -440,11 +440,17 @@ public class PropertyUtil {
 | 
				
			|||||||
            tableConf = clazz.getAnnotation(TableConf.class);
 | 
					            tableConf = clazz.getAnnotation(TableConf.class);
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            tableConf = new TableConf() {
 | 
					            tableConf = new TableConf() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
                public Class<? extends Annotation> annotationType() {
 | 
					                public Class<? extends Annotation> annotationType() {
 | 
				
			||||||
                    return TableConf.class;
 | 
					                    return TableConf.class;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                @Override
 | 
				
			||||||
 | 
					                public String[] uniKeys() {
 | 
				
			||||||
 | 
					                    return new String[0];
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                @Override
 | 
					                @Override
 | 
				
			||||||
                public int headLine() {
 | 
					                public int headLine() {
 | 
				
			||||||
                    return 1;
 | 
					                    return 1;
 | 
				
			||||||
 | 
				
			|||||||
@ -147,4 +147,24 @@ public class StringUtil {
 | 
				
			|||||||
        return sb.toString();
 | 
					        return sb.toString();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static String listToJson(List<Object> uniKeyValues) {
 | 
				
			||||||
 | 
					        StringBuilder jsonBuilder = new StringBuilder();
 | 
				
			||||||
 | 
					        jsonBuilder.append("[");
 | 
				
			||||||
 | 
					        for (int i = 0; i < uniKeyValues.size(); i++) {
 | 
				
			||||||
 | 
					            Object value = uniKeyValues.get(i);
 | 
				
			||||||
 | 
					            if (value instanceof String) {
 | 
				
			||||||
 | 
					                jsonBuilder.append("\"").append(value).append("\"");
 | 
				
			||||||
 | 
					            } else if (value == null) {
 | 
				
			||||||
 | 
					                jsonBuilder.append("null");
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                jsonBuilder.append(value);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if (i < uniKeyValues.size() - 1) {
 | 
				
			||||||
 | 
					                jsonBuilder.append(",");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        jsonBuilder.append("]");
 | 
				
			||||||
 | 
					        return jsonBuilder.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user