refactor(core): 重构飞书客户端并优化单元格操作
- 重构 FeishuClient 类,优化自定义服务管理 - 新增 ServiceManager 类,统一管理自定义服务 - 优化 CustomCellService 中的单元格操作逻辑- 移除不必要的请求类型和参数处理 - 新增合并单元格功能 - 调整表格样式设置接口 - 优化 FsClient 类,使用 ThreadLocal管理客户端实例
This commit is contained in:
		
							parent
							
								
									5a43a7f2e3
								
							
						
					
					
						commit
						8a49e5280b
					
				
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -14,6 +14,7 @@ test/
 | 
				
			|||||||
*.ipr
 | 
					*.ipr
 | 
				
			||||||
.idea
 | 
					.idea
 | 
				
			||||||
.kiro
 | 
					.kiro
 | 
				
			||||||
 | 
					src/main/java/cn/isliu/example/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Eclipse ###
 | 
					### Eclipse ###
 | 
				
			||||||
.apt_generated
 | 
					.apt_generated
 | 
				
			||||||
 | 
				
			|||||||
@ -11,6 +11,7 @@ import cn.isliu.core.enums.ErrorCode;
 | 
				
			|||||||
import cn.isliu.core.enums.FileType;
 | 
					import cn.isliu.core.enums.FileType;
 | 
				
			||||||
import cn.isliu.core.logging.FsLogger;
 | 
					import cn.isliu.core.logging.FsLogger;
 | 
				
			||||||
import cn.isliu.core.pojo.FieldProperty;
 | 
					import cn.isliu.core.pojo.FieldProperty;
 | 
				
			||||||
 | 
					import cn.isliu.core.service.CustomCellService;
 | 
				
			||||||
import cn.isliu.core.service.CustomValueService;
 | 
					import cn.isliu.core.service.CustomValueService;
 | 
				
			||||||
import cn.isliu.core.utils.*;
 | 
					import cn.isliu.core.utils.*;
 | 
				
			||||||
import com.google.gson.JsonObject;
 | 
					import com.google.gson.JsonObject;
 | 
				
			||||||
