feat(core): 添加Map方式操作飞书表格的功能- 新增MapSheetConfig和MapTableConfig配置类,支持通过Map方式定义表格结构
- 添加MapFieldDefinition字段定义类,提供丰富的字段类型和配置选项 - 实现MapSheetBuilder构建器,支持链式调用创建飞书表格- 实现MapReadBuilder构建器,支持从飞书表格读取数据并转换为Map格式- 添加MapOptionsUtil工具类,提供枚举选项提取和动态选项处理功能 - 在FsHelper中新增createMapSheet、createMapSheetBuilder、readMap和readMapBuilder方法-优化包导入结构,使用通配符简化builder包的导入 - 添加详细的JavaDoc文档和使用示例,提升API易用性- 支持动态字段、分组表格、唯一键计算等高级功能 - 实现数据过滤机制,自动过滤空行数据
This commit is contained in:
		
							parent
							
								
									f9cff00b2f
								
							
						
					
					
						commit
						f191e57c9a
					
				@ -5,12 +5,10 @@ import cn.isliu.core.FileData;
 | 
			
		||||
import cn.isliu.core.FsTableData;
 | 
			
		||||
import cn.isliu.core.Sheet;
 | 
			
		||||
import cn.isliu.core.annotation.TableConf;
 | 
			
		||||
import cn.isliu.core.builder.MapWriteBuilder;
 | 
			
		||||
import cn.isliu.core.builder.ReadBuilder;
 | 
			
		||||
import cn.isliu.core.builder.SheetBuilder;
 | 
			
		||||
import cn.isliu.core.builder.WriteBuilder;
 | 
			
		||||
import cn.isliu.core.builder.*;
 | 
			
		||||
import cn.isliu.core.client.FeishuClient;
 | 
			
		||||
import cn.isliu.core.client.FsClient;
 | 
			
		||||
import cn.isliu.core.config.MapSheetConfig;
 | 
			
		||||
import cn.isliu.core.config.MapTableConfig;
 | 
			
		||||
import cn.isliu.core.enums.ErrorCode;
 | 
			
		||||
import cn.isliu.core.enums.FileType;
 | 
			
		||||
