feat(core): 添加文件上传功能- 新增 FileData 类用于存储文件相关数据

- 新增 FileType 枚举用于区分文件类型- 修改 FsHelper 类,支持文件上传
- 更新 FileUrlProcess 类,处理文件 URL
-调整 FsApiUtil 类,使用新逻辑上传文件
- 修改 GenerateUtil 类,处理文件数据
This commit is contained in:
liushuang 2025-08-16 14:07:45 +08:00
parent af7c1a3a51
commit b3f4463bcf
8 changed files with 245 additions and 78 deletions

@ -1,11 +1,15 @@
package cn.isliu;
import cn.isliu.core.BaseEntity;
import cn.isliu.core.FileData;
import cn.isliu.core.FsTableData;
import cn.isliu.core.Sheet;
import cn.isliu.core.client.FeishuClient;
import cn.isliu.core.client.FsClient;
import cn.isliu.core.config.FsConfig;
import cn.isliu.core.enums.ErrorCode;
import cn.isliu.core.enums.FileType;
import cn.isliu.core.logging.FsLogger;
import cn.isliu.core.pojo.FieldProperty;
import cn.isliu.core.service.CustomValueService;
import cn.isliu.core.utils.*;
@ -137,6 +141,7 @@ public class FsHelper {
// 初始化批量插入对象
CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder = CustomValueService.ValueRequest.batchPutValues();
List<FileData> fileDataList = new ArrayList<>();
FsConfig fsConfig = FsConfig.getInstance();
AtomicInteger rowCount = new AtomicInteger(row[0] + 1);
@ -155,8 +160,19 @@ public class FsHelper {
}
String position = titlePostionMap.get(field);
if (fieldValue instanceof FileData) {
FileData fileData = (FileData) fieldValue;
String fileType = fileData.getFileType();
if (fileType.equals(FileType.IMAGE.getType())) {
fileData.setSheetId(sheetId);
fileData.setSpreadsheetToken(spreadsheetToken);
fileData.setPosition(position + rowNum.get());
fileDataList.add(fileData);
}
}
resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
.addRow(fieldValue instanceof List ? GenerateUtil.getFieldValueList(fieldValue) : fieldValue);
.addRow(GenerateUtil.getRowData(fieldValue));
});
} else {
int rowCou = rowCount.incrementAndGet();
@ -166,9 +182,15 @@ public class FsHelper {
}
String position = titlePostionMap.get(field);
if (fieldValue instanceof FileData) {
FileData fileData = (FileData) fieldValue;
fileData.setSheetId(sheetId);
fileData.setSpreadsheetToken(spreadsheetToken);
fileData.setPosition(position + rowCou);
fileDataList.add(fileData);
}
resultValuesBuilder.addRange(sheetId, position + rowCou, position + rowCou)
.addRow(fieldValue instanceof List ? GenerateUtil.getFieldValueList(fieldValue) : fieldValue);
.addRow(GenerateUtil.getRowData(fieldValue));
});
}
}
@ -179,6 +201,16 @@ public class FsHelper {
FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", rowTotal - rowNum, client);
}
return FsApiUtil.batchPutValues(sheetId, spreadsheetToken, resultValuesBuilder.build(), client);
Object resp = FsApiUtil.batchPutValues(sheetId, spreadsheetToken, resultValuesBuilder.build(), client);
fileDataList.forEach(fileData -> {
try {
FsApiUtil.imageUpload(fileData.getImageData(), fileData.getFileName(), fileData.getPosition(), fileData.getSheetId(), fileData.getSpreadsheetToken(), client);
} catch (Exception e) {
FsLogger.error(ErrorCode.BUSINESS_LOGIC_ERROR, "【飞书表格】 文件上传-文件上传异常! " + fileData.getFileUrl());
}
});
return resp;
}
}

