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;
|
||||||
@ -36,8 +35,8 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建客户端构建器
|
* 创建客户端构建器
|
||||||
*
|
*
|
||||||
* @param appId 应用ID
|
* @param appId 应用ID
|
||||||
* @param appSecret 应用密钥
|
* @param appSecret 应用密钥
|
||||||
* @return 构建器
|
* @return 构建器
|
||||||
*/
|
*/
|
||||||
@ -47,7 +46,7 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取官方表格服务
|
* 获取官方表格服务
|
||||||
*
|
*
|
||||||
* @return 官方表格服务
|
* @return 官方表格服务
|
||||||
*/
|
*/
|
||||||
public SheetsService sheets() {
|
public SheetsService sheets() {
|
||||||
@ -56,7 +55,7 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取官方驱动服务
|
* 获取官方驱动服务
|
||||||
*
|
*
|
||||||
* @return 官方驱动服务
|
* @return 官方驱动服务
|
||||||
*/
|
*/
|
||||||
public DriveService drive() {
|
public DriveService drive() {
|
||||||
@ -65,103 +64,61 @@ 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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取扩展行列服务
|
* 获取扩展行列服务
|
||||||
*
|
*
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取扩展单元格服务
|
* 获取扩展单元格服务
|
||||||
*
|
*
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取扩展数据值服务
|
* 获取扩展数据值服务
|
||||||
*
|
*
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取自定义数据验证服务
|
* 获取自定义数据验证服务
|
||||||
*
|
*
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取扩展保护范围服务
|
* 获取扩展保护范围服务
|
||||||
*
|
*
|
||||||
* @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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取官方客户端
|
* 获取官方客户端
|
||||||
*
|
*
|
||||||
* @return 官方Client实例
|
* @return 官方Client实例
|
||||||
*/
|
*/
|
||||||
public Client getOfficialClient() {
|
public Client getOfficialClient() {
|
||||||
@ -170,7 +127,7 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取HTTP客户端
|
* 获取HTTP客户端
|
||||||
*
|
*
|
||||||
* @return OkHttp客户端实例
|
* @return OkHttp客户端实例
|
||||||
*/
|
*/
|
||||||
public OkHttpClient getHttpClient() {
|
public OkHttpClient getHttpClient() {
|
||||||
@ -179,7 +136,7 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取应用ID
|
* 获取应用ID
|
||||||
*
|
*
|
||||||
* @return 应用ID
|
* @return 应用ID
|
||||||
*/
|
*/
|
||||||
public String getAppId() {
|
public String getAppId() {
|
||||||
@ -188,7 +145,7 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取应用密钥
|
* 获取应用密钥
|
||||||
*
|
*
|
||||||
* @return 应用密钥
|
* @return 应用密钥
|
||||||
*/
|
*/
|
||||||
public String getAppSecret() {
|
public String getAppSecret() {
|
||||||
@ -210,13 +167,13 @@ public class FeishuClient {
|
|||||||
this.appSecret = appSecret;
|
this.appSecret = appSecret;
|
||||||
// 默认OkHttp配置
|
// 默认OkHttp配置
|
||||||
this.httpClientBuilder =
|
this.httpClientBuilder =
|
||||||
new OkHttpClient.Builder().connectTimeout(10, TimeUnit.MINUTES).readTimeout(10, TimeUnit.MINUTES)
|
new OkHttpClient.Builder().connectTimeout(10, TimeUnit.MINUTES).readTimeout(10, TimeUnit.MINUTES)
|
||||||
.writeTimeout(10, TimeUnit.MINUTES).connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES));
|
.writeTimeout(10, TimeUnit.MINUTES).connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置HTTP客户端
|
* 配置HTTP客户端
|
||||||
*
|
*
|
||||||
* @param builder OkHttp客户端构建器
|
* @param builder OkHttp客户端构建器
|
||||||
* @return 当前构建器
|
* @return 当前构建器
|
||||||
*/
|
*/
|
||||||
@ -227,7 +184,7 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置应用类型
|
* 设置应用类型
|
||||||
*
|
*
|
||||||
* @param appType 应用类型
|
* @param appType 应用类型
|
||||||
* @return 当前构建器
|
* @return 当前构建器
|
||||||
*/
|
*/
|
||||||
@ -238,7 +195,7 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否在debug级别打印请求
|
* 是否在debug级别打印请求
|
||||||
*
|
*
|
||||||
* @param logReqAtDebug 是否打印
|
* @param logReqAtDebug 是否打印
|
||||||
* @return 当前构建器
|
* @return 当前构建器
|
||||||
*/
|
*/
|
||||||
@ -249,13 +206,13 @@ public class FeishuClient {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建FeishuClient实例
|
* 构建FeishuClient实例
|
||||||
*
|
*
|
||||||
* @return FeishuClient实例
|
* @return FeishuClient实例
|
||||||
*/
|
*/
|
||||||
public FeishuClient build() {
|
public FeishuClient build() {
|
||||||
// 构建官方Client
|
// 构建官方Client
|
||||||
Client officialClient =
|
Client officialClient =
|
||||||
Client.newBuilder(appId, appSecret).appType(appType).logReqAtDebug(logReqAtDebug).build();
|
Client.newBuilder(appId, appSecret).appType(appType).logReqAtDebug(logReqAtDebug).build();
|
||||||
|
|
||||||
// 构建OkHttpClient
|
// 构建OkHttpClient
|
||||||
OkHttpClient httpClient = httpClientBuilder.build();
|
OkHttpClient httpClient = httpClientBuilder.build();
|
||||||
|
@ -2,21 +2,20 @@ 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() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取单例实例 - 使用双重检查锁定模式
|
* 获取单例实例 - 使用双重检查锁定模式
|
||||||
* @return FeishuClientManager实例
|
* @return FsClient实例
|
||||||
*/
|
*/
|
||||||
public static FsClient getInstance() {
|
public static FsClient getInstance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
@ -28,22 +27,23 @@ public class FsClient {
|
|||||||
}
|
}
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 线程安全的客户端获取
|
* 线程安全的客户端获取
|
||||||
* @return FeishuClient实例
|
* @return FeishuClient实例
|
||||||
* @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.");
|
||||||
}
|
}
|
||||||
return currentClient;
|
return currentClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 线程安全的客户端初始化
|
* 线程安全的客户端初始化
|
||||||
|
* 每个线程调用此方法会创建并维护自己的客户端实例
|
||||||
* @param appId 飞书应用ID
|
* @param appId 飞书应用ID
|
||||||
* @param appSecret 飞书应用密钥
|
* @param appSecret 飞书应用密钥
|
||||||
* @return 初始化的FeishuClient实例
|
* @return 初始化的FeishuClient实例
|
||||||
@ -55,43 +55,53 @@ public class FsClient {
|
|||||||
if (appSecret == null || appSecret.trim().isEmpty()) {
|
if (appSecret == null || appSecret.trim().isEmpty()) {
|
||||||
throw new IllegalArgumentException("appSecret cannot be null or empty");
|
throw new IllegalArgumentException("appSecret cannot be null or empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (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) {
|
||||||
if (feishuClient == null) {
|
if (feishuClient == null) {
|
||||||
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,22 +50,15 @@ 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 range = cellRequest.getMergeCells().getRange();
|
||||||
String type = cellRequest.getMergeCells().getType();
|
if (range == null) {
|
||||||
if (type != null && !type.isEmpty() && "JSON_STR".equals(type)) {
|
ApiResponse errorResponse = new ApiResponse();
|
||||||
params = cellRequest.getMergeCells().getParams();
|
errorResponse.setCode(400);
|
||||||
} else {
|
errorResponse.setMsg("Invalid cell range");
|
||||||
// 获取合并单元格范围
|
return errorResponse;
|
||||||
String range = cellRequest.getMergeCells().getRange();
|
|
||||||
if (range == null) {
|
|
||||||
ApiResponse errorResponse = new ApiResponse();
|
|
||||||
errorResponse.setCode(400);
|
|
||||||
errorResponse.setMsg("Invalid cell range");
|
|
||||||
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,32 +125,25 @@ 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 = "";
|
List<String> ranges = cellRequest.getStyleCellsBatch().getRanges();
|
||||||
if (type != null && !type.isEmpty() && "JSON_STR".equals(type)) {
|
Style style = cellRequest.getStyleCellsBatch().getStyle();
|
||||||
params = cellRequest.getStyleCellsBatch().getParams();
|
|
||||||
} else {
|
|
||||||
// 获取单元格范围和样式
|
|
||||||
List<String> ranges = cellRequest.getStyleCellsBatch().getRanges();
|
|
||||||
Style style = cellRequest.getStyleCellsBatch().getStyle();
|
|
||||||
|
|
||||||
if (ranges == null || ranges.isEmpty()) {
|
if (ranges == null || ranges.isEmpty()) {
|
||||||
ApiResponse errorResponse = new ApiResponse();
|
ApiResponse errorResponse = new ApiResponse();
|
||||||
errorResponse.setCode(400);
|
errorResponse.setCode(400);
|
||||||
errorResponse.setMsg("Invalid cell ranges");
|
errorResponse.setMsg("Invalid cell ranges");
|
||||||
return errorResponse;
|
return errorResponse;
|
||||||
}
|
|
||||||
|
|
||||||
// 构建批量设置样式请求体
|
|
||||||
StyleBatchUpdateRequest styleBatchRequest = new StyleBatchUpdateRequest();
|
|
||||||
StyleBatchData styleBatchData = new StyleBatchData();
|
|
||||||
styleBatchData.setRanges(ranges);
|
|
||||||
styleBatchData.setStyle(style);
|
|
||||||
styleBatchRequest.getData().add(styleBatchData);
|
|
||||||
params = gson.toJson(styleBatchRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestBody body = RequestBody.create(params, JSON_MEDIA_TYPE);
|
// 构建批量设置样式请求体
|
||||||
|
StyleBatchUpdateRequest styleBatchRequest = new StyleBatchUpdateRequest();
|
||||||
|
StyleBatchData styleBatchData = new StyleBatchData();
|
||||||
|
styleBatchData.setRanges(ranges);
|
||||||
|
styleBatchData.setStyle(style);
|
||||||
|
styleBatchRequest.getData().add(styleBatchData);
|
||||||
|
|
||||||
|
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;
|
|
||||||
|
|
||||||
int titleRow = tableConf.titleRow();
|
if (maxLevel == 1) {
|
||||||
if (tableConf.enableDesc()) {
|
// 单层级表头:按order排序的headers
|
||||||
int descRow = titleRow + 1;
|
List<String> sortedHeaders = getSortedHeaders(fieldsMap);
|
||||||
batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + descRow);
|
int titleRow = tableConf.titleRow();
|
||||||
batchPutValuesBuilder.addRow(headers.toArray());
|
if (tableConf.enableDesc()) {
|
||||||
batchPutValuesBuilder.addRow(getDescArray(headers, fieldsMap));
|
int descRow = titleRow + 1;
|
||||||
|
batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + descRow);
|
||||||
|
batchPutValuesBuilder.addRow(sortedHeaders.toArray());
|
||||||
|
batchPutValuesBuilder.addRow(getDescArray(sortedHeaders, fieldsMap));
|
||||||
|
} else {
|
||||||
|
batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + titleRow);
|
||||||
|
batchPutValuesBuilder.addRow(sortedHeaders.toArray());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + titleRow);
|
|
||||||
batchPutValuesBuilder.addRow(headers.toArray());
|
// 多层级表头:构建层级结构并处理合并单元格
|
||||||
|
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