@ -54,7 +55,13 @@ public class FsHelper {
 | 
				
			|||||||
        FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf), client);
 | 
					        FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf), client);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 3 设置表格样式
 | 
					        // 3 设置表格样式
 | 
				
			||||||
        FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, headers.size(), tableConf), sheetId, client, spreadsheetToken);
 | 
					        FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, fieldsMap, tableConf), client, spreadsheetToken);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 4 合并单元格
 | 
				
			||||||
 | 
					        List<CustomCellService.CellRequest> mergeCell = FsTableUtil.getMergeCell(sheetId, fieldsMap);
 | 
				
			||||||
 | 
					        if (!mergeCell.isEmpty()) {
 | 
				
			||||||
 | 
					            mergeCell.forEach(cell ->  FsApiUtil.mergeCells(cell, client, spreadsheetToken));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 4 设置单元格为文本格式
 | 
					        // 4 设置单元格为文本格式
 | 
				
			||||||
        if (tableConf.isText()) {
 | 
					        if (tableConf.isText()) {
 | 
				
			||||||
@ -84,9 +91,10 @@ public class FsHelper {
 | 
				
			|||||||
        FeishuClient client = FsClient.getInstance().getClient();
 | 
					        FeishuClient client = FsClient.getInstance().getClient();
 | 
				
			||||||
        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
					        Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
 | 
				
			||||||
        TableConf tableConf = PropertyUtil.getTableConf(clazz);
 | 
					        TableConf tableConf = PropertyUtil.getTableConf(clazz);
 | 
				
			||||||
        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Map<String, FieldProperty> fieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
 | 
					        Map<String, FieldProperty> fieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
 | 
				
			||||||
 | 
					        List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
 | 
					        List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        fsTableDataList.forEach(tableData -> {
 | 
					        fsTableDataList.forEach(tableData -> {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
package cn.isliu.core.client;
 | 
					package cn.isliu.core.client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import cn.isliu.core.service.*;
 | 
					 | 
				
			||||||
import com.lark.oapi.Client;
 | 
					import com.lark.oapi.Client;
 | 
				
			||||||
import com.lark.oapi.core.enums.AppType;
 | 
					import com.lark.oapi.core.enums.AppType;
 | 
				
			||||||
import com.lark.oapi.service.drive.DriveService;
 | 
					import com.lark.oapi.service.drive.DriveService;
 | 
				
			||||||
@ -10,8 +9,13 @@ import okhttp3.OkHttpClient;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.concurrent.TimeUnit;
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import cn.isliu.core.service.*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 飞书扩展客户端 封装官方SDK客户端并提供额外API支持
 | 
					 * 飞书客户端,用于与飞书API进行交互
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * 该客户端整合了官方SDK和自定义扩展功能,提供了对飞书表格的完整操作能力。
 | 
				
			||||||
 | 
					 * 包括官方提供的基础功能和项目自定义的扩展功能。
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class FeishuClient {
 | 
					public class FeishuClient {
 | 
				
			||||||
    private final Client officialClient;
 | 
					    private final Client officialClient;
 | 
				
			||||||
@ -19,13 +23,8 @@ public class FeishuClient {
 | 
				
			|||||||
    private final String appId;
 | 
					    private final String appId;
 | 
				
			||||||
    private final String appSecret;
 | 
					    private final String appSecret;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 自定义服务,处理官方SDK未覆盖的API
 | 
					    // 服务管理器,用于统一管理自定义服务实例
 | 
				
			||||||
    private volatile CustomSheetService customSheetService;
 | 
					    private final ServiceManager<FeishuClient> serviceManager = new ServiceManager<>(this);
 | 
				
			||||||
    private volatile CustomDimensionService customDimensionService;
 | 
					 | 
				
			||||||
    private volatile CustomCellService customCellService;
 | 
					 | 
				
			||||||
    private volatile CustomValueService customValueService;
 | 
					 | 
				
			||||||
    private volatile CustomDataValidationService customDataValidationService;
 | 
					 | 
				
			||||||
    private volatile CustomProtectedDimensionService customProtectedDimensionService;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private FeishuClient(String appId, String appSecret, Client officialClient, OkHttpClient httpClient) {
 | 
					    private FeishuClient(String appId, String appSecret, Client officialClient, OkHttpClient httpClient) {
 | 
				
			||||||
        this.appId = appId;
 | 
					        this.appId = appId;
 | 
				
			||||||
@ -69,14 +68,7 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展表格服务
 | 
					     * @return 扩展表格服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomSheetService customSheets() {
 | 
					    public CustomSheetService customSheets() {
 | 
				
			||||||
        if (customSheetService == null) {
 | 
					        return serviceManager.getService(CustomSheetService.class, () -> new CustomSheetService(this));
 | 
				
			||||||
            synchronized (this) {
 | 
					 | 
				
			||||||
                if (customSheetService == null) {
 | 
					 | 
				
			||||||
                    customSheetService = new CustomSheetService(this);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return customSheetService;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -85,14 +77,7 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展行列服务
 | 
					     * @return 扩展行列服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomDimensionService customDimensions() {
 | 
					    public CustomDimensionService customDimensions() {
 | 
				
			||||||
        if (customDimensionService == null) {
 | 
					        return serviceManager.getService(CustomDimensionService.class, () -> new CustomDimensionService(this));
 | 
				
			||||||
            synchronized (this) {
 | 
					 | 
				
			||||||
                if (customDimensionService == null) {
 | 
					 | 
				
			||||||
                    customDimensionService = new CustomDimensionService(this);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return customDimensionService;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -101,14 +86,7 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展单元格服务
 | 
					     * @return 扩展单元格服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomCellService customCells() {
 | 
					    public CustomCellService customCells() {
 | 
				
			||||||
        if (customCellService == null) {
 | 
					        return serviceManager.getService(CustomCellService.class, () -> new CustomCellService(this));
 | 
				
			||||||
            synchronized (this) {
 | 
					 | 
				
			||||||
                if (customCellService == null) {
 | 
					 | 
				
			||||||
                    customCellService = new CustomCellService(this);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return customCellService;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -117,14 +95,7 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展数据值服务
 | 
					     * @return 扩展数据值服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomValueService customValues() {
 | 
					    public CustomValueService customValues() {
 | 
				
			||||||
        if (customValueService == null) {
 | 
					        return serviceManager.getService(CustomValueService.class, () -> new CustomValueService(this));
 | 
				
			||||||
            synchronized (this) {
 | 
					 | 
				
			||||||
                if (customValueService == null) {
 | 
					 | 
				
			||||||
                    customValueService = new CustomValueService(this);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return customValueService;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -133,14 +104,7 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 自定义数据验证服务
 | 
					     * @return 自定义数据验证服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomDataValidationService customDataValidations() {
 | 
					    public CustomDataValidationService customDataValidations() {
 | 
				
			||||||
        if (customDataValidationService == null) {
 | 
					        return serviceManager.getService(CustomDataValidationService.class, () -> new CustomDataValidationService(this));
 | 
				
			||||||
            synchronized (this) {
 | 
					 | 
				
			||||||
                if (customDataValidationService == null) {
 | 
					 | 
				
			||||||
                    customDataValidationService = new CustomDataValidationService(this);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return customDataValidationService;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -149,14 +113,7 @@ public class FeishuClient {
 | 
				
			|||||||
     * @return 扩展保护范围服务
 | 
					     * @return 扩展保护范围服务
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public CustomProtectedDimensionService customProtectedDimensions() {
 | 
					    public CustomProtectedDimensionService customProtectedDimensions() {
 | 
				
			||||||
        if (customProtectedDimensionService == null) {
 | 
					        return serviceManager.getService(CustomProtectedDimensionService.class, () -> new CustomProtectedDimensionService(this));
 | 
				
			||||||
            synchronized (this) {
 | 
					 | 
				
			||||||
                if (customProtectedDimensionService == null) {
 | 
					 | 
				
			||||||
                    customProtectedDimensionService = new CustomProtectedDimensionService(this);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return customProtectedDimensionService;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
 | 
				
			|||||||
@ -2,13 +2,12 @@ package cn.isliu.core.client;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 线程安全的飞书客户端管理器
 | 
					 * 线程安全的飞书客户端管理器
 | 
				
			||||||
 * 使用双重检查锁定单例模式确保线程安全
 | 
					 * 使用ThreadLocal为每个线程维护独立的客户端实例
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class FsClient {
 | 
					public class FsClient implements AutoCloseable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static volatile FsClient instance;
 | 
					    private static volatile FsClient instance;
 | 
				
			||||||
    private volatile FeishuClient client;
 | 
					    private final ThreadLocal<FeishuClient> clientHolder = new ThreadLocal<>();
 | 
				
			||||||
    private final Object lock = new Object();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 私有构造函数防止外部实例化
 | 
					    // 私有构造函数防止外部实例化
 | 
				
			||||||
    private FsClient() {
 | 
					    private FsClient() {
 | 
				
			||||||
@ -16,7 +15,7 @@ public class FsClient {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 获取单例实例 - 使用双重检查锁定模式
 | 
					     * 获取单例实例 - 使用双重检查锁定模式
 | 
				
			||||||
     * @return FeishuClientManager实例
 | 
					     * @return FsClient实例
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static FsClient getInstance() {
 | 
					    public static FsClient getInstance() {
 | 
				
			||||||
        if (instance == null) {
 | 
					        if (instance == null) {
 | 
				
			||||||
@ -35,7 +34,7 @@ public class FsClient {
 | 
				
			|||||||
     * @throws IllegalStateException 如果客户端未初始化
 | 
					     * @throws IllegalStateException 如果客户端未初始化
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public FeishuClient getClient() {
 | 
					    public FeishuClient getClient() {
 | 
				
			||||||
        FeishuClient currentClient = client;
 | 
					        FeishuClient currentClient = clientHolder.get();
 | 
				
			||||||
        if (currentClient == null) {
 | 
					        if (currentClient == null) {
 | 
				
			||||||
            throw new IllegalStateException("FeishuClient not initialized. Please call initializeClient first.");
 | 
					            throw new IllegalStateException("FeishuClient not initialized. Please call initializeClient first.");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -44,6 +43,7 @@ public class FsClient {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 线程安全的客户端初始化
 | 
					     * 线程安全的客户端初始化
 | 
				
			||||||
 | 
					     * 每个线程调用此方法会创建并维护自己的客户端实例
 | 
				
			||||||
     * @param appId 飞书应用ID
 | 
					     * @param appId 飞书应用ID
 | 
				
			||||||
     * @param appSecret 飞书应用密钥
 | 
					     * @param appSecret 飞书应用密钥
 | 
				
			||||||
     * @return 初始化的FeishuClient实例
 | 
					     * @return 初始化的FeishuClient实例
 | 
				
			||||||
@ -56,18 +56,14 @@ public class FsClient {
 | 
				
			|||||||
            throw new IllegalArgumentException("appSecret cannot be null or empty");
 | 
					            throw new IllegalArgumentException("appSecret cannot be null or empty");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (client == null) {
 | 
					        FeishuClient client = FeishuClient.newBuilder(appId, appSecret).build();
 | 
				
			||||||
            synchronized (lock) {
 | 
					        clientHolder.set(client);
 | 
				
			||||||
                if (client == null) {
 | 
					 | 
				
			||||||
                    client = FeishuClient.newBuilder(appId, appSecret).build();
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return client;
 | 
					        return client;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 设置客户端实例(用于外部已构建的客户端)
 | 
					     * 设置客户端实例(用于外部已构建的客户端)
 | 
				
			||||||
 | 
					     * 每个线程调用此方法会设置自己的客户端实例
 | 
				
			||||||
     * @param feishuClient 外部构建的FeishuClient实例
 | 
					     * @param feishuClient 外部构建的FeishuClient实例
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public void setClient(FeishuClient feishuClient) {
 | 
					    public void setClient(FeishuClient feishuClient) {
 | 
				
			||||||
@ -75,23 +71,37 @@ public class FsClient {
 | 
				
			|||||||
            throw new IllegalArgumentException("FeishuClient cannot be null");
 | 
					            throw new IllegalArgumentException("FeishuClient cannot be null");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        synchronized (lock) {
 | 
					        clientHolder.set(feishuClient);
 | 
				
			||||||
            this.client = feishuClient;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 检查客户端是否已初始化
 | 
					     * 检查当前线程的客户端是否已初始化
 | 
				
			||||||
     * @return true如果客户端已初始化,否则false
 | 
					     * @return true如果当前线程客户端已初始化,否则false
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public boolean isInitialized() {
 | 
					    public boolean isInitialized() {
 | 
				
			||||||
        return client != null;
 | 
					        return clientHolder.get() != null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 清除当前线程的客户端实例(主要用于资源清理)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public void clearClient() {
 | 
				
			||||||
 | 
					        clientHolder.remove();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 重置客户端(主要用于测试)
 | 
					     * 重置客户端(主要用于测试)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public synchronized void resetForTesting() {
 | 
					    public synchronized void resetForTesting() {
 | 
				
			||||||
        client = null;
 | 
					        clientHolder.remove();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 实现AutoCloseable接口,用于try-with-resources语句
 | 
				
			||||||
 | 
					     * 清理当前线程的客户端实例
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @Override
 | 
				
			||||||
 | 
					    public void close() {
 | 
				
			||||||
 | 
					        clearClient();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/main/java/cn/isliu/core/client/ServiceManager.java
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										36
									
								
								src/main/java/cn/isliu/core/client/ServiceManager.java
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package cn.isliu.core.client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
 | 
					import java.util.function.Supplier;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 服务管理器,用于统一管理FeishuClient中的各种服务实例
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param <T> 服务类型
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class ServiceManager<T> {
 | 
				
			||||||
 | 
					    private final ConcurrentHashMap<Class<?>, Object> services = new ConcurrentHashMap<>();
 | 
				
			||||||
 | 
					    private final FeishuClient client;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 构造函数
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param client FeishuClient实例
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    ServiceManager(FeishuClient client) {
 | 
				
			||||||
 | 
					        this.client = client;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取指定类型的服务实例
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param serviceClass 服务类
 | 
				
			||||||
 | 
					     * @param supplier 服务实例提供者
 | 
				
			||||||
 | 
					     * @param <T> 服务类型
 | 
				
			||||||
 | 
					     * @return 服务实例
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    @SuppressWarnings("unchecked")
 | 
				
			||||||
 | 
					    <T> T getService(Class<T> serviceClass, Supplier<T> supplier) {
 | 
				
			||||||
 | 
					        return (T) services.computeIfAbsent(serviceClass, k -> supplier.get());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -45,7 +45,7 @@ public abstract class AbstractFeishuApiService {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    protected String getTenantAccessToken() throws IOException {
 | 
					    protected String getTenantAccessToken() throws IOException {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            return tokenManager.getCachedTenantAccessToken();
 | 
					            return tokenManager.getTenantAccessToken();
 | 
				
			||||||
        } catch (FsHelperException e) {
 | 
					        } catch (FsHelperException e) {
 | 
				
			||||||
            throw new IOException("Failed to get tenant access token: " + e.getMessage(), e);
 | 
					            throw new IOException("Failed to get tenant access token: " + e.getMessage(), e);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
package cn.isliu.core.service;
 | 
					package cn.isliu.core.service;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
import cn.isliu.core.client.FeishuClient;
 | 
					import cn.isliu.core.client.FeishuClient;
 | 
				
			||||||
import cn.isliu.core.pojo.ApiResponse;
 | 
					import cn.isliu.core.pojo.ApiResponse;
 | 
				
			||||||
import okhttp3.Request;
 | 
					import okhttp3.Request;
 | 
				
			||||||
@ -51,12 +50,6 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
            if (cellRequest.getMergeCells() != null) {
 | 
					            if (cellRequest.getMergeCells() != null) {
 | 
				
			||||||
                String url = BASE_URL + "/sheets/v2/spreadsheets/" + spreadsheetToken + "/merge_cells";
 | 
					                String url = BASE_URL + "/sheets/v2/spreadsheets/" + spreadsheetToken + "/merge_cells";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                String params;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                String type = cellRequest.getMergeCells().getType();
 | 
					 | 
				
			||||||
                if (type != null && !type.isEmpty() && "JSON_STR".equals(type)) {
 | 
					 | 
				
			||||||
                    params = cellRequest.getMergeCells().getParams();
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                // 获取合并单元格范围
 | 
					                // 获取合并单元格范围
 | 
				
			||||||
                String range = cellRequest.getMergeCells().getRange();
 | 
					                String range = cellRequest.getMergeCells().getRange();
 | 
				
			||||||
                if (range == null) {
 | 
					                if (range == null) {
 | 
				
			||||||
@ -65,8 +58,7 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
                    errorResponse.setMsg("Invalid cell range");
 | 
					                    errorResponse.setMsg("Invalid cell range");
 | 
				
			||||||
                    return errorResponse;
 | 
					                    return errorResponse;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                    params = gson.toJson(new MergeCellsRequestBody(range, cellRequest.getMergeCells().getMergeType()));
 | 
					                String params = gson.toJson(new MergeCellsRequestBody(range, cellRequest.getMergeCells().getMergeType()));
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // 构建合并单元格请求体
 | 
					                // 构建合并单元格请求体
 | 
				
			||||||
                RequestBody body = RequestBody.create(params, JSON_MEDIA_TYPE);
 | 
					                RequestBody body = RequestBody.create(params, JSON_MEDIA_TYPE);
 | 
				
			||||||
@ -133,11 +125,6 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
            else if (cellRequest.getStyleCellsBatch() != null) {
 | 
					            else if (cellRequest.getStyleCellsBatch() != null) {
 | 
				
			||||||
                String url = BASE_URL + "/sheets/v2/spreadsheets/" + spreadsheetToken + "/styles_batch_update";
 | 
					                String url = BASE_URL + "/sheets/v2/spreadsheets/" + spreadsheetToken + "/styles_batch_update";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                String type = cellRequest.getStyleCellsBatch().getType();
 | 
					 | 
				
			||||||
                String params = "";
 | 
					 | 
				
			||||||
                if (type != null && !type.isEmpty() && "JSON_STR".equals(type)) {
 | 
					 | 
				
			||||||
                    params = cellRequest.getStyleCellsBatch().getParams();
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                // 获取单元格范围和样式
 | 
					                // 获取单元格范围和样式
 | 
				
			||||||
                List<String> ranges = cellRequest.getStyleCellsBatch().getRanges();
 | 
					                List<String> ranges = cellRequest.getStyleCellsBatch().getRanges();
 | 
				
			||||||
                Style style = cellRequest.getStyleCellsBatch().getStyle();
 | 
					                Style style = cellRequest.getStyleCellsBatch().getStyle();
 | 
				
			||||||
@ -155,10 +142,8 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
                styleBatchData.setRanges(ranges);
 | 
					                styleBatchData.setRanges(ranges);
 | 
				
			||||||
                styleBatchData.setStyle(style);
 | 
					                styleBatchData.setStyle(style);
 | 
				
			||||||
                styleBatchRequest.getData().add(styleBatchData);
 | 
					                styleBatchRequest.getData().add(styleBatchData);
 | 
				
			||||||
                    params = gson.toJson(styleBatchRequest);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                RequestBody body = RequestBody.create(params, JSON_MEDIA_TYPE);
 | 
					                RequestBody body = RequestBody.create(gson.toJson(styleBatchRequest), JSON_MEDIA_TYPE);
 | 
				
			||||||
                Request httpRequest = createAuthenticatedRequest(url, "PUT", body).build();
 | 
					                Request httpRequest = createAuthenticatedRequest(url, "PUT", body).build();
 | 
				
			||||||
                response = executeRequest(httpRequest, ApiResponse.class);
 | 
					                response = executeRequest(httpRequest, ApiResponse.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -371,16 +356,6 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
                request.setMergeCells(mergeCells);
 | 
					                request.setMergeCells(mergeCells);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            public MergeCellsBuilder setReqType(String reqType) {
 | 
					 | 
				
			||||||
                mergeCells.setType(reqType);
 | 
					 | 
				
			||||||
                return this;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            public MergeCellsBuilder setReqParams(String reqParams) {
 | 
					 | 
				
			||||||
                mergeCells.setParams(reqParams);
 | 
					 | 
				
			||||||
                return this;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            /**
 | 
					            /**
 | 
				
			||||||
             * 设置要合并的单元格所在的工作表ID
 | 
					             * 设置要合并的单元格所在的工作表ID
 | 
				
			||||||
             *
 | 
					             *
 | 
				
			||||||
@ -519,10 +494,7 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * 合并方式 可选值: MERGE_ALL:合并所有单元格 MERGE_ROWS:按行合并 MERGE_COLUMNS:按列合并
 | 
					         * 合并方式 可选值: MERGE_ALL:合并所有单元格 MERGE_ROWS:按行合并 MERGE_COLUMNS:按列合并
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        private String mergeType;
 | 
					        private String mergeType = "MERGE_ALL";
 | 
				
			||||||
 | 
					 | 
				
			||||||
        private String type;
 | 
					 | 
				
			||||||
        private String params;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * 获取要合并的单元格范围
 | 
					         * 获取要合并的单元格范围
 | 
				
			||||||
@ -612,23 +584,6 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
        public void setMergeType(String mergeType) {
 | 
					        public void setMergeType(String mergeType) {
 | 
				
			||||||
            this.mergeType = mergeType;
 | 
					            this.mergeType = mergeType;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public String getType() {
 | 
					 | 
				
			||||||
            return type;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void setType(String type) {
 | 
					 | 
				
			||||||
            this.type = type;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public String getParams() {
 | 
					 | 
				
			||||||
            return params;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void setParams(String params) {
 | 
					 | 
				
			||||||
            this.params = params;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -859,16 +814,16 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
     * 单元格样式
 | 
					     * 单元格样式
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static class Style {
 | 
					    public static class Style {
 | 
				
			||||||
        private Font font;
 | 
					        private Font font = new Font();
 | 
				
			||||||
        private Integer textDecoration;
 | 
					        private Integer textDecoration = 0;
 | 
				
			||||||
        private String formatter;
 | 
					        private String formatter= "";
 | 
				
			||||||
        private Integer hAlign;
 | 
					        private Integer hAlign = 1;
 | 
				
			||||||
        private Integer vAlign;
 | 
					        private Integer vAlign = 1;
 | 
				
			||||||
        private String foreColor;
 | 
					        private String foreColor = "#ffffff";
 | 
				
			||||||
        private String backColor;
 | 
					        private String backColor = "#000000";
 | 
				
			||||||
        private String borderType;
 | 
					        private String borderType = "FULL_BORDER";
 | 
				
			||||||
        private String borderColor;
 | 
					        private String borderColor = "#6d6d6d";
 | 
				
			||||||
        private Boolean clean;
 | 
					        private Boolean clean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * 获取字体样式
 | 
					         * 获取字体样式
 | 
				
			||||||
@ -1056,10 +1011,10 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
     * 字体样式
 | 
					     * 字体样式
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static class Font {
 | 
					    public static class Font {
 | 
				
			||||||
        private Boolean bold;
 | 
					        private Boolean bold = true;
 | 
				
			||||||
        private Boolean italic;
 | 
					        private Boolean italic = false;
 | 
				
			||||||
        private String fontSize;
 | 
					        private String fontSize = "10pt/1.5";
 | 
				
			||||||
        private Boolean clean;
 | 
					        private Boolean clean = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * 获取是否加粗
 | 
					         * 获取是否加粗
 | 
				
			||||||
@ -1778,8 +1733,6 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
        private List<String> ranges;
 | 
					        private List<String> ranges;
 | 
				
			||||||
        private List<CellRange> cellRanges;
 | 
					        private List<CellRange> cellRanges;
 | 
				
			||||||
        private Style style;
 | 
					        private Style style;
 | 
				
			||||||
        private String type;
 | 
					 | 
				
			||||||
        private String params;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public StyleCellsBatchRequest() {
 | 
					        public StyleCellsBatchRequest() {
 | 
				
			||||||
            this.ranges = new ArrayList<>();
 | 
					            this.ranges = new ArrayList<>();
 | 
				
			||||||
@ -1851,21 +1804,6 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
        public void setStyle(Style style) {
 | 
					        public void setStyle(Style style) {
 | 
				
			||||||
            this.style = style;
 | 
					            this.style = style;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        public String getType() {
 | 
					 | 
				
			||||||
            return type;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public void setType(String type) {
 | 
					 | 
				
			||||||
            this.type = type;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public String getParams() {
 | 
					 | 
				
			||||||
            return params;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        public void setParams(String params) {
 | 
					 | 
				
			||||||
            this.params = params;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
@ -1968,16 +1906,6 @@ public class CustomCellService extends AbstractFeishuApiService {
 | 
				
			|||||||
            request.setStyleCellsBatch(styleCellsBatch);
 | 
					            request.setStyleCellsBatch(styleCellsBatch);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public StyleCellsBatchBuilder setReqType(String reqType) {
 | 
					 | 
				
			||||||
            styleCellsBatch.setType(reqType);
 | 
					 | 
				
			||||||
            return this;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public StyleCellsBatchBuilder setParams(String params) {
 | 
					 | 
				
			||||||
            styleCellsBatch.setParams(params);
 | 
					 | 
				
			||||||
            return this;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * 添加要设置样式的单元格范围
 | 
					         * 添加要设置样式的单元格范围
 | 
				
			||||||
         *
 | 
					         *
 | 
				
			||||||
 | 
				
			|||||||
@ -125,25 +125,41 @@ public class FsApiUtil {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void setTableStyle(String style, String sheetId, FeishuClient client, String spreadsheetToken) {
 | 
					    public static void setTableStyle(CustomCellService.StyleCellsBatchBuilder styleCellsBatchBuilder, FeishuClient client, String spreadsheetToken) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            CustomCellService.CellBatchUpdateRequest batchUpdateRequest = CustomCellService.CellBatchUpdateRequest.newBuilder()
 | 
					            CustomCellService.CellBatchUpdateRequest batchUpdateRequest = CustomCellService.CellBatchUpdateRequest.newBuilder()
 | 
				
			||||||
                    .addRequest(CustomCellService.CellRequest.styleCellsBatch().setReqType(REQ_TYPE)
 | 
					                    .addRequest(styleCellsBatchBuilder.build())
 | 
				
			||||||
                            .setParams(style.replaceAll("%SHEET_ID%", sheetId))
 | 
					 | 
				
			||||||
                            .build())
 | 
					 | 
				
			||||||
                    .build();
 | 
					                    .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            ApiResponse apiResponse = client.customCells().cellsBatchUpdate(spreadsheetToken, batchUpdateRequest);
 | 
					            ApiResponse apiResponse = client.customCells().cellsBatchUpdate(spreadsheetToken, batchUpdateRequest);
 | 
				
			||||||
            if (!apiResponse.success()) {
 | 
					            if (!apiResponse.success()) {
 | 
				
			||||||
                FsLogger.warn("【飞书表格】 写入表格样式数据异常!参数:{},错误信息:{}", style, apiResponse.getMsg());
 | 
					                FsLogger.warn("【飞书表格】 写入表格样式数据异常!参数:{},错误信息:{}", styleCellsBatchBuilder, apiResponse.getMsg());
 | 
				
			||||||
                throw new FsHelperException("【飞书表格】 写入表格样式数据异常!");
 | 
					                throw new FsHelperException("【飞书表格】 写入表格样式数据异常!");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        } catch (Exception e) {
 | 
					        } catch (Exception e) {
 | 
				
			||||||
            FsLogger.warn("【飞书表格】 写入表格样式异常!参数:{},错误信息:{}", style, e.getMessage());
 | 
					            FsLogger.warn("【飞书表格】 写入表格样式异常!参数:{},错误信息:{}", styleCellsBatchBuilder, e.getMessage());
 | 
				
			||||||
            throw new FsHelperException("【飞书表格】 写入表格样式异常!");
 | 
					            throw new FsHelperException("【飞书表格】 写入表格样式异常!");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static void mergeCells(CustomCellService.CellRequest cellRequest, FeishuClient client, String spreadsheetToken) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            CustomCellService.CellBatchUpdateRequest batchMergeRequest = CustomCellService.CellBatchUpdateRequest.newBuilder()
 | 
				
			||||||
 | 
					                    .addRequest(cellRequest)
 | 
				
			||||||
 | 
					                    .build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ApiResponse batchMergeResp = client.customCells().cellsBatchUpdate(spreadsheetToken, batchMergeRequest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!batchMergeResp.success()) {
 | 
				
			||||||
 | 
					                FsLogger.warn("【飞书表格】 合并单元格请求异常!参数:{},错误信息:{}", cellRequest.toString(), batchMergeResp.getMsg());
 | 
				
			||||||
 | 
					                throw new FsHelperException("【飞书表格】 合并单元格请求异常!");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (Exception e) {
 | 
				
			||||||
 | 
					            FsLogger.warn("【飞书表格】 合并单元格异常!参数:{},错误信息:{}", cellRequest.toString(), e.getMessage(), e);
 | 
				
			||||||
 | 
					            throw new FsHelperException("【飞书表格】 合并单元格异常!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static String createSheet(String title, FeishuClient client, String spreadsheetToken) {
 | 
					    public static String createSheet(String title, FeishuClient client, String spreadsheetToken) {
 | 
				
			||||||
        String sheetId = null;
 | 
					        String sheetId = null;
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
 | 
				
			|||||||
@ -9,6 +9,7 @@ import cn.isliu.core.converters.OptionsValueProcess;
 | 
				
			|||||||
import cn.isliu.core.enums.BaseEnum;
 | 
					import cn.isliu.core.enums.BaseEnum;
 | 
				
			||||||
import cn.isliu.core.enums.TypeEnum;
 | 
					import cn.isliu.core.enums.TypeEnum;
 | 
				
			||||||
import cn.isliu.core.pojo.FieldProperty;
 | 
					import cn.isliu.core.pojo.FieldProperty;
 | 
				
			||||||
 | 
					import cn.isliu.core.service.CustomCellService;
 | 
				
			||||||
import cn.isliu.core.service.CustomValueService;
 | 
					import cn.isliu.core.service.CustomValueService;
 | 
				
			||||||
import com.google.gson.JsonElement;
 | 
					import com.google.gson.JsonElement;
 | 
				
			||||||
import com.google.gson.JsonParser;
 | 
					import com.google.gson.JsonParser;
 | 
				
			||||||
@ -337,31 +338,71 @@ public class FsTableUtil {
 | 
				
			|||||||
                = CustomValueService.ValueRequest.batchPutValues();
 | 
					                = CustomValueService.ValueRequest.batchPutValues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // 获取父级表头
 | 
					        // 获取父级表头
 | 
				
			||||||
//        int maxLevel = getMaxLevel(fieldsMap);
 | 
					        int maxLevel = getMaxLevel(fieldsMap);
 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        Map<Integer, List<String>> levelListMap = groupFieldsByLevel(fieldsMap);
 | 
					 | 
				
			||||||
//        for (int i = maxLevel; i >= 1; i--) {
 | 
					 | 
				
			||||||
//            List<String> values = levelListMap.get(i);
 | 
					 | 
				
			||||||
//            batchPutValuesBuilder.addRange(sheetId + "!A" + i + ":" + position + i);
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        }
 | 
					 | 
				
			||||||
//
 | 
					 | 
				
			||||||
//        int titleRow = maxLevel;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (maxLevel == 1) {
 | 
				
			||||||
 | 
					            // 单层级表头:按order排序的headers
 | 
				
			||||||
 | 
					            List<String> sortedHeaders = getSortedHeaders(fieldsMap);
 | 
				
			||||||
            int titleRow = tableConf.titleRow();
 | 
					            int titleRow = tableConf.titleRow();
 | 
				
			||||||
            if (tableConf.enableDesc()) {
 | 
					            if (tableConf.enableDesc()) {
 | 
				
			||||||
                int descRow = titleRow + 1;
 | 
					                int descRow = titleRow + 1;
 | 
				
			||||||
                batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + descRow);
 | 
					                batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + descRow);
 | 
				
			||||||
            batchPutValuesBuilder.addRow(headers.toArray());
 | 
					                batchPutValuesBuilder.addRow(sortedHeaders.toArray());
 | 
				
			||||||
            batchPutValuesBuilder.addRow(getDescArray(headers, fieldsMap));
 | 
					                batchPutValuesBuilder.addRow(getDescArray(sortedHeaders, fieldsMap));
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + titleRow);
 | 
					                batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + titleRow);
 | 
				
			||||||
            batchPutValuesBuilder.addRow(headers.toArray());
 | 
					                batchPutValuesBuilder.addRow(sortedHeaders.toArray());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 多层级表头:构建层级结构并处理合并单元格
 | 
				
			||||||
 | 
					            List<List<HeaderCell>> hierarchicalHeaders = buildHierarchicalHeaders(fieldsMap);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 处理每一行表头
 | 
				
			||||||
 | 
					            for (int rowIndex = 0; rowIndex < hierarchicalHeaders.size(); rowIndex++) {
 | 
				
			||||||
 | 
					                List<HeaderCell> headerRow = hierarchicalHeaders.get(rowIndex);
 | 
				
			||||||
 | 
					                List<Object> rowValues = new ArrayList<>();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 将HeaderCell转换为字符串值,并处理合并单元格
 | 
				
			||||||
 | 
					                for (HeaderCell cell : headerRow) {
 | 
				
			||||||
 | 
					                    rowValues.add(cell.getValue());
 | 
				
			||||||
 | 
					                    // 对于合并单元格,添加空值占位符
 | 
				
			||||||
 | 
					                    for (int span = 1; span < cell.getColSpan(); span++) {
 | 
				
			||||||
 | 
					                        rowValues.add(""); // 合并单元格的占位符
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                int actualRow = rowIndex + 1; // 从第1行开始
 | 
				
			||||||
 | 
					                batchPutValuesBuilder.addRange(sheetId + "!A" + actualRow + ":" + position + actualRow);
 | 
				
			||||||
 | 
					                batchPutValuesBuilder.addRow(rowValues.toArray());
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 如果启用了描述,在最后一行添加描述
 | 
				
			||||||
 | 
					            if (tableConf.enableDesc()) {
 | 
				
			||||||
 | 
					                List<String> finalHeaders = getSortedHeaders(fieldsMap);
 | 
				
			||||||
 | 
					                int descRow = maxLevel + 1;
 | 
				
			||||||
 | 
					                batchPutValuesBuilder.addRange(sheetId + "!A" + descRow + ":" + position + descRow);
 | 
				
			||||||
 | 
					                batchPutValuesBuilder.addRow(getDescArray(finalHeaders, fieldsMap));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return batchPutValuesBuilder.build();
 | 
					        return batchPutValuesBuilder.build();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取按order排序的表头列表
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param fieldsMap 字段属性映射
 | 
				
			||||||
 | 
					     * @return 按order排序的表头列表
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static List<String> getSortedHeaders(Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
 | 
					        return fieldsMap.entrySet().stream()
 | 
				
			||||||
 | 
					            .filter(entry -> entry.getValue() != null && entry.getValue().getTableProperty() != null)
 | 
				
			||||||
 | 
					            .sorted(Comparator.comparingInt(entry -> entry.getValue().getTableProperty().order()))
 | 
				
			||||||
 | 
					            .map(Map.Entry::getKey)
 | 
				
			||||||
 | 
					            .collect(Collectors.toList());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static int getMaxLevel(Map<String, FieldProperty> fieldsMap) {
 | 
					    private static int getMaxLevel(Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
        AtomicInteger maxLevel = new AtomicInteger(1);
 | 
					        AtomicInteger maxLevel = new AtomicInteger(1);
 | 
				
			||||||
        fieldsMap.forEach((field, fieldProperty) -> {
 | 
					        fieldsMap.forEach((field, fieldProperty) -> {
 | 
				
			||||||
@ -389,6 +430,7 @@ public class FsTableUtil {
 | 
				
			|||||||
                        } else if (element.isJsonArray()) {
 | 
					                        } else if (element.isJsonArray()) {
 | 
				
			||||||
                            descArray[i] = element.getAsJsonArray();
 | 
					                            descArray[i] = element.getAsJsonArray();
 | 
				
			||||||
                        } else {
 | 
					                        } else {
 | 
				
			||||||
 | 
					//                            desc = addLineBreaksPer8Chars(desc);
 | 
				
			||||||
                            descArray[i] = desc;
 | 
					                            descArray[i] = desc;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    } catch (JsonSyntaxException e) {
 | 
					                    } catch (JsonSyntaxException e) {
 | 
				
			||||||
@ -404,40 +446,426 @@ public class FsTableUtil {
 | 
				
			|||||||
        return descArray;
 | 
					        return descArray;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static String getDefaultTableStyle(String sheetId, int size, TableConf tableConf) {
 | 
					    public static String getDefaultTableStyle(String sheetId, int size, Map<String, FieldProperty> fieldsMap, TableConf tableConf) {
 | 
				
			||||||
        int row = tableConf.titleRow();
 | 
					        int maxLevel = getMaxLevel(fieldsMap);
 | 
				
			||||||
        String colorTemplate = "{\"data\": [{\"style\": {\"font\": {\"bold\": true, \"clean\": false, \"italic\": false, \"fontSize\": \"10pt/1.5\"}, \"clean\": false, \"hAlign\": 1, \"vAlign\": 1, \"backColor\": \"#000000\", \"foreColor\": \"#ffffff\", \"formatter\": \"\", \"borderType\": \"FULL_BORDER\", \"borderColor\": \"#000000\", \"textDecoration\": 0}, \"ranges\": [\"SHEET_ID!RANG\"]}]}";
 | 
					        String colorTemplate = "{\"data\": [{\"style\": {\"font\": {\"bold\": true, \"clean\": false, \"italic\": false, \"fontSize\": \"10pt/1.5\"}, \"clean\": false, \"hAlign\": 1, \"vAlign\": 1, \"backColor\": \"#000000\", \"foreColor\": \"#ffffff\", \"formatter\": \"\", \"borderType\": \"FULL_BORDER\", \"borderColor\": \"#000000\", \"textDecoration\": 0}, \"ranges\": [\"SHEET_ID!RANG\"]}]}";
 | 
				
			||||||
        colorTemplate = colorTemplate.replace("SHEET_ID", sheetId);
 | 
					        colorTemplate = colorTemplate.replace("SHEET_ID", sheetId);
 | 
				
			||||||
        colorTemplate = colorTemplate.replace("RANG", "A" + row + ":" + FsTableUtil.getColumnNameByNuNumber(size) + row);
 | 
					        colorTemplate = colorTemplate.replace("RANG", "A1:" + FsTableUtil.getColumnNameByNuNumber(size) + maxLevel);
 | 
				
			||||||
        colorTemplate = colorTemplate.replace("FORE_COLOR", tableConf.headFontColor())
 | 
					        colorTemplate = colorTemplate.replace("FORE_COLOR", tableConf.headFontColor())
 | 
				
			||||||
                .replace("BACK_COLOR", tableConf.headBackColor());
 | 
					                .replace("BACK_COLOR", tableConf.headBackColor());
 | 
				
			||||||
        return colorTemplate;
 | 
					        return colorTemplate;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static CustomCellService.StyleCellsBatchBuilder getDefaultTableStyle(String sheetId, Map<String, FieldProperty> fieldsMap, TableConf tableConf) {
 | 
				
			||||||
 | 
					        int maxLevel = getMaxLevel(fieldsMap);
 | 
				
			||||||
 | 
					        CustomCellService.StyleCellsBatchBuilder styleCellsBatchBuilder = CustomCellService.CellRequest.styleCellsBatch()
 | 
				
			||||||
 | 
					                .addRange(sheetId, "A1", FsTableUtil.getColumnNameByNuNumber(fieldsMap.size()) + maxLevel)
 | 
				
			||||||
 | 
					                .backColor(tableConf.headBackColor())
 | 
				
			||||||
 | 
					                .foreColor(tableConf.headFontColor());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return styleCellsBatchBuilder;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 根据层级分组字段属性
 | 
					     * 根据层级分组字段属性,并按order排序
 | 
				
			||||||
     * 
 | 
					     * 
 | 
				
			||||||
     * @param fieldsMap 字段属性映射
 | 
					     * @param fieldsMap 字段属性映射
 | 
				
			||||||
     * @return 按层级分组的映射,key为层级,value为该层级的字段名数组
 | 
					     * @return 按层级分组的映射,key为层级,value为该层级的字段名数组(已按order排序)
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    public static Map<Integer, List<String>> groupFieldsByLevel(Map<String, FieldProperty> fieldsMap) {
 | 
					    public static Map<Integer, List<String>> groupFieldsByLevel(Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
        Map<Integer, List<String>> levelMap = new HashMap<>();
 | 
					        Map<Integer, List<String>> levelMap = new HashMap<>();
 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        for (Map.Entry<String, FieldProperty> entry : fieldsMap.entrySet()) {
 | 
					        // 按order排序的字段条目
 | 
				
			||||||
 | 
					        List<Map.Entry<String, FieldProperty>> sortedEntries = fieldsMap.entrySet().stream()
 | 
				
			||||||
 | 
					            .filter(entry -> entry.getValue() != null && entry.getValue().getTableProperty() != null)
 | 
				
			||||||
 | 
					            .sorted(Comparator.comparingInt(entry -> entry.getValue().getTableProperty().order()))
 | 
				
			||||||
 | 
					            .collect(Collectors.toList());
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (Map.Entry<String, FieldProperty> entry : sortedEntries) {
 | 
				
			||||||
            FieldProperty fieldProperty = entry.getValue();
 | 
					            FieldProperty fieldProperty = entry.getValue();
 | 
				
			||||||
            if (fieldProperty != null && fieldProperty.getTableProperty() != null) {
 | 
					 | 
				
			||||||
            String[] values = fieldProperty.getTableProperty().value();
 | 
					            String[] values = fieldProperty.getTableProperty().value();
 | 
				
			||||||
            for (int i = 0; i < values.length; i++) {
 | 
					            for (int i = 0; i < values.length; i++) {
 | 
				
			||||||
                levelMap.computeIfAbsent(i, k -> new ArrayList<>()).add(values[i]);
 | 
					                levelMap.computeIfAbsent(i, k -> new ArrayList<>()).add(values[i]);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        
 | 
					        
 | 
				
			||||||
        return levelMap;
 | 
					        return levelMap;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static void main(String[] args) {
 | 
					    /**
 | 
				
			||||||
        String str ="支持1~3个搜索";
 | 
					     * 构建多层级表头结构,支持按层级排序和合并
 | 
				
			||||||
        System.out.println(str.length());
 | 
					     * 根据需求实现层级分组和order排序:
 | 
				
			||||||
 | 
					     * 1. 按全局order排序,但确保同一分组的字段相邻
 | 
				
			||||||
 | 
					     * 2. 同一分组内的字段能够正确合并
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param fieldsMap 字段属性映射
 | 
				
			||||||
 | 
					     * @return 多层级表头结构,外层为行,内层为列
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static List<List<HeaderCell>> buildHierarchicalHeaders(Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
 | 
					        int maxLevel = getMaxLevel(fieldsMap);
 | 
				
			||||||
 | 
					        List<List<HeaderCell>> headerRows = new ArrayList<>();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 初始化每行的表头列表
 | 
				
			||||||
 | 
					        for (int i = 0; i < maxLevel; i++) {
 | 
				
			||||||
 | 
					            headerRows.add(new ArrayList<>());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 获取排序后的字段列表,按照特殊规则排序:
 | 
				
			||||||
 | 
					        // 1. 相同第一层级的字段必须相邻
 | 
				
			||||||
 | 
					        // 2. 在满足条件1的情况下,尽可能按order排序
 | 
				
			||||||
 | 
					        List<Map.Entry<String, FieldProperty>> sortedFields = getSortedFieldsWithGrouping(fieldsMap);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 按排序后的顺序处理每个字段
 | 
				
			||||||
 | 
					        for (Map.Entry<String, FieldProperty> entry : sortedFields) {
 | 
				
			||||||
 | 
					            String[] values = entry.getValue().getTableProperty().value();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 统一处理:所有字段都对齐到maxLevel层级
 | 
				
			||||||
 | 
					            // 核心思路:最后一个值总是出现在最后一行,前面的值依次向上排列
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            for (int level = 0; level < maxLevel; level++) {
 | 
				
			||||||
 | 
					                List<HeaderCell> currentRow = headerRows.get(level);
 | 
				
			||||||
 | 
					                HeaderCell headerCell = new HeaderCell();
 | 
				
			||||||
 | 
					                headerCell.setLevel(level);
 | 
				
			||||||
 | 
					                headerCell.setColSpan(1);
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 计算当前层级应该显示的值
 | 
				
			||||||
 | 
					                String currentValue = "";
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                if (values.length == 1) {
 | 
				
			||||||
 | 
					                    // 单层级字段:只在最后一行显示
 | 
				
			||||||
 | 
					                    if (level == maxLevel - 1) {
 | 
				
			||||||
 | 
					                        currentValue = values[0];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // 多层级字段:需要对齐到maxLevel
 | 
				
			||||||
 | 
					                    // 计算从当前层级到值数组的映射
 | 
				
			||||||
 | 
					                    int valueIndex = level - (maxLevel - values.length);
 | 
				
			||||||
 | 
					                    if (valueIndex >= 0 && valueIndex < values.length) {
 | 
				
			||||||
 | 
					                        currentValue = values[valueIndex];
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                headerCell.setValue(currentValue);
 | 
				
			||||||
 | 
					                currentRow.add(headerCell);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return headerRows;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 获取排序后的字段列表,基于最子级字段排序的新规则
 | 
				
			||||||
 | 
					     * 核心规则:
 | 
				
			||||||
 | 
					     * 1. 根据最子级字段的order进行主排序
 | 
				
			||||||
 | 
					     * 2. 相同父级字段形成分组,组内按子级order排序
 | 
				
			||||||
 | 
					     * 3. 分组按组内最小order值参与全局排序
 | 
				
			||||||
 | 
					     * 4. 三级及以上层级遵循约定大于配置,要求order连续
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param fieldsMap 字段属性映射
 | 
				
			||||||
 | 
					     * @return 排序后的字段列表
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static List<Map.Entry<String, FieldProperty>> getSortedFieldsWithGrouping(Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
 | 
					        int maxLevel = getMaxLevel(fieldsMap);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 统一的分组排序逻辑,适用于所有层级
 | 
				
			||||||
 | 
					        // 按层级路径分组
 | 
				
			||||||
 | 
					        Map<String, List<Map.Entry<String, FieldProperty>>> groupedFields = groupFieldsByFirstLevel(fieldsMap);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 创建分组信息列表
 | 
				
			||||||
 | 
					        List<GroupInfo> allGroups = new ArrayList<>();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (Map.Entry<String, List<Map.Entry<String, FieldProperty>>> groupEntry : groupedFields.entrySet()) {
 | 
				
			||||||
 | 
					            List<Map.Entry<String, FieldProperty>> fieldsInGroup = groupEntry.getValue();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 在组内按order排序(基于最子级字段)
 | 
				
			||||||
 | 
					            fieldsInGroup.sort(Comparator.comparingInt(entry -> entry.getValue().getTableProperty().order()));
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 验证组内order连续性(仅对需要合并的分组进行检查,且仅在三级及以上时检查)
 | 
				
			||||||
 | 
					            if (maxLevel >= 3 && fieldsInGroup.size() > 1 && !"default".equals(groupEntry.getKey())) {
 | 
				
			||||||
 | 
					                validateOrderContinuity(groupEntry.getKey(), fieldsInGroup);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 计算组的最小order(用于组间排序)
 | 
				
			||||||
 | 
					            int minOrder = fieldsInGroup.stream()
 | 
				
			||||||
 | 
					                .mapToInt(entry -> entry.getValue().getTableProperty().order())
 | 
				
			||||||
 | 
					                .min()
 | 
				
			||||||
 | 
					                .orElse(Integer.MAX_VALUE);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            allGroups.add(new GroupInfo(groupEntry.getKey(), minOrder, fieldsInGroup));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 新的排序逻辑:分组作为整体参与全局order排序
 | 
				
			||||||
 | 
					        // 创建排序单元列表(每个单元可能是单个字段或一个分组)
 | 
				
			||||||
 | 
					        List<SortUnit> sortUnits = new ArrayList<>();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (GroupInfo group : allGroups) {
 | 
				
			||||||
 | 
					            if ("default".equals(group.getGroupKey())) {
 | 
				
			||||||
 | 
					                // 单层级字段:每个字段都是独立的排序单元
 | 
				
			||||||
 | 
					                for (Map.Entry<String, FieldProperty> field : group.getFields()) {
 | 
				
			||||||
 | 
					                    int order = field.getValue().getTableProperty().order();
 | 
				
			||||||
 | 
					                    sortUnits.add(new SortUnit(order, Arrays.asList(field), false));
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                // 多层级分组:整个分组作为一个排序单元,使用最小order
 | 
				
			||||||
 | 
					                sortUnits.add(new SortUnit(group.getMinOrder(), group.getFields(), true));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 按order排序所有排序单元(实现真正的全局排序)
 | 
				
			||||||
 | 
					        sortUnits.sort(Comparator.comparingInt(SortUnit::getOrder));
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 展开为字段列表
 | 
				
			||||||
 | 
					        List<Map.Entry<String, FieldProperty>> result = new ArrayList<>();
 | 
				
			||||||
 | 
					        for (SortUnit unit : sortUnits) {
 | 
				
			||||||
 | 
					            result.addAll(unit.getFields());
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static List<CustomCellService.CellRequest> getMergeCell(String sheetId, Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
 | 
					        List<CustomCellService.CellRequest> mergeRequests = new ArrayList<>();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 构建层级表头结构
 | 
				
			||||||
 | 
					        List<List<HeaderCell>> headerRows = buildHierarchicalHeaders(fieldsMap);
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        // 遍历每一行,查找需要合并的单元格
 | 
				
			||||||
 | 
					        for (int rowIndex = 0; rowIndex < headerRows.size(); rowIndex++) {
 | 
				
			||||||
 | 
					            List<HeaderCell> headerRow = headerRows.get(rowIndex);
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            // 查找连续的相同值区域
 | 
				
			||||||
 | 
					            int colIndex = 0;
 | 
				
			||||||
 | 
					            for (int i = 0; i < headerRow.size(); i++) {
 | 
				
			||||||
 | 
					                HeaderCell currentCell = headerRow.get(i);
 | 
				
			||||||
 | 
					                String currentValue = currentCell.getValue();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 跳过空值,空值不需要合并
 | 
				
			||||||
 | 
					                if (currentValue == null || currentValue.trim().isEmpty()) {
 | 
				
			||||||
 | 
					                    colIndex++;
 | 
				
			||||||
 | 
					                    continue;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 查找相同值的连续区域
 | 
				
			||||||
 | 
					                int startCol = colIndex;
 | 
				
			||||||
 | 
					                int endCol = startCol;
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 向后查找相同值
 | 
				
			||||||
 | 
					                for (int j = i + 1; j < headerRow.size(); j++) {
 | 
				
			||||||
 | 
					                    HeaderCell nextCell = headerRow.get(j);
 | 
				
			||||||
 | 
					                    if (currentValue.equals(nextCell.getValue())) {
 | 
				
			||||||
 | 
					                        endCol++;
 | 
				
			||||||
 | 
					                        i++; // 跳过已经处理的单元格
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        break;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                // 如果跨越多列,则需要合并
 | 
				
			||||||
 | 
					                if (endCol > startCol) {
 | 
				
			||||||
 | 
					                    String startPosition = getColumnName(startCol) + (rowIndex + 1);
 | 
				
			||||||
 | 
					                    String endPosition = getColumnName(endCol) + (rowIndex + 1);
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    CustomCellService.CellRequest mergeRequest = CustomCellService.CellRequest.mergeCells()
 | 
				
			||||||
 | 
					                            .sheetId(sheetId)
 | 
				
			||||||
 | 
					                            .startPosition(startPosition)
 | 
				
			||||||
 | 
					                            .endPosition(endPosition)
 | 
				
			||||||
 | 
					                            .build();
 | 
				
			||||||
 | 
					                    
 | 
				
			||||||
 | 
					                    mergeRequests.add(mergeRequest);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                colIndex = endCol + 1;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return mergeRequests;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 分组信息类,用于辅助排序
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static class GroupInfo {
 | 
				
			||||||
 | 
					        private final String groupKey;
 | 
				
			||||||
 | 
					        private final int minOrder;
 | 
				
			||||||
 | 
					        private final List<Map.Entry<String, FieldProperty>> fields;
 | 
				
			||||||
 | 
					        private final int groupDepth;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public GroupInfo(String groupKey, int minOrder, List<Map.Entry<String, FieldProperty>> fields) {
 | 
				
			||||||
 | 
					            this(groupKey, minOrder, fields, 1);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public GroupInfo(String groupKey, int minOrder, List<Map.Entry<String, FieldProperty>> fields, int groupDepth) {
 | 
				
			||||||
 | 
					            this.groupKey = groupKey;
 | 
				
			||||||
 | 
					            this.minOrder = minOrder;
 | 
				
			||||||
 | 
					            this.fields = fields;
 | 
				
			||||||
 | 
					            this.groupDepth = groupDepth;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public String getGroupKey() { return groupKey; }
 | 
				
			||||||
 | 
					        public int getMinOrder() { return minOrder; }
 | 
				
			||||||
 | 
					        public List<Map.Entry<String, FieldProperty>> getFields() { return fields; }
 | 
				
			||||||
 | 
					        public int getGroupDepth() { return groupDepth; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 排序项类,用于全局排序
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static class SortItem {
 | 
				
			||||||
 | 
					        private final int order;
 | 
				
			||||||
 | 
					        private final List<Map.Entry<String, FieldProperty>> fields;
 | 
				
			||||||
 | 
					        private final boolean isGroup;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public SortItem(int order, List<Map.Entry<String, FieldProperty>> fields, boolean isGroup) {
 | 
				
			||||||
 | 
					            this.order = order;
 | 
				
			||||||
 | 
					            this.fields = fields;
 | 
				
			||||||
 | 
					            this.isGroup = isGroup;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public int getOrder() { return order; }
 | 
				
			||||||
 | 
					        public List<Map.Entry<String, FieldProperty>> getFields() { return fields; }
 | 
				
			||||||
 | 
					        public boolean isGroup() { return isGroup; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 排序单元类,用于分组整体排序
 | 
				
			||||||
 | 
					     * 一个排序单元可以是单个字段或一个完整的分组
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static class SortUnit {
 | 
				
			||||||
 | 
					        private final int order;
 | 
				
			||||||
 | 
					        private final List<Map.Entry<String, FieldProperty>> fields;
 | 
				
			||||||
 | 
					        private final boolean isGroup;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public SortUnit(int order, List<Map.Entry<String, FieldProperty>> fields, boolean isGroup) {
 | 
				
			||||||
 | 
					            this.order = order;
 | 
				
			||||||
 | 
					            this.fields = fields;
 | 
				
			||||||
 | 
					            this.isGroup = isGroup;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public int getOrder() { return order; }
 | 
				
			||||||
 | 
					        public List<Map.Entry<String, FieldProperty>> getFields() { return fields; }
 | 
				
			||||||
 | 
					        public boolean isGroup() { return isGroup; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 按层级路径分组字段
 | 
				
			||||||
 | 
					     * 根据需求:
 | 
				
			||||||
 | 
					     * 1. 单层级字段(如"部门")放入"default"分组
 | 
				
			||||||
 | 
					     * 2. 多层级字段按完整的层级路径分组(除最后一级)
 | 
				
			||||||
 | 
					     * 例如:["ID", "员工信息", "姓名"] → 分组key为 "ID|员工信息"
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param fieldsMap 字段属性映射
 | 
				
			||||||
 | 
					     * @return 按层级路径分组的字段映射
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static Map<String, List<Map.Entry<String, FieldProperty>>> groupFieldsByFirstLevel(Map<String, FieldProperty> fieldsMap) {
 | 
				
			||||||
 | 
					        Map<String, List<Map.Entry<String, FieldProperty>>> groupedFields = new LinkedHashMap<>();
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (Map.Entry<String, FieldProperty> entry : fieldsMap.entrySet()) {
 | 
				
			||||||
 | 
					            FieldProperty fieldProperty = entry.getValue();
 | 
				
			||||||
 | 
					            if (fieldProperty != null && fieldProperty.getTableProperty() != null) {
 | 
				
			||||||
 | 
					                String[] values = fieldProperty.getTableProperty().value();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                String groupKey;
 | 
				
			||||||
 | 
					                if (values.length == 1) {
 | 
				
			||||||
 | 
					                    // 单层级字段放入默认分组
 | 
				
			||||||
 | 
					                    groupKey = "default";
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    // 多层级字段按完整路径分组(除最后一级)
 | 
				
			||||||
 | 
					                    StringBuilder pathBuilder = new StringBuilder();
 | 
				
			||||||
 | 
					                    for (int i = 0; i < values.length - 1; i++) {
 | 
				
			||||||
 | 
					                        if (i > 0) pathBuilder.append("|");
 | 
				
			||||||
 | 
					                        pathBuilder.append(values[i]);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    groupKey = pathBuilder.toString();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                groupedFields.computeIfAbsent(groupKey, k -> new ArrayList<>()).add(entry);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        return groupedFields;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 验证组内字段order的连续性
 | 
				
			||||||
 | 
					     * 三级及以上层级要求同一分组内的字段order必须连续
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param groupKey 分组key
 | 
				
			||||||
 | 
					     * @param fieldsInGroup 分组内的字段列表(已按order排序)
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    private static void validateOrderContinuity(String groupKey, List<Map.Entry<String, FieldProperty>> fieldsInGroup) {
 | 
				
			||||||
 | 
					        if (fieldsInGroup.size() <= 1) {
 | 
				
			||||||
 | 
					            return; // 单个字段无需验证
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        for (int i = 1; i < fieldsInGroup.size(); i++) {
 | 
				
			||||||
 | 
					            int prevOrder = fieldsInGroup.get(i - 1).getValue().getTableProperty().order();
 | 
				
			||||||
 | 
					            int currentOrder = fieldsInGroup.get(i).getValue().getTableProperty().order();
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					            if (currentOrder != prevOrder + 1) {
 | 
				
			||||||
 | 
					                String prevFieldName = fieldsInGroup.get(i - 1).getKey();
 | 
				
			||||||
 | 
					                String currentFieldName = fieldsInGroup.get(i).getKey();
 | 
				
			||||||
 | 
					                
 | 
				
			||||||
 | 
					                throw new IllegalArgumentException(
 | 
				
			||||||
 | 
					                    String.format("分组 '%s' 中的字段order不连续: %s(order=%d) 和 %s(order=%d). " +
 | 
				
			||||||
 | 
					                                "三级及以上层级要求同一分组内的order必须连续。",
 | 
				
			||||||
 | 
					                                groupKey, prevFieldName, prevOrder, currentFieldName, currentOrder)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 表头单元格类,用于支持合并单元格
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static class HeaderCell {
 | 
				
			||||||
 | 
					        private String value;
 | 
				
			||||||
 | 
					        private int level;
 | 
				
			||||||
 | 
					        private int colSpan = 1;
 | 
				
			||||||
 | 
					        private int rowSpan = 1;
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public String getValue() { return value; }
 | 
				
			||||||
 | 
					        public void setValue(String value) { this.value = value; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public int getLevel() { return level; }
 | 
				
			||||||
 | 
					        public void setLevel(int level) { this.level = level; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public int getColSpan() { return colSpan; }
 | 
				
			||||||
 | 
					        public void setColSpan(int colSpan) { this.colSpan = colSpan; }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        public int getRowSpan() { return rowSpan; }
 | 
				
			||||||
 | 
					        public void setRowSpan(int rowSpan) { this.rowSpan = rowSpan; }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 按指定字符数给文本添加换行符
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param text 需要处理的文本
 | 
				
			||||||
 | 
					     * @param charsPerLine 每行字符数
 | 
				
			||||||
 | 
					     * @return 添加换行符后的文本
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static String addLineBreaks(String text, int charsPerLine) {
 | 
				
			||||||
 | 
					        if (text == null || text.isEmpty()) {
 | 
				
			||||||
 | 
					            return text;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        StringBuilder result = new StringBuilder();
 | 
				
			||||||
 | 
					        for (int i = 0; i < text.length(); i += charsPerLine) {
 | 
				
			||||||
 | 
					            if (i > 0) {
 | 
				
			||||||
 | 
					                result.append("\n");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            int endIndex = Math.min(i + charsPerLine, text.length());
 | 
				
			||||||
 | 
					            result.append(text.substring(i, endIndex));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return result.toString();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * 每8个字符添加一个换行符(默认方法)
 | 
				
			||||||
 | 
					     * 
 | 
				
			||||||
 | 
					     * @param text 需要处理的文本
 | 
				
			||||||
 | 
					     * @return 添加换行符后的文本
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    public static String addLineBreaksPer8Chars(String text) {
 | 
				
			||||||
 | 
					        return addLineBreaks(text, 8);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user