feat(core): 添加Map方式操作飞书表格的功能- 新增MapSheetConfig和MapTableConfig配置类,支持通过Map方式定义表格结构
- 添加MapFieldDefinition字段定义类,提供丰富的字段类型和配置选项 - 实现MapSheetBuilder构建器,支持链式调用创建飞书表格- 实现MapReadBuilder构建器,支持从飞书表格读取数据并转换为Map格式- 添加MapOptionsUtil工具类,提供枚举选项提取和动态选项处理功能 - 在FsHelper中新增createMapSheet、createMapSheetBuilder、readMap和readMapBuilder方法-优化包导入结构,使用通配符简化builder包的导入 - 添加详细的JavaDoc文档和使用示例,提升API易用性- 支持动态字段、分组表格、唯一键计算等高级功能 - 实现数据过滤机制,自动过滤空行数据
This commit is contained in:
parent
f9cff00b2f
commit
f191e57c9a
@ -5,12 +5,10 @@ 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.builder.MapWriteBuilder;
|
||||
import cn.isliu.core.builder.ReadBuilder;
|
||||
import cn.isliu.core.builder.SheetBuilder;
|
||||
import cn.isliu.core.builder.WriteBuilder;
|
||||
import cn.isliu.core.builder.*;
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.client.FsClient;
|
||||
import cn.isliu.core.config.MapSheetConfig;
|
||||
import cn.isliu.core.config.MapTableConfig;
|
||||
import cn.isliu.core.enums.ErrorCode;
|
||||
import cn.isliu.core.enums.FileType;
|
||||
@ -101,6 +99,86 @@ public class FsHelper {
|
||||
return new SheetBuilder<>(sheetName, spreadsheetToken, clazz);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Map 配置创建飞书表格
|
||||
*
|
||||
* 直接使用 Map 配置创建飞书表格,无需定义实体类和注解。
|
||||
* 适用于动态字段、临时表格创建等场景。
|
||||
*
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* MapSheetConfig config = MapSheetConfig.sheetBuilder()
|
||||
* .titleRow(2)
|
||||
* .headLine(3)
|
||||
* .headStyle("#ffffff", "#000000")
|
||||
* .isText(true)
|
||||
* .enableDesc(true)
|
||||
* .addField(MapFieldDefinition.text("字段1", 0))
|
||||
* .addField(MapFieldDefinition.singleSelect("字段2", 1, "选项1", "选项2"))
|
||||
* .build();
|
||||
*
|
||||
* String sheetId = FsHelper.createMapSheet("表格名称", spreadsheetToken, config);
|
||||
* </pre>
|
||||
*
|
||||
* @param sheetName 工作表名称
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @param config 表格配置,包含字段定义、样式等信息
|
||||
* @return 创建成功返回工作表ID
|
||||
*/
|
||||
public static String createMapSheet(String sheetName, String spreadsheetToken, MapSheetConfig config) {
|
||||
return new MapSheetBuilder(sheetName, spreadsheetToken)
|
||||
.config(config)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Map 表格构建器
|
||||
*
|
||||
* 返回一个 Map 表格构建器实例,支持链式调用和灵活配置。
|
||||
* 相比直接使用 createMapSheet 方法,构建器方式提供了更灵活的配置方式。
|
||||
*
|
||||
* 支持动态添加字段,包括:
|
||||
* - addField(field) - 添加单个字段
|
||||
* - addFields(fieldList) - 批量添加字段列表
|
||||
* - addFields(field1, field2, ...) - 批量添加字段(可变参数)
|
||||
* - fields(fieldList) - 直接设置所有字段(覆盖现有)
|
||||
*
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* // 动态添加字段
|
||||
* List<MapFieldDefinition> dynamicFields = getDynamicFields();
|
||||
*
|
||||
* String sheetId = FsHelper.createMapSheetBuilder("表格名称", spreadsheetToken)
|
||||
* .titleRow(2)
|
||||
* .headLine(3)
|
||||
* .headStyle("#ffffff", "#000000")
|
||||
* .isText(true)
|
||||
* .addFields(dynamicFields) // 批量添加
|
||||
* .build();
|
||||
*
|
||||
* // 或者使用可变参数
|
||||
* String sheetId = FsHelper.createMapSheetBuilder("表格名称", spreadsheetToken)
|
||||
* .addFields(
|
||||
* MapFieldDefinition.text("字段1", 0),
|
||||
* MapFieldDefinition.singleSelect("字段2", 1, "选项1", "选项2")
|
||||
* )
|
||||
* .build();
|
||||
*
|
||||
* // 创建分组表格
|
||||
* String sheetId = FsHelper.createMapSheetBuilder("分组表格", spreadsheetToken)
|
||||
* .addFields(dynamicFields)
|
||||
* .groupFields("分组A", "分组B")
|
||||
* .build();
|
||||
* </pre>
|
||||
*
|
||||
* @param sheetName 工作表名称
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @return MapSheetBuilder实例,支持链式调用
|
||||
*/
|
||||
public static MapSheetBuilder createMapSheetBuilder(String sheetName, String spreadsheetToken) {
|
||||
return new MapSheetBuilder(sheetName, spreadsheetToken);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从飞书表格中读取数据
|
||||
@ -368,4 +446,83 @@ public class FsHelper {
|
||||
List<Map<String, Object>> dataList) {
|
||||
return new MapWriteBuilder(sheetId, spreadsheetToken, dataList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从飞书表格读取数据并转换为 Map 格式
|
||||
*
|
||||
* 直接从飞书表格读取数据并转换为 Map 列表,无需定义实体类和注解。
|
||||
* 适用于动态字段、临时数据读取等场景。
|
||||
*
|
||||
* 返回的Map中包含两个特殊字段:
|
||||
* - _uniqueId: 根据唯一键计算的唯一标识
|
||||
* - _rowNumber: 数据所在的行号(从1开始)
|
||||
*
|
||||
* 注意:
|
||||
* - 如果某行数据的所有业务字段值都为null或空字符串,该行数据将被自动过滤,不会包含在返回结果中
|
||||
*
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* MapTableConfig config = MapTableConfig.builder()
|
||||
* .titleRow(2)
|
||||
* .headLine(3)
|
||||
* .addUniKeyName("字段名1")
|
||||
* .build();
|
||||
*
|
||||
* List<Map<String, Object>> dataList = FsHelper.readMap(sheetId, spreadsheetToken, config);
|
||||
* for (Map<String, Object> data : dataList) {
|
||||
* String value1 = (String) data.get("字段名1");
|
||||
* String value2 = (String) data.get("字段名2");
|
||||
* String uniqueId = (String) data.get("_uniqueId");
|
||||
* Integer rowNumber = (Integer) data.get("_rowNumber");
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param sheetId 工作表ID
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @param config 表格配置,包含标题行、数据起始行、唯一键等配置信息
|
||||
* @return Map数据列表,每个Map的key为字段名,value为字段值
|
||||
*/
|
||||
public static List<Map<String, Object>> readMap(String sheetId, String spreadsheetToken, MapTableConfig config) {
|
||||
return new MapReadBuilder(sheetId, spreadsheetToken)
|
||||
.config(config)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 Map 数据读取构建器
|
||||
*
|
||||
* 返回一个 Map 数据读取构建器实例,支持链式调用和灵活配置。
|
||||
* 相比直接使用 readMap 方法,构建器方式提供了更灵活的配置方式。
|
||||
*
|
||||
* 返回的Map中包含两个特殊字段:
|
||||
* - _uniqueId: 根据唯一键计算的唯一标识
|
||||
* - _rowNumber: 数据所在的行号(从1开始)
|
||||
*
|
||||
* 注意:
|
||||
* - 如果某行数据的所有业务字段值都为null或空字符串,该行数据将被自动过滤,不会包含在返回结果中
|
||||
* - 分组读取时,如果某个分组下的某行数据所有字段值都为null或空字符串,该行数据也会被自动过滤
|
||||
*
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* // 基础读取
|
||||
* List<Map<String, Object>> dataList = FsHelper.readMapBuilder(sheetId, spreadsheetToken)
|
||||
* .titleRow(2)
|
||||
* .headLine(3)
|
||||
* .addUniKeyName("字段名1")
|
||||
* .build();
|
||||
*
|
||||
* // 分组读取
|
||||
* Map<String, List<Map<String, Object>>> groupedData = FsHelper.readMapBuilder(sheetId, spreadsheetToken)
|
||||
* .titleRow(2)
|
||||
* .headLine(3)
|
||||
* .groupBuild();
|
||||
* </pre>
|
||||
*
|
||||
* @param sheetId 工作表ID
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @return MapReadBuilder实例,支持链式调用
|
||||
*/
|
||||
public static MapReadBuilder readMapBuilder(String sheetId, String spreadsheetToken) {
|
||||
return new MapReadBuilder(sheetId, spreadsheetToken);
|
||||
}
|
||||
}
|
||||
434
src/main/java/cn/isliu/core/builder/MapReadBuilder.java
Normal file
434
src/main/java/cn/isliu/core/builder/MapReadBuilder.java
Normal file
@ -0,0 +1,434 @@
|
||||
package cn.isliu.core.builder;
|
||||
|
||||
import cn.isliu.core.*;
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.client.FsClient;
|
||||
import cn.isliu.core.config.MapTableConfig;
|
||||
import cn.isliu.core.logging.FsLogger;
|
||||
import cn.isliu.core.utils.FsApiUtil;
|
||||
import cn.isliu.core.utils.MapDataUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.isliu.core.utils.FsTableUtil.*;
|
||||
|
||||
/**
|
||||
* Map 数据读取构建器
|
||||
*
|
||||
* 提供链式调用方式从飞书表格读取数据并转换为 Map 格式
|
||||
*
|
||||
* @author Ls
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapReadBuilder {
|
||||
|
||||
private final String sheetId;
|
||||
private final String spreadsheetToken;
|
||||
private MapTableConfig config;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param sheetId 工作表ID
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
*/
|
||||
public MapReadBuilder(String sheetId, String spreadsheetToken) {
|
||||
this.sheetId = sheetId;
|
||||
this.spreadsheetToken = spreadsheetToken;
|
||||
this.config = MapTableConfig.createDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表格配置
|
||||
*
|
||||
* @param config Map表格配置
|
||||
* @return MapReadBuilder实例
|
||||
*/
|
||||
public MapReadBuilder config(MapTableConfig config) {
|
||||
this.config = config;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标题行
|
||||
*
|
||||
* @param titleRow 标题行行号
|
||||
* @return MapReadBuilder实例
|
||||
*/
|
||||
public MapReadBuilder titleRow(int titleRow) {
|
||||
this.config.setTitleRow(titleRow);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据起始行
|
||||
*
|
||||
* @param headLine 数据起始行号
|
||||
* @return MapReadBuilder实例
|
||||
*/
|
||||
public MapReadBuilder headLine(int headLine) {
|
||||
this.config.setHeadLine(headLine);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置唯一键字段
|
||||
*
|
||||
* @param uniKeyNames 唯一键字段名集合
|
||||
* @return MapReadBuilder实例
|
||||
*/
|
||||
public MapReadBuilder uniKeyNames(Set<String> uniKeyNames) {
|
||||
this.config.setUniKeyNames(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加唯一键字段
|
||||
*
|
||||
* @param uniKeyName 唯一键字段名
|
||||
* @return MapReadBuilder实例
|
||||
*/
|
||||
public MapReadBuilder addUniKeyName(String uniKeyName) {
|
||||
this.config.addUniKeyName(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行数据读取
|
||||
*
|
||||
* 注意:
|
||||
* - 如果某行数据的所有业务字段值都为null或空字符串,该行数据将被自动过滤
|
||||
* - 返回的Map中包含 _uniqueId 和 _rowNumber 两个特殊字段
|
||||
*
|
||||
* @return Map数据列表
|
||||
*/
|
||||
public List<Map<String, Object>> build() {
|
||||
FeishuClient client = FsClient.getInstance().getClient();
|
||||
Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
|
||||
|
||||
// 读取字段位置映射
|
||||
Map<String, String> titlePostionMap = readFieldsPositionMap(sheet, client);
|
||||
config.setFieldsPositionMap(titlePostionMap);
|
||||
|
||||
// 读取表格数据并转换为Map格式
|
||||
List<Map<String, Object>> dataList = readTableData(sheet, client, titlePostionMap);
|
||||
|
||||
FsLogger.info("【Map读取】成功读取 {} 条数据", dataList.size());
|
||||
return dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行分组数据读取
|
||||
*
|
||||
* 注意:
|
||||
* - 如果某个分组下某行数据的所有字段值都为null或空字符串,该行数据将被自动过滤
|
||||
* - 返回的Map中包含 _uniqueId 和 _rowNumber 两个特殊字段
|
||||
*
|
||||
* @return 按分组字段组织的Map数据
|
||||
*/
|
||||
public Map<String, List<Map<String, Object>>> groupBuild() {
|
||||
FeishuClient client = FsClient.getInstance().getClient();
|
||||
Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, client, spreadsheetToken);
|
||||
|
||||
// 读取字段位置映射
|
||||
Map<String, String> titlePostionMap = readFieldsPositionMap(sheet, client);
|
||||
config.setFieldsPositionMap(titlePostionMap);
|
||||
|
||||
// 读取分组表格数据
|
||||
Map<String, List<Map<String, Object>>> groupedData = readGroupedTableData(sheet, client, titlePostionMap);
|
||||
|
||||
int totalCount = groupedData.values().stream().mapToInt(List::size).sum();
|
||||
FsLogger.info("【Map读取】成功读取 {} 个分组,共 {} 条数据", groupedData.size(), totalCount);
|
||||
return groupedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取字段位置映射
|
||||
*/
|
||||
private Map<String, String> readFieldsPositionMap(Sheet sheet, FeishuClient client) {
|
||||
int titleRow = config.getTitleRow();
|
||||
int colCount = sheet.getGridProperties().getColumnCount();
|
||||
|
||||
// 读取标题行数据
|
||||
ValuesBatch valuesBatch = FsApiUtil.getSheetData(
|
||||
sheet.getSheetId(), spreadsheetToken,
|
||||
"A" + titleRow,
|
||||
getColumnName(colCount - 1) + titleRow,
|
||||
client
|
||||
);
|
||||
|
||||
Map<String, String> fieldsPositionMap = new HashMap<>();
|
||||
|
||||
if (valuesBatch != null && valuesBatch.getValueRanges() != null) {
|
||||
for (ValueRange valueRange : valuesBatch.getValueRanges()) {
|
||||
if (valueRange.getValues() != null && !valueRange.getValues().isEmpty()) {
|
||||
List<Object> titleRowValues = valueRange.getValues().get(0);
|
||||
for (int i = 0; i < titleRowValues.size(); i++) {
|
||||
Object value = titleRowValues.get(i);
|
||||
if (value != null) {
|
||||
String fieldName = value.toString();
|
||||
String columnPosition = getColumnName(i);
|
||||
fieldsPositionMap.put(fieldName, columnPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fieldsPositionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取表格数据并转换为Map格式
|
||||
*/
|
||||
private List<Map<String, Object>> readTableData(Sheet sheet, FeishuClient client, Map<String, String> titlePostionMap) {
|
||||
int headLine = config.getHeadLine();
|
||||
int titleRow = config.getTitleRow();
|
||||
int totalRow = sheet.getGridProperties().getRowCount();
|
||||
int colCount = sheet.getGridProperties().getColumnCount();
|
||||
int startOffset = 1;
|
||||
|
||||
// 批量读取数据
|
||||
int rowCountPerBatch = Math.min(totalRow, 100);
|
||||
int actualRows = Math.max(0, totalRow - startOffset);
|
||||
int batchCount = (actualRows + rowCountPerBatch - 1) / rowCountPerBatch;
|
||||
|
||||
List<List<Object>> values = new LinkedList<>();
|
||||
for (int i = 0; i < batchCount; i++) {
|
||||
int startRowIndex = startOffset + i * rowCountPerBatch;
|
||||
int endRowIndex = Math.min(startRowIndex + rowCountPerBatch - 1, totalRow - 1);
|
||||
|
||||
ValuesBatch valuesBatch = FsApiUtil.getSheetData(
|
||||
sheet.getSheetId(), spreadsheetToken,
|
||||
"A" + startRowIndex,
|
||||
getColumnName(colCount - 1) + endRowIndex,
|
||||
client
|
||||
);
|
||||
|
||||
if (valuesBatch != null && valuesBatch.getValueRanges() != null) {
|
||||
for (ValueRange valueRange : valuesBatch.getValueRanges()) {
|
||||
if (valueRange.getValues() != null) {
|
||||
values.addAll(valueRange.getValues());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表格数据
|
||||
TableData tableData = processSheetData(sheet, values);
|
||||
List<FsTableData> fsTableDataList = getFsTableData(tableData, new ArrayList<>());
|
||||
|
||||
// 获取标题映射
|
||||
Map<String, String> titleMap = new HashMap<>();
|
||||
fsTableDataList.stream()
|
||||
.filter(d -> d.getRow() == (titleRow - 1))
|
||||
.findFirst()
|
||||
.ifPresent(d -> {
|
||||
Map<String, String> map = (Map<String, String>) d.getData();
|
||||
titleMap.putAll(map);
|
||||
});
|
||||
|
||||
// 转换为带字段名的Map数据
|
||||
return fsTableDataList.stream()
|
||||
.filter(fsTableData -> fsTableData.getRow() >= headLine)
|
||||
.map(item -> {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
Map<String, Object> map = (Map<String, Object>) item.getData();
|
||||
|
||||
map.forEach((k, v) -> {
|
||||
String title = titleMap.get(k);
|
||||
if (title != null) {
|
||||
resultMap.put(title, v);
|
||||
}
|
||||
});
|
||||
|
||||
// 计算并设置唯一ID
|
||||
String uniqueId = MapDataUtil.calculateUniqueId(resultMap, config);
|
||||
if (uniqueId != null) {
|
||||
resultMap.put("_uniqueId", uniqueId);
|
||||
}
|
||||
|
||||
// 设置行号
|
||||
resultMap.put("_rowNumber", item.getRow() + 1);
|
||||
|
||||
return resultMap;
|
||||
})
|
||||
.filter(map -> {
|
||||
// 过滤条件1:Map不能为空
|
||||
if (map.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 过滤条件2:除了 _uniqueId 和 _rowNumber 外还有其他数据
|
||||
if (map.size() <= 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 过滤条件3:检查是否所有业务字段的值都为null或空字符串
|
||||
boolean hasNonNullValue = false;
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
// 跳过特殊字段
|
||||
if (key.equals("_uniqueId") || key.equals("_rowNumber")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否有非null且非空字符串的值
|
||||
if (value != null && !(value instanceof String && ((String) value).isEmpty())) {
|
||||
hasNonNullValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hasNonNullValue;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取分组表格数据
|
||||
*/
|
||||
private Map<String, List<Map<String, Object>>> readGroupedTableData(Sheet sheet, FeishuClient client, Map<String, String> titlePostionMap) {
|
||||
int headLine = config.getHeadLine();
|
||||
int titleRow = config.getTitleRow();
|
||||
int totalRow = sheet.getGridProperties().getRowCount();
|
||||
int colCount = sheet.getGridProperties().getColumnCount();
|
||||
int startOffset = 1;
|
||||
|
||||
// 批量读取数据
|
||||
int rowCountPerBatch = Math.min(totalRow, 100);
|
||||
int actualRows = Math.max(0, totalRow - startOffset);
|
||||
int batchCount = (actualRows + rowCountPerBatch - 1) / rowCountPerBatch;
|
||||
|
||||
List<List<Object>> values = new LinkedList<>();
|
||||
for (int i = 0; i < batchCount; i++) {
|
||||
int startRowIndex = startOffset + i * rowCountPerBatch;
|
||||
int endRowIndex = Math.min(startRowIndex + rowCountPerBatch - 1, totalRow - 1);
|
||||
|
||||
ValuesBatch valuesBatch = FsApiUtil.getSheetData(
|
||||
sheet.getSheetId(), spreadsheetToken,
|
||||
"A" + startRowIndex,
|
||||
getColumnName(colCount - 1) + endRowIndex,
|
||||
client
|
||||
);
|
||||
|
||||
if (valuesBatch != null && valuesBatch.getValueRanges() != null) {
|
||||
for (ValueRange valueRange : valuesBatch.getValueRanges()) {
|
||||
if (valueRange.getValues() != null) {
|
||||
values.addAll(valueRange.getValues());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 处理表格数据
|
||||
TableData tableData = processSheetData(sheet, values);
|
||||
List<FsTableData> fsTableDataList = getFsTableData(tableData, new ArrayList<>());
|
||||
|
||||
// 获取分组行和标题行
|
||||
Map<String, String> categoryMap = new HashMap<>();
|
||||
Map<String, List<String>> categoryPositionMap = new HashMap<>();
|
||||
|
||||
// 读取分组行(titleRow - 1)
|
||||
fsTableDataList.stream()
|
||||
.filter(d -> d.getRow() == (titleRow - 2))
|
||||
.findFirst()
|
||||
.ifPresent(d -> {
|
||||
Map<String, String> map = (Map<String, String>) d.getData();
|
||||
map.forEach((k, v) -> {
|
||||
if (v != null && !v.isEmpty()) {
|
||||
categoryMap.put(k, v);
|
||||
categoryPositionMap.computeIfAbsent(v, k1 -> new ArrayList<>()).add(k);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 读取标题行
|
||||
Map<String, String> titleMap = new HashMap<>();
|
||||
fsTableDataList.stream()
|
||||
.filter(d -> d.getRow() == (titleRow - 1))
|
||||
.findFirst()
|
||||
.ifPresent(d -> {
|
||||
Map<String, String> map = (Map<String, String>) d.getData();
|
||||
map.forEach((k, v) -> {
|
||||
if (v != null && !v.isEmpty()) {
|
||||
String category = categoryMap.get(k);
|
||||
if (category != null && !category.isEmpty()) {
|
||||
titleMap.put(k, v);
|
||||
} else {
|
||||
titleMap.put(k, v);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 按分组组织数据
|
||||
Map<String, List<Map<String, Object>>> groupedResult = new LinkedHashMap<>();
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : categoryPositionMap.entrySet()) {
|
||||
String groupName = entry.getKey();
|
||||
List<String> positions = entry.getValue();
|
||||
|
||||
List<Map<String, Object>> groupData = fsTableDataList.stream()
|
||||
.filter(fsTableData -> fsTableData.getRow() >= headLine)
|
||||
.map(item -> {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
Map<String, Object> map = (Map<String, Object>) item.getData();
|
||||
|
||||
// 只提取该分组的字段
|
||||
positions.forEach(pos -> {
|
||||
String title = titleMap.get(pos);
|
||||
if (title != null && map.containsKey(pos)) {
|
||||
resultMap.put(title, map.get(pos));
|
||||
}
|
||||
});
|
||||
|
||||
// 计算并设置唯一ID
|
||||
String uniqueId = MapDataUtil.calculateUniqueId(resultMap, config);
|
||||
if (uniqueId != null) {
|
||||
resultMap.put("_uniqueId", uniqueId);
|
||||
}
|
||||
|
||||
// 设置行号
|
||||
resultMap.put("_rowNumber", item.getRow() + 1);
|
||||
|
||||
return resultMap;
|
||||
})
|
||||
.filter(map -> {
|
||||
// 过滤条件1:除了 _uniqueId 和 _rowNumber 外还有其他数据
|
||||
if (map.size() <= 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 过滤条件2:检查是否所有业务字段的值都为null
|
||||
boolean hasNonNullValue = false;
|
||||
for (Map.Entry<String, Object> nullEntry : map.entrySet()) {
|
||||
String key = nullEntry.getKey();
|
||||
Object value = nullEntry.getValue();
|
||||
|
||||
// 跳过特殊字段
|
||||
if (key.equals("_uniqueId") || key.equals("_rowNumber")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查是否有非null且非空字符串的值
|
||||
if (value != null && !(value instanceof String && ((String) value).isEmpty())) {
|
||||
hasNonNullValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hasNonNullValue;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
groupedResult.put(groupName, groupData);
|
||||
}
|
||||
|
||||
return groupedResult;
|
||||
}
|
||||
}
|
||||
|
||||
525
src/main/java/cn/isliu/core/builder/MapSheetBuilder.java
Normal file
525
src/main/java/cn/isliu/core/builder/MapSheetBuilder.java
Normal file
@ -0,0 +1,525 @@
|
||||
package cn.isliu.core.builder;
|
||||
|
||||
import cn.isliu.core.annotation.TableConf;
|
||||
import cn.isliu.core.annotation.TableProperty;
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.client.FsClient;
|
||||
import cn.isliu.core.config.MapFieldDefinition;
|
||||
import cn.isliu.core.config.MapSheetConfig;
|
||||
import cn.isliu.core.converters.FieldValueProcess;
|
||||
import cn.isliu.core.converters.OptionsValueProcess;
|
||||
import cn.isliu.core.enums.BaseEnum;
|
||||
import cn.isliu.core.enums.TypeEnum;
|
||||
import cn.isliu.core.pojo.FieldProperty;
|
||||
import cn.isliu.core.service.CustomCellService;
|
||||
import cn.isliu.core.utils.FsApiUtil;
|
||||
import cn.isliu.core.utils.FsTableUtil;
|
||||
import cn.isliu.core.utils.MapOptionsUtil;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Map 表格构建器
|
||||
*
|
||||
* 提供链式调用方式创建飞书表格,使用配置对象而不是注解
|
||||
*
|
||||
* @author Ls
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapSheetBuilder {
|
||||
|
||||
private final String sheetName;
|
||||
private final String spreadsheetToken;
|
||||
private MapSheetConfig config;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param sheetName 工作表名称
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
*/
|
||||
public MapSheetBuilder(String sheetName, String spreadsheetToken) {
|
||||
this.sheetName = sheetName;
|
||||
this.spreadsheetToken = spreadsheetToken;
|
||||
this.config = MapSheetConfig.createDefault();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表格配置
|
||||
*
|
||||
* @param config Map表格配置
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder config(MapSheetConfig config) {
|
||||
this.config = config;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置标题行
|
||||
*
|
||||
* @param titleRow 标题行行号
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder titleRow(int titleRow) {
|
||||
this.config.setTitleRow(titleRow);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据起始行
|
||||
*
|
||||
* @param headLine 数据起始行号
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder headLine(int headLine) {
|
||||
this.config.setHeadLine(headLine);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置唯一键字段
|
||||
*
|
||||
* @param uniKeyNames 唯一键字段名集合
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder uniKeyNames(Set<String> uniKeyNames) {
|
||||
this.config.setUniKeyNames(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加唯一键字段
|
||||
*
|
||||
* @param uniKeyName 唯一键字段名
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder addUniKeyName(String uniKeyName) {
|
||||
this.config.addUniKeyName(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段列表
|
||||
*
|
||||
* @param fields 字段定义列表
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder fields(List<MapFieldDefinition> fields) {
|
||||
this.config.setFields(new ArrayList<>(fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加单个字段
|
||||
*
|
||||
* @param field 字段定义
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder addField(MapFieldDefinition field) {
|
||||
this.config.addField(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加字段
|
||||
*
|
||||
* @param fields 字段定义列表
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder addFields(List<MapFieldDefinition> fields) {
|
||||
this.config.addFields(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加字段(可变参数)
|
||||
*
|
||||
* @param fields 字段定义可变参数
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder addFields(MapFieldDefinition... fields) {
|
||||
this.config.addFields(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表头样式
|
||||
*
|
||||
* @param fontColor 字体颜色
|
||||
* @param backColor 背景颜色
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder headStyle(String fontColor, String backColor) {
|
||||
this.config.setHeadFontColor(fontColor);
|
||||
this.config.setHeadBackColor(backColor);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为纯文本格式
|
||||
*
|
||||
* @param isText 是否设置为纯文本
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder isText(boolean isText) {
|
||||
this.config.setText(isText);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否启用字段描述
|
||||
*
|
||||
* @param enableDesc 是否启用
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder enableDesc(boolean enableDesc) {
|
||||
this.config.setEnableDesc(enableDesc);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分组字段
|
||||
*
|
||||
* @param groupFields 分组字段可变参数
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder groupFields(String... groupFields) {
|
||||
this.config.setGroupFields(Arrays.asList(groupFields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义属性
|
||||
*
|
||||
* @param key 属性键
|
||||
* @param value 属性值
|
||||
* @return MapSheetBuilder实例
|
||||
*/
|
||||
public MapSheetBuilder addCustomProperty(String key, Object value) {
|
||||
this.config.addCustomProperty(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建表格并返回工作表ID
|
||||
*
|
||||
* @return 创建成功返回工作表ID
|
||||
*/
|
||||
public String build() {
|
||||
// 检查字段列表
|
||||
if (config.getFields().isEmpty()) {
|
||||
throw new IllegalArgumentException("字段定义列表不能为空");
|
||||
}
|
||||
|
||||
// 判断是否为分组表格
|
||||
if (config.getGroupFields() != null && !config.getGroupFields().isEmpty()) {
|
||||
return buildGroupSheet();
|
||||
} else {
|
||||
return buildNormalSheet();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建普通表格
|
||||
*/
|
||||
private String buildNormalSheet() {
|
||||
// 转换字段定义为 FieldProperty
|
||||
Map<String, FieldProperty> fieldsMap = convertToFieldsMap(config.getFields());
|
||||
|
||||
// 生成表头
|
||||
List<String> headers = config.getFields().stream()
|
||||
.sorted(Comparator.comparingInt(MapFieldDefinition::getOrder))
|
||||
.map(MapFieldDefinition::getFieldName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 创建 TableConf
|
||||
TableConf tableConf = createTableConf();
|
||||
|
||||
// 创建飞书客户端
|
||||
FeishuClient client = FsClient.getInstance().getClient();
|
||||
|
||||
// 1、创建sheet
|
||||
String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
|
||||
|
||||
// 2、添加表头数据
|
||||
Map<String, String> fieldDescriptions = buildFieldDescriptions();
|
||||
FsApiUtil.putValues(spreadsheetToken,
|
||||
FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf, fieldDescriptions),
|
||||
client);
|
||||
|
||||
// 3、设置单元格为文本格式
|
||||
if (config.isText()) {
|
||||
String column = FsTableUtil.getColumnNameByNuNumber(headers.size());
|
||||
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、设置表格下拉
|
||||
try {
|
||||
// 准备自定义属性,包含字段的 options 配置
|
||||
Map<String, Object> customProps = prepareCustomProperties(fieldsMap);
|
||||
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId,
|
||||
config.isEnableDesc(), customProps);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(MapSheetBuilder.class.getName()).log(Level.SEVERE,
|
||||
"【Map表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
|
||||
}
|
||||
|
||||
return sheetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建分组表格
|
||||
*/
|
||||
private String buildGroupSheet() {
|
||||
// 转换字段定义为 FieldProperty
|
||||
Map<String, FieldProperty> fieldsMap = convertToFieldsMap(config.getFields());
|
||||
|
||||
// 生成表头
|
||||
List<String> headers = config.getFields().stream()
|
||||
.sorted(Comparator.comparingInt(MapFieldDefinition::getOrder))
|
||||
.map(MapFieldDefinition::getFieldName)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 创建 TableConf
|
||||
TableConf tableConf = createTableConf();
|
||||
|
||||
// 创建飞书客户端
|
||||
FeishuClient client = FsClient.getInstance().getClient();
|
||||
|
||||
// 1、创建sheet
|
||||
String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
|
||||
|
||||
// 2、添加表头数据(分组模式)
|
||||
List<String> groupFieldList = config.getGroupFields();
|
||||
List<String> headerList = FsTableUtil.getGroupHeaders(groupFieldList, headers);
|
||||
Map<String, String> fieldDescriptions = buildFieldDescriptions();
|
||||
FsApiUtil.putValues(spreadsheetToken,
|
||||
FsTableUtil.getHeadTemplateBuilder(sheetId, headers, headerList, fieldsMap,
|
||||
tableConf, fieldDescriptions, groupFieldList),
|
||||
client);
|
||||
|
||||
// 3、设置单元格为文本格式
|
||||
if (config.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);
|
||||
// 准备自定义属性,包含字段的 options 配置
|
||||
Map<String, Object> customProps = prepareCustomProperties(fieldsMap);
|
||||
FsTableUtil.setTableOptions(spreadsheetToken, headerWithColumnIdentifiers, fieldsMap,
|
||||
sheetId, config.isEnableDesc(), customProps);
|
||||
} catch (Exception e) {
|
||||
Logger.getLogger(MapSheetBuilder.class.getName()).log(Level.SEVERE,
|
||||
"【Map表格构建器】设置表格下拉异常!sheetId:" + sheetId + ", 错误信息:{}", e.getMessage());
|
||||
}
|
||||
|
||||
return sheetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 MapFieldDefinition 列表转换为 FieldProperty Map
|
||||
*/
|
||||
private Map<String, FieldProperty> convertToFieldsMap(List<MapFieldDefinition> fields) {
|
||||
Map<String, FieldProperty> fieldsMap = new LinkedHashMap<>();
|
||||
|
||||
for (MapFieldDefinition field : fields) {
|
||||
TableProperty tableProperty = createTableProperty(field);
|
||||
FieldProperty fieldProperty = new FieldProperty(field.getFieldName(), tableProperty);
|
||||
fieldsMap.put(field.getFieldName(), fieldProperty);
|
||||
}
|
||||
|
||||
return fieldsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 MapFieldDefinition 创建 TableProperty 注解实例
|
||||
*/
|
||||
private TableProperty createTableProperty(MapFieldDefinition field) {
|
||||
return new TableProperty() {
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return TableProperty.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] value() {
|
||||
return new String[]{field.getFieldName()};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String desc() {
|
||||
return field.getDescription() != null ? field.getDescription() : "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String field() {
|
||||
return field.getFieldName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return field.getOrder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeEnum type() {
|
||||
return field.getType() != null ? field.getType() : TypeEnum.TEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends BaseEnum> enumClass() {
|
||||
// 优先级1:如果配置了 enumClass,直接返回
|
||||
if (field.getEnumClass() != null && field.getEnumClass() != BaseEnum.class) {
|
||||
return field.getEnumClass();
|
||||
}
|
||||
|
||||
// 优先级2:如果没有配置 enumClass 但配置了 options,创建动态枚举类
|
||||
// 注意:这里返回 BaseEnum.class,实际的 options 通过 optionsClass 处理
|
||||
return BaseEnum.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends FieldValueProcess> fieldFormatClass() {
|
||||
return FieldValueProcess.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends OptionsValueProcess> optionsClass() {
|
||||
// 优先级1:如果配置了 optionsClass,直接返回
|
||||
if (field.getOptionsClass() != null && field.getOptionsClass() != OptionsValueProcess.class) {
|
||||
return field.getOptionsClass();
|
||||
}
|
||||
|
||||
// 优先级2:如果配置了 options 但没有 optionsClass,创建动态的处理类
|
||||
if (field.getOptions() != null && !field.getOptions().isEmpty()) {
|
||||
return MapOptionsUtil.createDynamicOptionsClass(field.getOptions());
|
||||
}
|
||||
|
||||
// 优先级3:返回默认值
|
||||
return OptionsValueProcess.class;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 TableConf 注解实例
|
||||
*/
|
||||
private TableConf createTableConf() {
|
||||
return new TableConf() {
|
||||
@Override
|
||||
public Class<? extends Annotation> annotationType() {
|
||||
return TableConf.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] uniKeys() {
|
||||
Set<String> uniKeyNames = config.getUniKeyNames();
|
||||
return uniKeyNames != null ? uniKeyNames.toArray(new String[0]) : new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public int headLine() {
|
||||
return config.getHeadLine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int titleRow() {
|
||||
return config.getTitleRow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableCover() {
|
||||
return config.isEnableCover();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isText() {
|
||||
return config.isText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enableDesc() {
|
||||
return config.isEnableDesc();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String headFontColor() {
|
||||
return config.getHeadFontColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String headBackColor() {
|
||||
return config.getHeadBackColor();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建字段描述映射
|
||||
*/
|
||||
private Map<String, String> buildFieldDescriptions() {
|
||||
Map<String, String> descriptions = new HashMap<>();
|
||||
for (MapFieldDefinition field : config.getFields()) {
|
||||
if (field.getDescription() != null && !field.getDescription().isEmpty()) {
|
||||
descriptions.put(field.getFieldName(), field.getDescription());
|
||||
}
|
||||
}
|
||||
return descriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备自定义属性
|
||||
*
|
||||
* 将字段配置的 options 放入 customProperties,供 DynamicOptionsProcess 使用
|
||||
*/
|
||||
private Map<String, Object> prepareCustomProperties(Map<String, FieldProperty> fieldsMap) {
|
||||
Map<String, Object> customProps = new HashMap<>();
|
||||
|
||||
// 复制原有的自定义属性
|
||||
if (config.getCustomProperties() != null) {
|
||||
customProps.putAll(config.getCustomProperties());
|
||||
}
|
||||
|
||||
// 为每个配置了 options 的字段添加选项到 customProperties
|
||||
for (MapFieldDefinition field : config.getFields()) {
|
||||
if (field.getOptions() != null && !field.getOptions().isEmpty()) {
|
||||
// 使用字段名作为 key 前缀,避免冲突
|
||||
customProps.put("_dynamicOptions_" + field.getFieldName(), field.getOptions());
|
||||
}
|
||||
}
|
||||
|
||||
return customProps;
|
||||
}
|
||||
}
|
||||
|
||||
522
src/main/java/cn/isliu/core/config/MapFieldDefinition.java
Normal file
522
src/main/java/cn/isliu/core/config/MapFieldDefinition.java
Normal file
@ -0,0 +1,522 @@
|
||||
package cn.isliu.core.config;
|
||||
|
||||
import cn.isliu.core.converters.OptionsValueProcess;
|
||||
import cn.isliu.core.enums.BaseEnum;
|
||||
import cn.isliu.core.enums.TypeEnum;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Map方式字段定义类
|
||||
*
|
||||
* 用于替代 @TableProperty 注解,定义单个字段的所有属性
|
||||
*
|
||||
* @author Ls
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapFieldDefinition {
|
||||
|
||||
/**
|
||||
* 字段名称(对应表格列名)
|
||||
*/
|
||||
private String fieldName;
|
||||
|
||||
/**
|
||||
* 字段描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 字段排序顺序,数值越小越靠前
|
||||
*/
|
||||
private int order;
|
||||
|
||||
/**
|
||||
* 字段类型
|
||||
*/
|
||||
private TypeEnum type = TypeEnum.TEXT;
|
||||
|
||||
/**
|
||||
* 下拉选项列表(当type为单选/多选时使用)
|
||||
*/
|
||||
private List<String> options;
|
||||
|
||||
/**
|
||||
* 选项映射(code -> label)
|
||||
*/
|
||||
private Map<String, String> optionsMap;
|
||||
|
||||
/**
|
||||
* 枚举类(可选,用于从枚举类生成选项)
|
||||
*/
|
||||
private Class<? extends BaseEnum> enumClass;
|
||||
|
||||
/**
|
||||
* 选项处理类(可选,用于自定义选项处理逻辑)
|
||||
*/
|
||||
private Class<? extends OptionsValueProcess> optionsClass;
|
||||
|
||||
/**
|
||||
* 是否必填
|
||||
*/
|
||||
private boolean required = false;
|
||||
|
||||
/**
|
||||
* 默认值
|
||||
*/
|
||||
private String defaultValue;
|
||||
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
public void setFieldName(String fieldName) {
|
||||
this.fieldName = fieldName;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
public TypeEnum getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(TypeEnum type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public List<String> getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public void setOptions(List<String> options) {
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
public Map<String, String> getOptionsMap() {
|
||||
return optionsMap;
|
||||
}
|
||||
|
||||
public void setOptionsMap(Map<String, String> optionsMap) {
|
||||
this.optionsMap = optionsMap;
|
||||
}
|
||||
|
||||
public Class<? extends BaseEnum> getEnumClass() {
|
||||
return enumClass;
|
||||
}
|
||||
|
||||
public void setEnumClass(Class<? extends BaseEnum> enumClass) {
|
||||
this.enumClass = enumClass;
|
||||
}
|
||||
|
||||
public Class<? extends OptionsValueProcess> getOptionsClass() {
|
||||
return optionsClass;
|
||||
}
|
||||
|
||||
public void setOptionsClass(Class<? extends OptionsValueProcess> optionsClass) {
|
||||
this.optionsClass = optionsClass;
|
||||
}
|
||||
|
||||
public boolean isRequired() {
|
||||
return required;
|
||||
}
|
||||
|
||||
public void setRequired(boolean required) {
|
||||
this.required = required;
|
||||
}
|
||||
|
||||
public String getDefaultValue() {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void setDefaultValue(String defaultValue) {
|
||||
this.defaultValue = defaultValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
MapFieldDefinition that = (MapFieldDefinition) o;
|
||||
return order == that.order && required == that.required && Objects.equals(fieldName, that.fieldName) && Objects.equals(description, that.description) && type == that.type && Objects.equals(options, that.options) && Objects.equals(optionsMap, that.optionsMap) && Objects.equals(enumClass, that.enumClass) && Objects.equals(optionsClass, that.optionsClass) && Objects.equals(defaultValue, that.defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(fieldName, description, order, type, options, optionsMap, enumClass, optionsClass, required, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "MapFieldDefinition{" +
|
||||
"fieldName='" + fieldName + '\'' +
|
||||
", description='" + description + '\'' +
|
||||
", order=" + order +
|
||||
", type=" + type +
|
||||
", options=" + options +
|
||||
", optionsMap=" + optionsMap +
|
||||
", enumClass=" + enumClass +
|
||||
", optionsClass=" + optionsClass +
|
||||
", required=" + required +
|
||||
", defaultValue='" + defaultValue + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建构建器
|
||||
*
|
||||
* @return Builder实例
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建文本字段
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition text(String fieldName, int order) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.TEXT)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建文本字段(带描述)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param description 字段描述
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition text(String fieldName, int order, String description) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.TEXT)
|
||||
.description(description)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建单选字段
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param options 选项列表
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition singleSelect(String fieldName, int order, String... options) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.SINGLE_SELECT)
|
||||
.options(Arrays.asList(options))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建单选字段(带选项列表)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param options 选项列表
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition singleSelect(String fieldName, int order, List<String> options) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.SINGLE_SELECT)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建单选字段(带描述)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param description 字段描述
|
||||
* @param options 选项列表
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition singleSelectWithDesc(String fieldName, int order, String description, List<String> options) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.SINGLE_SELECT)
|
||||
.description(description)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建多选字段
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param options 选项列表
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition multiSelect(String fieldName, int order, String... options) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.MULTI_SELECT)
|
||||
.options(Arrays.asList(options))
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建多选字段(带选项列表)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param options 选项列表
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition multiSelect(String fieldName, int order, List<String> options) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.MULTI_SELECT)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建多选字段(带描述)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param description 字段描述
|
||||
* @param options 选项列表
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition multiSelectWithDesc(String fieldName, int order, String description, List<String> options) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.MULTI_SELECT)
|
||||
.description(description)
|
||||
.options(options)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建单选字段(使用枚举类)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param enumClass 枚举类
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition singleSelectWithEnum(String fieldName, int order, Class<? extends BaseEnum> enumClass) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.SINGLE_SELECT)
|
||||
.enumClass(enumClass)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建单选字段(使用枚举类,带描述)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param description 字段描述
|
||||
* @param enumClass 枚举类
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition singleSelectWithEnum(String fieldName, int order, String description, Class<? extends BaseEnum> enumClass) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.SINGLE_SELECT)
|
||||
.description(description)
|
||||
.enumClass(enumClass)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建多选字段(使用枚举类)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param enumClass 枚举类
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition multiSelectWithEnum(String fieldName, int order, Class<? extends BaseEnum> enumClass) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.MULTI_SELECT)
|
||||
.enumClass(enumClass)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建多选字段(使用枚举类,带描述)
|
||||
*
|
||||
* @param fieldName 字段名称
|
||||
* @param order 排序顺序
|
||||
* @param description 字段描述
|
||||
* @param enumClass 枚举类
|
||||
* @return MapFieldDefinition实例
|
||||
*/
|
||||
public static MapFieldDefinition multiSelectWithEnum(String fieldName, int order, String description, Class<? extends BaseEnum> enumClass) {
|
||||
return builder()
|
||||
.fieldName(fieldName)
|
||||
.order(order)
|
||||
.type(TypeEnum.MULTI_SELECT)
|
||||
.description(description)
|
||||
.enumClass(enumClass)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Map 批量创建字段定义
|
||||
*
|
||||
* @param fieldMap key为字段名,value为字段类型字符串
|
||||
* @return 字段定义列表
|
||||
*/
|
||||
public static List<MapFieldDefinition> fromMap(Map<String, String> fieldMap) {
|
||||
List<MapFieldDefinition> fields = new ArrayList<>();
|
||||
int order = 0;
|
||||
for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
|
||||
TypeEnum type = parseType(entry.getValue());
|
||||
fields.add(builder()
|
||||
.fieldName(entry.getKey())
|
||||
.order(order++)
|
||||
.type(type)
|
||||
.build());
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字段名列表快速创建文本字段
|
||||
*
|
||||
* @param fieldNames 字段名称列表
|
||||
* @return 字段定义列表
|
||||
*/
|
||||
public static List<MapFieldDefinition> fromFieldNames(List<String> fieldNames) {
|
||||
List<MapFieldDefinition> fields = new ArrayList<>();
|
||||
for (int i = 0; i < fieldNames.size(); i++) {
|
||||
fields.add(text(fieldNames.get(i), i));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析类型字符串
|
||||
*/
|
||||
private static TypeEnum parseType(String typeStr) {
|
||||
if (typeStr == null || typeStr.isEmpty()) {
|
||||
return TypeEnum.TEXT;
|
||||
}
|
||||
|
||||
try {
|
||||
return TypeEnum.valueOf(typeStr.toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return TypeEnum.TEXT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 字段定义构建器
|
||||
*/
|
||||
public static class Builder {
|
||||
private final MapFieldDefinition definition = new MapFieldDefinition();
|
||||
|
||||
public Builder fieldName(String fieldName) {
|
||||
definition.fieldName = fieldName;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder description(String description) {
|
||||
definition.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder order(int order) {
|
||||
definition.order = order;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder type(TypeEnum type) {
|
||||
definition.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder options(List<String> options) {
|
||||
if (options == null || options.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
definition.options = new ArrayList<>(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder options(String... options) {
|
||||
if (options == null || options.length == 0) {
|
||||
return this;
|
||||
}
|
||||
definition.options = Arrays.asList(options);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder optionsMap(Map<String, String> optionsMap) {
|
||||
definition.optionsMap = new HashMap<>(optionsMap);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder enumClass(Class<? extends BaseEnum> enumClass) {
|
||||
definition.enumClass = enumClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder optionsClass(Class<? extends OptionsValueProcess> optionsClass) {
|
||||
definition.optionsClass = optionsClass;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder required(boolean required) {
|
||||
definition.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder defaultValue(String defaultValue) {
|
||||
definition.defaultValue = defaultValue;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MapFieldDefinition build() {
|
||||
// 验证必填字段
|
||||
if (definition.fieldName == null || definition.fieldName.isEmpty()) {
|
||||
throw new IllegalArgumentException("字段名称不能为空");
|
||||
}
|
||||
|
||||
return definition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
426
src/main/java/cn/isliu/core/config/MapSheetConfig.java
Normal file
426
src/main/java/cn/isliu/core/config/MapSheetConfig.java
Normal file
@ -0,0 +1,426 @@
|
||||
package cn.isliu.core.config;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Map方式表格创建配置类
|
||||
*
|
||||
* 继承 MapTableConfig,专门用于创建飞书表格
|
||||
* 相比父类增加了字段定义、样式配置等创建表格所需的属性
|
||||
*
|
||||
* @author Ls
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapSheetConfig extends MapTableConfig {
|
||||
|
||||
/**
|
||||
* 字段定义列表
|
||||
*/
|
||||
private List<MapFieldDefinition> fields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 表头字体颜色(十六进制,如 #ffffff)
|
||||
*/
|
||||
private String headFontColor = "#ffffff";
|
||||
|
||||
/**
|
||||
* 表头背景颜色(十六进制,如 #000000)
|
||||
*/
|
||||
private String headBackColor = "#000000";
|
||||
|
||||
/**
|
||||
* 是否将单元格设置为纯文本格式
|
||||
*/
|
||||
private boolean isText = false;
|
||||
|
||||
/**
|
||||
* 是否启用字段描述行
|
||||
*/
|
||||
private boolean enableDesc = false;
|
||||
|
||||
public List<MapFieldDefinition> getFields() {
|
||||
return fields;
|
||||
}
|
||||
|
||||
public void setFields(List<MapFieldDefinition> fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
public String getHeadFontColor() {
|
||||
return headFontColor;
|
||||
}
|
||||
|
||||
public void setHeadFontColor(String headFontColor) {
|
||||
this.headFontColor = headFontColor;
|
||||
}
|
||||
|
||||
public String getHeadBackColor() {
|
||||
return headBackColor;
|
||||
}
|
||||
|
||||
public void setHeadBackColor(String headBackColor) {
|
||||
this.headBackColor = headBackColor;
|
||||
}
|
||||
|
||||
public boolean isText() {
|
||||
return isText;
|
||||
}
|
||||
|
||||
public void setText(boolean text) {
|
||||
isText = text;
|
||||
}
|
||||
|
||||
public boolean isEnableDesc() {
|
||||
return enableDesc;
|
||||
}
|
||||
|
||||
public void setEnableDesc(boolean enableDesc) {
|
||||
this.enableDesc = enableDesc;
|
||||
}
|
||||
|
||||
public List<String> getGroupFields() {
|
||||
return groupFields;
|
||||
}
|
||||
|
||||
public void setGroupFields(List<String> groupFields) {
|
||||
this.groupFields = groupFields;
|
||||
}
|
||||
|
||||
public Map<String, Object> getCustomProperties() {
|
||||
return customProperties;
|
||||
}
|
||||
|
||||
public void setCustomProperties(Map<String, Object> customProperties) {
|
||||
this.customProperties = customProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分组字段列表(用于创建分组表格)
|
||||
*/
|
||||
private List<String> groupFields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* 自定义属性映射(用于传递额外配置)
|
||||
*/
|
||||
private Map<String, Object> customProperties = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 创建默认配置
|
||||
*
|
||||
* @return 默认配置实例
|
||||
*/
|
||||
public static MapSheetConfig createDefault() {
|
||||
return new MapSheetConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建表格配置构建器
|
||||
*
|
||||
* @return 配置构建器实例
|
||||
*/
|
||||
public static SheetBuilder sheetBuilder() {
|
||||
return new SheetBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加单个字段
|
||||
*
|
||||
* @param field 字段定义
|
||||
* @return MapSheetConfig实例,支持链式调用
|
||||
*/
|
||||
public MapSheetConfig addField(MapFieldDefinition field) {
|
||||
this.fields.add(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加字段
|
||||
*
|
||||
* @param fields 字段定义列表
|
||||
* @return MapSheetConfig实例,支持链式调用
|
||||
*/
|
||||
public MapSheetConfig addFields(List<MapFieldDefinition> fields) {
|
||||
this.fields.addAll(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加字段(可变参数)
|
||||
*
|
||||
* @param fields 字段定义可变参数
|
||||
* @return MapSheetConfig实例,支持链式调用
|
||||
*/
|
||||
public MapSheetConfig addFields(MapFieldDefinition... fields) {
|
||||
this.fields.addAll(Arrays.asList(fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分组字段
|
||||
*
|
||||
* @param groupField 分组字段名
|
||||
* @return MapSheetConfig实例,支持链式调用
|
||||
*/
|
||||
public MapSheetConfig addGroupField(String groupField) {
|
||||
this.groupFields.add(groupField);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义属性
|
||||
*
|
||||
* @param key 属性键
|
||||
* @param value 属性值
|
||||
* @return MapSheetConfig实例,支持链式调用
|
||||
*/
|
||||
public MapSheetConfig addCustomProperty(String key, Object value) {
|
||||
this.customProperties.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
MapSheetConfig that = (MapSheetConfig) o;
|
||||
return isText == that.isText && enableDesc == that.enableDesc && Objects.equals(fields, that.fields) && Objects.equals(headFontColor, that.headFontColor) && Objects.equals(headBackColor, that.headBackColor) && Objects.equals(groupFields, that.groupFields) && Objects.equals(customProperties, that.customProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(fields, headFontColor, headBackColor, isText, enableDesc, groupFields, customProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格配置构建器
|
||||
*/
|
||||
public static class SheetBuilder {
|
||||
private final MapSheetConfig config = new MapSheetConfig();
|
||||
|
||||
/**
|
||||
* 设置标题行行号
|
||||
*
|
||||
* @param titleRow 标题行行号
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder titleRow(int titleRow) {
|
||||
config.setTitleRow(titleRow);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置数据起始行行号
|
||||
*
|
||||
* @param headLine 数据起始行行号
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder headLine(int headLine) {
|
||||
config.setHeadLine(headLine);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置唯一键字段名集合
|
||||
*
|
||||
* @param uniKeyNames 唯一键字段名集合
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder uniKeyNames(Set<String> uniKeyNames) {
|
||||
config.setUniKeyNames(uniKeyNames);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加唯一键字段名
|
||||
*
|
||||
* @param uniKeyName 唯一键字段名
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder addUniKeyName(String uniKeyName) {
|
||||
config.addUniKeyName(uniKeyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否覆盖已存在数据
|
||||
*
|
||||
* @param enableCover true表示覆盖,false表示不覆盖
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder enableCover(boolean enableCover) {
|
||||
config.setEnableCover(enableCover);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置字段定义列表
|
||||
*
|
||||
* @param fields 字段定义列表
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder fields(List<MapFieldDefinition> fields) {
|
||||
config.fields = new ArrayList<>(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加单个字段
|
||||
*
|
||||
* @param field 字段定义
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder addField(MapFieldDefinition field) {
|
||||
config.fields.add(field);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加字段
|
||||
*
|
||||
* @param fields 字段定义列表
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder addFields(List<MapFieldDefinition> fields) {
|
||||
config.fields.addAll(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加字段(可变参数)
|
||||
*
|
||||
* @param fields 字段定义可变参数
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder addFields(MapFieldDefinition... fields) {
|
||||
config.fields.addAll(Arrays.asList(fields));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表头字体颜色
|
||||
*
|
||||
* @param headFontColor 表头字体颜色(十六进制)
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder headFontColor(String headFontColor) {
|
||||
config.headFontColor = headFontColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表头背景颜色
|
||||
*
|
||||
* @param headBackColor 表头背景颜色(十六进制)
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder headBackColor(String headBackColor) {
|
||||
config.headBackColor = headBackColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置表头样式
|
||||
*
|
||||
* @param fontColor 字体颜色
|
||||
* @param backColor 背景颜色
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder headStyle(String fontColor, String backColor) {
|
||||
config.headFontColor = fontColor;
|
||||
config.headBackColor = backColor;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否将单元格设置为纯文本
|
||||
*
|
||||
* @param isText 是否设置为纯文本
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder isText(boolean isText) {
|
||||
config.isText = isText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否启用字段描述行
|
||||
*
|
||||
* @param enableDesc 是否启用
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder enableDesc(boolean enableDesc) {
|
||||
config.enableDesc = enableDesc;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分组字段列表
|
||||
*
|
||||
* @param groupFields 分组字段列表
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder groupFields(List<String> groupFields) {
|
||||
config.groupFields = new ArrayList<>(groupFields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置分组字段(可变参数)
|
||||
*
|
||||
* @param groupFields 分组字段可变参数
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder groupFields(String... groupFields) {
|
||||
config.groupFields = Arrays.asList(groupFields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分组字段
|
||||
*
|
||||
* @param groupField 分组字段名
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder addGroupField(String groupField) {
|
||||
config.groupFields.add(groupField);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置自定义属性映射
|
||||
*
|
||||
* @param customProperties 自定义属性映射
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder customProperties(Map<String, Object> customProperties) {
|
||||
config.customProperties = new HashMap<>(customProperties);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加自定义属性
|
||||
*
|
||||
* @param key 属性键
|
||||
* @param value 属性值
|
||||
* @return SheetBuilder实例
|
||||
*/
|
||||
public SheetBuilder addCustomProperty(String key, Object value) {
|
||||
config.customProperties.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建配置对象
|
||||
*
|
||||
* @return MapSheetConfig实例
|
||||
*/
|
||||
public MapSheetConfig build() {
|
||||
// 验证必填字段
|
||||
if (config.fields.isEmpty()) {
|
||||
throw new IllegalArgumentException("字段定义列表不能为空");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
122
src/main/java/cn/isliu/core/utils/MapOptionsUtil.java
Normal file
122
src/main/java/cn/isliu/core/utils/MapOptionsUtil.java
Normal file
@ -0,0 +1,122 @@
|
||||
package cn.isliu.core.utils;
|
||||
|
||||
import cn.isliu.core.converters.OptionsValueProcess;
|
||||
import cn.isliu.core.enums.BaseEnum;
|
||||
import cn.isliu.core.pojo.FieldProperty;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Map 选项工具类
|
||||
*
|
||||
* 提供选项提取、转换和处理的工具方法
|
||||
*
|
||||
* @author Ls
|
||||
* @since 2025-10-16
|
||||
*/
|
||||
public class MapOptionsUtil {
|
||||
|
||||
/**
|
||||
* 从枚举类提取选项列表
|
||||
*
|
||||
* 提取枚举类中所有枚举常量的描述(desc)作为下拉选项
|
||||
*
|
||||
* @param enumClass 枚举类
|
||||
* @return 选项列表(枚举的desc值)
|
||||
*/
|
||||
public static List<String> extractOptionsFromEnum(Class<? extends BaseEnum> enumClass) {
|
||||
if (enumClass == null || enumClass == BaseEnum.class) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return Arrays.stream(enumClass.getEnumConstants())
|
||||
.map(BaseEnum::getDesc)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 从枚举类提取选项映射
|
||||
*
|
||||
* 提取枚举类中所有枚举常量的 code -> desc 映射
|
||||
*
|
||||
* @param enumClass 枚举类
|
||||
* @return 选项映射(code -> desc)
|
||||
*/
|
||||
public static Map<String, String> extractOptionsMapFromEnum(Class<? extends BaseEnum> enumClass) {
|
||||
if (enumClass == null || enumClass == BaseEnum.class) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<String, String> optionsMap = new LinkedHashMap<>();
|
||||
Arrays.stream(enumClass.getEnumConstants())
|
||||
.forEach(e -> optionsMap.put(e.getCode(), e.getDesc()));
|
||||
return optionsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建动态的 OptionsValueProcess 类
|
||||
*
|
||||
* 用于处理直接配置的 options 列表,将其包装为 OptionsValueProcess
|
||||
*
|
||||
* @param options 选项列表
|
||||
* @return 动态创建的 OptionsValueProcess 类
|
||||
*/
|
||||
public static Class<? extends OptionsValueProcess> createDynamicOptionsClass(final List<String> options) {
|
||||
if (options == null || options.isEmpty()) {
|
||||
return OptionsValueProcess.class;
|
||||
}
|
||||
|
||||
// 返回一个匿名内部类
|
||||
// 注意:由于 Java 的限制,我们不能真正动态创建类
|
||||
// 这里返回一个包装器类,在 MapSheetBuilder 中特殊处理
|
||||
return DynamicOptionsProcess.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态选项处理类(内部使用)
|
||||
*
|
||||
* 从 customProperties 中读取预先配置的选项列表
|
||||
* 在 MapSheetBuilder 中,会将字段的 options 放入 customProperties
|
||||
*/
|
||||
public static class DynamicOptionsProcess implements OptionsValueProcess {
|
||||
@Override
|
||||
public List<String> process(Object value) {
|
||||
if (value instanceof Map) {
|
||||
Map<String, Object> properties = (Map<String, Object>) value;
|
||||
|
||||
// 从 _field 获取当前字段信息
|
||||
Object fieldObj = properties.get("_field");
|
||||
if (fieldObj instanceof FieldProperty) {
|
||||
FieldProperty fieldProperty =
|
||||
(FieldProperty) fieldObj;
|
||||
|
||||
// 获取字段名
|
||||
String[] fieldNames = fieldProperty.getTableProperty().value();
|
||||
if (fieldNames != null && fieldNames.length > 0) {
|
||||
String fieldName = fieldNames[0];
|
||||
|
||||
// 从 customProperties 中获取该字段的 options
|
||||
Object optionsObj = properties.get("_dynamicOptions_" + fieldName);
|
||||
if (optionsObj instanceof List) {
|
||||
return (List<String>) optionsObj;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为动态选项处理类
|
||||
*
|
||||
* @param optionsClass 选项处理类
|
||||
* @return 是否为动态选项处理类
|
||||
*/
|
||||
public static boolean isDynamicOptionsClass(Class<? extends OptionsValueProcess> optionsClass) {
|
||||
return optionsClass != null && optionsClass == DynamicOptionsProcess.class;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user