```
feat(core): 增强飞书表格功能与工具类支持 -为 ConvertFieldUtil、FileUtil、FsApiUtil 和 FsTableUtil 类添加更详细的 JavaDoc 注释,提升代码可读性- 在 FileUtil 中优化图片文件判断逻辑并增强 getFileName 方法以支持 URL 场景 - 扩展 FsApiUtil 支持更多飞书 API 参数格式化及异常处理 - 在 FsHelper 中引入 ReadBuilder 和 WriteBuilder 构建器模式,提供链式调用方式操作飞书表格读写 - FsTableUtil 新增对忽略唯一字段的支持,并扩展表头模板构建能力,支持字段描述自定义- GenerateUtil优化数字转换精度控制,并修复部分日志提示文案 - OptionsValueProcess 接口增加泛型参数支持,提高灵活性和类型安全性```
This commit is contained in:
parent
3e25a91aed
commit
891b019ae3
@ -5,7 +5,9 @@ import cn.isliu.core.FileData;
|
|||||||
import cn.isliu.core.FsTableData;
|
import cn.isliu.core.FsTableData;
|
||||||
import cn.isliu.core.Sheet;
|
import cn.isliu.core.Sheet;
|
||||||
import cn.isliu.core.annotation.TableConf;
|
import cn.isliu.core.annotation.TableConf;
|
||||||
|
import cn.isliu.core.builder.ReadBuilder;
|
||||||
import cn.isliu.core.builder.SheetBuilder;
|
import cn.isliu.core.builder.SheetBuilder;
|
||||||
|
import cn.isliu.core.builder.WriteBuilder;
|
||||||
import cn.isliu.core.client.FeishuClient;
|
import cn.isliu.core.client.FeishuClient;
|
||||||
import cn.isliu.core.client.FsClient;
|
import cn.isliu.core.client.FsClient;
|
||||||
import cn.isliu.core.enums.ErrorCode;
|
import cn.isliu.core.enums.ErrorCode;
|
||||||
@ -20,6 +22,8 @@ import com.google.gson.JsonObject;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
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.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,8 +74,12 @@ public class FsHelper {
|
|||||||
FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
|
FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5 设置表格下拉
|
try {
|
||||||
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, tableConf.enableDesc());
|
// 5 设置表格下拉
|
||||||
|
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, tableConf.enableDesc());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger(SheetBuilder.class.getName()).log(Level.SEVERE,"【表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
|
||||||
|
}
|
||||||
return sheetId;
|
return sheetId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +141,22 @@ public class FsHelper {
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建飞书表格数据读取构建器
|
||||||
|
*
|
||||||
|
* 返回一个数据读取构建器实例,支持链式调用和高级配置选项,
|
||||||
|
* 如忽略唯一字段等功能。
|
||||||
|
*
|
||||||
|
* @param sheetId 工作表ID
|
||||||
|
* @param spreadsheetToken 电子表格Token
|
||||||
|
* @param clazz 实体类Class对象,用于数据映射
|
||||||
|
* @param <T> 实体类泛型
|
||||||
|
* @return ReadBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public static <T> ReadBuilder<T> readBuilder(String sheetId, String spreadsheetToken, Class<T> clazz) {
|
||||||
|
return new ReadBuilder<>(sheetId, spreadsheetToken, clazz);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将数据写入飞书表格
|
* 将数据写入飞书表格
|
||||||
*
|
*
|
||||||
@ -244,4 +268,20 @@ public class FsHelper {
|
|||||||
|
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建飞书表格数据写入构建器
|
||||||
|
*
|
||||||
|
* 返回一个数据写入构建器实例,支持链式调用和高级配置选项,
|
||||||
|
* 如忽略唯一字段等功能。
|
||||||
|
*
|
||||||
|
* @param sheetId 工作表ID
|
||||||
|
* @param spreadsheetToken 电子表格Token
|
||||||
|
* @param dataList 要写入的数据列表
|
||||||
|
* @param <T> 实体类泛型
|
||||||
|
* @return WriteBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public static <T> WriteBuilder<T> writeBuilder(String sheetId, String spreadsheetToken, List<T> dataList) {
|
||||||
|
return new WriteBuilder<>(sheetId, spreadsheetToken, dataList);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
130
src/main/java/cn/isliu/core/builder/ReadBuilder.java
Normal file
130
src/main/java/cn/isliu/core/builder/ReadBuilder.java
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
package cn.isliu.core.builder;
|
||||||
|
|
||||||
|
import cn.isliu.core.BaseEntity;
|
||||||
|
import cn.isliu.core.FsTableData;
|
||||||
|
import cn.isliu.core.Sheet;
|
||||||
|
import cn.isliu.core.annotation.TableConf;
|
||||||
|
import cn.isliu.core.client.FeishuClient;
|
||||||
|
import cn.isliu.core.client.FsClient;
|
||||||
|
import cn.isliu.core.pojo.FieldProperty;
|
||||||
|
import cn.isliu.core.utils.*;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据读取构建器
|
||||||
|
*
|
||||||
|
* 提供链式调用方式读取飞书表格数据,支持忽略唯一字段等高级功能。
|
||||||
|
*/
|
||||||
|
public class ReadBuilder<T> {
|
||||||
|
|
||||||
|
private final String sheetId;
|
||||||
|
private final String spreadsheetToken;
|
||||||
|
private final Class<T> clazz;
|
||||||
|
private List<String> ignoreUniqueFields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
*
|
||||||
|
* @param sheetId 工作表ID
|
||||||
|
* @param spreadsheetToken 电子表格Token
|
||||||
|
* @param clazz 实体类Class对象
|
||||||
|
*/
|
||||||
|
public ReadBuilder(String sheetId, String spreadsheetToken, Class<T> clazz) {
|
||||||
|
this.sheetId = sheetId;
|
||||||
|
this.spreadsheetToken = spreadsheetToken;
|
||||||
|
this.clazz = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置计算唯一标识时忽略的字段列表
|
||||||
|
*
|
||||||
|
* 指定在计算数据行唯一标识时要忽略的字段名称列表。
|
||||||
|
* 这些字段的值变化不会影响数据行的唯一性判断。
|
||||||
|
*
|
||||||
|
* @param fields 要忽略的字段名称列表
|
||||||
|
* @return ReadBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public ReadBuilder<T> ignoreUniqueFields(List<String> fields) {
|
||||||
|
this.ignoreUniqueFields = new ArrayList<>(fields);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行数据读取并返回实体类对象列表
|
||||||
|
*
|
||||||
|
* 根据配置的参数从飞书表格中读取数据并映射到实体类对象列表中。
|
||||||
|
*
|
||||||
|
* @return 映射后的实体类对象列表
|
||||||
|
*/
|
||||||
|
public List<T> build() {
|
||||||
|
List<T> results = new ArrayList<>();
|
||||||
|
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);
|
||||||
|
|
||||||
|
// 使用支持忽略字段的方法获取表格数据
|
||||||
|
List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken, tableConf, processedIgnoreFields);
|
||||||
|
|
||||||
|
List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
|
||||||
|
|
||||||
|
fsTableDataList.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);
|
||||||
|
}
|
||||||
|
results.add(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理忽略字段名称映射
|
||||||
|
*
|
||||||
|
* 将实体字段名称映射为表格列名称
|
||||||
|
*
|
||||||
|
* @param fieldsMap 字段映射
|
||||||
|
* @return 处理后的忽略字段列表
|
||||||
|
*/
|
||||||
|
private List<String> processIgnoreFields(Map<String, FieldProperty> fieldsMap) {
|
||||||
|
if (ignoreUniqueFields == null || ignoreUniqueFields.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> processedFields = new ArrayList<>();
|
||||||
|
|
||||||
|
// 遍历字段映射,找到对应的表格列名
|
||||||
|
for (Map.Entry<String, FieldProperty> entry : fieldsMap.entrySet()) {
|
||||||
|
String fieldName = entry.getValue().getField();
|
||||||
|
// 获取字段的最后一部分名称(去掉嵌套路径)
|
||||||
|
String simpleFieldName = fieldName.substring(fieldName.lastIndexOf(".") + 1);
|
||||||
|
|
||||||
|
// 如果忽略字段列表中包含此字段名,则添加对应的表格列名
|
||||||
|
if (ignoreUniqueFields.contains(simpleFieldName)) {
|
||||||
|
String tableColumnName = entry.getKey(); // 表格列名(注解中的value值)
|
||||||
|
processedFields.add(tableColumnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedFields;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,10 +8,14 @@ import cn.isliu.core.service.CustomCellService;
|
|||||||
import cn.isliu.core.utils.FsApiUtil;
|
import cn.isliu.core.utils.FsApiUtil;
|
||||||
import cn.isliu.core.utils.FsTableUtil;
|
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 java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -25,6 +29,8 @@ public class SheetBuilder<T> {
|
|||||||
private final String spreadsheetToken;
|
private final String spreadsheetToken;
|
||||||
private final Class<T> clazz;
|
private final Class<T> clazz;
|
||||||
private List<String> includeFields;
|
private List<String> includeFields;
|
||||||
|
private final Map<String, Object> customProperties = new HashMap<>();
|
||||||
|
private final Map<String, String> fieldDescriptions = new HashMap<>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
@ -52,6 +58,91 @@ public class SheetBuilder<T> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置自定义属性
|
||||||
|
*
|
||||||
|
* 添加一个自定义属性,可以在构建表格时使用
|
||||||
|
*
|
||||||
|
* @param key 属性键
|
||||||
|
* @param value 属性值
|
||||||
|
* @return SheetBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public SheetBuilder<T> addCustomProperty(String key, Object value) {
|
||||||
|
this.customProperties.put(key, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量设置自定义属性
|
||||||
|
*
|
||||||
|
* 批量添加自定义属性,可以在构建表格时使用
|
||||||
|
*
|
||||||
|
* @param properties 自定义属性映射
|
||||||
|
* @return SheetBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public SheetBuilder<T> addCustomProperties(Map<String, Object> properties) {
|
||||||
|
this.customProperties.putAll(properties);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取自定义属性
|
||||||
|
*
|
||||||
|
* 根据键获取已设置的自定义属性值
|
||||||
|
*
|
||||||
|
* @param key 属性键
|
||||||
|
* @return 属性值,如果不存在则返回null
|
||||||
|
*/
|
||||||
|
public Object getCustomProperty(String key) {
|
||||||
|
return this.customProperties.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有自定义属性
|
||||||
|
*
|
||||||
|
* @return 包含所有自定义属性的映射
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getCustomProperties() {
|
||||||
|
return new HashMap<>(this.customProperties);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置字段描述映射
|
||||||
|
*
|
||||||
|
* 为实体类字段设置自定义描述信息,用于在表格描述行中显示。
|
||||||
|
* 如果字段在映射中存在描述,则使用映射中的描述;否则使用注解中的描述。
|
||||||
|
*
|
||||||
|
* @param fieldDescriptions 字段名到描述的映射,key为字段名,value为描述文本
|
||||||
|
* @return SheetBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public SheetBuilder<T> fieldDescription(Map<String, String> fieldDescriptions) {
|
||||||
|
this.fieldDescriptions.putAll(fieldDescriptions);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置单个字段描述
|
||||||
|
*
|
||||||
|
* 为指定字段设置自定义描述信息。
|
||||||
|
*
|
||||||
|
* @param fieldName 字段名
|
||||||
|
* @param description 描述文本
|
||||||
|
* @return SheetBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public SheetBuilder<T> fieldDescription(String fieldName, String description) {
|
||||||
|
this.fieldDescriptions.put(fieldName, description);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字段描述映射
|
||||||
|
*
|
||||||
|
* @return 包含所有字段描述的映射
|
||||||
|
*/
|
||||||
|
public Map<String, String> getFieldDescriptions() {
|
||||||
|
return new HashMap<>(this.fieldDescriptions);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建表格并返回工作表ID
|
* 构建表格并返回工作表ID
|
||||||
*
|
*
|
||||||
@ -79,7 +170,7 @@ public class SheetBuilder<T> {
|
|||||||
String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
|
String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
|
||||||
|
|
||||||
// 2、添加表头数据
|
// 2、添加表头数据
|
||||||
FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf), client);
|
FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, includeFields, tableConf, fieldDescriptions), client);
|
||||||
|
|
||||||
// 3、设置表格样式
|
// 3、设置表格样式
|
||||||
FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, fieldsMap, tableConf), client, spreadsheetToken);
|
FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, fieldsMap, tableConf), client, spreadsheetToken);
|
||||||
@ -97,7 +188,11 @@ public class SheetBuilder<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6、设置表格下拉
|
// 6、设置表格下拉
|
||||||
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, tableConf.enableDesc());
|
try {
|
||||||
|
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, tableConf.enableDesc(), customProperties);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Logger.getLogger(SheetBuilder.class.getName()).log(Level.SEVERE,"【表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
return sheetId;
|
return sheetId;
|
||||||
}
|
}
|
||||||
@ -116,7 +211,14 @@ public class SheetBuilder<T> {
|
|||||||
|
|
||||||
// 根据字段名过滤,保留指定的字段
|
// 根据字段名过滤,保留指定的字段
|
||||||
return allFieldsMap.entrySet().stream()
|
return allFieldsMap.entrySet().stream()
|
||||||
.filter(entry -> includeFields.contains(entry.getValue().getField()))
|
.filter(entry -> {
|
||||||
|
String field = entry.getValue().getField();
|
||||||
|
field = field.substring(field.lastIndexOf(".") + 1);
|
||||||
|
if (field.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return includeFields.contains(StringUtil.toUnderscoreCase(field));
|
||||||
|
})
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
293
src/main/java/cn/isliu/core/builder/WriteBuilder.java
Normal file
293
src/main/java/cn/isliu/core/builder/WriteBuilder.java
Normal file
@ -0,0 +1,293 @@
|
|||||||
|
package cn.isliu.core.builder;
|
||||||
|
|
||||||
|
import cn.isliu.core.FileData;
|
||||||
|
import cn.isliu.core.FsTableData;
|
||||||
|
import cn.isliu.core.Sheet;
|
||||||
|
import cn.isliu.core.annotation.TableConf;
|
||||||
|
import cn.isliu.core.client.FeishuClient;
|
||||||
|
import cn.isliu.core.client.FsClient;
|
||||||
|
import cn.isliu.core.enums.ErrorCode;
|
||||||
|
import cn.isliu.core.enums.FileType;
|
||||||
|
import cn.isliu.core.logging.FsLogger;
|
||||||
|
import cn.isliu.core.pojo.FieldProperty;
|
||||||
|
import cn.isliu.core.service.CustomValueService;
|
||||||
|
import cn.isliu.core.utils.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据写入构建器
|
||||||
|
*
|
||||||
|
* 提供链式调用方式写入飞书表格数据,支持忽略唯一字段等高级功能。
|
||||||
|
*/
|
||||||
|
public class WriteBuilder<T> {
|
||||||
|
|
||||||
|
private final String sheetId;
|
||||||
|
private final String spreadsheetToken;
|
||||||
|
private final List<T> dataList;
|
||||||
|
private List<String> ignoreUniqueFields;
|
||||||
|
private Class<?> clazz;
|
||||||
|
private boolean ignoreNotFound;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
*
|
||||||
|
* @param sheetId 工作表ID
|
||||||
|
* @param spreadsheetToken 电子表格Token
|
||||||
|
* @param dataList 要写入的数据列表
|
||||||
|
*/
|
||||||
|
public WriteBuilder(String sheetId, String spreadsheetToken, List<T> dataList) {
|
||||||
|
this.sheetId = sheetId;
|
||||||
|
this.spreadsheetToken = spreadsheetToken;
|
||||||
|
this.dataList = dataList;
|
||||||
|
this.clazz = null;
|
||||||
|
this.ignoreNotFound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置计算唯一标识时忽略的字段列表
|
||||||
|
*
|
||||||
|
* 指定在计算数据行唯一标识时要忽略的字段名称列表。
|
||||||
|
* 这些字段的值变化不会影响数据行的唯一性判断。
|
||||||
|
*
|
||||||
|
* @param fields 要忽略的字段名称列表
|
||||||
|
* @return WriteBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public WriteBuilder<T> ignoreUniqueFields(List<String> fields) {
|
||||||
|
this.ignoreUniqueFields = new ArrayList<>(fields);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置用于解析注解的实体类
|
||||||
|
*
|
||||||
|
* 指定用于解析@TableProperty注解的实体类。这个类可以与数据列表的类型不同,
|
||||||
|
* 主要用于获取字段映射关系和表格配置信息。
|
||||||
|
*
|
||||||
|
* @param clazz 用于解析注解的实体类
|
||||||
|
* @return WriteBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public WriteBuilder<T> clazz(Class<?> clazz) {
|
||||||
|
this.clazz = clazz;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置是否忽略未找到的数据
|
||||||
|
*
|
||||||
|
* 指定在写入数据时是否忽略未找到的字段。如果设置为true,
|
||||||
|
* 当数据中包含表格中不存在的字段时,写入操作将继续执行,
|
||||||
|
* 而不会抛出异常。默认值为false。
|
||||||
|
*
|
||||||
|
* @param ignoreNotFound 是否忽略未找到的字段,默认值为false
|
||||||
|
* @return WriteBuilder实例,支持链式调用
|
||||||
|
*/
|
||||||
|
public WriteBuilder<T> ignoreNotFound(boolean ignoreNotFound) {
|
||||||
|
this.ignoreNotFound = ignoreNotFound;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行数据写入并返回操作结果
|
||||||
|
*
|
||||||
|
* 根据配置的参数将数据写入到飞书表格中,支持新增和更新操作。
|
||||||
|
*
|
||||||
|
* @return 写入操作结果
|
||||||
|
*/
|
||||||
|
public Object build() {
|
||||||
|
if (dataList.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<?> aClass = clazz;
|
||||||
|
Map<String, FieldProperty> fieldsMap;
|
||||||
|
TableConf tableConf = PropertyUtil.getTableConf(aClass);
|
||||||
|
|
||||||
|
Map<String, String> fieldMap = new HashMap<>();
|
||||||
|
Class<?> sourceClass = dataList.get(0).getClass();
|
||||||
|
if (aClass.equals(sourceClass)) {
|
||||||
|
fieldsMap = PropertyUtil.getTablePropertyFieldsMap(aClass);
|
||||||
|
} else {
|
||||||
|
fieldsMap = PropertyUtil.getTablePropertyFieldsMap(sourceClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldsMap.forEach((field, fieldProperty) -> fieldMap.put(field, fieldProperty.getField()));
|
||||||
|
|
||||||
|
// 处理忽略字段名称映射
|
||||||
|
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);
|
||||||
|
Map<String, Integer> currTableRowMap = fsTableDataList.stream().collect(Collectors.toMap(FsTableData::getUniqueId, FsTableData::getRow));
|
||||||
|
|
||||||
|
final Integer[] row = {0};
|
||||||
|
fsTableDataList.forEach(fsTableData -> {
|
||||||
|
if (fsTableData.getRow() > row[0]) {
|
||||||
|
row[0] = fsTableData.getRow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, String> titlePostionMap = FsTableUtil.getTitlePostionMap(sheet, spreadsheetToken, tableConf);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// 初始化批量插入对象
|
||||||
|
CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder = CustomValueService.ValueRequest.batchPutValues();
|
||||||
|
|
||||||
|
List<FileData> fileDataList = new ArrayList<>();
|
||||||
|
|
||||||
|
AtomicInteger rowCount = new AtomicInteger(row[0] + 1);
|
||||||
|
|
||||||
|
for (T data : dataList) {
|
||||||
|
Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
|
||||||
|
|
||||||
|
// 计算唯一标识:如果data类型与aClass相同,使用忽略字段逻辑;否则直接从data获取uniqueId
|
||||||
|
String uniqueId;
|
||||||
|
if (data.getClass().equals(aClass)) {
|
||||||
|
// 类型相同,使用忽略字段逻辑计算唯一标识
|
||||||
|
uniqueId = calculateUniqueIdWithIgnoreFields(data, processedIgnoreFields, aClass);
|
||||||
|
} else {
|
||||||
|
uniqueId = GenerateUtil.getUniqueId(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
|
||||||
|
if (uniqueId != null && rowNum.get() != null) {
|
||||||
|
rowNum.set(rowNum.get() + 1);
|
||||||
|
values.forEach((field, fieldValue) -> {
|
||||||
|
if (!tableConf.enableCover() && fieldValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String position = titlePostionMap.get(field);
|
||||||
|
|
||||||
|
if (fieldValue instanceof FileData) {
|
||||||
|
FileData fileData = (FileData) fieldValue;
|
||||||
|
String fileType = fileData.getFileType();
|
||||||
|
if (fileType.equals(FileType.IMAGE.getType())) {
|
||||||
|
fileData.setSheetId(sheetId);
|
||||||
|
fileData.setSpreadsheetToken(spreadsheetToken);
|
||||||
|
fileData.setPosition(position + rowNum.get());
|
||||||
|
fileDataList.add(fileData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
|
||||||
|
.addRow(GenerateUtil.getRowData(fieldValue));
|
||||||
|
});
|
||||||
|
} else if (!ignoreNotFound) {
|
||||||
|
int rowCou = rowCount.incrementAndGet();
|
||||||
|
values.forEach((field, fieldValue) -> {
|
||||||
|
if (!tableConf.enableCover() && fieldValue == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String position = titlePostionMap.get(field);
|
||||||
|
if (fieldValue instanceof FileData) {
|
||||||
|
FileData fileData = (FileData) fieldValue;
|
||||||
|
fileData.setSheetId(sheetId);
|
||||||
|
fileData.setSpreadsheetToken(spreadsheetToken);
|
||||||
|
fileData.setPosition(position + rowCou);
|
||||||
|
fileDataList.add(fileData);
|
||||||
|
}
|
||||||
|
resultValuesBuilder.addRange(sheetId, position + rowCou, position + rowCou)
|
||||||
|
.addRow(GenerateUtil.getRowData(fieldValue));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int rowTotal = sheet.getGridProperties().getRowCount();
|
||||||
|
int rowNum = rowCount.get();
|
||||||
|
if (rowNum > rowTotal) {
|
||||||
|
FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", rowTotal - rowNum, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDataList.forEach(fileData -> {
|
||||||
|
try {
|
||||||
|
FsApiUtil.imageUpload(fileData.getImageData(), fileData.getFileName(), fileData.getPosition(), fileData.getSheetId(), fileData.getSpreadsheetToken(), client);
|
||||||
|
} catch (Exception e) {
|
||||||
|
FsLogger.error(ErrorCode.BUSINESS_LOGIC_ERROR, "【飞书表格】 文件上传-文件上传异常! " + fileData.getFileUrl());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CustomValueService.ValueRequest build = resultValuesBuilder.build();
|
||||||
|
CustomValueService.ValueBatchUpdatePutRequest batchPutValues = build.getBatchPutValues();
|
||||||
|
List<CustomValueService.ValueRangeItem> valueRanges = batchPutValues.getValueRanges();
|
||||||
|
if (valueRanges != null && !valueRanges.isEmpty()) {
|
||||||
|
return FsApiUtil.batchPutValues(sheetId, spreadsheetToken, resultValuesBuilder.build(), client);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理忽略字段名称映射
|
||||||
|
*
|
||||||
|
* 将实体字段名称映射为表格列名称
|
||||||
|
*
|
||||||
|
* @param fieldsMap 字段映射
|
||||||
|
* @return 处理后的忽略字段列表
|
||||||
|
*/
|
||||||
|
private List<String> processIgnoreFields(Map<String, FieldProperty> fieldsMap) {
|
||||||
|
if (ignoreUniqueFields == null || ignoreUniqueFields.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> processedFields = new ArrayList<>();
|
||||||
|
|
||||||
|
// 遍历字段映射,找到对应的表格列名
|
||||||
|
for (Map.Entry<String, FieldProperty> entry : fieldsMap.entrySet()) {
|
||||||
|
String fieldName = entry.getValue().getField();
|
||||||
|
// 获取字段的最后一部分名称(去掉嵌套路径)
|
||||||
|
String simpleFieldName = fieldName.substring(fieldName.lastIndexOf(".") + 1);
|
||||||
|
|
||||||
|
// 如果忽略字段列表中包含此字段名,则添加对应的表格列名
|
||||||
|
if (ignoreUniqueFields.contains(simpleFieldName)) {
|
||||||
|
String tableColumnName = entry.getKey(); // 表格列名(注解中的value值)
|
||||||
|
processedFields.add(tableColumnName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processedFields;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算考虑忽略字段的唯一标识
|
||||||
|
*
|
||||||
|
* 根据忽略字段列表计算数据的唯一标识
|
||||||
|
*
|
||||||
|
* @param data 数据对象
|
||||||
|
* @param processedIgnoreFields 处理后的忽略字段列表
|
||||||
|
* @param clazz 用于解析注解的实体类
|
||||||
|
* @return 唯一标识
|
||||||
|
*/
|
||||||
|
private String calculateUniqueIdWithIgnoreFields(T data, List<String> processedIgnoreFields, Class<?> clazz) {
|
||||||
|
try {
|
||||||
|
// 获取所有字段值
|
||||||
|
Map<String, Object> allFieldValues = GenerateUtil.getFieldValue(data, new HashMap<>());
|
||||||
|
|
||||||
|
// 如果不需要忽略字段,使用原有逻辑
|
||||||
|
if (processedIgnoreFields.isEmpty()) {
|
||||||
|
return GenerateUtil.getUniqueId(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除忽略字段后计算唯一标识
|
||||||
|
Map<String, Object> filteredValues = new HashMap<>(allFieldValues);
|
||||||
|
processedIgnoreFields.forEach(filteredValues::remove);
|
||||||
|
|
||||||
|
// 将过滤后的值转换为JSON字符串并计算SHA256
|
||||||
|
String jsonStr = StringUtil.mapToJson(filteredValues);
|
||||||
|
return StringUtil.getSHA256(jsonStr);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 如果计算失败,回退到原有逻辑
|
||||||
|
return GenerateUtil.getUniqueId(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
package cn.isliu.core.converters;
|
package cn.isliu.core.converters;
|
||||||
|
|
||||||
public interface OptionsValueProcess<T> {
|
public interface OptionsValueProcess<T, R> {
|
||||||
|
|
||||||
|
T process(R r);
|
||||||
|
|
||||||
T process();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -148,6 +148,9 @@ public class FileUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static String getFileName(String path) {
|
public static String getFileName(String path) {
|
||||||
|
if (path.startsWith("http")) {
|
||||||
|
return path.substring(path.lastIndexOf("/") + 1);
|
||||||
|
}
|
||||||
return Paths.get(path).getFileName().toString();
|
return Paths.get(path).getFileName().toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,14 +38,14 @@ public class FsApiUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取工作表数据
|
* 获取工作表数据
|
||||||
*
|
* <p>
|
||||||
* 从指定的飞书表格中读取指定范围的数据
|
* 从指定的飞书表格中读取指定范围的数据
|
||||||
*
|
*
|
||||||
* @param sheetId 工作表ID
|
* @param sheetId 工作表ID
|
||||||
* @param spreadsheetToken 电子表格Token
|
* @param spreadsheetToken 电子表格Token
|
||||||
* @param startPosition 起始位置(如"A1")
|
* @param startPosition 起始位置(如"A1")
|
||||||
* @param endPosition 结束位置(如"Z100")
|
* @param endPosition 结束位置(如"Z100")
|
||||||
* @param client 飞书客户端
|
* @param client 飞书客户端
|
||||||
* @return 表格数据对象
|
* @return 表格数据对象
|
||||||
*/
|
*/
|
||||||
public static ValuesBatch getSheetData(String sheetId, String spreadsheetToken, String startPosition, String endPosition, FeishuClient client) {
|
public static ValuesBatch getSheetData(String sheetId, String spreadsheetToken, String startPosition, String endPosition, FeishuClient client) {
|
||||||
@ -78,11 +78,11 @@ public class FsApiUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取工作表元数据
|
* 获取工作表元数据
|
||||||
*
|
* <p>
|
||||||
* 获取指定工作表的元数据信息,包括行列数、工作表名称等
|
* 获取指定工作表的元数据信息,包括行列数、工作表名称等
|
||||||
*
|
*
|
||||||
* @param sheetId 工作表ID
|
* @param sheetId 工作表ID
|
||||||
* @param client 飞书客户端
|
* @param client 飞书客户端
|
||||||
* @param spreadsheetToken 电子表格Token
|
* @param spreadsheetToken 电子表格Token
|
||||||
* @return 工作表对象
|
* @return 工作表对象
|
||||||
*/
|
*/
|
||||||
@ -157,7 +157,7 @@ public class FsApiUtil {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取根目录Token
|
* 获取根目录Token
|
||||||
*
|
* <p>
|
||||||
* 调用飞书开放平台API获取当前租户的根目录token,用于后续的文件夹和文件操作
|
* 调用飞书开放平台API获取当前租户的根目录token,用于后续的文件夹和文件操作
|
||||||
* API接口: GET https://open.feishu.cn/open-apis/drive/v1/files/root_folder/meta
|
* API接口: GET https://open.feishu.cn/open-apis/drive/v1/files/root_folder/meta
|
||||||
*
|
*
|
||||||
@ -266,7 +266,7 @@ public class FsApiUtil {
|
|||||||
String message = e.getMessage();
|
String message = e.getMessage();
|
||||||
FsLogger.warn("【飞书表格】 创建 sheet 异常!错误信息:{}", message);
|
FsLogger.warn("【飞书表格】 创建 sheet 异常!错误信息:{}", message);
|
||||||
|
|
||||||
throw new FsHelperException(message != null && message.contains("403")? "请按照上方操作,当前智投无法操作对应文档哦" : "【飞书表格】 创建 sheet 异常!");
|
throw new FsHelperException(message != null && message.contains("403") ? "请按照上方操作,当前智投无法操作对应文档哦" : "【飞书表格】 创建 sheet 异常!");
|
||||||
}
|
}
|
||||||
return sheetId;
|
return sheetId;
|
||||||
}
|
}
|
||||||
@ -400,7 +400,7 @@ public class FsApiUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String downloadTmpMaterialUrl(String fileToken, FeishuClient client) {
|
public static String downloadTmpMaterialUrl(String fileToken, FeishuClient client) {
|
||||||
String tmpUrl = "";
|
String tmpUrl = "";
|
||||||
try {
|
try {
|
||||||
BatchGetTmpDownloadUrlMediaReq req = BatchGetTmpDownloadUrlMediaReq.newBuilder()
|
BatchGetTmpDownloadUrlMediaReq req = BatchGetTmpDownloadUrlMediaReq.newBuilder()
|
||||||
@ -450,8 +450,8 @@ public class FsApiUtil {
|
|||||||
try {
|
try {
|
||||||
CustomValueService.ValueBatchUpdateRequest batchPutDataRequest =
|
CustomValueService.ValueBatchUpdateRequest batchPutDataRequest =
|
||||||
CustomValueService.ValueBatchUpdateRequest.newBuilder()
|
CustomValueService.ValueBatchUpdateRequest.newBuilder()
|
||||||
.addRequest(batchPutRequest)
|
.addRequest(batchPutRequest)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
ApiResponse batchPutResp = client.customValues().valueBatchUpdate(spreadsheetToken, batchPutDataRequest);
|
ApiResponse batchPutResp = client.customValues().valueBatchUpdate(spreadsheetToken, batchPutDataRequest);
|
||||||
if (batchPutResp.success()) {
|
if (batchPutResp.success()) {
|
||||||
@ -466,7 +466,7 @@ public class FsApiUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object addRowColumns(String sheetId, String spreadsheetToken, String type, int length,FeishuClient client) {
|
public static Object addRowColumns(String sheetId, String spreadsheetToken, String type, int length, FeishuClient client) {
|
||||||
|
|
||||||
CustomDimensionService.DimensionBatchUpdateRequest batchRequest = CustomDimensionService.DimensionBatchUpdateRequest.newBuilder()
|
CustomDimensionService.DimensionBatchUpdateRequest batchRequest = CustomDimensionService.DimensionBatchUpdateRequest.newBuilder()
|
||||||
.addRequest(CustomDimensionService.DimensionRequest.addDimension()
|
.addRequest(CustomDimensionService.DimensionRequest.addDimension()
|
||||||
@ -512,7 +512,7 @@ public class FsApiUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 字符串类型: formatter: "@"
|
* 字符串类型: formatter: "@"
|
||||||
*/
|
*/
|
||||||
public static void setCellType(String sheetId, String formatter, String startPosition, String endPosition, FeishuClient client, String spreadsheetToken) {
|
public static void setCellType(String sheetId, String formatter, String startPosition, String endPosition, FeishuClient client, String spreadsheetToken) {
|
||||||
try {
|
try {
|
||||||
@ -533,7 +533,7 @@ public class FsApiUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object imageUpload(byte[] imageData, String fileName, String position ,String sheetId, String spreadsheetToken, FeishuClient client) {
|
public static Object imageUpload(byte[] imageData, String fileName, String position, String sheetId, String spreadsheetToken, FeishuClient client) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
CustomValueService.ValueRequest imageRequest = CustomValueService.ValueRequest.imageValues()
|
CustomValueService.ValueRequest imageRequest = CustomValueService.ValueRequest.imageValues()
|
||||||
@ -553,7 +553,7 @@ public class FsApiUtil {
|
|||||||
}
|
}
|
||||||
return imageResp.getData();
|
return imageResp.getData();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FsLogger.error(ErrorCode.API_SERVER_ERROR,"【飞书表格】 文件上传异常!" + e.getMessage(), fileName, e);
|
FsLogger.error(ErrorCode.API_SERVER_ERROR, "【飞书表格】 文件上传异常!" + e.getMessage(), fileName, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -38,6 +38,19 @@ public class FsTableUtil {
|
|||||||
* @return 飞书表格数据列表
|
* @return 飞书表格数据列表
|
||||||
*/
|
*/
|
||||||
public static List<FsTableData> getFsTableData(Sheet sheet, String spreadsheetToken, TableConf tableConf) {
|
public static List<FsTableData> getFsTableData(Sheet sheet, String spreadsheetToken, TableConf tableConf) {
|
||||||
|
return getFsTableData(sheet, spreadsheetToken, tableConf, new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取飞书表格数据(支持忽略唯一字段)
|
||||||
|
*
|
||||||
|
* @param sheet 工作表对象
|
||||||
|
* @param spreadsheetToken 电子表格Token
|
||||||
|
* @param tableConf 表格配置
|
||||||
|
* @param ignoreUniqueFields 计算唯一标识时忽略的字段列表
|
||||||
|
* @return 飞书表格数据列表
|
||||||
|
*/
|
||||||
|
public static List<FsTableData> getFsTableData(Sheet sheet, String spreadsheetToken, TableConf tableConf, List<String> ignoreUniqueFields) {
|
||||||
|
|
||||||
// 计算数据范围
|
// 计算数据范围
|
||||||
GridProperties gridProperties = sheet.getGridProperties();
|
GridProperties gridProperties = sheet.getGridProperties();
|
||||||
@ -70,7 +83,7 @@ public class FsTableUtil {
|
|||||||
// 获取飞书表格数据
|
// 获取飞书表格数据
|
||||||
TableData tableData = processSheetData(sheet, values);
|
TableData tableData = processSheetData(sheet, values);
|
||||||
|
|
||||||
List<FsTableData> dataList = getFsTableData(tableData);
|
List<FsTableData> dataList = getFsTableData(tableData, ignoreUniqueFields);
|
||||||
Map<String, String> titleMap = new HashMap<>();
|
Map<String, String> titleMap = new HashMap<>();
|
||||||
|
|
||||||
dataList.stream().filter(d -> d.getRow() == (tableConf.titleRow() - 1)).findFirst()
|
dataList.stream().filter(d -> d.getRow() == (tableConf.titleRow() - 1)).findFirst()
|
||||||
@ -293,7 +306,9 @@ public class FsTableUtil {
|
|||||||
return resultMap;
|
return resultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
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, Map<String, Object> customProperties) {
|
||||||
|
|
||||||
List<Object> list = Arrays.asList(headers.toArray());
|
List<Object> list = Arrays.asList(headers.toArray());
|
||||||
int line = getMaxLevel(fieldsMap) + (enableDesc ? 2 : 1);
|
int line = getMaxLevel(fieldsMap) + (enableDesc ? 2 : 1);
|
||||||
fieldsMap.forEach((field, fieldProperty) -> {
|
fieldsMap.forEach((field, fieldProperty) -> {
|
||||||
@ -316,21 +331,43 @@ public class FsTableUtil {
|
|||||||
List<String> result;
|
List<String> result;
|
||||||
Class<? extends OptionsValueProcess> optionsClass = tableProperty.optionsClass();
|
Class<? extends OptionsValueProcess> optionsClass = tableProperty.optionsClass();
|
||||||
try {
|
try {
|
||||||
|
Map<String, Object> properties = new HashMap<>();
|
||||||
|
if (customProperties == null) {
|
||||||
|
properties.put("_field", fieldProperty);
|
||||||
|
} else {
|
||||||
|
customProperties.put("_field", fieldProperty);
|
||||||
|
}
|
||||||
OptionsValueProcess optionsValueProcess = optionsClass.getDeclaredConstructor().newInstance();
|
OptionsValueProcess optionsValueProcess = optionsClass.getDeclaredConstructor().newInstance();
|
||||||
result = (List<String>) optionsValueProcess.process();
|
result = (List<String>) optionsValueProcess.process(customProperties == null ? properties : customProperties);
|
||||||
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
FsApiUtil.setOptions(sheetId, FsClient.getInstance().getClient(), spreadsheetToken, tableProperty.type() == TypeEnum.MULTI_SELECT, position + line, position + 200,
|
if (result != null && !result.isEmpty()) {
|
||||||
result);
|
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) {
|
||||||
|
setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, enableDesc, null);
|
||||||
|
}
|
||||||
|
|
||||||
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, FieldProperty> fieldsMap, TableConf tableConf) {
|
||||||
|
return getHeadTemplateBuilder(sheetId, headers, fieldsMap, null, tableConf);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomValueService.ValueRequest getHeadTemplateBuilder(String sheetId, List<String> headers,
|
||||||
|
Map<String, FieldProperty> fieldsMap, List<String> includeFields, TableConf tableConf) {
|
||||||
|
return getHeadTemplateBuilder(sheetId, headers, fieldsMap, includeFields, tableConf, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CustomValueService.ValueRequest getHeadTemplateBuilder(String sheetId, List<String> headers,
|
||||||
|
Map<String, FieldProperty> fieldsMap, List<String> includeFields, TableConf tableConf, Map<String, String> fieldDescriptions) {
|
||||||
|
|
||||||
String position = FsTableUtil.getColumnNameByNuNumber(headers.size());
|
String position = FsTableUtil.getColumnNameByNuNumber(headers.size());
|
||||||
|
|
||||||
@ -342,13 +379,18 @@ public class FsTableUtil {
|
|||||||
|
|
||||||
if (maxLevel == 1) {
|
if (maxLevel == 1) {
|
||||||
// 单层级表头:按order排序的headers
|
// 单层级表头:按order排序的headers
|
||||||
List<String> sortedHeaders = getSortedHeaders(fieldsMap);
|
List<String> sortedHeaders;
|
||||||
|
if (includeFields != null && !includeFields.isEmpty()) {
|
||||||
|
sortedHeaders = includeFields.stream().sorted(Comparator.comparingInt(headers::indexOf)).collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
sortedHeaders = getSortedHeaders(fieldsMap);
|
||||||
|
}
|
||||||
int titleRow = tableConf.titleRow();
|
int titleRow = tableConf.titleRow();
|
||||||
if (tableConf.enableDesc()) {
|
if (tableConf.enableDesc()) {
|
||||||
int descRow = titleRow + 1;
|
int descRow = titleRow + 1;
|
||||||
batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + descRow);
|
batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + descRow);
|
||||||
batchPutValuesBuilder.addRow(sortedHeaders.toArray());
|
batchPutValuesBuilder.addRow(sortedHeaders.toArray());
|
||||||
batchPutValuesBuilder.addRow(getDescArray(sortedHeaders, fieldsMap));
|
batchPutValuesBuilder.addRow(getDescArray(sortedHeaders, fieldsMap, fieldDescriptions));
|
||||||
} else {
|
} else {
|
||||||
batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + titleRow);
|
batchPutValuesBuilder.addRange(sheetId + "!A" + titleRow + ":" + position + titleRow);
|
||||||
batchPutValuesBuilder.addRow(sortedHeaders.toArray());
|
batchPutValuesBuilder.addRow(sortedHeaders.toArray());
|
||||||
@ -379,10 +421,15 @@ public class FsTableUtil {
|
|||||||
|
|
||||||
// 如果启用了描述,在最后一行添加描述
|
// 如果启用了描述,在最后一行添加描述
|
||||||
if (tableConf.enableDesc()) {
|
if (tableConf.enableDesc()) {
|
||||||
List<String> finalHeaders = getSortedHeaders(fieldsMap);
|
List<String> finalHeaders;
|
||||||
|
if (includeFields != null && !includeFields.isEmpty()) {
|
||||||
|
finalHeaders = includeFields.stream().sorted(Comparator.comparingInt(headers::indexOf)).collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
finalHeaders = getSortedHeaders(fieldsMap);
|
||||||
|
}
|
||||||
int descRow = maxLevel + 1;
|
int descRow = maxLevel + 1;
|
||||||
batchPutValuesBuilder.addRange(sheetId + "!A" + descRow + ":" + position + descRow);
|
batchPutValuesBuilder.addRange(sheetId + "!A" + descRow + ":" + position + descRow);
|
||||||
batchPutValuesBuilder.addRow(getDescArray(finalHeaders, fieldsMap));
|
batchPutValuesBuilder.addRow(getDescArray(finalHeaders, fieldsMap, fieldDescriptions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,10 +444,10 @@ public class FsTableUtil {
|
|||||||
*/
|
*/
|
||||||
private static List<String> getSortedHeaders(Map<String, FieldProperty> fieldsMap) {
|
private static List<String> getSortedHeaders(Map<String, FieldProperty> fieldsMap) {
|
||||||
return fieldsMap.entrySet().stream()
|
return fieldsMap.entrySet().stream()
|
||||||
.filter(entry -> entry.getValue() != null && entry.getValue().getTableProperty() != null)
|
.filter(entry -> entry.getValue() != null && entry.getValue().getTableProperty() != null)
|
||||||
.sorted(Comparator.comparingInt(entry -> entry.getValue().getTableProperty().order()))
|
.sorted(Comparator.comparingInt(entry -> entry.getValue().getTableProperty().order()))
|
||||||
.map(Map.Entry::getKey)
|
.map(Map.Entry::getKey)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getMaxLevel(Map<String, FieldProperty> fieldsMap) {
|
private static int getMaxLevel(Map<String, FieldProperty> fieldsMap) {
|
||||||
@ -416,12 +463,30 @@ public class FsTableUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Object[] getDescArray(List<String> headers, Map<String, FieldProperty> fieldsMap) {
|
private static Object[] getDescArray(List<String> headers, Map<String, FieldProperty> fieldsMap) {
|
||||||
|
return getDescArray(headers, fieldsMap, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object[] getDescArray(List<String> headers, Map<String, FieldProperty> fieldsMap, Map<String, String> fieldDescriptions) {
|
||||||
Object[] descArray = new String[headers.size()];
|
Object[] descArray = new String[headers.size()];
|
||||||
for (int i = 0; i < headers.size(); i++) {
|
for (int i = 0; i < headers.size(); i++) {
|
||||||
String header = headers.get(i);
|
String header = headers.get(i);
|
||||||
FieldProperty fieldProperty = fieldsMap.get(header);
|
FieldProperty fieldProperty = fieldsMap.get(header);
|
||||||
if (fieldProperty != null && fieldProperty.getTableProperty() != null) {
|
if (fieldProperty != null && fieldProperty.getTableProperty() != null) {
|
||||||
String desc = fieldProperty.getTableProperty().desc();
|
String desc = null;
|
||||||
|
|
||||||
|
// 优先从字段描述映射中获取描述
|
||||||
|
if (fieldDescriptions != null && !fieldDescriptions.isEmpty()) {
|
||||||
|
// 从字段路径中提取字段名(最后一个.后面的部分)
|
||||||
|
String fieldPath = fieldProperty.getField();
|
||||||
|
String fieldName = fieldPath.substring(fieldPath.lastIndexOf(".") + 1);
|
||||||
|
desc = fieldDescriptions.get(fieldName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果映射中没有找到,则从注解中获取
|
||||||
|
if (desc == null || desc.isEmpty()) {
|
||||||
|
desc = fieldProperty.getTableProperty().desc();
|
||||||
|
}
|
||||||
|
|
||||||
if (desc != null && !desc.isEmpty()) {
|
if (desc != null && !desc.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
JsonElement element = JsonParser.parseString(desc);
|
JsonElement element = JsonParser.parseString(desc);
|
||||||
@ -477,9 +542,9 @@ public class FsTableUtil {
|
|||||||
|
|
||||||
// 按order排序的字段条目
|
// 按order排序的字段条目
|
||||||
List<Map.Entry<String, FieldProperty>> sortedEntries = fieldsMap.entrySet().stream()
|
List<Map.Entry<String, FieldProperty>> sortedEntries = fieldsMap.entrySet().stream()
|
||||||
.filter(entry -> entry.getValue() != null && entry.getValue().getTableProperty() != null)
|
.filter(entry -> entry.getValue() != null && entry.getValue().getTableProperty() != null)
|
||||||
.sorted(Comparator.comparingInt(entry -> entry.getValue().getTableProperty().order()))
|
.sorted(Comparator.comparingInt(entry -> entry.getValue().getTableProperty().order()))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
for (Map.Entry<String, FieldProperty> entry : sortedEntries) {
|
for (Map.Entry<String, FieldProperty> entry : sortedEntries) {
|
||||||
FieldProperty fieldProperty = entry.getValue();
|
FieldProperty fieldProperty = entry.getValue();
|
||||||
@ -587,9 +652,9 @@ public class FsTableUtil {
|
|||||||
|
|
||||||
// 计算组的最小order(用于组间排序)
|
// 计算组的最小order(用于组间排序)
|
||||||
int minOrder = fieldsInGroup.stream()
|
int minOrder = fieldsInGroup.stream()
|
||||||
.mapToInt(entry -> entry.getValue().getTableProperty().order())
|
.mapToInt(entry -> entry.getValue().getTableProperty().order())
|
||||||
.min()
|
.min()
|
||||||
.orElse(Integer.MAX_VALUE);
|
.orElse(Integer.MAX_VALUE);
|
||||||
|
|
||||||
allGroups.add(new GroupInfo(groupEntry.getKey(), minOrder, fieldsInGroup));
|
allGroups.add(new GroupInfo(groupEntry.getKey(), minOrder, fieldsInGroup));
|
||||||
}
|
}
|
||||||
@ -681,6 +746,10 @@ public class FsTableUtil {
|
|||||||
return mergeRequests;
|
return mergeRequests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TableConf getTableConf(Class<?> zClass) {
|
||||||
|
return zClass.getAnnotation(TableConf.class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分组信息类,用于辅助排序
|
* 分组信息类,用于辅助排序
|
||||||
*/
|
*/
|
||||||
@ -806,8 +875,8 @@ public class FsTableUtil {
|
|||||||
String currentFieldName = fieldsInGroup.get(i).getKey();
|
String currentFieldName = fieldsInGroup.get(i).getKey();
|
||||||
|
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
String.format("分组 '%s' 中的字段order不连续: %s(order=%d) 和 %s(order=%d). " +
|
String.format("分组 '%s' 中的字段order不连续: %s(order=%d) 和 %s(order=%d). " +
|
||||||
"三级及以上层级要求同一分组内的order必须连续。",
|
"三级及以上层级要求同一分组内的order必须连续。",
|
||||||
groupKey, prevFieldName, prevOrder, currentFieldName, currentOrder)
|
groupKey, prevFieldName, prevOrder, currentFieldName, currentOrder)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import java.util.stream.Collectors;
|
|||||||
public class GenerateUtil {
|
public class GenerateUtil {
|
||||||
|
|
||||||
// 使用统一的FsLogger替代java.util.logging.Logger
|
// 使用统一的FsLogger替代java.util.logging.Logger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据配置和数据生成DTO对象(通用版本)
|
* 根据配置和数据生成DTO对象(通用版本)
|
||||||
*
|
*
|
||||||
@ -49,7 +48,7 @@ public class GenerateUtil {
|
|||||||
try {
|
try {
|
||||||
setNestedField(t, fieldPath, value);
|
setNestedField(t, fieldPath, value);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
FsLogger.error(ErrorCode.DATA_CONVERSION_ERROR, "【巨量广告助手】 获取字段值异常!参数:" + fieldPath + ",异常:" + e.getMessage(), "generateList", e);
|
FsLogger.error(ErrorCode.DATA_CONVERSION_ERROR, "【飞书助手】 获取字段值异常!参数:" + fieldPath + ",异常:" + e.getMessage(), "generateList", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -179,11 +178,15 @@ public class GenerateUtil {
|
|||||||
Class<?> fieldType = field.getType();
|
Class<?> fieldType = field.getType();
|
||||||
|
|
||||||
// 简单类型转换
|
// 简单类型转换
|
||||||
if (value != null) {
|
if (value != null && value != "") {
|
||||||
if (fieldType == String.class) {
|
if (fieldType == String.class) {
|
||||||
field.set(target, convertStrValue(value));
|
field.set(target, convertStrValue(value));
|
||||||
} else if (fieldType == Integer.class || fieldType == int.class) {
|
} else if (fieldType == Integer.class || fieldType == int.class) {
|
||||||
field.set(target, Integer.parseInt(convertValue(value)));
|
String val = convertValue(value);
|
||||||
|
BigDecimal bd = new BigDecimal(val);
|
||||||
|
bd = bd.setScale(0, BigDecimal.ROUND_DOWN);
|
||||||
|
String result = bd.toPlainString();
|
||||||
|
field.set(target, Integer.parseInt(result));
|
||||||
} else if (fieldType == Double.class || fieldType == double.class) {
|
} else if (fieldType == Double.class || fieldType == double.class) {
|
||||||
field.set(target, Double.parseDouble(convertValue(value)));
|
field.set(target, Double.parseDouble(convertValue(value)));
|
||||||
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
|
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
|
||||||
@ -456,7 +459,7 @@ public class GenerateUtil {
|
|||||||
return fieldValue;
|
return fieldValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T> @Nullable String getUniqueId(T data) {
|
public static <T> String getUniqueId(T data) {
|
||||||
String uniqueId = null;
|
String uniqueId = null;
|
||||||
try {
|
try {
|
||||||
Object uniqueIdObj = GenerateUtil.getNestedFieldValue(data, "uniqueId");
|
Object uniqueIdObj = GenerateUtil.getNestedFieldValue(data, "uniqueId");
|
||||||
|
|||||||
@ -56,6 +56,28 @@ public class StringUtil {
|
|||||||
return uniqueId;
|
return uniqueId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 驼峰转下划线命名
|
||||||
|
* @param camelCaseName 驼峰命名的字符串
|
||||||
|
* @return 下划线命名的字符串
|
||||||
|
*/
|
||||||
|
public static String toUnderscoreCase(String camelCaseName) {
|
||||||
|
if (camelCaseName == null || camelCaseName.isEmpty()) {
|
||||||
|
return camelCaseName;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
for (char c : camelCaseName.toCharArray()) {
|
||||||
|
if (Character.isUpperCase(c)) {
|
||||||
|
result.append('_').append(Character.toLowerCase(c));
|
||||||
|
} else {
|
||||||
|
result.append(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下划线转驼峰命名
|
* 下划线转驼峰命名
|
||||||
* @param underscoreName 下划线命名的字符串
|
* @param underscoreName 下划线命名的字符串
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user