@ -0,0 +1,107 @@
package cn.isliu.core;
import java.util.Arrays;
import java.util.Objects;
public class FileData {
private String sheetId;
private String spreadsheetToken;
private String fileName;
private byte[] imageData;
private String position;
private String fileType;
private String fileUrl;
public FileData() {}
public FileData(String sheetId, String spreadsheetToken, String fileName, byte[] imageData, String position, String fileType) {
this.sheetId = sheetId;
this.spreadsheetToken = spreadsheetToken;
this.fileName = fileName;
this.imageData = imageData;
this.position = position;
this.fileType = fileType;
}
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
public String getSpreadsheetToken() {
return spreadsheetToken;
}
public void setSpreadsheetToken(String spreadsheetToken) {
this.spreadsheetToken = spreadsheetToken;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public byte[] getImageData() {
return imageData;
}
public void setImageData(byte[] imageData) {
this.imageData = imageData;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public String getFileType() {
return fileType;
}
public void setFileType(String fileType) {
this.fileType = fileType;
}
public String getFileUrl() {
return fileUrl;
}
public void setFileUrl(String fileUrl) {
this.fileUrl = fileUrl;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
FileData fileData = (FileData) o;
return Objects.equals(sheetId, fileData.sheetId) && Objects.equals(spreadsheetToken, fileData.spreadsheetToken) && Objects.equals(fileName, fileData.fileName) && Objects.deepEquals(imageData, fileData.imageData) && Objects.equals(position, fileData.position) && Objects.equals(fileType, fileData.fileType) && Objects.equals(fileUrl, fileData.fileUrl);
}
@Override
public int hashCode() {
return Objects.hash(sheetId, spreadsheetToken, fileName, Arrays.hashCode(imageData), position, fileType, fileUrl);
}
@Override
public String toString() {
return "FileData{" +
"sheetId='" + sheetId + '\'' +
", spreadsheetToken='" + spreadsheetToken + '\'' +
", fileName='" + fileName + '\'' +
", imageData=" + Arrays.toString(imageData) +
", position='" + position + '\'' +
", fileType='" + fileType + '\'' +
", fileUrl='" + fileUrl + '\'' +
'}';
}
}

@ -7,5 +7,5 @@ public interface FieldValueProcess<T> {
/**
* 反向处理将枚举值转换为原始值
*/
T reverseProcess(Object value);
Object reverseProcess(Object value);
}

@ -1,7 +1,8 @@
package cn.isliu.core.converters;
import cn.isliu.core.FileData;
import cn.isliu.core.client.FsClient;
import cn.isliu.core.config.FsConfig;
import cn.isliu.core.enums.ErrorCode;
import cn.isliu.core.utils.FsApiUtil;
import cn.isliu.core.utils.FileUtil;
import com.google.gson.JsonArray;
@ -15,8 +16,6 @@ import cn.isliu.core.logging.FsLogger;
public class FileUrlProcess implements FieldValueProcess<String> {
// 使用统一的FsLogger替代java.util.logging.Logger
@Override
public String process(Object value) {
if (value instanceof String) {
@ -43,16 +42,27 @@ public class FileUrlProcess implements FieldValueProcess<String> {
}
@Override
public String reverseProcess(Object value) {
boolean cover = FsConfig.getInstance().isCover();
if (!cover && value != null) {
String str = value.toString();
byte[] imageData = FileUtil.getImageData(str);
}
public Object reverseProcess(Object value) {
if (value == null) {
return null;
} else {
if (value instanceof String) {
String path = value.toString();
try {
FileData fileData = new FileData();
fileData.setFileUrl( path);
fileData.setFileType(FileUtil.isImageFile(path) ? "image" : "file");
fileData.setFileName(FileUtil.getFileName(path));
fileData.setImageData(FileUtil.getImageData(path));
return fileData;
} catch (Exception e) {
FsLogger.error(ErrorCode.BUSINESS_LOGIC_ERROR,"【飞书表格】 文件上传-文件URL处理异常!" + e.getMessage(), path, e);
return value;
}
} else {
return value;
}
}
return value.toString();
}
private synchronized String getUrlByTextFile(JsonObject jsb) {

@ -0,0 +1,27 @@
package cn.isliu.core.enums;
public enum FileType {
IMAGE("image"),
FILE("file"),
UNKNOWN("unknown");
private String type;
FileType(String type) {
this.type = type;
}
public String getType() {
return type;
}
public static FileType getType(String type) {
for (FileType fileType : FileType.values()) {
if (fileType.getType().equals(type)) {
return fileType;
}
}
return UNKNOWN;
}
}

@ -7,6 +7,7 @@ import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
public class FileUtil {
@ -118,4 +119,35 @@ public class FileUtil {
return tempDir;
}
/**
* 判断文件是否为图片类型
*
* @param filePath 文件路径可以是URL或本地路径
* @return 如果是图片类型返回true否则返回false
*/
public static boolean isImageFile(String filePath) {
if (filePath == null || filePath.isEmpty()) {
return false;
}
// 常见的图片文件扩展名
String[] imageExtensions = {".jpg", ".jpeg", ".png", ".gif", ".bmp", ".webp", ".svg", ".ico"};
// 转换为小写进行比较
String path = filePath.toLowerCase();
// 检查URL或本地路径是否以图片扩展名结尾
for (String extension : imageExtensions) {
if (path.endsWith(extension)) {
return true;
}
}
return false;
}
public static String getFileName(String path) {
return Paths.get(path).getFileName().toString();
}
}

@ -38,7 +38,6 @@ import cn.isliu.core.enums.ErrorCode;
public class FsApiUtil {
private static final Gson gson = new Gson();
// 使用统一的FsLogger替代java.util.logging.Logger
private static final String REQ_TYPE = "JSON_STR";
public static final int DEFAULT_ROW_NUM = 1000;
@ -126,57 +125,6 @@ public class FsApiUtil {
}
}
/**
* 合并单元格
*
* 在指定工作表中合并指定范围的单元格
*
* @param cell 合并范围"A1:B2"
* @param sheetId 工作表ID
* @param client 飞书客户端
* @param spreadsheetToken 电子表格Token
*/
public static void mergeCells(String cell, String sheetId, FeishuClient client, String spreadsheetToken) {
try {
CustomCellService.CellBatchUpdateRequest batchMergeRequest = CustomCellService.CellBatchUpdateRequest.newBuilder()
.addRequest(CustomCellService.CellRequest.mergeCells().setReqType(REQ_TYPE)
.setReqParams(cell.replaceAll("%SHEET_ID%", sheetId)).build())
.build();
ApiResponse batchMergeResp = client.customCells().cellsBatchUpdate(spreadsheetToken, batchMergeRequest);
if (!batchMergeResp.success()) {
FsLogger.warn("【飞书表格】 合并单元格请求异常!参数:{},错误信息:{}", cell, batchMergeResp.getMsg());
throw new FsHelperException("【飞书表格】 合并单元格请求异常!");
}
} catch (Exception e) {
FsLogger.warn("【飞书表格】 合并单元格异常!参数:{},错误信息:{}", cell, e.getMessage());
throw new FsHelperException("【飞书表格】 合并单元格异常!");
}
}
public static void createTemplateHead(String head, String sheetId, FeishuClient client, String spreadsheetToken) {
try {
// 批量操作数据值在一个请求中同时执行多个数据操作
CustomValueService.ValueBatchUpdateRequest batchValueRequest = CustomValueService.ValueBatchUpdateRequest.newBuilder()
// 在指定范围前插入数据
.addRequest(CustomValueService.ValueRequest.batchPutValues()
.setReqType(REQ_TYPE)
.setReqParams(head.replaceAll("%SHEET_ID%", sheetId))
.build())
.build();
ApiResponse apiResponse = client.customValues().valueBatchUpdate(spreadsheetToken, batchValueRequest);
if (!apiResponse.success()) {
FsLogger.warn("【飞书表格】 写入表格头数据异常!错误信息:{}", apiResponse.getMsg());
throw new FsHelperException("【飞书表格】 写入表格头数据异常!");
}
} catch (Exception e) {
FsLogger.warn("【飞书表格】 写入表格头异常!错误信息:{}", e.getMessage());
throw new FsHelperException("【飞书表格】 写入表格头异常!");
}
}
public static void setTableStyle(String style, String sheetId, FeishuClient client, String spreadsheetToken) {
try {
CustomCellService.CellBatchUpdateRequest batchUpdateRequest = CustomCellService.CellBatchUpdateRequest.newBuilder()
@ -497,9 +445,8 @@ public class FsApiUtil {
}
}
public static Object imageUpload(String filePath, String fileName, String position ,String sheetId, String spreadsheetToken, FeishuClient client) {
public static Object imageUpload(byte[] imageData, String fileName, String position ,String sheetId, String spreadsheetToken, FeishuClient client) {
try {
byte[] imageData = FileUtil.getImageData(filePath);
CustomValueService.ValueRequest imageRequest = CustomValueService.ValueRequest.imageValues()
.range(sheetId, position)
@ -514,11 +461,11 @@ public class FsApiUtil {
ApiResponse imageResp = client.customValues().valueBatchUpdate(spreadsheetToken, imageWriteRequest);
if (!imageResp.success()) {
FsLogger.warn("【飞书表格】 图片上传失败!参数:{},错误信息:{}", filePath, gson.toJson(imageResp));
FsLogger.error(ErrorCode.API_SERVER_ERROR, "【飞书表格】 文件上传失败!" + gson.toJson(imageResp));
}
return imageResp.getData();
} catch (Exception e) {
FsLogger.warn("【飞书表格】 图片上传异常!参数:{},错误信息:{}", filePath, e.getMessage());
FsLogger.error(ErrorCode.API_SERVER_ERROR,"【飞书表格】 文件上传异常!" + e.getMessage(), fileName, e);
}
return null;

@ -1,9 +1,9 @@
package cn.isliu.core.utils;
import cn.isliu.core.FileData;
import cn.isliu.core.annotation.TableProperty;
import cn.isliu.core.converters.FieldValueProcess;
import cn.isliu.core.enums.BaseEnum;
import cn.isliu.core.utils.StringUtil;
import cn.isliu.core.enums.FileType;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
@ -437,12 +437,24 @@ public class GenerateUtil {
return newObject;
}
public static Object getFieldValueList(Object fieldValue) {
public static Object getRowData(Object fieldValue) {
if (fieldValue instanceof FileData) {
FileData fileData = (FileData) fieldValue;
if (Objects.equals(fileData.getFileType(), FileType.IMAGE.getType())) {
return null;
} else if (Objects.equals(fileData.getFileType(), FileType.FILE.getType())) {
return fileData.getFileUrl();
}
}
if (fieldValue instanceof List) {
Map<String, Object> params = new HashMap<>();
params.put("values", fieldValue);
params.put("type", "multipleValue");
return params;
}
return fieldValue;
}
public static <T> @Nullable String getUniqueId(T data) {
String uniqueId = null;