feat(core): 添加Map方式操作飞书表格的功能- 新增MapSheetConfig和MapTableConfig配置类,支持通过Map方式定义表格结构

- 添加MapFieldDefinition字段定义类,提供丰富的字段类型和配置选项
- 实现MapSheetBuilder构建器,支持链式调用创建飞书表格- 实现MapReadBuilder构建器,支持从飞书表格读取数据并转换为Map格式- 添加MapOptionsUtil工具类,提供枚举选项提取和动态选项处理功能
- 在FsHelper中新增createMapSheet、createMapSheetBuilder、readMap和readMapBuilder方法-优化包导入结构,使用通配符简化builder包的导入
- 添加详细的JavaDoc文档和使用示例,提升API易用性- 支持动态字段、分组表格、唯一键计算等高级功能
- 实现数据过滤机制,自动过滤空行数据
This commit is contained in:
liushuang 2025-10-16 20:43:36 +08:00
parent f9cff00b2f
commit f191e57c9a
6 changed files with 2190 additions and 4 deletions

@ -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&lt;MapFieldDefinition&gt; 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&lt;Map&lt;String, Object&gt;&gt; dataList = FsHelper.readMap(sheetId, spreadsheetToken, config);
* for (Map&lt;String, Object&gt; 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&lt;Map&lt;String, Object&gt;&gt; dataList = FsHelper.readMapBuilder(sheetId, spreadsheetToken)
* .titleRow(2)
* .headLine(3)
* .addUniKeyName("字段名1")
* .build();
*
* // 分组读取
* Map&lt;String, List&lt;Map&lt;String, Object&gt;&gt;&gt; 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);
}
}

@ -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 -> {
// 过滤条件1Map不能为空
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;
}
}

@ -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;
}
}

@ -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;
}
}
}

@ -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;
}
}
}

@ -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;
}
}