@ -101,6 +99,86 @@ public class FsHelper {
 | 
			
		||||
        return new SheetBuilder<>(sheetName, spreadsheetToken, clazz);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 使用 Map 配置创建飞书表格
 | 
			
		||||
     *
 | 
			
		||||
     * 直接使用 Map 配置创建飞书表格,无需定义实体类和注解。
 | 
			
		||||
     * 适用于动态字段、临时表格创建等场景。
 | 
			
		||||
     *
 | 
			
		||||
     * 使用示例:
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * MapSheetConfig config = MapSheetConfig.sheetBuilder()
 | 
			
		||||
     *     .titleRow(2)
 | 
			
		||||
     *     .headLine(3)
 | 
			
		||||
     *     .headStyle("#ffffff", "#000000")
 | 
			
		||||
     *     .isText(true)
 | 
			
		||||
     *     .enableDesc(true)
 | 
			
		||||
     *     .addField(MapFieldDefinition.text("字段1", 0))
 | 
			
		||||
     *     .addField(MapFieldDefinition.singleSelect("字段2", 1, "选项1", "选项2"))
 | 
			
		||||
     *     .build();
 | 
			
		||||
     *
 | 
			
		||||
     * String sheetId = FsHelper.createMapSheet("表格名称", spreadsheetToken, config);
 | 
			
		||||
     * </pre>
 | 
			
		||||
     *
 | 
			
		||||
     * @param sheetName 工作表名称
 | 
			
		||||
     * @param spreadsheetToken 电子表格Token
 | 
			
		||||
     * @param config 表格配置,包含字段定义、样式等信息
 | 
			
		||||
     * @return 创建成功返回工作表ID
 | 
			
		||||
     */
 | 
			
		||||
    public static String createMapSheet(String sheetName, String spreadsheetToken, MapSheetConfig config) {
 | 
			
		||||
        return new MapSheetBuilder(sheetName, spreadsheetToken)
 | 
			
		||||
                .config(config)
 | 
			
		||||
                .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建 Map 表格构建器
 | 
			
		||||
     *
 | 
			
		||||
     * 返回一个 Map 表格构建器实例,支持链式调用和灵活配置。
 | 
			
		||||
     * 相比直接使用 createMapSheet 方法,构建器方式提供了更灵活的配置方式。
 | 
			
		||||
     *
 | 
			
		||||
     * 支持动态添加字段,包括:
 | 
			
		||||
     * - addField(field) - 添加单个字段
 | 
			
		||||
     * - addFields(fieldList) - 批量添加字段列表
 | 
			
		||||
     * - addFields(field1, field2, ...) - 批量添加字段(可变参数)
 | 
			
		||||
     * - fields(fieldList) - 直接设置所有字段(覆盖现有)
 | 
			
		||||
     *
 | 
			
		||||
     * 使用示例:
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * // 动态添加字段
 | 
			
		||||
     * List<MapFieldDefinition> dynamicFields = getDynamicFields();
 | 
			
		||||
     *
 | 
			
		||||
     * String sheetId = FsHelper.createMapSheetBuilder("表格名称", spreadsheetToken)
 | 
			
		||||
     *     .titleRow(2)
 | 
			
		||||
     *     .headLine(3)
 | 
			
		||||
     *     .headStyle("#ffffff", "#000000")
 | 
			
		||||
     *     .isText(true)
 | 
			
		||||
     *     .addFields(dynamicFields)  // 批量添加
 | 
			
		||||
     *     .build();
 | 
			
		||||
     *
 | 
			
		||||
     * // 或者使用可变参数
 | 
			
		||||
     * String sheetId = FsHelper.createMapSheetBuilder("表格名称", spreadsheetToken)
 | 
			
		||||
     *     .addFields(
 | 
			
		||||
     *         MapFieldDefinition.text("字段1", 0),
 | 
			
		||||
     *         MapFieldDefinition.singleSelect("字段2", 1, "选项1", "选项2")
 | 
			
		||||
     *     )
 | 
			
		||||
     *     .build();
 | 
			
		||||
     *
 | 
			
		||||
     * // 创建分组表格
 | 
			
		||||
     * String sheetId = FsHelper.createMapSheetBuilder("分组表格", spreadsheetToken)
 | 
			
		||||
     *     .addFields(dynamicFields)
 | 
			
		||||
     *     .groupFields("分组A", "分组B")
 | 
			
		||||
     *     .build();
 | 
			
		||||
     * </pre>
 | 
			
		||||
     *
 | 
			
		||||
     * @param sheetName 工作表名称
 | 
			
		||||
     * @param spreadsheetToken 电子表格Token
 | 
			
		||||
     * @return MapSheetBuilder实例,支持链式调用
 | 
			
		||||
     */
 | 
			
		||||
    public static MapSheetBuilder createMapSheetBuilder(String sheetName, String spreadsheetToken) {
 | 
			
		||||
        return new MapSheetBuilder(sheetName, spreadsheetToken);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从飞书表格中读取数据
 | 
			
		||||
@ -368,4 +446,83 @@ public class FsHelper {
 | 
			
		||||
                                                  List<Map<String, Object>> dataList) {
 | 
			
		||||
        return new MapWriteBuilder(sheetId, spreadsheetToken, dataList);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 从飞书表格读取数据并转换为 Map 格式
 | 
			
		||||
     *
 | 
			
		||||
     * 直接从飞书表格读取数据并转换为 Map 列表,无需定义实体类和注解。
 | 
			
		||||
     * 适用于动态字段、临时数据读取等场景。
 | 
			
		||||
     *
 | 
			
		||||
     * 返回的Map中包含两个特殊字段:
 | 
			
		||||
     * - _uniqueId: 根据唯一键计算的唯一标识
 | 
			
		||||
     * - _rowNumber: 数据所在的行号(从1开始)
 | 
			
		||||
     *
 | 
			
		||||
     * 注意:
 | 
			
		||||
     * - 如果某行数据的所有业务字段值都为null或空字符串,该行数据将被自动过滤,不会包含在返回结果中
 | 
			
		||||
     *
 | 
			
		||||
     * 使用示例:
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * MapTableConfig config = MapTableConfig.builder()
 | 
			
		||||
     *     .titleRow(2)
 | 
			
		||||
     *     .headLine(3)
 | 
			
		||||
     *     .addUniKeyName("字段名1")
 | 
			
		||||
     *     .build();
 | 
			
		||||
     *
 | 
			
		||||
     * List<Map<String, Object>> dataList = FsHelper.readMap(sheetId, spreadsheetToken, config);
 | 
			
		||||
     * for (Map<String, Object> data : dataList) {
 | 
			
		||||
     *     String value1 = (String) data.get("字段名1");
 | 
			
		||||
     *     String value2 = (String) data.get("字段名2");
 | 
			
		||||
     *     String uniqueId = (String) data.get("_uniqueId");
 | 
			
		||||
     *     Integer rowNumber = (Integer) data.get("_rowNumber");
 | 
			
		||||
     * }
 | 
			
		||||
     * </pre>
 | 
			
		||||
     *
 | 
			
		||||
     * @param sheetId 工作表ID
 | 
			
		||||
     * @param spreadsheetToken 电子表格Token
 | 
			
		||||
     * @param config 表格配置,包含标题行、数据起始行、唯一键等配置信息
 | 
			
		||||
     * @return Map数据列表,每个Map的key为字段名,value为字段值
 | 
			
		||||
     */
 | 
			
		||||
    public static List<Map<String, Object>> readMap(String sheetId, String spreadsheetToken, MapTableConfig config) {
 | 
			
		||||
        return new MapReadBuilder(sheetId, spreadsheetToken)
 | 
			
		||||
                .config(config)
 | 
			
		||||
                .build();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建 Map 数据读取构建器
 | 
			
		||||
     *
 | 
			
		||||
     * 返回一个 Map 数据读取构建器实例,支持链式调用和灵活配置。
 | 
			
		||||
     * 相比直接使用 readMap 方法,构建器方式提供了更灵活的配置方式。
 | 
			
		||||
     *
 | 
			
		||||
     * 返回的Map中包含两个特殊字段:
 | 
			
		||||
     * - _uniqueId: 根据唯一键计算的唯一标识
 | 
			
		||||
     * - _rowNumber: 数据所在的行号(从1开始)
 | 
			
		||||
     *
 | 
			
		||||
     * 注意:
 | 
			
		||||
     * - 如果某行数据的所有业务字段值都为null或空字符串,该行数据将被自动过滤,不会包含在返回结果中
 | 
			
		||||
     * - 分组读取时,如果某个分组下的某行数据所有字段值都为null或空字符串,该行数据也会被自动过滤
 | 
			
		||||
     *
 | 
			
		||||
     * 使用示例:
 | 
			
		||||
     * <pre>
 | 
			
		||||
     * // 基础读取
 | 
			
		||||
     * List<Map<String, Object>> dataList = FsHelper.readMapBuilder(sheetId, spreadsheetToken)
 | 
			
		||||
     *     .titleRow(2)
 | 
			
		||||
     *     .headLine(3)
 | 
			
		||||
     *     .addUniKeyName("字段名1")
 | 
			
		||||
     *     .build();
 | 
			
		||||
     *
 | 
			
		||||
     * // 分组读取
 | 
			
		||||
     * Map<String, List<Map<String, Object>>> groupedData = FsHelper.readMapBuilder(sheetId, spreadsheetToken)
 | 
			
		||||
     *     .titleRow(2)
 | 
			
		||||
     *     .headLine(3)
 | 
			
		||||
     *     .groupBuild();
 | 
			
		||||
     * </pre>
 | 
			
		||||
     *
 | 
			
		||||
     * @param sheetId 工作表ID
 | 
			
		||||
     * @param spreadsheetToken 电子表格Token
 | 
			
		||||
     * @return MapReadBuilder实例,支持链式调用
 | 
			
		||||
     */
 | 
			
		||||
    public static MapReadBuilder readMapBuilder(String sheetId, String spreadsheetToken) {
 | 
			
		||||
        return new MapReadBuilder(sheetId, spreadsheetToken);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										434
									
								
								src/main/java/cn/isliu/core/builder/MapReadBuilder.java
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										434
									
								
								src/main/java/cn/isliu/core/builder/MapReadBuilder.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,434 @@
 | 
			
		||||
package cn.isliu.core.builder;
 | 
			
		||||
 | 
			
		||||
import cn.isliu.core.*;
 | 
			
		||||
import cn.isliu.core.client.FeishuClient;
 | 
			
		||||
import cn.isliu.core.client.FsClient;
 | 
			
		||||
import cn.isliu.core.config.MapTableConfig;
 | 
			
		||||
import cn.isliu.core.logging.FsLogger;
 | 
			
		||||
import cn.isliu.core.utils.FsApiUtil;
 | 
			
		||||
import cn.isliu.core.utils.MapDataUtil;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
import static cn.isliu.core.utils.FsTableUtil.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Map 数据读取构建器
 | 
			
		||||
 * 
 | 
			
		||||
 * 提供链式调用方式从飞书表格读取数据并转换为 Map 格式
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Ls
 | 
			
		||||
 * @since 2025-10-16
 | 
			
		||||
 */
 | 
			
		||||
public class MapReadBuilder {
 | 
			
		||||
    
 | 
			
		||||
    private final String sheetId;
 | 
			
		||||
    private final String spreadsheetToken;
 | 
			
		||||
    private MapTableConfig config;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 构造函数
 | 
			
		||||
     *
 | 
			
		||||
     * @param sheetId 工作表ID
 | 
			
		||||
     * @param spreadsheetToken 电子表格Token
 | 
			
		||||
     */
 | 
			
		||||
    public MapReadBuilder(String sheetId, String spreadsheetToken) {
 | 
			
		||||
        this.sheetId = sheetId;
 | 
			
		||||
        this.spreadsheetToken = spreadsheetToken;
 | 
			
		||||
        this.config = MapTableConfig.createDefault();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置表格配置
 | 
			
		||||
     *
 | 
			
		||||
     * @param config Map表格配置
 | 
			
		||||
     * @return MapReadBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapReadBuilder config(MapTableConfig config) {
 | 
			
		||||
        this.config = config;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置标题行
 | 
			
		||||
     *
 | 
			
		||||
     * @param titleRow 标题行行号
 | 
			
		||||
     * @return MapReadBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapReadBuilder titleRow(int titleRow) {
 | 
			
		||||
        this.config.setTitleRow(titleRow);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置数据起始行
 | 
			
		||||
     *
 | 
			
		||||
     * @param headLine 数据起始行号
 | 
			
		||||
     * @return MapReadBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapReadBuilder headLine(int headLine) {
 | 
			
		||||
        this.config.setHeadLine(headLine);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置唯一键字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param uniKeyNames 唯一键字段名集合
 | 
			
		||||
     * @return MapReadBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapReadBuilder uniKeyNames(Set<String> uniKeyNames) {
 | 
			
		||||
        this.config.setUniKeyNames(uniKeyNames);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加唯一键字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param uniKeyName 唯一键字段名
 | 
			
		||||
     * @return MapReadBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapReadBuilder addUniKeyName(String uniKeyName) {
 | 
			
		||||
        this.config.addUniKeyName(uniKeyName);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行数据读取
 | 
			
		||||
     *
 | 
			
		||||
     * 注意:
 | 
			
		||||
     * - 如果某行数据的所有业务字段值都为null或空字符串,该行数据将被自动过滤
 | 
			
		||||
     * - 返回的Map中包含 _uniqueId 和 _rowNumber 两个特殊字段
 | 
			
		||||
     *
 | 
			
		||||
     * @return Map数据列表
 | 
			
		||||
     */
 | 
			
		||||
    public List<Map<String, Object>> build() {
 | 
			
		||||
        FeishuClient client = FsClient.getInstance().getClient();
 | 
			
		||||
        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
			
		||||
        
 | 
			
		||||
        // 读取字段位置映射
 | 
			
		||||
        Map<String, String> titlePostionMap = readFieldsPositionMap(sheet, client);
 | 
			
		||||
        config.setFieldsPositionMap(titlePostionMap);
 | 
			
		||||
        
 | 
			
		||||
        // 读取表格数据并转换为Map格式
 | 
			
		||||
        List<Map<String, Object>> dataList = readTableData(sheet, client, titlePostionMap);
 | 
			
		||||
        
 | 
			
		||||
        FsLogger.info("【Map读取】成功读取 {} 条数据", dataList.size());
 | 
			
		||||
        return dataList;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 执行分组数据读取
 | 
			
		||||
     *
 | 
			
		||||
     * 注意:
 | 
			
		||||
     * - 如果某个分组下某行数据的所有字段值都为null或空字符串,该行数据将被自动过滤
 | 
			
		||||
     * - 返回的Map中包含 _uniqueId 和 _rowNumber 两个特殊字段
 | 
			
		||||
     *
 | 
			
		||||
     * @return 按分组字段组织的Map数据
 | 
			
		||||
     */
 | 
			
		||||
    public Map<String, List<Map<String, Object>>> groupBuild() {
 | 
			
		||||
        FeishuClient client = FsClient.getInstance().getClient();
 | 
			
		||||
        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
			
		||||
        
 | 
			
		||||
        // 读取字段位置映射
 | 
			
		||||
        Map<String, String> titlePostionMap = readFieldsPositionMap(sheet, client);
 | 
			
		||||
        config.setFieldsPositionMap(titlePostionMap);
 | 
			
		||||
        
 | 
			
		||||
        // 读取分组表格数据
 | 
			
		||||
        Map<String, List<Map<String, Object>>> groupedData = readGroupedTableData(sheet, client, titlePostionMap);
 | 
			
		||||
        
 | 
			
		||||
        int totalCount = groupedData.values().stream().mapToInt(List::size).sum();
 | 
			
		||||
        FsLogger.info("【Map读取】成功读取 {} 个分组,共 {} 条数据", groupedData.size(), totalCount);
 | 
			
		||||
        return groupedData;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 读取字段位置映射
 | 
			
		||||
     */
 | 
			
		||||
    private Map<String, String> readFieldsPositionMap(Sheet sheet, FeishuClient client) {
 | 
			
		||||
        int titleRow = config.getTitleRow();
 | 
			
		||||
        int colCount = sheet.getGridProperties().getColumnCount();
 | 
			
		||||
        
 | 
			
		||||
        // 读取标题行数据
 | 
			
		||||
        ValuesBatch valuesBatch = FsApiUtil.getSheetData(
 | 
			
		||||
            sheet.getSheetId(), spreadsheetToken,
 | 
			
		||||
            "A" + titleRow,
 | 
			
		||||
            getColumnName(colCount - 1) + titleRow,
 | 
			
		||||
            client
 | 
			
		||||
        );
 | 
			
		||||
        
 | 
			
		||||
        Map<String, String> fieldsPositionMap = new HashMap<>();
 | 
			
		||||
        
 | 
			
		||||
        if (valuesBatch != null && valuesBatch.getValueRanges() != null) {
 | 
			
		||||
            for (ValueRange valueRange : valuesBatch.getValueRanges()) {
 | 
			
		||||
                if (valueRange.getValues() != null && !valueRange.getValues().isEmpty()) {
 | 
			
		||||
                    List<Object> titleRowValues = valueRange.getValues().get(0);
 | 
			
		||||
                    for (int i = 0; i < titleRowValues.size(); i++) {
 | 
			
		||||
                        Object value = titleRowValues.get(i);
 | 
			
		||||
                        if (value != null) {
 | 
			
		||||
                            String fieldName = value.toString();
 | 
			
		||||
                            String columnPosition = getColumnName(i);
 | 
			
		||||
                            fieldsPositionMap.put(fieldName, columnPosition);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return fieldsPositionMap;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 读取表格数据并转换为Map格式
 | 
			
		||||
     */
 | 
			
		||||
    private List<Map<String, Object>> readTableData(Sheet sheet, FeishuClient client, Map<String, String> titlePostionMap) {
 | 
			
		||||
        int headLine = config.getHeadLine();
 | 
			
		||||
        int titleRow = config.getTitleRow();
 | 
			
		||||
        int totalRow = sheet.getGridProperties().getRowCount();
 | 
			
		||||
        int colCount = sheet.getGridProperties().getColumnCount();
 | 
			
		||||
        int startOffset = 1;
 | 
			
		||||
        
 | 
			
		||||
        // 批量读取数据
 | 
			
		||||
        int rowCountPerBatch = Math.min(totalRow, 100);
 | 
			
		||||
        int actualRows = Math.max(0, totalRow - startOffset);
 | 
			
		||||
        int batchCount = (actualRows + rowCountPerBatch - 1) / rowCountPerBatch;
 | 
			
		||||
        
 | 
			
		||||
        List<List<Object>> values = new LinkedList<>();
 | 
			
		||||
        for (int i = 0; i < batchCount; i++) {
 | 
			
		||||
            int startRowIndex = startOffset + i * rowCountPerBatch;
 | 
			
		||||
            int endRowIndex = Math.min(startRowIndex + rowCountPerBatch - 1, totalRow - 1);
 | 
			
		||||
            
 | 
			
		||||
            ValuesBatch valuesBatch = FsApiUtil.getSheetData(
 | 
			
		||||
                sheet.getSheetId(), spreadsheetToken,
 | 
			
		||||
                "A" + startRowIndex,
 | 
			
		||||
                getColumnName(colCount - 1) + endRowIndex,
 | 
			
		||||
                client
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            if (valuesBatch != null && valuesBatch.getValueRanges() != null) {
 | 
			
		||||
                for (ValueRange valueRange : valuesBatch.getValueRanges()) {
 | 
			
		||||
                    if (valueRange.getValues() != null) {
 | 
			
		||||
                        values.addAll(valueRange.getValues());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 处理表格数据
 | 
			
		||||
        TableData tableData = processSheetData(sheet, values);
 | 
			
		||||
        List<FsTableData> fsTableDataList = getFsTableData(tableData, new ArrayList<>());
 | 
			
		||||
        
 | 
			
		||||
        // 获取标题映射
 | 
			
		||||
        Map<String, String> titleMap = new HashMap<>();
 | 
			
		||||
        fsTableDataList.stream()
 | 
			
		||||
            .filter(d -> d.getRow() == (titleRow - 1))
 | 
			
		||||
            .findFirst()
 | 
			
		||||
            .ifPresent(d -> {
 | 
			
		||||
                Map<String, String> map = (Map<String, String>) d.getData();
 | 
			
		||||
                titleMap.putAll(map);
 | 
			
		||||
            });
 | 
			
		||||
        
 | 
			
		||||
        // 转换为带字段名的Map数据
 | 
			
		||||
        return fsTableDataList.stream()
 | 
			
		||||
            .filter(fsTableData -> fsTableData.getRow() >= headLine)
 | 
			
		||||
            .map(item -> {
 | 
			
		||||
                Map<String, Object> resultMap = new HashMap<>();
 | 
			
		||||
                Map<String, Object> map = (Map<String, Object>) item.getData();
 | 
			
		||||
                
 | 
			
		||||
                map.forEach((k, v) -> {
 | 
			
		||||
                    String title = titleMap.get(k);
 | 
			
		||||
                    if (title != null) {
 | 
			
		||||
                        resultMap.put(title, v);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                // 计算并设置唯一ID
 | 
			
		||||
                String uniqueId = MapDataUtil.calculateUniqueId(resultMap, config);
 | 
			
		||||
                if (uniqueId != null) {
 | 
			
		||||
                    resultMap.put("_uniqueId", uniqueId);
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // 设置行号
 | 
			
		||||
                resultMap.put("_rowNumber", item.getRow() + 1);
 | 
			
		||||
                
 | 
			
		||||
                return resultMap;
 | 
			
		||||
            })
 | 
			
		||||
            .filter(map -> {
 | 
			
		||||
                // 过滤条件1:Map不能为空
 | 
			
		||||
                if (map.isEmpty()) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // 过滤条件2:除了 _uniqueId 和 _rowNumber 外还有其他数据
 | 
			
		||||
                if (map.size() <= 2) {
 | 
			
		||||
                    return false;
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // 过滤条件3:检查是否所有业务字段的值都为null或空字符串
 | 
			
		||||
                boolean hasNonNullValue = false;
 | 
			
		||||
                for (Map.Entry<String, Object> entry : map.entrySet()) {
 | 
			
		||||
                    String key = entry.getKey();
 | 
			
		||||
                    Object value = entry.getValue();
 | 
			
		||||
                    
 | 
			
		||||
                    // 跳过特殊字段
 | 
			
		||||
                    if (key.equals("_uniqueId") || key.equals("_rowNumber")) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // 检查是否有非null且非空字符串的值
 | 
			
		||||
                    if (value != null && !(value instanceof String && ((String) value).isEmpty())) {
 | 
			
		||||
                        hasNonNullValue = true;
 | 
			
		||||
                        break;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                return hasNonNullValue;
 | 
			
		||||
            })
 | 
			
		||||
            .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 读取分组表格数据
 | 
			
		||||
     */
 | 
			
		||||
    private Map<String, List<Map<String, Object>>> readGroupedTableData(Sheet sheet, FeishuClient client, Map<String, String> titlePostionMap) {
 | 
			
		||||
        int headLine = config.getHeadLine();
 | 
			
		||||
        int titleRow = config.getTitleRow();
 | 
			
		||||
        int totalRow = sheet.getGridProperties().getRowCount();
 | 
			
		||||
        int colCount = sheet.getGridProperties().getColumnCount();
 | 
			
		||||
        int startOffset = 1;
 | 
			
		||||
        
 | 
			
		||||
        // 批量读取数据
 | 
			
		||||
        int rowCountPerBatch = Math.min(totalRow, 100);
 | 
			
		||||
        int actualRows = Math.max(0, totalRow - startOffset);
 | 
			
		||||
        int batchCount = (actualRows + rowCountPerBatch - 1) / rowCountPerBatch;
 | 
			
		||||
        
 | 
			
		||||
        List<List<Object>> values = new LinkedList<>();
 | 
			
		||||
        for (int i = 0; i < batchCount; i++) {
 | 
			
		||||
            int startRowIndex = startOffset + i * rowCountPerBatch;
 | 
			
		||||
            int endRowIndex = Math.min(startRowIndex + rowCountPerBatch - 1, totalRow - 1);
 | 
			
		||||
            
 | 
			
		||||
            ValuesBatch valuesBatch = FsApiUtil.getSheetData(
 | 
			
		||||
                sheet.getSheetId(), spreadsheetToken,
 | 
			
		||||
                "A" + startRowIndex,
 | 
			
		||||
                getColumnName(colCount - 1) + endRowIndex,
 | 
			
		||||
                client
 | 
			
		||||
            );
 | 
			
		||||
            
 | 
			
		||||
            if (valuesBatch != null && valuesBatch.getValueRanges() != null) {
 | 
			
		||||
                for (ValueRange valueRange : valuesBatch.getValueRanges()) {
 | 
			
		||||
                    if (valueRange.getValues() != null) {
 | 
			
		||||
                        values.addAll(valueRange.getValues());
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 处理表格数据
 | 
			
		||||
        TableData tableData = processSheetData(sheet, values);
 | 
			
		||||
        List<FsTableData> fsTableDataList = getFsTableData(tableData, new ArrayList<>());
 | 
			
		||||
        
 | 
			
		||||
        // 获取分组行和标题行
 | 
			
		||||
        Map<String, String> categoryMap = new HashMap<>();
 | 
			
		||||
        Map<String, List<String>> categoryPositionMap = new HashMap<>();
 | 
			
		||||
        
 | 
			
		||||
        // 读取分组行(titleRow - 1)
 | 
			
		||||
        fsTableDataList.stream()
 | 
			
		||||
            .filter(d -> d.getRow() == (titleRow - 2))
 | 
			
		||||
            .findFirst()
 | 
			
		||||
            .ifPresent(d -> {
 | 
			
		||||
                Map<String, String> map = (Map<String, String>) d.getData();
 | 
			
		||||
                map.forEach((k, v) -> {
 | 
			
		||||
                    if (v != null && !v.isEmpty()) {
 | 
			
		||||
                        categoryMap.put(k, v);
 | 
			
		||||
                        categoryPositionMap.computeIfAbsent(v, k1 -> new ArrayList<>()).add(k);
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        
 | 
			
		||||
        // 读取标题行
 | 
			
		||||
        Map<String, String> titleMap = new HashMap<>();
 | 
			
		||||
        fsTableDataList.stream()
 | 
			
		||||
            .filter(d -> d.getRow() == (titleRow - 1))
 | 
			
		||||
            .findFirst()
 | 
			
		||||
            .ifPresent(d -> {
 | 
			
		||||
                Map<String, String> map = (Map<String, String>) d.getData();
 | 
			
		||||
                map.forEach((k, v) -> {
 | 
			
		||||
                    if (v != null && !v.isEmpty()) {
 | 
			
		||||
                        String category = categoryMap.get(k);
 | 
			
		||||
                        if (category != null && !category.isEmpty()) {
 | 
			
		||||
                            titleMap.put(k, v);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            titleMap.put(k, v);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
        
 | 
			
		||||
        // 按分组组织数据
 | 
			
		||||
        Map<String, List<Map<String, Object>>> groupedResult = new LinkedHashMap<>();
 | 
			
		||||
        
 | 
			
		||||
        for (Map.Entry<String, List<String>> entry : categoryPositionMap.entrySet()) {
 | 
			
		||||
            String groupName = entry.getKey();
 | 
			
		||||
            List<String> positions = entry.getValue();
 | 
			
		||||
            
 | 
			
		||||
            List<Map<String, Object>> groupData = fsTableDataList.stream()
 | 
			
		||||
                .filter(fsTableData -> fsTableData.getRow() >= headLine)
 | 
			
		||||
                .map(item -> {
 | 
			
		||||
                    Map<String, Object> resultMap = new HashMap<>();
 | 
			
		||||
                    Map<String, Object> map = (Map<String, Object>) item.getData();
 | 
			
		||||
                    
 | 
			
		||||
                    // 只提取该分组的字段
 | 
			
		||||
                    positions.forEach(pos -> {
 | 
			
		||||
                        String title = titleMap.get(pos);
 | 
			
		||||
                        if (title != null && map.containsKey(pos)) {
 | 
			
		||||
                            resultMap.put(title, map.get(pos));
 | 
			
		||||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
                    
 | 
			
		||||
                    // 计算并设置唯一ID
 | 
			
		||||
                    String uniqueId = MapDataUtil.calculateUniqueId(resultMap, config);
 | 
			
		||||
                    if (uniqueId != null) {
 | 
			
		||||
                        resultMap.put("_uniqueId", uniqueId);
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // 设置行号
 | 
			
		||||
                    resultMap.put("_rowNumber", item.getRow() + 1);
 | 
			
		||||
                    
 | 
			
		||||
                    return resultMap;
 | 
			
		||||
                })
 | 
			
		||||
                .filter(map -> {
 | 
			
		||||
                    // 过滤条件1:除了 _uniqueId 和 _rowNumber 外还有其他数据
 | 
			
		||||
                    if (map.size() <= 2) {
 | 
			
		||||
                        return false;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // 过滤条件2:检查是否所有业务字段的值都为null
 | 
			
		||||
                    boolean hasNonNullValue = false;
 | 
			
		||||
                    for (Map.Entry<String, Object> nullEntry : map.entrySet()) {
 | 
			
		||||
                        String key = nullEntry.getKey();
 | 
			
		||||
                        Object value = nullEntry.getValue();
 | 
			
		||||
                        
 | 
			
		||||
                        // 跳过特殊字段
 | 
			
		||||
                        if (key.equals("_uniqueId") || key.equals("_rowNumber")) {
 | 
			
		||||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        
 | 
			
		||||
                        // 检查是否有非null且非空字符串的值
 | 
			
		||||
                        if (value != null && !(value instanceof String && ((String) value).isEmpty())) {
 | 
			
		||||
                            hasNonNullValue = true;
 | 
			
		||||
                            break;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    return hasNonNullValue;
 | 
			
		||||
                })
 | 
			
		||||
                .collect(Collectors.toList());
 | 
			
		||||
            
 | 
			
		||||
            groupedResult.put(groupName, groupData);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return groupedResult;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										525
									
								
								src/main/java/cn/isliu/core/builder/MapSheetBuilder.java
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										525
									
								
								src/main/java/cn/isliu/core/builder/MapSheetBuilder.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,525 @@
 | 
			
		||||
package cn.isliu.core.builder;
 | 
			
		||||
 | 
			
		||||
import cn.isliu.core.annotation.TableConf;
 | 
			
		||||
import cn.isliu.core.annotation.TableProperty;
 | 
			
		||||
import cn.isliu.core.client.FeishuClient;
 | 
			
		||||
import cn.isliu.core.client.FsClient;
 | 
			
		||||
import cn.isliu.core.config.MapFieldDefinition;
 | 
			
		||||
import cn.isliu.core.config.MapSheetConfig;
 | 
			
		||||
import cn.isliu.core.converters.FieldValueProcess;
 | 
			
		||||
import cn.isliu.core.converters.OptionsValueProcess;
 | 
			
		||||
import cn.isliu.core.enums.BaseEnum;
 | 
			
		||||
import cn.isliu.core.enums.TypeEnum;
 | 
			
		||||
import cn.isliu.core.pojo.FieldProperty;
 | 
			
		||||
import cn.isliu.core.service.CustomCellService;
 | 
			
		||||
import cn.isliu.core.utils.FsApiUtil;
 | 
			
		||||
import cn.isliu.core.utils.FsTableUtil;
 | 
			
		||||
import cn.isliu.core.utils.MapOptionsUtil;
 | 
			
		||||
 | 
			
		||||
import java.lang.annotation.Annotation;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.logging.Level;
 | 
			
		||||
import java.util.logging.Logger;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Map 表格构建器
 | 
			
		||||
 * 
 | 
			
		||||
 * 提供链式调用方式创建飞书表格,使用配置对象而不是注解
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Ls
 | 
			
		||||
 * @since 2025-10-16
 | 
			
		||||
 */
 | 
			
		||||
public class MapSheetBuilder {
 | 
			
		||||
    
 | 
			
		||||
    private final String sheetName;
 | 
			
		||||
    private final String spreadsheetToken;
 | 
			
		||||
    private MapSheetConfig config;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 构造函数
 | 
			
		||||
     *
 | 
			
		||||
     * @param sheetName 工作表名称
 | 
			
		||||
     * @param spreadsheetToken 电子表格Token
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder(String sheetName, String spreadsheetToken) {
 | 
			
		||||
        this.sheetName = sheetName;
 | 
			
		||||
        this.spreadsheetToken = spreadsheetToken;
 | 
			
		||||
        this.config = MapSheetConfig.createDefault();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置表格配置
 | 
			
		||||
     *
 | 
			
		||||
     * @param config Map表格配置
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder config(MapSheetConfig config) {
 | 
			
		||||
        this.config = config;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置标题行
 | 
			
		||||
     *
 | 
			
		||||
     * @param titleRow 标题行行号
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder titleRow(int titleRow) {
 | 
			
		||||
        this.config.setTitleRow(titleRow);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置数据起始行
 | 
			
		||||
     *
 | 
			
		||||
     * @param headLine 数据起始行号
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder headLine(int headLine) {
 | 
			
		||||
        this.config.setHeadLine(headLine);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置唯一键字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param uniKeyNames 唯一键字段名集合
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder uniKeyNames(Set<String> uniKeyNames) {
 | 
			
		||||
        this.config.setUniKeyNames(uniKeyNames);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加唯一键字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param uniKeyName 唯一键字段名
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder addUniKeyName(String uniKeyName) {
 | 
			
		||||
        this.config.addUniKeyName(uniKeyName);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置字段列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param fields 字段定义列表
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder fields(List<MapFieldDefinition> fields) {
 | 
			
		||||
        this.config.setFields(new ArrayList<>(fields));
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加单个字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param field 字段定义
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder addField(MapFieldDefinition field) {
 | 
			
		||||
        this.config.addField(field);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量添加字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param fields 字段定义列表
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder addFields(List<MapFieldDefinition> fields) {
 | 
			
		||||
        this.config.addFields(fields);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量添加字段(可变参数)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fields 字段定义可变参数
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder addFields(MapFieldDefinition... fields) {
 | 
			
		||||
        this.config.addFields(fields);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置表头样式
 | 
			
		||||
     *
 | 
			
		||||
     * @param fontColor 字体颜色
 | 
			
		||||
     * @param backColor 背景颜色
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder headStyle(String fontColor, String backColor) {
 | 
			
		||||
        this.config.setHeadFontColor(fontColor);
 | 
			
		||||
        this.config.setHeadBackColor(backColor);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置是否为纯文本格式
 | 
			
		||||
     *
 | 
			
		||||
     * @param isText 是否设置为纯文本
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder isText(boolean isText) {
 | 
			
		||||
        this.config.setText(isText);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置是否启用字段描述
 | 
			
		||||
     *
 | 
			
		||||
     * @param enableDesc 是否启用
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder enableDesc(boolean enableDesc) {
 | 
			
		||||
        this.config.setEnableDesc(enableDesc);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 设置分组字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param groupFields 分组字段可变参数
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder groupFields(String... groupFields) {
 | 
			
		||||
        this.config.setGroupFields(Arrays.asList(groupFields));
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加自定义属性
 | 
			
		||||
     *
 | 
			
		||||
     * @param key 属性键
 | 
			
		||||
     * @param value 属性值
 | 
			
		||||
     * @return MapSheetBuilder实例
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetBuilder addCustomProperty(String key, Object value) {
 | 
			
		||||
        this.config.addCustomProperty(key, value);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建表格并返回工作表ID
 | 
			
		||||
     *
 | 
			
		||||
     * @return 创建成功返回工作表ID
 | 
			
		||||
     */
 | 
			
		||||
    public String build() {
 | 
			
		||||
        // 检查字段列表
 | 
			
		||||
        if (config.getFields().isEmpty()) {
 | 
			
		||||
            throw new IllegalArgumentException("字段定义列表不能为空");
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 判断是否为分组表格
 | 
			
		||||
        if (config.getGroupFields() != null && !config.getGroupFields().isEmpty()) {
 | 
			
		||||
            return buildGroupSheet();
 | 
			
		||||
        } else {
 | 
			
		||||
            return buildNormalSheet();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建普通表格
 | 
			
		||||
     */
 | 
			
		||||
    private String buildNormalSheet() {
 | 
			
		||||
        // 转换字段定义为 FieldProperty
 | 
			
		||||
        Map<String, FieldProperty> fieldsMap = convertToFieldsMap(config.getFields());
 | 
			
		||||
        
 | 
			
		||||
        // 生成表头
 | 
			
		||||
        List<String> headers = config.getFields().stream()
 | 
			
		||||
            .sorted(Comparator.comparingInt(MapFieldDefinition::getOrder))
 | 
			
		||||
            .map(MapFieldDefinition::getFieldName)
 | 
			
		||||
            .collect(Collectors.toList());
 | 
			
		||||
        
 | 
			
		||||
        // 创建 TableConf
 | 
			
		||||
        TableConf tableConf = createTableConf();
 | 
			
		||||
        
 | 
			
		||||
        // 创建飞书客户端
 | 
			
		||||
        FeishuClient client = FsClient.getInstance().getClient();
 | 
			
		||||
        
 | 
			
		||||
        // 1、创建sheet
 | 
			
		||||
        String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
 | 
			
		||||
        
 | 
			
		||||
        // 2、添加表头数据
 | 
			
		||||
        Map<String, String> fieldDescriptions = buildFieldDescriptions();
 | 
			
		||||
        FsApiUtil.putValues(spreadsheetToken, 
 | 
			
		||||
            FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf, fieldDescriptions), 
 | 
			
		||||
            client);
 | 
			
		||||
        
 | 
			
		||||
        // 3、设置单元格为文本格式
 | 
			
		||||
        if (config.isText()) {
 | 
			
		||||
            String column = FsTableUtil.getColumnNameByNuNumber(headers.size());
 | 
			
		||||
            FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 4、设置表格样式
 | 
			
		||||
        FsApiUtil.setTableStyle(
 | 
			
		||||
            FsTableUtil.getDefaultTableStyle(sheetId, fieldsMap, tableConf), 
 | 
			
		||||
            client, spreadsheetToken);
 | 
			
		||||
        
 | 
			
		||||
        // 5、合并单元格
 | 
			
		||||
        List<CustomCellService.CellRequest> mergeCell = FsTableUtil.getMergeCell(sheetId, fieldsMap);
 | 
			
		||||
        if (!mergeCell.isEmpty()) {
 | 
			
		||||
            mergeCell.forEach(cell -> FsApiUtil.mergeCells(cell, client, spreadsheetToken));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 6、设置表格下拉
 | 
			
		||||
        try {
 | 
			
		||||
            // 准备自定义属性,包含字段的 options 配置
 | 
			
		||||
            Map<String, Object> customProps = prepareCustomProperties(fieldsMap);
 | 
			
		||||
            FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, 
 | 
			
		||||
                config.isEnableDesc(), customProps);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            Logger.getLogger(MapSheetBuilder.class.getName()).log(Level.SEVERE,
 | 
			
		||||
                "【Map表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return sheetId;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建分组表格
 | 
			
		||||
     */
 | 
			
		||||
    private String buildGroupSheet() {
 | 
			
		||||
        // 转换字段定义为 FieldProperty
 | 
			
		||||
        Map<String, FieldProperty> fieldsMap = convertToFieldsMap(config.getFields());
 | 
			
		||||
        
 | 
			
		||||
        // 生成表头
 | 
			
		||||
        List<String> headers = config.getFields().stream()
 | 
			
		||||
            .sorted(Comparator.comparingInt(MapFieldDefinition::getOrder))
 | 
			
		||||
            .map(MapFieldDefinition::getFieldName)
 | 
			
		||||
            .collect(Collectors.toList());
 | 
			
		||||
        
 | 
			
		||||
        // 创建 TableConf
 | 
			
		||||
        TableConf tableConf = createTableConf();
 | 
			
		||||
        
 | 
			
		||||
        // 创建飞书客户端
 | 
			
		||||
        FeishuClient client = FsClient.getInstance().getClient();
 | 
			
		||||
        
 | 
			
		||||
        // 1、创建sheet
 | 
			
		||||
        String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
 | 
			
		||||
        
 | 
			
		||||
        // 2、添加表头数据(分组模式)
 | 
			
		||||
        List<String> groupFieldList = config.getGroupFields();
 | 
			
		||||
        List<String> headerList = FsTableUtil.getGroupHeaders(groupFieldList, headers);
 | 
			
		||||
        Map<String, String> fieldDescriptions = buildFieldDescriptions();
 | 
			
		||||
        FsApiUtil.putValues(spreadsheetToken,
 | 
			
		||||
            FsTableUtil.getHeadTemplateBuilder(sheetId, headers, headerList, fieldsMap, 
 | 
			
		||||
                tableConf, fieldDescriptions, groupFieldList),
 | 
			
		||||
            client);
 | 
			
		||||
        
 | 
			
		||||
        // 3、设置单元格为文本格式
 | 
			
		||||
        if (config.isText()) {
 | 
			
		||||
            String column = FsTableUtil.getColumnNameByNuNumber(headerList.size());
 | 
			
		||||
            FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 4、设置表格样式(分组模式)
 | 
			
		||||
        Map<String, String[]> positions = FsTableUtil.calculateGroupPositions(headers, groupFieldList);
 | 
			
		||||
        positions.forEach((key, value) -> 
 | 
			
		||||
            FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, value, tableConf), 
 | 
			
		||||
                client, spreadsheetToken));
 | 
			
		||||
        
 | 
			
		||||
        // 5、合并单元格
 | 
			
		||||
        List<CustomCellService.CellRequest> mergeCell = FsTableUtil.getMergeCell(sheetId, positions.values());
 | 
			
		||||
        if (!mergeCell.isEmpty()) {
 | 
			
		||||
            mergeCell.forEach(cell -> FsApiUtil.mergeCells(cell, client, spreadsheetToken));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 6、设置表格下拉
 | 
			
		||||
        try {
 | 
			
		||||
            String[] headerWithColumnIdentifiers = FsTableUtil.generateHeaderWithColumnIdentifiers(headers, groupFieldList);
 | 
			
		||||
            // 准备自定义属性,包含字段的 options 配置
 | 
			
		||||
            Map<String, Object> customProps = prepareCustomProperties(fieldsMap);
 | 
			
		||||
            FsTableUtil.setTableOptions(spreadsheetToken, headerWithColumnIdentifiers, fieldsMap, 
 | 
			
		||||
                sheetId, config.isEnableDesc(), customProps);
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            Logger.getLogger(MapSheetBuilder.class.getName()).log(Level.SEVERE,
 | 
			
		||||
                "【Map表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return sheetId;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 将 MapFieldDefinition 列表转换为 FieldProperty Map
 | 
			
		||||
     */
 | 
			
		||||
    private Map<String, FieldProperty> convertToFieldsMap(List<MapFieldDefinition> fields) {
 | 
			
		||||
        Map<String, FieldProperty> fieldsMap = new LinkedHashMap<>();
 | 
			
		||||
        
 | 
			
		||||
        for (MapFieldDefinition field : fields) {
 | 
			
		||||
            TableProperty tableProperty = createTableProperty(field);
 | 
			
		||||
            FieldProperty fieldProperty = new FieldProperty(field.getFieldName(), tableProperty);
 | 
			
		||||
            fieldsMap.put(field.getFieldName(), fieldProperty);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return fieldsMap;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据 MapFieldDefinition 创建 TableProperty 注解实例
 | 
			
		||||
     */
 | 
			
		||||
    private TableProperty createTableProperty(MapFieldDefinition field) {
 | 
			
		||||
        return new TableProperty() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Class<? extends Annotation> annotationType() {
 | 
			
		||||
                return TableProperty.class;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public String[] value() {
 | 
			
		||||
                return new String[]{field.getFieldName()};
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public String desc() {
 | 
			
		||||
                return field.getDescription() != null ? field.getDescription() : "";
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public String field() {
 | 
			
		||||
                return field.getFieldName();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public int order() {
 | 
			
		||||
                return field.getOrder();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public TypeEnum type() {
 | 
			
		||||
                return field.getType() != null ? field.getType() : TypeEnum.TEXT;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public Class<? extends BaseEnum> enumClass() {
 | 
			
		||||
                // 优先级1:如果配置了 enumClass,直接返回
 | 
			
		||||
                if (field.getEnumClass() != null && field.getEnumClass() != BaseEnum.class) {
 | 
			
		||||
                    return field.getEnumClass();
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // 优先级2:如果没有配置 enumClass 但配置了 options,创建动态枚举类
 | 
			
		||||
                // 注意:这里返回 BaseEnum.class,实际的 options 通过 optionsClass 处理
 | 
			
		||||
                return BaseEnum.class;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public Class<? extends FieldValueProcess> fieldFormatClass() {
 | 
			
		||||
                return FieldValueProcess.class;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public Class<? extends OptionsValueProcess> optionsClass() {
 | 
			
		||||
                // 优先级1:如果配置了 optionsClass,直接返回
 | 
			
		||||
                if (field.getOptionsClass() != null && field.getOptionsClass() != OptionsValueProcess.class) {
 | 
			
		||||
                    return field.getOptionsClass();
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // 优先级2:如果配置了 options 但没有 optionsClass,创建动态的处理类
 | 
			
		||||
                if (field.getOptions() != null && !field.getOptions().isEmpty()) {
 | 
			
		||||
                    return MapOptionsUtil.createDynamicOptionsClass(field.getOptions());
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                // 优先级3:返回默认值
 | 
			
		||||
                return OptionsValueProcess.class;
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建 TableConf 注解实例
 | 
			
		||||
     */
 | 
			
		||||
    private TableConf createTableConf() {
 | 
			
		||||
        return new TableConf() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public Class<? extends Annotation> annotationType() {
 | 
			
		||||
                return TableConf.class;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public String[] uniKeys() {
 | 
			
		||||
                Set<String> uniKeyNames = config.getUniKeyNames();
 | 
			
		||||
                return uniKeyNames != null ? uniKeyNames.toArray(new String[0]) : new String[0];
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public int headLine() {
 | 
			
		||||
                return config.getHeadLine();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public int titleRow() {
 | 
			
		||||
                return config.getTitleRow();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public boolean enableCover() {
 | 
			
		||||
                return config.isEnableCover();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public boolean isText() {
 | 
			
		||||
                return config.isText();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public boolean enableDesc() {
 | 
			
		||||
                return config.isEnableDesc();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public String headFontColor() {
 | 
			
		||||
                return config.getHeadFontColor();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            @Override
 | 
			
		||||
            public String headBackColor() {
 | 
			
		||||
                return config.getHeadBackColor();
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 构建字段描述映射
 | 
			
		||||
     */
 | 
			
		||||
    private Map<String, String> buildFieldDescriptions() {
 | 
			
		||||
        Map<String, String> descriptions = new HashMap<>();
 | 
			
		||||
        for (MapFieldDefinition field : config.getFields()) {
 | 
			
		||||
            if (field.getDescription() != null && !field.getDescription().isEmpty()) {
 | 
			
		||||
                descriptions.put(field.getFieldName(), field.getDescription());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return descriptions;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 准备自定义属性
 | 
			
		||||
     * 
 | 
			
		||||
     * 将字段配置的 options 放入 customProperties,供 DynamicOptionsProcess 使用
 | 
			
		||||
     */
 | 
			
		||||
    private Map<String, Object> prepareCustomProperties(Map<String, FieldProperty> fieldsMap) {
 | 
			
		||||
        Map<String, Object> customProps = new HashMap<>();
 | 
			
		||||
        
 | 
			
		||||
        // 复制原有的自定义属性
 | 
			
		||||
        if (config.getCustomProperties() != null) {
 | 
			
		||||
            customProps.putAll(config.getCustomProperties());
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 为每个配置了 options 的字段添加选项到 customProperties
 | 
			
		||||
        for (MapFieldDefinition field : config.getFields()) {
 | 
			
		||||
            if (field.getOptions() != null && !field.getOptions().isEmpty()) {
 | 
			
		||||
                // 使用字段名作为 key 前缀,避免冲突
 | 
			
		||||
                customProps.put("_dynamicOptions_" + field.getFieldName(), field.getOptions());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return customProps;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										522
									
								
								src/main/java/cn/isliu/core/config/MapFieldDefinition.java
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										522
									
								
								src/main/java/cn/isliu/core/config/MapFieldDefinition.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,522 @@
 | 
			
		||||
package cn.isliu.core.config;
 | 
			
		||||
 | 
			
		||||
import cn.isliu.core.converters.OptionsValueProcess;
 | 
			
		||||
import cn.isliu.core.enums.BaseEnum;
 | 
			
		||||
import cn.isliu.core.enums.TypeEnum;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Map方式字段定义类
 | 
			
		||||
 * 
 | 
			
		||||
 * 用于替代 @TableProperty 注解,定义单个字段的所有属性
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Ls
 | 
			
		||||
 * @since 2025-10-16
 | 
			
		||||
 */
 | 
			
		||||
public class MapFieldDefinition {
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 字段名称(对应表格列名)
 | 
			
		||||
     */
 | 
			
		||||
    private String fieldName;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 字段描述
 | 
			
		||||
     */
 | 
			
		||||
    private String description;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 字段排序顺序,数值越小越靠前
 | 
			
		||||
     */
 | 
			
		||||
    private int order;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 字段类型
 | 
			
		||||
     */
 | 
			
		||||
    private TypeEnum type = TypeEnum.TEXT;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 下拉选项列表(当type为单选/多选时使用)
 | 
			
		||||
     */
 | 
			
		||||
    private List<String> options;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 选项映射(code -> label)
 | 
			
		||||
     */
 | 
			
		||||
    private Map<String, String> optionsMap;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 枚举类(可选,用于从枚举类生成选项)
 | 
			
		||||
     */
 | 
			
		||||
    private Class<? extends BaseEnum> enumClass;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 选项处理类(可选,用于自定义选项处理逻辑)
 | 
			
		||||
     */
 | 
			
		||||
    private Class<? extends OptionsValueProcess> optionsClass;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否必填
 | 
			
		||||
     */
 | 
			
		||||
    private boolean required = false;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 默认值
 | 
			
		||||
     */
 | 
			
		||||
    private String defaultValue;
 | 
			
		||||
 | 
			
		||||
    public String getFieldName() {
 | 
			
		||||
        return fieldName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setFieldName(String fieldName) {
 | 
			
		||||
        this.fieldName = fieldName;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getDescription() {
 | 
			
		||||
        return description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setDescription(String description) {
 | 
			
		||||
        this.description = description;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getOrder() {
 | 
			
		||||
        return order;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOrder(int order) {
 | 
			
		||||
        this.order = order;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public TypeEnum getType() {
 | 
			
		||||
        return type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setType(TypeEnum type) {
 | 
			
		||||
        this.type = type;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<String> getOptions() {
 | 
			
		||||
        return options;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOptions(List<String> options) {
 | 
			
		||||
        this.options = options;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Map<String, String> getOptionsMap() {
 | 
			
		||||
        return optionsMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOptionsMap(Map<String, String> optionsMap) {
 | 
			
		||||
        this.optionsMap = optionsMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Class<? extends BaseEnum> getEnumClass() {
 | 
			
		||||
        return enumClass;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setEnumClass(Class<? extends BaseEnum> enumClass) {
 | 
			
		||||
        this.enumClass = enumClass;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Class<? extends OptionsValueProcess> getOptionsClass() {
 | 
			
		||||
        return optionsClass;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setOptionsClass(Class<? extends OptionsValueProcess> optionsClass) {
 | 
			
		||||
        this.optionsClass = optionsClass;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isRequired() {
 | 
			
		||||
        return required;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setRequired(boolean required) {
 | 
			
		||||
        this.required = required;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getDefaultValue() {
 | 
			
		||||
        return defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setDefaultValue(String defaultValue) {
 | 
			
		||||
        this.defaultValue = defaultValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (o == null || getClass() != o.getClass()) return false;
 | 
			
		||||
        MapFieldDefinition that = (MapFieldDefinition) o;
 | 
			
		||||
        return order == that.order && required == that.required && Objects.equals(fieldName, that.fieldName) && Objects.equals(description, that.description) && type == that.type && Objects.equals(options, that.options) && Objects.equals(optionsMap, that.optionsMap) && Objects.equals(enumClass, that.enumClass) && Objects.equals(optionsClass, that.optionsClass) && Objects.equals(defaultValue, that.defaultValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return Objects.hash(fieldName, description, order, type, options, optionsMap, enumClass, optionsClass, required, defaultValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "MapFieldDefinition{" +
 | 
			
		||||
                "fieldName='" + fieldName + '\'' +
 | 
			
		||||
                ", description='" + description + '\'' +
 | 
			
		||||
                ", order=" + order +
 | 
			
		||||
                ", type=" + type +
 | 
			
		||||
                ", options=" + options +
 | 
			
		||||
                ", optionsMap=" + optionsMap +
 | 
			
		||||
                ", enumClass=" + enumClass +
 | 
			
		||||
                ", optionsClass=" + optionsClass +
 | 
			
		||||
                ", required=" + required +
 | 
			
		||||
                ", defaultValue='" + defaultValue + '\'' +
 | 
			
		||||
                '}';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建构建器
 | 
			
		||||
     *
 | 
			
		||||
     * @return Builder实例
 | 
			
		||||
     */
 | 
			
		||||
    public static Builder builder() {
 | 
			
		||||
        return new Builder();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建文本字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition text(String fieldName, int order) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.TEXT)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建文本字段(带描述)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param description 字段描述
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition text(String fieldName, int order, String description) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.TEXT)
 | 
			
		||||
            .description(description)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建单选字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param options 选项列表
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition singleSelect(String fieldName, int order, String... options) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.SINGLE_SELECT)
 | 
			
		||||
            .options(Arrays.asList(options))
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建单选字段(带选项列表)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param options 选项列表
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition singleSelect(String fieldName, int order, List<String> options) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.SINGLE_SELECT)
 | 
			
		||||
            .options(options)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建单选字段(带描述)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param description 字段描述
 | 
			
		||||
     * @param options 选项列表
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition singleSelectWithDesc(String fieldName, int order, String description, List<String> options) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.SINGLE_SELECT)
 | 
			
		||||
            .description(description)
 | 
			
		||||
            .options(options)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建多选字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param options 选项列表
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition multiSelect(String fieldName, int order, String... options) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.MULTI_SELECT)
 | 
			
		||||
            .options(Arrays.asList(options))
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建多选字段(带选项列表)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param options 选项列表
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition multiSelect(String fieldName, int order, List<String> options) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.MULTI_SELECT)
 | 
			
		||||
            .options(options)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建多选字段(带描述)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param description 字段描述
 | 
			
		||||
     * @param options 选项列表
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition multiSelectWithDesc(String fieldName, int order, String description, List<String> options) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.MULTI_SELECT)
 | 
			
		||||
            .description(description)
 | 
			
		||||
            .options(options)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建单选字段(使用枚举类)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param enumClass 枚举类
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition singleSelectWithEnum(String fieldName, int order, Class<? extends BaseEnum> enumClass) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.SINGLE_SELECT)
 | 
			
		||||
            .enumClass(enumClass)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建单选字段(使用枚举类,带描述)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param description 字段描述
 | 
			
		||||
     * @param enumClass 枚举类
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition singleSelectWithEnum(String fieldName, int order, String description, Class<? extends BaseEnum> enumClass) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.SINGLE_SELECT)
 | 
			
		||||
            .description(description)
 | 
			
		||||
            .enumClass(enumClass)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建多选字段(使用枚举类)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param enumClass 枚举类
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition multiSelectWithEnum(String fieldName, int order, Class<? extends BaseEnum> enumClass) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.MULTI_SELECT)
 | 
			
		||||
            .enumClass(enumClass)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 快速创建多选字段(使用枚举类,带描述)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldName 字段名称
 | 
			
		||||
     * @param order 排序顺序
 | 
			
		||||
     * @param description 字段描述
 | 
			
		||||
     * @param enumClass 枚举类
 | 
			
		||||
     * @return MapFieldDefinition实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapFieldDefinition multiSelectWithEnum(String fieldName, int order, String description, Class<? extends BaseEnum> enumClass) {
 | 
			
		||||
        return builder()
 | 
			
		||||
            .fieldName(fieldName)
 | 
			
		||||
            .order(order)
 | 
			
		||||
            .type(TypeEnum.MULTI_SELECT)
 | 
			
		||||
            .description(description)
 | 
			
		||||
            .enumClass(enumClass)
 | 
			
		||||
            .build();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 从 Map 批量创建字段定义
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldMap key为字段名,value为字段类型字符串
 | 
			
		||||
     * @return 字段定义列表
 | 
			
		||||
     */
 | 
			
		||||
    public static List<MapFieldDefinition> fromMap(Map<String, String> fieldMap) {
 | 
			
		||||
        List<MapFieldDefinition> fields = new ArrayList<>();
 | 
			
		||||
        int order = 0;
 | 
			
		||||
        for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
 | 
			
		||||
            TypeEnum type = parseType(entry.getValue());
 | 
			
		||||
            fields.add(builder()
 | 
			
		||||
                .fieldName(entry.getKey())
 | 
			
		||||
                .order(order++)
 | 
			
		||||
                .type(type)
 | 
			
		||||
                .build());
 | 
			
		||||
        }
 | 
			
		||||
        return fields;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 从字段名列表快速创建文本字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param fieldNames 字段名称列表
 | 
			
		||||
     * @return 字段定义列表
 | 
			
		||||
     */
 | 
			
		||||
    public static List<MapFieldDefinition> fromFieldNames(List<String> fieldNames) {
 | 
			
		||||
        List<MapFieldDefinition> fields = new ArrayList<>();
 | 
			
		||||
        for (int i = 0; i < fieldNames.size(); i++) {
 | 
			
		||||
            fields.add(text(fieldNames.get(i), i));
 | 
			
		||||
        }
 | 
			
		||||
        return fields;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 解析类型字符串
 | 
			
		||||
     */
 | 
			
		||||
    private static TypeEnum parseType(String typeStr) {
 | 
			
		||||
        if (typeStr == null || typeStr.isEmpty()) {
 | 
			
		||||
            return TypeEnum.TEXT;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        try {
 | 
			
		||||
            return TypeEnum.valueOf(typeStr.toUpperCase());
 | 
			
		||||
        } catch (IllegalArgumentException e) {
 | 
			
		||||
            return TypeEnum.TEXT;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 字段定义构建器
 | 
			
		||||
     */
 | 
			
		||||
    public static class Builder {
 | 
			
		||||
        private final MapFieldDefinition definition = new MapFieldDefinition();
 | 
			
		||||
        
 | 
			
		||||
        public Builder fieldName(String fieldName) {
 | 
			
		||||
            definition.fieldName = fieldName;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder description(String description) {
 | 
			
		||||
            definition.description = description;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder order(int order) {
 | 
			
		||||
            definition.order = order;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder type(TypeEnum type) {
 | 
			
		||||
            definition.type = type;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder options(List<String> options) {
 | 
			
		||||
            if (options == null || options.isEmpty()) {
 | 
			
		||||
                return this;
 | 
			
		||||
            }
 | 
			
		||||
            definition.options = new ArrayList<>(options);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder options(String... options) {
 | 
			
		||||
            if (options == null || options.length == 0) {
 | 
			
		||||
                return this;
 | 
			
		||||
            }
 | 
			
		||||
            definition.options = Arrays.asList(options);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder optionsMap(Map<String, String> optionsMap) {
 | 
			
		||||
            definition.optionsMap = new HashMap<>(optionsMap);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder enumClass(Class<? extends BaseEnum> enumClass) {
 | 
			
		||||
            definition.enumClass = enumClass;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder optionsClass(Class<? extends OptionsValueProcess> optionsClass) {
 | 
			
		||||
            definition.optionsClass = optionsClass;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder required(boolean required) {
 | 
			
		||||
            definition.required = required;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public Builder defaultValue(String defaultValue) {
 | 
			
		||||
            definition.defaultValue = defaultValue;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        public MapFieldDefinition build() {
 | 
			
		||||
            // 验证必填字段
 | 
			
		||||
            if (definition.fieldName == null || definition.fieldName.isEmpty()) {
 | 
			
		||||
                throw new IllegalArgumentException("字段名称不能为空");
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return definition;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										426
									
								
								src/main/java/cn/isliu/core/config/MapSheetConfig.java
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										426
									
								
								src/main/java/cn/isliu/core/config/MapSheetConfig.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,426 @@
 | 
			
		||||
package cn.isliu.core.config;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Map方式表格创建配置类
 | 
			
		||||
 * 
 | 
			
		||||
 * 继承 MapTableConfig,专门用于创建飞书表格
 | 
			
		||||
 * 相比父类增加了字段定义、样式配置等创建表格所需的属性
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Ls
 | 
			
		||||
 * @since 2025-10-16
 | 
			
		||||
 */
 | 
			
		||||
public class MapSheetConfig extends MapTableConfig {
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 字段定义列表
 | 
			
		||||
     */
 | 
			
		||||
    private List<MapFieldDefinition> fields = new ArrayList<>();
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 表头字体颜色(十六进制,如 #ffffff)
 | 
			
		||||
     */
 | 
			
		||||
    private String headFontColor = "#ffffff";
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 表头背景颜色(十六进制,如 #000000)
 | 
			
		||||
     */
 | 
			
		||||
    private String headBackColor = "#000000";
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否将单元格设置为纯文本格式
 | 
			
		||||
     */
 | 
			
		||||
    private boolean isText = false;
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否启用字段描述行
 | 
			
		||||
     */
 | 
			
		||||
    private boolean enableDesc = false;
 | 
			
		||||
 | 
			
		||||
    public List<MapFieldDefinition> getFields() {
 | 
			
		||||
        return fields;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setFields(List<MapFieldDefinition> fields) {
 | 
			
		||||
        this.fields = fields;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getHeadFontColor() {
 | 
			
		||||
        return headFontColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHeadFontColor(String headFontColor) {
 | 
			
		||||
        this.headFontColor = headFontColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public String getHeadBackColor() {
 | 
			
		||||
        return headBackColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setHeadBackColor(String headBackColor) {
 | 
			
		||||
        this.headBackColor = headBackColor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isText() {
 | 
			
		||||
        return isText;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setText(boolean text) {
 | 
			
		||||
        isText = text;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isEnableDesc() {
 | 
			
		||||
        return enableDesc;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setEnableDesc(boolean enableDesc) {
 | 
			
		||||
        this.enableDesc = enableDesc;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public List<String> getGroupFields() {
 | 
			
		||||
        return groupFields;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setGroupFields(List<String> groupFields) {
 | 
			
		||||
        this.groupFields = groupFields;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Map<String, Object> getCustomProperties() {
 | 
			
		||||
        return customProperties;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setCustomProperties(Map<String, Object> customProperties) {
 | 
			
		||||
        this.customProperties = customProperties;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分组字段列表(用于创建分组表格)
 | 
			
		||||
     */
 | 
			
		||||
    private List<String> groupFields = new ArrayList<>();
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 自定义属性映射(用于传递额外配置)
 | 
			
		||||
     */
 | 
			
		||||
    private Map<String, Object> customProperties = new HashMap<>();
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建默认配置
 | 
			
		||||
     *
 | 
			
		||||
     * @return 默认配置实例
 | 
			
		||||
     */
 | 
			
		||||
    public static MapSheetConfig createDefault() {
 | 
			
		||||
        return new MapSheetConfig();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建表格配置构建器
 | 
			
		||||
     *
 | 
			
		||||
     * @return 配置构建器实例
 | 
			
		||||
     */
 | 
			
		||||
    public static SheetBuilder sheetBuilder() {
 | 
			
		||||
        return new SheetBuilder();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加单个字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param field 字段定义
 | 
			
		||||
     * @return MapSheetConfig实例,支持链式调用
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetConfig addField(MapFieldDefinition field) {
 | 
			
		||||
        this.fields.add(field);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量添加字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param fields 字段定义列表
 | 
			
		||||
     * @return MapSheetConfig实例,支持链式调用
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetConfig addFields(List<MapFieldDefinition> fields) {
 | 
			
		||||
        this.fields.addAll(fields);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 批量添加字段(可变参数)
 | 
			
		||||
     *
 | 
			
		||||
     * @param fields 字段定义可变参数
 | 
			
		||||
     * @return MapSheetConfig实例,支持链式调用
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetConfig addFields(MapFieldDefinition... fields) {
 | 
			
		||||
        this.fields.addAll(Arrays.asList(fields));
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加分组字段
 | 
			
		||||
     *
 | 
			
		||||
     * @param groupField 分组字段名
 | 
			
		||||
     * @return MapSheetConfig实例,支持链式调用
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetConfig addGroupField(String groupField) {
 | 
			
		||||
        this.groupFields.add(groupField);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 添加自定义属性
 | 
			
		||||
     *
 | 
			
		||||
     * @param key 属性键
 | 
			
		||||
     * @param value 属性值
 | 
			
		||||
     * @return MapSheetConfig实例,支持链式调用
 | 
			
		||||
     */
 | 
			
		||||
    public MapSheetConfig addCustomProperty(String key, Object value) {
 | 
			
		||||
        this.customProperties.put(key, value);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object o) {
 | 
			
		||||
        if (o == null || getClass() != o.getClass()) return false;
 | 
			
		||||
        MapSheetConfig that = (MapSheetConfig) o;
 | 
			
		||||
        return isText == that.isText && enableDesc == that.enableDesc && Objects.equals(fields, that.fields) && Objects.equals(headFontColor, that.headFontColor) && Objects.equals(headBackColor, that.headBackColor) && Objects.equals(groupFields, that.groupFields) && Objects.equals(customProperties, that.customProperties);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return Objects.hash(fields, headFontColor, headBackColor, isText, enableDesc, groupFields, customProperties);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 表格配置构建器
 | 
			
		||||
     */
 | 
			
		||||
    public static class SheetBuilder {
 | 
			
		||||
        private final MapSheetConfig config = new MapSheetConfig();
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置标题行行号
 | 
			
		||||
         *
 | 
			
		||||
         * @param titleRow 标题行行号
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder titleRow(int titleRow) {
 | 
			
		||||
            config.setTitleRow(titleRow);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置数据起始行行号
 | 
			
		||||
         *
 | 
			
		||||
         * @param headLine 数据起始行行号
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder headLine(int headLine) {
 | 
			
		||||
            config.setHeadLine(headLine);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置唯一键字段名集合
 | 
			
		||||
         *
 | 
			
		||||
         * @param uniKeyNames 唯一键字段名集合
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder uniKeyNames(Set<String> uniKeyNames) {
 | 
			
		||||
            config.setUniKeyNames(uniKeyNames);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 添加唯一键字段名
 | 
			
		||||
         *
 | 
			
		||||
         * @param uniKeyName 唯一键字段名
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder addUniKeyName(String uniKeyName) {
 | 
			
		||||
            config.addUniKeyName(uniKeyName);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置是否覆盖已存在数据
 | 
			
		||||
         *
 | 
			
		||||
         * @param enableCover true表示覆盖,false表示不覆盖
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder enableCover(boolean enableCover) {
 | 
			
		||||
            config.setEnableCover(enableCover);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置字段定义列表
 | 
			
		||||
         *
 | 
			
		||||
         * @param fields 字段定义列表
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder fields(List<MapFieldDefinition> fields) {
 | 
			
		||||
            config.fields = new ArrayList<>(fields);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 添加单个字段
 | 
			
		||||
         *
 | 
			
		||||
         * @param field 字段定义
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder addField(MapFieldDefinition field) {
 | 
			
		||||
            config.fields.add(field);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 批量添加字段
 | 
			
		||||
         *
 | 
			
		||||
         * @param fields 字段定义列表
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder addFields(List<MapFieldDefinition> fields) {
 | 
			
		||||
            config.fields.addAll(fields);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 批量添加字段(可变参数)
 | 
			
		||||
         *
 | 
			
		||||
         * @param fields 字段定义可变参数
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder addFields(MapFieldDefinition... fields) {
 | 
			
		||||
            config.fields.addAll(Arrays.asList(fields));
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置表头字体颜色
 | 
			
		||||
         *
 | 
			
		||||
         * @param headFontColor 表头字体颜色(十六进制)
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder headFontColor(String headFontColor) {
 | 
			
		||||
            config.headFontColor = headFontColor;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置表头背景颜色
 | 
			
		||||
         *
 | 
			
		||||
         * @param headBackColor 表头背景颜色(十六进制)
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder headBackColor(String headBackColor) {
 | 
			
		||||
            config.headBackColor = headBackColor;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置表头样式
 | 
			
		||||
         *
 | 
			
		||||
         * @param fontColor 字体颜色
 | 
			
		||||
         * @param backColor 背景颜色
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder headStyle(String fontColor, String backColor) {
 | 
			
		||||
            config.headFontColor = fontColor;
 | 
			
		||||
            config.headBackColor = backColor;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置是否将单元格设置为纯文本
 | 
			
		||||
         *
 | 
			
		||||
         * @param isText 是否设置为纯文本
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder isText(boolean isText) {
 | 
			
		||||
            config.isText = isText;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置是否启用字段描述行
 | 
			
		||||
         *
 | 
			
		||||
         * @param enableDesc 是否启用
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder enableDesc(boolean enableDesc) {
 | 
			
		||||
            config.enableDesc = enableDesc;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置分组字段列表
 | 
			
		||||
         *
 | 
			
		||||
         * @param groupFields 分组字段列表
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder groupFields(List<String> groupFields) {
 | 
			
		||||
            config.groupFields = new ArrayList<>(groupFields);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置分组字段(可变参数)
 | 
			
		||||
         *
 | 
			
		||||
         * @param groupFields 分组字段可变参数
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder groupFields(String... groupFields) {
 | 
			
		||||
            config.groupFields = Arrays.asList(groupFields);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 添加分组字段
 | 
			
		||||
         *
 | 
			
		||||
         * @param groupField 分组字段名
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder addGroupField(String groupField) {
 | 
			
		||||
            config.groupFields.add(groupField);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 设置自定义属性映射
 | 
			
		||||
         *
 | 
			
		||||
         * @param customProperties 自定义属性映射
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder customProperties(Map<String, Object> customProperties) {
 | 
			
		||||
            config.customProperties = new HashMap<>(customProperties);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 添加自定义属性
 | 
			
		||||
         *
 | 
			
		||||
         * @param key 属性键
 | 
			
		||||
         * @param value 属性值
 | 
			
		||||
         * @return SheetBuilder实例
 | 
			
		||||
         */
 | 
			
		||||
        public SheetBuilder addCustomProperty(String key, Object value) {
 | 
			
		||||
            config.customProperties.put(key, value);
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        /**
 | 
			
		||||
         * 构建配置对象
 | 
			
		||||
         *
 | 
			
		||||
         * @return MapSheetConfig实例
 | 
			
		||||
         */
 | 
			
		||||
        public MapSheetConfig build() {
 | 
			
		||||
            // 验证必填字段
 | 
			
		||||
            if (config.fields.isEmpty()) {
 | 
			
		||||
                throw new IllegalArgumentException("字段定义列表不能为空");
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return config;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										122
									
								
								src/main/java/cn/isliu/core/utils/MapOptionsUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										122
									
								
								src/main/java/cn/isliu/core/utils/MapOptionsUtil.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,122 @@
 | 
			
		||||
package cn.isliu.core.utils;
 | 
			
		||||
 | 
			
		||||
import cn.isliu.core.converters.OptionsValueProcess;
 | 
			
		||||
import cn.isliu.core.enums.BaseEnum;
 | 
			
		||||
import cn.isliu.core.pojo.FieldProperty;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Map 选项工具类
 | 
			
		||||
 * 
 | 
			
		||||
 * 提供选项提取、转换和处理的工具方法
 | 
			
		||||
 * 
 | 
			
		||||
 * @author Ls
 | 
			
		||||
 * @since 2025-10-16
 | 
			
		||||
 */
 | 
			
		||||
public class MapOptionsUtil {
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 从枚举类提取选项列表
 | 
			
		||||
     * 
 | 
			
		||||
     * 提取枚举类中所有枚举常量的描述(desc)作为下拉选项
 | 
			
		||||
     *
 | 
			
		||||
     * @param enumClass 枚举类
 | 
			
		||||
     * @return 选项列表(枚举的desc值)
 | 
			
		||||
     */
 | 
			
		||||
    public static List<String> extractOptionsFromEnum(Class<? extends BaseEnum> enumClass) {
 | 
			
		||||
        if (enumClass == null || enumClass == BaseEnum.class) {
 | 
			
		||||
            return new ArrayList<>();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return Arrays.stream(enumClass.getEnumConstants())
 | 
			
		||||
            .map(BaseEnum::getDesc)
 | 
			
		||||
            .collect(Collectors.toList());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 从枚举类提取选项映射
 | 
			
		||||
     * 
 | 
			
		||||
     * 提取枚举类中所有枚举常量的 code -> desc 映射
 | 
			
		||||
     *
 | 
			
		||||
     * @param enumClass 枚举类
 | 
			
		||||
     * @return 选项映射(code -> desc)
 | 
			
		||||
     */
 | 
			
		||||
    public static Map<String, String> extractOptionsMapFromEnum(Class<? extends BaseEnum> enumClass) {
 | 
			
		||||
        if (enumClass == null || enumClass == BaseEnum.class) {
 | 
			
		||||
            return new HashMap<>();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        Map<String, String> optionsMap = new LinkedHashMap<>();
 | 
			
		||||
        Arrays.stream(enumClass.getEnumConstants())
 | 
			
		||||
            .forEach(e -> optionsMap.put(e.getCode(), e.getDesc()));
 | 
			
		||||
        return optionsMap;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 创建动态的 OptionsValueProcess 类
 | 
			
		||||
     * 
 | 
			
		||||
     * 用于处理直接配置的 options 列表,将其包装为 OptionsValueProcess
 | 
			
		||||
     *
 | 
			
		||||
     * @param options 选项列表
 | 
			
		||||
     * @return 动态创建的 OptionsValueProcess 类
 | 
			
		||||
     */
 | 
			
		||||
    public static Class<? extends OptionsValueProcess> createDynamicOptionsClass(final List<String> options) {
 | 
			
		||||
        if (options == null || options.isEmpty()) {
 | 
			
		||||
            return OptionsValueProcess.class;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // 返回一个匿名内部类
 | 
			
		||||
        // 注意:由于 Java 的限制,我们不能真正动态创建类
 | 
			
		||||
        // 这里返回一个包装器类,在 MapSheetBuilder 中特殊处理
 | 
			
		||||
        return DynamicOptionsProcess.class;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 动态选项处理类(内部使用)
 | 
			
		||||
     * 
 | 
			
		||||
     * 从 customProperties 中读取预先配置的选项列表
 | 
			
		||||
     * 在 MapSheetBuilder 中,会将字段的 options 放入 customProperties
 | 
			
		||||
     */
 | 
			
		||||
    public static class DynamicOptionsProcess implements OptionsValueProcess {
 | 
			
		||||
        @Override
 | 
			
		||||
        public List<String> process(Object value) {
 | 
			
		||||
            if (value instanceof Map) {
 | 
			
		||||
                Map<String, Object> properties = (Map<String, Object>) value;
 | 
			
		||||
                
 | 
			
		||||
                // 从 _field 获取当前字段信息
 | 
			
		||||
                Object fieldObj = properties.get("_field");
 | 
			
		||||
                if (fieldObj instanceof FieldProperty) {
 | 
			
		||||
                    FieldProperty fieldProperty =
 | 
			
		||||
                        (FieldProperty) fieldObj;
 | 
			
		||||
                    
 | 
			
		||||
                    // 获取字段名
 | 
			
		||||
                    String[] fieldNames = fieldProperty.getTableProperty().value();
 | 
			
		||||
                    if (fieldNames != null && fieldNames.length > 0) {
 | 
			
		||||
                        String fieldName = fieldNames[0];
 | 
			
		||||
                        
 | 
			
		||||
                        // 从 customProperties 中获取该字段的 options
 | 
			
		||||
                        Object optionsObj = properties.get("_dynamicOptions_" + fieldName);
 | 
			
		||||
                        if (optionsObj instanceof List) {
 | 
			
		||||
                            return (List<String>) optionsObj;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            return new ArrayList<>();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断是否为动态选项处理类
 | 
			
		||||
     *
 | 
			
		||||
     * @param optionsClass 选项处理类
 | 
			
		||||
     * @return 是否为动态选项处理类
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isDynamicOptionsClass(Class<? extends OptionsValueProcess> optionsClass) {
 | 
			
		||||
        return optionsClass != null && optionsClass == DynamicOptionsProcess.class;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user