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