refactor(core): 重构飞书客户端并优化单元格操作

- 重构 FeishuClient 类,优化自定义服务管理
- 新增 ServiceManager 类,统一管理自定义服务
- 优化 CustomCellService 中的单元格操作逻辑- 移除不必要的请求类型和参数处理
- 新增合并单元格功能
- 调整表格样式设置接口
- 优化 FsClient 类,使用 ThreadLocal管理客户端实例
This commit is contained in:
liushuang 2025-08-26 18:24:52 +08:00
parent 5a43a7f2e3
commit 8a49e5280b
9 changed files with 645 additions and 261 deletions

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;
@ -37,7 +36,7 @@ public class FeishuClient {
/** /**
* 创建客户端构建器 * 创建客户端构建器
* *
* @param appId 应用ID * @param appId 应用ID
* @param appSecret 应用密钥 * @param appSecret 应用密钥
* @return 构建器 * @return 构建器
*/ */
@ -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;
} }
/** /**
@ -210,8 +167,8 @@ 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));
} }
/** /**
@ -255,7 +212,7 @@ public class 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,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();
} }
} }

@ -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 ="支持13个搜索"; * 构建多层级表头结构支持按层级排序和合并
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);
} }
} }