feat(core): 新增飞书文件服务和表格构建器功能
- 新增 CustomFileService 类,用于获取根目录元数据等文件操作 - 新增 RootFolderMetaResponse 类,用于解析根目录元数据响应 - 新增 SheetBuilder 类,提供链式调用方式创建飞书表格 - 在 FeishuClient 中添加 customFiles 方法,返回 CustomFileService 实例 - 在 FsApiUtil 中添加创建文件夹和表格的方法 - 在 FsHelper 中添加创建表格构建器方法
This commit is contained in:
parent
a34df121bc
commit
3e25a91aed
@ -5,6 +5,7 @@ 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.SheetBuilder;
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.client.FsClient;
|
||||
import cn.isliu.core.enums.ErrorCode;
|
||||
@ -74,6 +75,22 @@ public class FsHelper {
|
||||
return sheetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建飞书表格构建器
|
||||
*
|
||||
* 返回一个表格构建器实例,支持链式调用和高级配置选项,
|
||||
* 如字段过滤等功能。
|
||||
*
|
||||
* @param sheetName 工作表名称
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @param clazz 实体类Class对象,用于解析表头和字段属性
|
||||
* @param <T> 实体类泛型
|
||||
* @return SheetBuilder实例,支持链式调用
|
||||
*/
|
||||
public static <T> SheetBuilder<T> createBuilder(String sheetName, String spreadsheetToken, Class<T> clazz) {
|
||||
return new SheetBuilder<>(sheetName, spreadsheetToken, clazz);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从飞书表格中读取数据
|
||||
|
122
src/main/java/cn/isliu/core/builder/SheetBuilder.java
Normal file
122
src/main/java/cn/isliu/core/builder/SheetBuilder.java
Normal file
@ -0,0 +1,122 @@
|
||||
package cn.isliu.core.builder;
|
||||
|
||||
import cn.isliu.core.annotation.TableConf;
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.client.FsClient;
|
||||
import cn.isliu.core.pojo.FieldProperty;
|
||||
import cn.isliu.core.service.CustomCellService;
|
||||
import cn.isliu.core.utils.FsApiUtil;
|
||||
import cn.isliu.core.utils.FsTableUtil;
|
||||
import cn.isliu.core.utils.PropertyUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 表格构建器
|
||||
*
|
||||
* 提供链式调用方式创建飞书表格,支持字段过滤等高级功能。
|
||||
*/
|
||||
public class SheetBuilder<T> {
|
||||
|
||||
private final String sheetName;
|
||||
private final String spreadsheetToken;
|
||||
private final Class<T> clazz;
|
||||
private List<String> includeFields;
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param sheetName 工作表名称
|
||||
* @param spreadsheetToken 电子表格Token
|
||||
* @param clazz 实体类Class对象
|
||||
*/
|
||||
public SheetBuilder(String sheetName, String spreadsheetToken, Class<T> clazz) {
|
||||
this.sheetName = sheetName;
|
||||
this.spreadsheetToken = spreadsheetToken;
|
||||
this.clazz = clazz;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置包含的字段列表
|
||||
*
|
||||
* 指定要包含在表格中的字段名称列表。如果不设置,则包含所有带有@TableProperty注解的字段。
|
||||
*
|
||||
* @param fields 要包含的字段名称列表
|
||||
* @return SheetBuilder实例,支持链式调用
|
||||
*/
|
||||
public SheetBuilder<T> includeColumnField(List<String> fields) {
|
||||
this.includeFields = new ArrayList<>(fields);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建表格并返回工作表ID
|
||||
*
|
||||
* 根据配置的参数创建飞书表格,包括表头、样式、单元格格式和下拉选项等。
|
||||
*
|
||||
* @return 创建成功返回工作表ID
|
||||
*/
|
||||
public String build() {
|
||||
// 获取所有字段映射
|
||||
Map<String, FieldProperty> allFieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
|
||||
|
||||
// 根据includeFields过滤字段映射
|
||||
Map<String, FieldProperty> fieldsMap = filterFieldsMap(allFieldsMap);
|
||||
|
||||
// 生成表头
|
||||
List<String> headers = PropertyUtil.getHeaders(fieldsMap);
|
||||
|
||||
// 获取表格配置
|
||||
TableConf tableConf = PropertyUtil.getTableConf(clazz);
|
||||
|
||||
// 创建飞书客户端
|
||||
FeishuClient client = FsClient.getInstance().getClient();
|
||||
|
||||
// 1、创建sheet
|
||||
String sheetId = FsApiUtil.createSheet(sheetName, client, spreadsheetToken);
|
||||
|
||||
// 2、添加表头数据
|
||||
FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers, fieldsMap, tableConf), client);
|
||||
|
||||
// 3、设置表格样式
|
||||
FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, fieldsMap, tableConf), client, spreadsheetToken);
|
||||
|
||||
// 4、合并单元格
|
||||
List<CustomCellService.CellRequest> mergeCell = FsTableUtil.getMergeCell(sheetId, fieldsMap);
|
||||
if (!mergeCell.isEmpty()) {
|
||||
mergeCell.forEach(cell -> FsApiUtil.mergeCells(cell, client, spreadsheetToken));
|
||||
}
|
||||
|
||||
// 5、设置单元格为文本格式
|
||||
if (tableConf.isText()) {
|
||||
String column = FsTableUtil.getColumnNameByNuNumber(headers.size());
|
||||
FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, client, spreadsheetToken);
|
||||
}
|
||||
|
||||
// 6、设置表格下拉
|
||||
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId, tableConf.enableDesc());
|
||||
|
||||
return sheetId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据包含字段列表过滤字段映射
|
||||
*
|
||||
* @param allFieldsMap 所有字段映射
|
||||
* @return 过滤后的字段映射
|
||||
*/
|
||||
private Map<String, FieldProperty> filterFieldsMap(Map<String, FieldProperty> allFieldsMap) {
|
||||
// 如果没有指定包含字段,返回所有字段
|
||||
if (includeFields == null || includeFields.isEmpty()) {
|
||||
return allFieldsMap;
|
||||
}
|
||||
|
||||
// 根据字段名过滤,保留指定的字段
|
||||
return allFieldsMap.entrySet().stream()
|
||||
.filter(entry -> includeFields.contains(entry.getValue().getField()))
|
||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
}
|
@ -116,6 +116,16 @@ public class FeishuClient {
|
||||
return serviceManager.getService(CustomProtectedDimensionService.class, () -> new CustomProtectedDimensionService(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展文件服务
|
||||
*
|
||||
* @return 扩展文件服务
|
||||
*/
|
||||
public CustomFileService customFiles() {
|
||||
return serviceManager.getService(CustomFileService.class, () -> new CustomFileService(this));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取官方客户端
|
||||
*
|
||||
|
280
src/main/java/cn/isliu/core/pojo/RootFolderMetaResponse.java
Normal file
280
src/main/java/cn/isliu/core/pojo/RootFolderMetaResponse.java
Normal file
@ -0,0 +1,280 @@
|
||||
package cn.isliu.core.pojo;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* 飞书API获取根目录元数据的响应模型类
|
||||
*
|
||||
* 对应飞书API返回的JSON格式:
|
||||
* {
|
||||
* "code": 0,
|
||||
* "msg": "success",
|
||||
* "data": {
|
||||
* "token": "fldbc0k5Zws8AQBpfzlFMKCpN4z",
|
||||
* "id": "fldbc0k5Zws8AQBpfzlFMKCpN4z",
|
||||
* "user_id": "ou_xxxxx",
|
||||
* "name": "我的空间"
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @author FsHelper
|
||||
* @since 1.0
|
||||
*/
|
||||
public class RootFolderMetaResponse {
|
||||
|
||||
/**
|
||||
* 响应状态码
|
||||
* 0表示成功,非0表示失败
|
||||
*/
|
||||
@SerializedName("code")
|
||||
private int code;
|
||||
|
||||
/**
|
||||
* 响应消息
|
||||
* 通常成功时为"success",失败时包含错误描述
|
||||
*/
|
||||
@SerializedName("msg")
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* 根目录元数据
|
||||
*/
|
||||
@SerializedName("data")
|
||||
private RootFolderMeta data;
|
||||
|
||||
/**
|
||||
* 默认构造函数
|
||||
*/
|
||||
public RootFolderMetaResponse() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整构造函数
|
||||
*
|
||||
* @param code 响应状态码
|
||||
* @param msg 响应消息
|
||||
* @param data 根目录元数据
|
||||
*/
|
||||
public RootFolderMetaResponse(int code, String msg, RootFolderMeta data) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应状态码
|
||||
*
|
||||
* @return 响应状态码,0表示成功
|
||||
*/
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应状态码
|
||||
*
|
||||
* @param code 响应状态码
|
||||
*/
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应消息
|
||||
*
|
||||
* @return 响应消息
|
||||
*/
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置响应消息
|
||||
*
|
||||
* @param msg 响应消息
|
||||
*/
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根目录元数据
|
||||
*
|
||||
* @return 根目录元数据
|
||||
*/
|
||||
public RootFolderMeta getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置根目录元数据
|
||||
*
|
||||
* @param data 根目录元数据
|
||||
*/
|
||||
public void setData(RootFolderMeta data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查响应是否成功
|
||||
*
|
||||
* @return true表示API调用成功,false表示失败
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return code == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否包含有效的根目录数据
|
||||
*
|
||||
* @return true表示包含有效的根目录数据
|
||||
*/
|
||||
public boolean hasValidData() {
|
||||
return isSuccess() &&
|
||||
data != null &&
|
||||
data.getToken() != null &&
|
||||
!data.getToken().trim().isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根目录元数据内部类
|
||||
*/
|
||||
public static class RootFolderMeta {
|
||||
|
||||
/**
|
||||
* 文件夹token
|
||||
*/
|
||||
@SerializedName("token")
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 文件夹ID
|
||||
*/
|
||||
@SerializedName("id")
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 用户ID
|
||||
*/
|
||||
@SerializedName("user_id")
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
* 文件夹名称
|
||||
*/
|
||||
@SerializedName("name")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 默认构造函数
|
||||
*/
|
||||
public RootFolderMeta() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整构造函数
|
||||
*
|
||||
* @param token 文件夹token
|
||||
* @param id 文件夹ID
|
||||
* @param userId 用户ID
|
||||
* @param name 文件夹名称
|
||||
*/
|
||||
public RootFolderMeta(String token, String id, String userId, String name) {
|
||||
this.token = token;
|
||||
this.id = id;
|
||||
this.userId = userId;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件夹token
|
||||
*
|
||||
* @return 文件夹token
|
||||
*/
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件夹token
|
||||
*
|
||||
* @param token 文件夹token
|
||||
*/
|
||||
public void setToken(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件夹ID
|
||||
*
|
||||
* @return 文件夹ID
|
||||
*/
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件夹ID
|
||||
*
|
||||
* @param id 文件夹ID
|
||||
*/
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户ID
|
||||
*
|
||||
* @return 用户ID
|
||||
*/
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置用户ID
|
||||
*
|
||||
* @param userId 用户ID
|
||||
*/
|
||||
public void setUserId(String userId) {
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件夹名称
|
||||
*
|
||||
* @return 文件夹名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置文件夹名称
|
||||
*
|
||||
* @param name 文件夹名称
|
||||
*/
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RootFolderMeta{" +
|
||||
"token='" + token + '\'' +
|
||||
", id='" + id + '\'' +
|
||||
", userId='" + userId + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RootFolderMetaResponse{" +
|
||||
"code=" + code +
|
||||
", msg='" + msg + '\'' +
|
||||
", data=" + data +
|
||||
'}';
|
||||
}
|
||||
}
|
44
src/main/java/cn/isliu/core/service/CustomFileService.java
Normal file
44
src/main/java/cn/isliu/core/service/CustomFileService.java
Normal file
@ -0,0 +1,44 @@
|
||||
package cn.isliu.core.service;
|
||||
|
||||
import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.pojo.RootFolderMetaResponse;
|
||||
import okhttp3.Request;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* 飞书文件服务
|
||||
*
|
||||
* 处理飞书云盘相关的API调用,包括获取根目录元数据等功能
|
||||
*
|
||||
* @author FsHelper
|
||||
* @since 1.0
|
||||
*/
|
||||
public class CustomFileService extends AbstractFeishuApiService {
|
||||
|
||||
/**
|
||||
* 构造函数
|
||||
*
|
||||
* @param feishuClient 飞书客户端
|
||||
*/
|
||||
public CustomFileService(FeishuClient feishuClient) {
|
||||
super(feishuClient);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根目录元数据
|
||||
*
|
||||
* 调用飞书开放平台API获取当前租户的根目录token和相关信息
|
||||
* API接口: GET https://open.feishu.cn/open-apis/drive/explorer/v2/root_folder/meta
|
||||
*
|
||||
* @return 根目录元数据响应
|
||||
* @throws IOException 网络请求异常
|
||||
*/
|
||||
public RootFolderMetaResponse getRootFolderMeta() throws IOException {
|
||||
String url = BASE_URL + "/drive/explorer/v2/root_folder/meta";
|
||||
|
||||
Request request = createAuthenticatedRequest(url, "GET", null).build();
|
||||
|
||||
return executeRequest(request, RootFolderMetaResponse.class);
|
||||
}
|
||||
}
|
@ -9,18 +9,13 @@ import cn.isliu.core.client.FeishuClient;
|
||||
import cn.isliu.core.exception.FsHelperException;
|
||||
import cn.isliu.core.logging.FsLogger;
|
||||
import cn.isliu.core.pojo.ApiResponse;
|
||||
import cn.isliu.core.pojo.RootFolderMetaResponse;
|
||||
import cn.isliu.core.service.*;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.lark.oapi.service.drive.v1.model.BatchGetTmpDownloadUrlMediaReq;
|
||||
import com.lark.oapi.service.drive.v1.model.BatchGetTmpDownloadUrlMediaResp;
|
||||
import com.lark.oapi.service.drive.v1.model.DownloadMediaReq;
|
||||
import com.lark.oapi.service.drive.v1.model.DownloadMediaResp;
|
||||
import com.lark.oapi.service.sheets.v3.model.GetSpreadsheetReq;
|
||||
import com.lark.oapi.service.sheets.v3.model.GetSpreadsheetResp;
|
||||
import com.lark.oapi.service.sheets.v3.model.QuerySpreadsheetSheetReq;
|
||||
import com.lark.oapi.service.sheets.v3.model.QuerySpreadsheetSheetResp;
|
||||
import com.lark.oapi.service.drive.v1.model.*;
|
||||
import com.lark.oapi.service.sheets.v3.model.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@ -160,6 +155,83 @@ public class FsApiUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取根目录Token
|
||||
*
|
||||
* 调用飞书开放平台API获取当前租户的根目录token,用于后续的文件夹和文件操作
|
||||
* API接口: GET https://open.feishu.cn/open-apis/drive/v1/files/root_folder/meta
|
||||
*
|
||||
* @param client 飞书客户端
|
||||
* @return 根目录token,获取失败时抛出异常
|
||||
*/
|
||||
public static String getRootFolderToken(FeishuClient client) {
|
||||
try {
|
||||
// 使用自定义文件服务获取根目录元数据
|
||||
RootFolderMetaResponse response = client.customFiles().getRootFolderMeta();
|
||||
|
||||
if (response.isSuccess() && response.hasValidData()) {
|
||||
String rootFolderToken = response.getData().getToken();
|
||||
FsLogger.info("【飞书表格】 获取根目录Token成功!Token: {}", rootFolderToken);
|
||||
return rootFolderToken;
|
||||
} else {
|
||||
FsLogger.warn("【飞书表格】 获取根目录Token失败!错误码:{},错误信息:{}",
|
||||
response.getCode(), response.getMsg());
|
||||
throw new FsHelperException("【飞书表格】 获取根目录Token失败!错误信息:" + response.getMsg());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FsLogger.warn("【飞书表格】 获取根目录Token异常!错误信息:{}", e.getMessage(), e);
|
||||
throw new FsHelperException("【飞书表格】 获取根目录Token异常!");
|
||||
}
|
||||
}
|
||||
|
||||
public static CreateFolderFileRespBody createFolder(String folderName, String folderToken, FeishuClient client) {
|
||||
try {
|
||||
// 创建请求对象
|
||||
CreateFolderFileReq req = CreateFolderFileReq.newBuilder()
|
||||
.createFolderFileReqBody(CreateFolderFileReqBody.newBuilder()
|
||||
.name(folderName)
|
||||
.folderToken(folderToken)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
// 发起请求
|
||||
CreateFolderFileResp resp = client.drive().v1().file().createFolder(req);
|
||||
if (resp.success()) {
|
||||
FsLogger.info("【飞书表格】 创建文件夹成功! {}", gson.toJson(resp));
|
||||
return resp.getData();
|
||||
} else {
|
||||
FsLogger.warn("【飞书表格】 创建文件夹失败!参数:{},错误信息:{}", String.format("folderName: %s, folderToken: %s", folderName, folderToken), resp.getMsg());
|
||||
throw new FsHelperException("【飞书表格】 创建文件夹失败!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FsLogger.warn("【飞书表格】 创建文件夹异常!参数:{},错误信息:{}", String.format("folderName: %s, folderToken: %s", folderName, folderToken), e.getMessage(), e);
|
||||
throw new FsHelperException("【飞书表格】 创建文件夹异常!");
|
||||
}
|
||||
}
|
||||
|
||||
public static CreateSpreadsheetRespBody createTable(String tableName, String folderToken, FeishuClient client) {
|
||||
try {
|
||||
CreateSpreadsheetReq req = CreateSpreadsheetReq.newBuilder()
|
||||
.spreadsheet(Spreadsheet.newBuilder()
|
||||
.title(tableName)
|
||||
.folderToken(folderToken)
|
||||
.build())
|
||||
.build();
|
||||
|
||||
CreateSpreadsheetResp resp = client.sheets().v3().spreadsheet().create(req);
|
||||
if (resp.success()) {
|
||||
FsLogger.info("【飞书表格】 创建表格成功! {}", gson.toJson(resp));
|
||||
return resp.getData();
|
||||
} else {
|
||||
FsLogger.warn("【飞书表格】 创建表格失败!错误信息:{}", gson.toJson(resp));
|
||||
throw new FsHelperException("【飞书表格】 创建表格异常!");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
FsLogger.warn("【飞书表格】 创建表格异常!参数:{},错误信息:{}", String.format("tableName:%s, folderToken:%s", tableName, folderToken), e.getMessage(), e);
|
||||
throw new FsHelperException("【飞书表格】 创建表格异常!");
|
||||
}
|
||||
}
|
||||
|
||||
public static String createSheet(String title, FeishuClient client, String spreadsheetToken) {
|
||||
String sheetId = null;
|
||||
try {
|
||||
|
Loading…
Reference in New Issue
Block a user