feat(core): 新增核心功能模块

This commit is contained in:
liushuang 2025-08-13 16:50:38 +08:00
parent f89ce6f884
commit 7bac7917a0
47 changed files with 11528 additions and 0 deletions

39
.gitignore vendored Normal file

@ -0,0 +1,39 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
.idea
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

34
pom.xml Normal file

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.isliu</groupId>
<artifactId>feishu-table-helper</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.larksuite.oapi</groupId>
<artifactId>oapi-sdk</artifactId>
<version>2.4.21</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.9</version>
</dependency>
</dependencies>
</project>

@ -0,0 +1,136 @@
package cn.isliu;
import cn.isliu.core.BaseEntity;
import cn.isliu.core.FsTableData;
import cn.isliu.core.Sheet;
import cn.isliu.core.config.FsConfig;
import cn.isliu.core.pojo.FieldProperty;
import cn.isliu.core.service.CustomValueService;
import cn.isliu.core.utils.*;
import com.google.gson.JsonObject;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
public class FsHelper {
public static <T> Boolean create(String sheetName, String spreadsheetToken, Class<T> clazz) {
Map<String, FieldProperty> fieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
List<String> headers = PropertyUtil.getHeaders(fieldsMap);
// 1创建sheet
String sheetId = FsApiUtil.createSheet(sheetName, FsClientUtil.getFeishuClient(), spreadsheetToken);
// 2 添加表头数据
FsApiUtil.putValues(spreadsheetToken, FsTableUtil.getHeadTemplateBuilder(sheetId, headers), FsClientUtil.getFeishuClient());
// 3 设置表格样式
FsApiUtil.setTableStyle(FsTableUtil.getDefaultTableStyle(sheetId, headers.size()), sheetId, FsClientUtil.getFeishuClient(), spreadsheetToken);
// 4 设置单元格为文本格式
if (FsConfig.CELL_TEXT) {
String column = FsTableUtil.getColumnNameByNuNumber(headers.size());
FsApiUtil.setCellType(sheetId, "@", "A1", column + 200, FsClientUtil.getFeishuClient(), spreadsheetToken);
}
// 5 设置表格下拉
FsTableUtil.setTableOptions(spreadsheetToken, headers, fieldsMap, sheetId);
return true;
}
public static <T> List<T> read(String sheetId, String spreadsheetToken, Class<T> clazz) {
List<T> results = new ArrayList<>();
Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, FsClientUtil.getFeishuClient(), spreadsheetToken);
List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken);
Map<String, FieldProperty> fieldsMap = PropertyUtil.getTablePropertyFieldsMap(clazz);
List<String> fieldPathList = fieldsMap.values().stream().map(FieldProperty::getField).collect(Collectors.toList());
fsTableDataList.forEach(tableData -> {
Object data = tableData.getData();
if (data instanceof HashMap) {
JsonObject jsonObject = JSONUtil.convertHashMapToJsonObject((HashMap<String, Object>) data);
Map<String, Object> dataMap = ConvertFieldUtil.convertPositionToField(jsonObject, fieldsMap);
T t = GenerateUtil.generateInstance(fieldPathList, clazz, dataMap);
if (t instanceof BaseEntity) {
((BaseEntity) t).setUniqueId(tableData.getUniqueId());
}
results.add(t);
}
});
return results;
}
public static <T> Object write(String sheetId, String spreadsheetToken, List<T> dataList) {
if (dataList.isEmpty()) {
return null;
}
Class<?> aClass = dataList.get(0).getClass();
Map<String, FieldProperty> fieldsMap = PropertyUtil.getTablePropertyFieldsMap(aClass);
Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, FsClientUtil.getFeishuClient(), spreadsheetToken);
List<FsTableData> fsTableDataList = FsTableUtil.getFsTableData(sheet, spreadsheetToken);
Map<String, Integer> currTableRowMap = fsTableDataList.stream().collect(Collectors.toMap(FsTableData::getUniqueId, FsTableData::getRow));
final Integer[] row = {0};
fsTableDataList.forEach(fsTableData -> {
if (fsTableData.getRow() > row[0]) {
row[0] = fsTableData.getRow();
}
});
Map<String, String> titlePostionMap = FsTableUtil.getTitlePostionMap(sheet, spreadsheetToken);
Map<String, String> fieldMap = new HashMap<>();
fieldsMap.forEach((field, fieldProperty) -> fieldMap.put(field, fieldProperty.getField()));
// 初始化批量插入对象
CustomValueService.ValueRequest.BatchPutValuesBuilder resultValuesBuilder = CustomValueService.ValueRequest.batchPutValues();
AtomicInteger rowCount = new AtomicInteger(row[0] + 1);
for (T data : dataList) {
Map<String, Object> values = GenerateUtil.getFieldValue(data, fieldMap);
String uniqueId = GenerateUtil.getUniqueId(data);
AtomicReference<Integer> rowNum = new AtomicReference<>(currTableRowMap.get(uniqueId));
if (uniqueId != null && rowNum.get() != null) {
rowNum.set(rowNum.get() + 1);
values.forEach((field, fieldValue) -> {
if (!FsConfig.isCover && fieldValue == null) {
return;
}
String position = titlePostionMap.get(field);
resultValuesBuilder.addRange(sheetId, position + rowNum.get(), position + rowNum.get())
.addRow(fieldValue instanceof List ? GenerateUtil.getFieldValueList(fieldValue) : fieldValue);
});
} else {
int rowCou = rowCount.incrementAndGet();
values.forEach((field, fieldValue) -> {
if (!FsConfig.isCover && fieldValue == null) {
return;
}
String position = titlePostionMap.get(field);
resultValuesBuilder.addRange(sheetId, position + rowCou, position + rowCou)
.addRow(fieldValue instanceof List ? GenerateUtil.getFieldValueList(fieldValue) : fieldValue);
});
}
}
int rowTotal = sheet.getGridProperties().getRowCount();
int rowNum = rowCount.get();
if (rowNum > rowTotal) {
FsApiUtil.addRowColumns(sheetId, spreadsheetToken, "ROWS", rowTotal - rowNum, FsClientUtil.getFeishuClient());
}
return FsApiUtil.batchPutValues(sheetId, spreadsheetToken, resultValuesBuilder.build(), FsClientUtil.getFeishuClient());
}
}

@ -0,0 +1,42 @@
package cn.isliu.core;
import java.util.Objects;
public class AddSheet {
private Properties properties;
public AddSheet() {
}
public AddSheet(Properties properties) {
this.properties = properties;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
AddSheet addSheet = (AddSheet) o;
return Objects.equals(properties, addSheet.properties);
}
@Override
public int hashCode() {
return Objects.hashCode(properties);
}
@Override
public String toString() {
return "AddSheet{" +
"properties=" + properties +
'}';
}
}

@ -0,0 +1,14 @@
package cn.isliu.core;
public abstract class BaseEntity {
public String uniqueId;
public String getUniqueId() {
return uniqueId;
}
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
}

@ -0,0 +1,72 @@
package cn.isliu.core;
public class Cell {
private int row;
private int col;
private Object value;
private Merge merge;
public int getRow() {
return row;
}
public void setRow(int row) {
this.row = row;
}
public Merge getMerge() {
return merge;
}
public void setMerge(Merge merge) {
this.merge = merge;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public int getCol() {
return col;
}
public void setCol(int col) {
this.col = col;
}
@Override
public String toString() {
return "Cell{" +
"row=" + row +
", col=" + col +
", value=" + value +
", merge=" + merge +
'}';
}
public Cell(int row, int col, Object o) {
this.row = row;
this.col = col;
this.value = o;
}
public Cell(int row, int col, Object o, Merge merge) {
this.row = row;
this.col = col;
this.value = o;
this.merge = merge;
}
public Cell() {
}
public boolean isMerged() {
return merge != null;
}
}

@ -0,0 +1,43 @@
package cn.isliu.core;
import java.util.Objects;
public class CopySheet {
private Properties properties;
public CopySheet() {
}
public CopySheet(Properties properties) {
this.properties = properties;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
CopySheet copySheet = (CopySheet) o;
return Objects.equals(properties, copySheet.properties);
}
@Override
public int hashCode() {
return Objects.hashCode(properties);
}
@Override
public String toString() {
return "CopySheet{" +
"properties=" + properties +
'}';
}
}

@ -0,0 +1,53 @@
package cn.isliu.core;
import java.util.Objects;
public class DeleteSheet {
private boolean result;
private String sheetId;
public boolean isResult() {
return result;
}
public void setResult(boolean result) {
this.result = result;
}
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
public DeleteSheet() {
}
public DeleteSheet(boolean result, String sheetId) {
this.result = result;
this.sheetId = sheetId;
}
@Override
public String toString() {
return "DeleteSheet{" +
"result=" + result +
", sheetId='" + sheetId + '\'' +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
DeleteSheet deleteSheet = (DeleteSheet) o;
return result == deleteSheet.result && Objects.equals(sheetId, deleteSheet.sheetId);
}
@Override
public int hashCode() {
return Objects.hash(result, sheetId);
}
}

@ -0,0 +1,64 @@
package cn.isliu.core;
import java.util.Objects;
public class FsTableData {
private Integer row;
private String uniqueId;
private Object data;
public FsTableData() {
}
public FsTableData(Integer row, String uniqueId, Object data) {
this.row = row;
this.uniqueId = uniqueId;
this.data = data;
}
public Integer getRow() {
return row;
}
public void setRow(Integer row) {
this.row = row;
}
public String getUniqueId() {
return uniqueId;
}
public void setUniqueId(String uniqueId) {
this.uniqueId = uniqueId;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
FsTableData that = (FsTableData) o;
return Objects.equals(row, that.row) && Objects.equals(uniqueId, that.uniqueId) && Objects.equals(data, that.data);
}
@Override
public int hashCode() {
return Objects.hash(row, uniqueId, data);
}
@Override
public String toString() {
return "FsTableData{" +
"row=" + row +
", uniqueId='" + uniqueId + '\'' +
", data=" + data +
'}';
}
}

@ -0,0 +1,70 @@
package cn.isliu.core;
import com.google.gson.annotations.SerializedName;
import java.util.Objects;
public class GridProperties {
@SerializedName("frozen_row_count")
private int frozenRowCount;
@SerializedName("frozen_column_count")
private int frozenColumnCount;
@SerializedName("row_count")
private int rowCount;
@SerializedName("column_count")
private int columnCount;
public GridProperties() {
}
public GridProperties(int frozenRowCount, int frozenColumnCount, int rowCount, int columnCount) {
this.frozenRowCount = frozenRowCount;
this.frozenColumnCount = frozenColumnCount;
this.rowCount = rowCount;
this.columnCount = columnCount;
}
public int getFrozenRowCount() {
return frozenRowCount;
}
public int getFrozenColumnCount() {
return frozenColumnCount;
}
public int getRowCount() {
return rowCount;
}
public int getColumnCount() {
return columnCount;
}
public void setFrozenRowCount(int frozenRowCount) {
this.frozenRowCount = frozenRowCount;
}
public void setFrozenColumnCount(int frozenColumnCount) {
this.frozenColumnCount = frozenColumnCount;
}
public void setRowCount(int rowCount) {
this.rowCount = rowCount;
}
public void setColumnCount(int columnCount) {
this.columnCount = columnCount;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
GridProperties gridProperties = (GridProperties) o;
return frozenRowCount == gridProperties.frozenRowCount && frozenColumnCount == gridProperties.frozenColumnCount && rowCount == gridProperties.rowCount && columnCount == gridProperties.columnCount;
}
@Override
public int hashCode() {
return Objects.hash(frozenRowCount, frozenColumnCount, rowCount, columnCount);
}
@Override
public String toString() {
return "GridProperties{" +
"frozenRowCount=" + frozenRowCount +
", frozenColumnCount=" + frozenColumnCount +
", rowCount=" + rowCount +
", columnCount=" + columnCount +
'}';
}
}

@ -0,0 +1,77 @@
package cn.isliu.core;
import com.google.gson.annotations.SerializedName;
public class Merge {
@SerializedName("start_row_index")
private int startRowIndex;
@SerializedName("end_row_index")
private int endRowIndex;
@SerializedName("start_column_index")
private int startColumnIndex;
@SerializedName("end_column_index")
private int endColumnIndex;
public Merge(int startRowIndex, int endRowIndex, int startColumnIndex, int endColumnIndex) {
this.startRowIndex = startRowIndex;
this.endRowIndex = endRowIndex;
this.startColumnIndex = startColumnIndex;
this.endColumnIndex = endColumnIndex;
}
public Merge() {
}
public int getStartRowIndex() {
return startRowIndex;
}
public void setStartRowIndex(int startRowIndex) {
this.startRowIndex = startRowIndex;
}
public int getEndRowIndex() {
return endRowIndex;
}
public void setEndRowIndex(int endRowIndex) {
this.endRowIndex = endRowIndex;
}
public int getStartColumnIndex() {
return startColumnIndex;
}
public void setStartColumnIndex(int startColumnIndex) {
this.startColumnIndex = startColumnIndex;
}
public int getEndColumnIndex() {
return endColumnIndex;
}
public void setEndColumnIndex(int endColumnIndex) {
this.endColumnIndex = endColumnIndex;
}
@Override
public String toString() {
return "Merge{" +
"startRowIndex=" + startRowIndex +
", endRowIndex=" + endRowIndex +
", startColumnIndex=" + startColumnIndex +
", endColumnIndex=" + endColumnIndex +
'}';
}
public int getRowSpan() {
return (endRowIndex - startRowIndex) + 1;
}
public int getColSpan() {
return (endColumnIndex - startColumnIndex) + 1;
}
}

@ -0,0 +1,55 @@
package cn.isliu.core;
import java.util.Objects;
public class MergedCell extends Cell {
private int rowSpan;
private int colSpan;
public MergedCell() {
super(0, 0, null);
}
public MergedCell(int rowSpan, int colSpan) {
super(rowSpan, colSpan, null);
this.rowSpan = rowSpan;
this.colSpan = colSpan;
}
public int getRowSpan() {
return rowSpan;
}
public void setRowSpan(int rowSpan) {
this.rowSpan = rowSpan;
}
public int getColSpan() {
return colSpan;
}
public void setColSpan(int colSpan) {
this.colSpan = colSpan;
}
@Override
public String toString() {
return "MergedCell{" +
"rowSpan=" + rowSpan +
", colSpan=" + colSpan +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
MergedCell that = (MergedCell) o;
return rowSpan == that.rowSpan && colSpan == that.colSpan;
}
@Override
public int hashCode() {
return Objects.hash(rowSpan, colSpan);
}
}

@ -0,0 +1,65 @@
package cn.isliu.core;
import java.util.Objects;
public class Properties {
private String sheetId;
private String title;
private int index;
public Properties() {
}
public Properties(String sheetId, String title, int index) {
this.sheetId = sheetId;
this.title = title;
this.index = index;
}
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Properties that = (Properties) o;
return index == that.index && Objects.equals(sheetId, that.sheetId) && Objects.equals(title, that.title);
}
@Override
public int hashCode() {
return Objects.hash(sheetId, title, index);
}
@Override
public String toString() {
return "Properties{" +
"sheetId='" + sheetId + '\'' +
", title='" + title + '\'' +
", index=" + index +
'}';
}
}

@ -0,0 +1,64 @@
package cn.isliu.core;
import java.util.Objects;
public class Reply {
private AddSheet addSheet;
private CopySheet copySheet;
private DeleteSheet deleteSheet;
public Reply() {
}
public Reply(AddSheet addSheet, CopySheet copySheet, DeleteSheet deleteSheet) {
this.addSheet = addSheet;
this.copySheet = copySheet;
this.deleteSheet = deleteSheet;
}
public AddSheet getAddSheet() {
return addSheet;
}
public void setAddSheet(AddSheet addSheet) {
this.addSheet = addSheet;
}
public CopySheet getCopySheet() {
return copySheet;
}
public void setCopySheet(CopySheet copySheet) {
this.copySheet = copySheet;
}
public DeleteSheet getDeleteSheet() {
return deleteSheet;
}
public void setDeleteSheet(DeleteSheet deleteSheet) {
this.deleteSheet = deleteSheet;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
Reply reply = (Reply) o;
return Objects.equals(addSheet, reply.addSheet) && Objects.equals(copySheet, reply.copySheet) && Objects.equals(deleteSheet, reply.deleteSheet);
}
@Override
public int hashCode() {
return Objects.hash(addSheet, copySheet, deleteSheet);
}
@Override
public String toString() {
return "Reply{" +
"addSheet=" + addSheet +
", copySheet=" + copySheet +
", deleteSheet=" + deleteSheet +
'}';
}
}

@ -0,0 +1,97 @@
package cn.isliu.core;
import com.google.gson.annotations.SerializedName;
import java.util.List;
public class Sheet {
@SerializedName("sheet_id")
private String sheetId;
@SerializedName("title")
private String title;
@SerializedName("index")
private int index;
@SerializedName("hidden")
private boolean hidden;
@SerializedName("grid_properties")
private GridProperties gridProperties;
@SerializedName("resource_type")
private String resourceType;
@SerializedName("merges")
private List<Merge> merges;
public Sheet() {
}
public Sheet(String sheetId, String title, int index, boolean hidden, GridProperties gridProperties, String resourceType, List<Merge> merges) {
this.sheetId = sheetId;
this.title = title;
this.index = index;
this.hidden = hidden;
this.gridProperties = gridProperties;
this.resourceType = resourceType;
this.merges = merges;
}
public String getSheetId() {
return sheetId;
}
public String getTitle() {
return title;
}
public int getIndex() {
return index;
}
public boolean isHidden() {
return hidden;
}
public GridProperties getGridProperties() {
return gridProperties;
}
public String getResourceType() {
return resourceType;
}
public List<Merge> getMerges() {
return merges;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
public void setTitle(String title) {
this.title = title;
}
public void setIndex(int index) {
this.index = index;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
public void setGridProperties(GridProperties gridProperties) {
this.gridProperties = gridProperties;
}
public void setResourceType(String resourceType) {
this.resourceType = resourceType;
}
public void setMerges(List<Merge> merges) {
this.merges = merges;
}
}

@ -0,0 +1,40 @@
package cn.isliu.core;
import com.google.gson.annotations.SerializedName;
import java.util.List;
import java.util.Objects;
public class SheetMeta {
@SerializedName("sheets")
private List<Sheet> sheets;
public List<Sheet> getSheets() {
return sheets;
}
public void setSheets(List<Sheet> sheets) {
this.sheets = sheets;
}
public SheetMeta() {
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
SheetMeta sheetMeta = (SheetMeta) o;
return Objects.equals(sheets, sheetMeta.sheets);
}
@Override
public int hashCode() {
return Objects.hashCode(sheets);
}
@Override
public String toString() {
return "SheetMeta{" +
"sheets=" + sheets +
'}';
}
}

@ -0,0 +1,38 @@
package cn.isliu.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class TableData {
private List<TableRow> rows = new ArrayList<>();
public List<TableRow> getRows() {
return rows;
}
public void setRows(List<TableRow> rows) {
this.rows = rows;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
TableData tableData = (TableData) o;
return Objects.equals(rows, tableData.rows);
}
@Override
public int hashCode() {
return Objects.hashCode(rows);
}
public TableData(List<TableRow> rows) {
this.rows = rows;
}
public TableData() {
}
}

@ -0,0 +1,43 @@
package cn.isliu.core;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public class TableRow {
private List<Cell> cells = new ArrayList<>();
public List<Cell> getCells() {
return cells;
}
public void setCells(List<Cell> cells) {
this.cells = cells;
}
public TableRow(List<Cell> cells) {
this.cells = cells;
}
public TableRow() {
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
TableRow tableRow = (TableRow) o;
return Objects.equals(cells, tableRow.cells);
}
@Override
public int hashCode() {
return Objects.hashCode(cells);
}
@Override
public String toString() {
return "TableRow{" +
"cells=" + cells +
'}';
}
}

@ -0,0 +1,76 @@
package cn.isliu.core;
import java.util.List;
import java.util.Objects;
public class ValueRange {
private String majorDimension;
private String range;
private int revision;
private List<List<Object>> values;
public ValueRange() {
}
public ValueRange(String majorDimension, String range, int revision, List<List<Object>> values) {
this.majorDimension = majorDimension;
this.range = range;
this.revision = revision;
this.values = values;
}
public String getMajorDimension() {
return majorDimension;
}
public void setMajorDimension(String majorDimension) {
this.majorDimension = majorDimension;
}
public String getRange() {
return range;
}
public void setRange(String range) {
this.range = range;
}
public int getRevision() {
return revision;
}
public void setRevision(int revision) {
this.revision = revision;
}
public List<List<Object>> getValues() {
return values;
}
public void setValues(List<List<Object>> values) {
this.values = values;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ValueRange that = (ValueRange) o;
return revision == that.revision && Objects.equals(majorDimension, that.majorDimension) && Objects.equals(range, that.range) && Objects.equals(values, that.values);
}
@Override
public int hashCode() {
return Objects.hash(majorDimension, range, revision, values);
}
@Override
public String toString() {
return "ValueRange{" +
"majorDimension='" + majorDimension + '\'' +
", range='" + range + '\'' +
", revision=" + revision +
", values=" + values +
'}';
}
}

@ -0,0 +1,76 @@
package cn.isliu.core;
import java.util.List;
import java.util.Objects;
public class ValuesBatch {
private int revision;
private String spreadsheetToken;
private int totalCells;
private List<ValueRange> valueRanges;
public ValuesBatch() {
}
public ValuesBatch(int revision, String spreadsheetToken, int totalCells, List<ValueRange> valueRanges) {
this.revision = revision;
this.spreadsheetToken = spreadsheetToken;
this.totalCells = totalCells;
this.valueRanges = valueRanges;
}
public int getRevision() {
return revision;
}
public void setRevision(int revision) {
this.revision = revision;
}
public String getSpreadsheetToken() {
return spreadsheetToken;
}
public void setSpreadsheetToken(String spreadsheetToken) {
this.spreadsheetToken = spreadsheetToken;
}
public int getTotalCells() {
return totalCells;
}
public void setTotalCells(int totalCells) {
this.totalCells = totalCells;
}
public List<ValueRange> getValueRanges() {
return valueRanges;
}
public void setValueRanges(List<ValueRange> valueRanges) {
this.valueRanges = valueRanges;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ValuesBatch that = (ValuesBatch) o;
return revision == that.revision && totalCells == that.totalCells && Objects.equals(spreadsheetToken, that.spreadsheetToken) && Objects.equals(valueRanges, that.valueRanges);
}
@Override
public int hashCode() {
return Objects.hash(revision, spreadsheetToken, totalCells, valueRanges);
}
@Override
public String toString() {
return "ValuesBatch{" +
"revision=" + revision +
", spreadsheetToken='" + spreadsheetToken + '\'' +
", totalCells=" + totalCells +
", valueRanges=" + valueRanges +
'}';
}
}

@ -0,0 +1,28 @@
package cn.isliu.core.annotation;
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 java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface TableProperty {
String value() default "";
String field() default "";
int order() default Integer.MAX_VALUE;
TypeEnum type() default TypeEnum.TEXT;
Class<? extends BaseEnum> enumClass() default BaseEnum.class;
Class<? extends FieldValueProcess> fieldFormatClass() default FieldValueProcess.class;
Class<? extends OptionsValueProcess> optionsClass() default OptionsValueProcess.class;
}

@ -0,0 +1,99 @@
package cn.isliu.core.client;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.lark.oapi.core.utils.Jsons;
import okhttp3.*;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 飞书API客户端抽象类 提供基础的HTTP请求处理和认证逻辑
*/
public abstract class FeishuApiClient {
protected final FeishuClient feishuClient;
protected final OkHttpClient httpClient;
protected final Gson gson;
protected static final String BASE_URL = "https://open.feishu.cn/open-apis";
protected static final MediaType JSON_MEDIA_TYPE = MediaType.parse("application/json; charset=utf-8");
/**
* 构造函数
*
* @param feishuClient 飞书客户端
*/
public FeishuApiClient(FeishuClient feishuClient) {
this.feishuClient = feishuClient;
this.httpClient = feishuClient.getHttpClient();
this.gson = Jsons.DEFAULT;
}
/**
* 获取租户访问令牌
*
* @return 访问令牌
* @throws IOException 请求异常
*/
protected String getTenantAccessToken() throws IOException {
Map<String, String> params = new HashMap<>();
params.put("app_id", feishuClient.getAppId());
params.put("app_secret", feishuClient.getAppSecret());
RequestBody body = RequestBody.create(gson.toJson(params), JSON_MEDIA_TYPE);
Request request =
new Request.Builder().url(BASE_URL + "/auth/v3/tenant_access_token/internal").post(body).build();
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful() || response.body() == null) {
throw new IOException("Failed to get tenant access token: " + response);
}
JsonObject jsonResponse = gson.fromJson(response.body().string(), JsonObject.class);
if (jsonResponse.has("tenant_access_token")) {
return jsonResponse.get("tenant_access_token").getAsString();
} else {
throw new IOException("Invalid token response: " + jsonResponse);
}
}
}
/**
* 构建带认证的请求
*
* @param url 请求URL
* @param method HTTP方法
* @param body 请求体
* @return 请求构建器
* @throws IOException 认证异常
*/
protected Request.Builder createAuthenticatedRequest(String url, String method, RequestBody body)
throws IOException {
String token = getTenantAccessToken();
return new Request.Builder().url(url).header("Authorization", "Bearer " + token)
.header("Content-Type", "application/json; charset=utf-8").method(method, body);
}
/**
* 执行请求并处理响应
*
* @param request 请求对象
* @param responseClass 响应类型
* @param <T> 响应类型
* @return 响应对象
* @throws IOException 请求异常
*/
protected <T> T executeRequest(Request request, Class<T> responseClass) throws IOException {
try (Response response = httpClient.newCall(request).execute()) {
if (!response.isSuccessful() || response.body() == null) {
throw new IOException("Request failed: " + response);
}
String responseBody = response.body().string();
return gson.fromJson(responseBody, responseClass);
}
}
}

@ -0,0 +1,266 @@
package cn.isliu.core.client;
import cn.isliu.core.service.*;
import com.lark.oapi.Client;
import com.lark.oapi.core.enums.AppType;
import com.lark.oapi.service.drive.DriveService;
import com.lark.oapi.service.sheets.SheetsService;
import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import java.util.concurrent.TimeUnit;
/**
* 飞书扩展客户端 封装官方SDK客户端并提供额外API支持
*/
public class FeishuClient {
private final Client officialClient;
private final OkHttpClient httpClient;
private final String appId;
private final String appSecret;
// 自定义服务处理官方SDK未覆盖的API
private volatile CustomSheetService customSheetService;
private volatile CustomDimensionService customDimensionService;
private volatile CustomCellService customCellService;
private volatile CustomValueService customValueService;
private volatile CustomDataValidationService customDataValidationService;
private volatile CustomProtectedDimensionService customProtectedDimensionService;
private FeishuClient(String appId, String appSecret, Client officialClient, OkHttpClient httpClient) {
this.appId = appId;
this.appSecret = appSecret;
this.officialClient = officialClient;
this.httpClient = httpClient;
}
/**
* 创建客户端构建器
*
* @param appId 应用ID
* @param appSecret 应用密钥
* @return 构建器
*/
public static Builder newBuilder(String appId, String appSecret) {
return new Builder(appId, appSecret);
}
/**
* 获取官方表格服务
*
* @return 官方表格服务
*/
public SheetsService sheets() {
return officialClient.sheets();
}
/**
* 获取官方驱动服务
*
* @return 官方驱动服务
*/
public DriveService drive() {
return officialClient.drive();
}
/**
* 获取扩展表格服务
*
* @return 扩展表格服务
*/
public CustomSheetService customSheets() {
if (customSheetService == null) {
synchronized (this) {
if (customSheetService == null) {
customSheetService = new CustomSheetService(this);
}
}
}
return customSheetService;
}
/**
* 获取扩展行列服务
*
* @return 扩展行列服务
*/
public CustomDimensionService customDimensions() {
if (customDimensionService == null) {
synchronized (this) {
if (customDimensionService == null) {
customDimensionService = new CustomDimensionService(this);
}
}
}
return customDimensionService;
}
/**
* 获取扩展单元格服务
*
* @return 扩展单元格服务
*/
public CustomCellService customCells() {
if (customCellService == null) {
synchronized (this) {
if (customCellService == null) {
customCellService = new CustomCellService(this);
}
}
}
return customCellService;
}
/**
* 获取扩展数据值服务
*
* @return 扩展数据值服务
*/
public CustomValueService customValues() {
if (customValueService == null) {
synchronized (this) {
if (customValueService == null) {
customValueService = new CustomValueService(this);
}
}
}
return customValueService;
}
/**
* 获取自定义数据验证服务
*
* @return 自定义数据验证服务
*/
public CustomDataValidationService customDataValidations() {
if (customDataValidationService == null) {
synchronized (this) {
if (customDataValidationService == null) {
customDataValidationService = new CustomDataValidationService(this);
}
}
}
return customDataValidationService;
}
/**
* 获取扩展保护范围服务
*
* @return 扩展保护范围服务
*/
public CustomProtectedDimensionService customProtectedDimensions() {
if (customProtectedDimensionService == null) {
synchronized (this) {
if (customProtectedDimensionService == null) {
customProtectedDimensionService = new CustomProtectedDimensionService(this);
}
}
}
return customProtectedDimensionService;
}
/**
* 获取官方客户端
*
* @return 官方Client实例
*/
public Client getOfficialClient() {
return officialClient;
}
/**
* 获取HTTP客户端
*
* @return OkHttp客户端实例
*/
OkHttpClient getHttpClient() {
return httpClient;
}
/**
* 获取应用ID
*
* @return 应用ID
*/
public String getAppId() {
return appId;
}
/**
* 获取应用密钥
*
* @return 应用密钥
*/
public String getAppSecret() {
return appSecret;
}
/**
* FeishuClient构建器
*/
public static class Builder {
private final String appId;
private final String appSecret;
private OkHttpClient.Builder httpClientBuilder;
private AppType appType = AppType.SELF_BUILT;
private boolean logReqAtDebug = false;
private Builder(String appId, String appSecret) {
this.appId = appId;
this.appSecret = appSecret;
// 默认OkHttp配置
this.httpClientBuilder =
new OkHttpClient.Builder().connectTimeout(10, TimeUnit.MINUTES).readTimeout(10, TimeUnit.MINUTES)
.writeTimeout(10, TimeUnit.MINUTES).connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES));
}
/**
* 配置HTTP客户端
*
* @param builder OkHttp客户端构建器
* @return 当前构建器
*/
public Builder httpClient(OkHttpClient.Builder builder) {
this.httpClientBuilder = builder;
return this;
}
/**
* 设置应用类型
*
* @param appType 应用类型
* @return 当前构建器
*/
public Builder appType(AppType appType) {
this.appType = appType;
return this;
}
/**
* 是否在debug级别打印请求
*
* @param logReqAtDebug 是否打印
* @return 当前构建器
*/
public Builder logReqAtDebug(boolean logReqAtDebug) {
this.logReqAtDebug = logReqAtDebug;
return this;
}
/**
* 构建FeishuClient实例
*
* @return FeishuClient实例
*/
public FeishuClient build() {
// 构建官方Client
Client officialClient =
Client.newBuilder(appId, appSecret).appType(appType).logReqAtDebug(logReqAtDebug).build();
// 构建OkHttpClient
OkHttpClient httpClient = httpClientBuilder.build();
return new FeishuClient(appId, appSecret, officialClient, httpClient);
}
}
}

@ -0,0 +1,56 @@
package cn.isliu.core.config;
import cn.isliu.core.client.FeishuClient;
import cn.isliu.core.utils.FsClientUtil;
public class FsConfig {
public static int headLine = 1;
public static int titleLine = 1;
public static boolean isCover = false;
public static boolean CELL_TEXT = false;
public static String FORE_COLOR = "#000000";
public static String BACK_COLOR = "#d5d5d5";
public static void initConfig(String appId, String appSecret) {
FsClientUtil.initFeishuClient(appId, appSecret);
}
public static void initConfig(int headLine, int titleLine, String appId, String appSecret) {
FsConfig.headLine = headLine;
FsConfig.titleLine = titleLine;
FsClientUtil.initFeishuClient(appId, appSecret);
}
public static void initConfig(int headLine, FeishuClient client) {
FsConfig.headLine = headLine;
FsClientUtil.client = client;
}
public static void initConfig(FeishuClient client) {
FsClientUtil.client = client;
}
public static int getHeadLine() {
return headLine;
}
public static int getTitleLine() {
return titleLine;
}
public static FeishuClient getFeishuClient() {
return FsClientUtil.client;
}
public static void setHeadLine(int headLine) {
FsConfig.headLine = headLine;
}
public static void setTitleLine(int titleLine) {
FsConfig.titleLine = titleLine;
}
}

@ -0,0 +1,11 @@
package cn.isliu.core.converters;
public interface FieldValueProcess<T> {
T process(Object value);
/**
* 反向处理将枚举值转换为原始值
*/
T reverseProcess(Object value);
}

@ -0,0 +1,128 @@
package cn.isliu.core.converters;
import cn.isliu.core.utils.FsApiUtil;
import cn.isliu.core.utils.FsClientUtil;
import cn.isliu.core.utils.FileUtil;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
public class FileUrlProcess implements FieldValueProcess<String> {
private static final Logger log = Logger.getLogger(FileUrlProcess.class.getName());
@Override
public String process(Object value) {
if (value instanceof String) {
return value.toString();
}
List<String> fileUrls = new ArrayList<>();
if (value instanceof JsonArray) {
JsonArray arr = (JsonArray) value;
for (int i = 0; i < arr.size(); i++) {
JsonElement jsonElement = arr.get(i);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
String url = getUrlByTextFile(jsonObject);
fileUrls.add(url);
}
}
} else if (value instanceof JsonObject) {
JsonObject jsb = (JsonObject) value;
String url = getUrlByTextFile(jsb);
fileUrls.add(url);
}
return String.join(",", fileUrls);
}
@Override
public String reverseProcess(Object value) {
// 简单实现可以根据需要进行更复杂的反向处理
if (value == null) {
return null;
}
return value.toString();
}
private synchronized String getUrlByTextFile(JsonObject jsb) {
String url = "";
String cellType = jsb.get("type").getAsString();
switch (cellType) {
case "url":
String link = jsb.get("link").getAsString();
if (link == null) {
url = jsb.get("text").getAsString();
} else {
url = link;
}
break;
case "embed-image":
url = getImageOssUrl(jsb);
break;
case "attachment":
url = getAttachmentOssUrl(jsb);
break;
}
return url;
}
public static String getImageOssUrl(JsonObject jsb) {
String url = "";
String fileToken = jsb.get("fileToken").getAsString();
String fileUuid = UUID.randomUUID().toString();
String filePath = FileUtil.getRootPath() + File.separator + fileUuid + ".png";
boolean isSuccess = true;
try {
FsApiUtil.downloadMaterial(fileToken, filePath , FsClientUtil.getFeishuClient(), null);
url = filePath;
} catch (Exception e) {
log.log(Level.WARNING,"【飞书表格】 根据文件FileToken下载失败fileToken: {0}, e: {1}", new Object[]{fileToken, e.getMessage()});
isSuccess = false;
}
if (!isSuccess) {
String tmpUrl = FsApiUtil.downloadTmpMaterialUrl(fileToken, FsClientUtil.getFeishuClient());
// 根据临时下载地址下载
FileUtil.downloadFile(tmpUrl, filePath);
}
log.info("【飞书表格】 文件上传-飞书图片上传成功fileToken: " + fileToken + ", filePath: " + filePath);
return url;
}
public String getAttachmentOssUrl(JsonObject jsb) {
String url = "";
String token = jsb.get("fileToken").getAsString();
String fileName = jsb.get("text").getAsString();
String fileUuid = UUID.randomUUID().toString();
String path = FileUtil.getRootPath() + File.separator + fileUuid + fileName;
boolean isSuccess = true;
try {
FsApiUtil.downloadMaterial(token, path , FsClientUtil.getFeishuClient(), null);
url = path;
} catch (Exception e) {
log.log(Level.WARNING, "【飞书表格】 附件-根据文件FileToken下载失败fileToken: {0}, e: {1}", new Object[]{token, e.getMessage()});
isSuccess = false;
}
if (!isSuccess) {
String tmpUrl = FsApiUtil.downloadTmpMaterialUrl(token, FsClientUtil.getFeishuClient());
FileUtil.downloadFile(tmpUrl, path);
}
log.info("【飞书表格】 文件上传-附件上传成功fileToken: " + token + ", filePath: " + path);
return url;
}
}

@ -0,0 +1,6 @@
package cn.isliu.core.converters;
public interface OptionsValueProcess<T> {
T process();
}

@ -0,0 +1,53 @@
package cn.isliu.core.enums;
import java.util.Arrays;
public interface BaseEnum {
/**
* 获取枚举代码
*/
String getCode();
/**
* 获取枚举描述
*/
String getDesc();
/**
* 根据描述获取枚举实例
*
* @param enumClass 枚举类
* @param desc 描述
* @param <T> 枚举类型
* @return 枚举实例
*/
static <T extends BaseEnum> T getByDesc(Class<T> enumClass, Object desc) {
if (desc == null) {
return null;
}
return Arrays.stream(enumClass.getEnumConstants())
.filter(e -> e.getDesc().equals(desc.toString()))
.findFirst()
.orElse(null);
}
/**
* 根据代码获取枚举实例
*
* @param enumClass 枚举类
* @param code 代码
* @param <T> 枚举类型
* @return 枚举实例
*/
static <T extends BaseEnum> T getByCode(Class<T> enumClass, Object code) {
if (code == null) {
return null;
}
return Arrays.stream(enumClass.getEnumConstants())
.filter(e -> e.getCode().equals(code.toString()))
.findFirst()
.orElse(null);
}
}

@ -0,0 +1,41 @@
package cn.isliu.core.enums;
public enum TypeEnum {
SINGLE_SELECT("SINGLE_SELECT", "单选"),
MULTI_SELECT("MULTI_SELECT", "多选"),
TEXT("TEXT", "文本"),
NUMBER("NUMBER", "数字"),
DATE("DATE", "日期"),
TEXT_FILE("TEXT_FILE", "文本文件"),
MULTI_TEXT("MULTI_TEXT", "多个文本(逗号分割)"),
TEXT_URL("TEXT_URL", "文本链接")
;
private final String code;
private final String desc;
public String getCode() {
return code;
}
public String getDesc() {
return desc;
}
TypeEnum(String code, String desc) {
this.code = code;
this.desc = desc;
}
public static TypeEnum getByCode(String code) {
for (TypeEnum value : values()) {
if (value.getCode().equals(code)) {
return value;
}
}
return null;
}
}

@ -0,0 +1,12 @@
package cn.isliu.core.exception;
public class FsHelperException extends RuntimeException {
public FsHelperException(String message) {
super(message);
}
public FsHelperException(String message, Throwable cause) {
super(message, cause);
}
}

@ -0,0 +1,80 @@
package cn.isliu.core.pojo;
/**
* API响应基类
*
* @param <T> 响应数据类型
*/
public class ApiResponse<T> {
private int code;
private String msg;
private T data;
/**
* 无参构造函数
*/
public ApiResponse() {}
/**
* 构造函数
*
* @param code 响应码
* @param msg 响应消息
* @param data 响应数据
*/
public ApiResponse(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
/**
* 获取响应码
*
* @return 响应码
*/
public int getCode() {
return code;
}
/**
* 获取响应消息
*
* @return 响应消息
*/
public String getMsg() {
return msg;
}
/**
* 获取响应数据
*
* @return 响应数据
*/
public T getData() {
return data;
}
public void setCode(int code) {
this.code = code;
}
public void setMsg(String msg) {
this.msg = msg;
}
public void setData(T data) {
this.data = data;
}
/**
* 判断请求是否成功
*
* @return true表示成功false表示失败
*/
public boolean success() {
return code == 0;
}
}

@ -0,0 +1,65 @@
package cn.isliu.core.pojo;
import cn.isliu.core.annotation.TableProperty;
import cn.isliu.core.converters.FieldValueProcess;
import cn.isliu.core.enums.BaseEnum;
import cn.isliu.core.enums.TypeEnum;
public class FieldProperty {
private String field;
private TableProperty tableProperty;
public FieldProperty() {
}
public FieldProperty(String field, TableProperty tableProperty) {
this.field = field;
this.tableProperty = tableProperty;
}
public String getField() {
return field;
}
public void setField(String field) {
this.field = field;
}
public TableProperty getTableProperty() {
return tableProperty;
}
public void setTableProperty(TableProperty tableProperty) {
this.tableProperty = tableProperty;
}
public String getFieldField() {
return tableProperty.field();
}
public String getFieldName() {
return tableProperty.value();
}
public TypeEnum getFieldType() {
return tableProperty.type();
}
public Class<? extends FieldValueProcess> getFieldFormat() {
return tableProperty.fieldFormatClass();
}
public Class<? extends BaseEnum> getFieldEnum() {
return tableProperty.enumClass();
}
@Override
public String toString() {
return "FieldProperty{" +
"field='" + field + '\'' +
", tableProperty=" + tableProperty +
'}';
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,453 @@
package cn.isliu.core.service;
import cn.isliu.core.client.FeishuApiClient;
import cn.isliu.core.client.FeishuClient;
import cn.isliu.core.pojo.ApiResponse;
import okhttp3.Request;
import okhttp3.RequestBody;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义保护范围服务 提供保护行列的API
*/
public class CustomProtectedDimensionService extends FeishuApiClient {
/**
* 构造函数
*
* @param feishuClient 飞书客户端
*/
public CustomProtectedDimensionService(FeishuClient feishuClient) {
super(feishuClient);
}
/**
* 批量操作保护范围 支持添加保护范围 支持处理多个请求如果有请求失败则中断后续请求
*
* @param spreadsheetToken 电子表格Token
* @param request 批量操作请求
* @return 批量操作响应
* @throws IOException 请求异常
*/
public ApiResponse protectedDimensionBatchUpdate(String spreadsheetToken,
ProtectedDimensionBatchUpdateRequest request) throws IOException {
List<ProtectedDimensionRequest> requests = request.getRequests();
ApiResponse response = null;
// 如果没有请求返回空响应
if (requests == null || requests.isEmpty()) {
ApiResponse emptyResponse = new ApiResponse();
emptyResponse.setCode(400);
emptyResponse.setMsg("No protected dimension operations found");
return emptyResponse;
}
// 依次处理每个请求
for (ProtectedDimensionRequest protectedDimensionRequest : requests) {
// 处理添加保护范围请求
if (protectedDimensionRequest.getAddProtectedDimension() != null) {
String url = BASE_URL + "/sheets/v2/spreadsheets/" + spreadsheetToken + "/protected_dimension";
// 构建添加保护范围请求体
RequestBody body = RequestBody.create(
gson.toJson(
new AddProtectedDimensionRequestBody(protectedDimensionRequest.getAddProtectedDimension())),
JSON_MEDIA_TYPE);
Request httpRequest = createAuthenticatedRequest(url, "POST", body).build();
response = executeRequest(httpRequest, ApiResponse.class);
// 如果请求失败中断后续请求
if (!response.success()) {
return response;
}
}
// 这里可以添加其他保护范围操作类型
}
// 如果所有请求都成功处理返回最后一个成功的响应
// 如果没有处理任何请求(没有有效的操作类型)返回错误响应
if (response == null) {
ApiResponse errorResponse = new ApiResponse();
errorResponse.setCode(400);
errorResponse.setMsg("No valid protected dimension operation found");
return errorResponse;
}
return response;
}
/**
* 批量操作保护范围请求
*/
public static class ProtectedDimensionBatchUpdateRequest {
private List<ProtectedDimensionRequest> requests;
public ProtectedDimensionBatchUpdateRequest() {
this.requests = new ArrayList<>();
}
public List<ProtectedDimensionRequest> getRequests() {
return requests;
}
public void setRequests(List<ProtectedDimensionRequest> requests) {
this.requests = requests;
}
/**
* 创建批量操作保护范围请求的构建器
*
* @return 批量操作保护范围请求的构建器
*/
public static Builder newBuilder() {
return new Builder();
}
/**
* 批量操作保护范围请求的构建器
*/
public static class Builder {
private final ProtectedDimensionBatchUpdateRequest request;
public Builder() {
request = new ProtectedDimensionBatchUpdateRequest();
}
/**
* 添加一个保护范围操作请求
*
* @param protectedDimensionRequest 保护范围操作请求
* @return 当前构建器
*/
public Builder addRequest(ProtectedDimensionRequest protectedDimensionRequest) {
request.requests.add(protectedDimensionRequest);
return this;
}
/**
* 构建批量操作保护范围请求
*
* @return 批量操作保护范围请求
*/
public ProtectedDimensionBatchUpdateRequest build() {
return request;
}
}
}
/**
* 保护范围操作请求
*/
public static class ProtectedDimensionRequest {
private List<AddProtectedDimensionRange> addProtectedDimension;
/**
* 获取添加保护范围的信息
*
* @return 添加保护范围的信息
*/
public List<AddProtectedDimensionRange> getAddProtectedDimension() {
return addProtectedDimension;
}
/**
* 设置添加保护范围的信息
*
* @param addProtectedDimension 添加保护范围的信息
*/
public void setAddProtectedDimension(List<AddProtectedDimensionRange> addProtectedDimension) {
this.addProtectedDimension = addProtectedDimension;
}
/**
* 创建添加保护范围构建器
*
* @return 添加保护范围构建器
*/
public static AddProtectedDimensionBuilder addProtectedDimension() {
return new AddProtectedDimensionBuilder();
}
/**
* 添加保护范围构建器
*/
public static class AddProtectedDimensionBuilder {
private final ProtectedDimensionRequest request;
private final AddProtectedDimensionRange protectedDimension;
public AddProtectedDimensionBuilder() {
request = new ProtectedDimensionRequest();
protectedDimension = new AddProtectedDimensionRange();
List<AddProtectedDimensionRange> list = new ArrayList<>();
list.add(protectedDimension);
request.setAddProtectedDimension(list);
}
/**
* 设置工作表ID
*
* @param sheetId 工作表ID
* @return 当前构建器
*/
public AddProtectedDimensionBuilder sheetId(String sheetId) {
protectedDimension.getDimension().setSheetId(sheetId);
return this;
}
/**
* 设置维度方向
*
* @param majorDimension 维度方向可选值ROWSCOLUMNS
* @return 当前构建器
*/
public AddProtectedDimensionBuilder majorDimension(String majorDimension) {
protectedDimension.getDimension().setMajorDimension(majorDimension);
return this;
}
/**
* 设置开始索引
*
* @param startIndex 开始索引从1开始计数
* @return 当前构建器
*/
public AddProtectedDimensionBuilder startIndex(Integer startIndex) {
protectedDimension.getDimension().setStartIndex(startIndex);
return this;
}
/**
* 设置结束索引
*
* @param endIndex 结束索引从1开始计数
* @return 当前构建器
*/
public AddProtectedDimensionBuilder endIndex(Integer endIndex) {
protectedDimension.getDimension().setEndIndex(endIndex);
return this;
}
/**
* 添加一个允许编辑的用户ID
*
* @param userId 用户ID
* @return 当前构建器
*/
public AddProtectedDimensionBuilder addUser(String userId) {
protectedDimension.getUsers().add(userId);
return this;
}
/**
* 设置多个允许编辑的用户ID
*
* @param userIds 用户ID列表
* @return 当前构建器
*/
public AddProtectedDimensionBuilder users(List<String> userIds) {
protectedDimension.setUsers(userIds);
return this;
}
/**
* 设置保护范围的备注信息
*
* @param lockInfo 备注信息
* @return 当前构建器
*/
public AddProtectedDimensionBuilder lockInfo(String lockInfo) {
protectedDimension.setLockInfo(lockInfo);
return this;
}
/**
* 构建保护范围请求
*
* @return 保护范围请求
*/
public ProtectedDimensionRequest build() {
return request;
}
}
}
/**
* 添加保护范围信息
*/
public static class AddProtectedDimensionRange {
private DimensionRange dimension;
private List<String> users;
private String lockInfo;
public AddProtectedDimensionRange() {
this.dimension = new DimensionRange();
this.users = new ArrayList<>();
}
/**
* 获取维度范围
*
* @return 维度范围
*/
public DimensionRange getDimension() {
return dimension;
}
/**
* 设置维度范围
*
* @param dimension 维度范围
*/
public void setDimension(DimensionRange dimension) {
this.dimension = dimension;
}
/**
* 获取允许编辑的用户ID列表
*
* @return 用户ID列表
*/
public List<String> getUsers() {
return users;
}
/**
* 设置允许编辑的用户ID列表
*
* @param users 用户ID列表
*/
public void setUsers(List<String> users) {
this.users = users;
}
/**
* 获取保护范围的备注信息
*
* @return 备注信息
*/
public String getLockInfo() {
return lockInfo;
}
/**
* 设置保护范围的备注信息
*
* @param lockInfo 备注信息
*/
public void setLockInfo(String lockInfo) {
this.lockInfo = lockInfo;
}
}
/**
* 维度范围
*/
public static class DimensionRange {
private String sheetId;
private String majorDimension;
private Integer startIndex;
private Integer endIndex;
/**
* 获取工作表ID
*
* @return 工作表ID
*/
public String getSheetId() {
return sheetId;
}
/**
* 设置工作表ID
*
* @param sheetId 工作表ID
*/
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
/**
* 获取维度方向
*
* @return 维度方向ROWS或COLUMNS
*/
public String getMajorDimension() {
return majorDimension;
}
/**
* 设置维度方向
*
* @param majorDimension 维度方向ROWS或COLUMNS
*/
public void setMajorDimension(String majorDimension) {
this.majorDimension = majorDimension;
}
/**
* 获取开始索引
*
* @return 开始索引从1开始计数
*/
public Integer getStartIndex() {
return startIndex;
}
/**
* 设置开始索引
*
* @param startIndex 开始索引从1开始计数
*/
public void setStartIndex(Integer startIndex) {
this.startIndex = startIndex;
}
/**
* 获取结束索引
*
* @return 结束索引从1开始计数
*/
public Integer getEndIndex() {
return endIndex;
}
/**
* 设置结束索引
*
* @param endIndex 结束索引从1开始计数
*/
public void setEndIndex(Integer endIndex) {
this.endIndex = endIndex;
}
}
/**
* 添加保护范围请求体
*/
private static class AddProtectedDimensionRequestBody {
private final List<AddProtectedDimensionRange> addProtectedDimension;
/**
* 构造函数
*
* @param addProtectedDimension 添加保护范围信息列表
*/
public AddProtectedDimensionRequestBody(List<AddProtectedDimensionRange> addProtectedDimension) {
this.addProtectedDimension = addProtectedDimension;
}
/**
* 获取添加保护范围信息列表
*
* @return 添加保护范围信息列表
*/
public List<AddProtectedDimensionRange> getAddProtectedDimension() {
return addProtectedDimension;
}
}
}

@ -0,0 +1,870 @@
package cn.isliu.core.service;
import cn.isliu.core.client.FeishuApiClient;
import cn.isliu.core.client.FeishuClient;
import cn.isliu.core.pojo.ApiResponse;
import okhttp3.Request;
import okhttp3.RequestBody;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 自定义表格服务 提供官方SDK未覆盖的表格API
*/
public class CustomSheetService extends FeishuApiClient {
/**
* 构造函数
*
* @param feishuClient 飞书客户端
*/
public CustomSheetService(FeishuClient feishuClient) {
super(feishuClient);
}
/**
* 批量更新工作表 支持添加复制删除等操作
*
* @param spreadsheetToken 电子表格Token
* @param request 批量更新请求
* @return 批量更新响应
* @throws IOException 请求异常
*/
public ApiResponse sheetsBatchUpdate(String spreadsheetToken, SheetBatchUpdateRequest request)
throws IOException {
List<SheetRequest> requests = request.getRequests();
// 更新工作表特殊字段兼容
String userIdType = null;
for (SheetRequest sheetRequest : requests) {
UpdateSheetRequest updateSheet = sheetRequest.getUpdateSheet();
if (updateSheet != null) {
SheetPropertiesUpdate properties = updateSheet.getProperties();
userIdType = properties.getUserIdType();
break;
}
}
String url = BASE_URL + "/sheets/v2/spreadsheets/" + spreadsheetToken + "/sheets_batch_update";
if (userIdType != null && !userIdType.isEmpty()) {
url += "?user_id_type=" + userIdType;
}
RequestBody body = RequestBody.create(gson.toJson(request), JSON_MEDIA_TYPE);
Request httpRequest = createAuthenticatedRequest(url, "POST", body).build();
return executeRequest(httpRequest, ApiResponse.class);
}
/**
* 批量更新工作表请求
*/
public static class SheetBatchUpdateRequest {
/**
* 支持增加复制删除和更新工作表 一次请求可以同时进行多个操作
*/
private List<SheetRequest> requests;
public SheetBatchUpdateRequest() {
this.requests = new ArrayList<>();
}
public List<SheetRequest> getRequests() {
return requests;
}
public void setRequests(List<SheetRequest> requests) {
this.requests = requests;
}
public static Builder newBuilder() {
return new Builder();
}
public static class Builder {
private final SheetBatchUpdateRequest request;
public Builder() {
request = new SheetBatchUpdateRequest();
}
public Builder addRequest(SheetRequest sheetRequest) {
request.requests.add(sheetRequest);
return this;
}
public SheetBatchUpdateRequest build() {
return request;
}
}
}
/**
* 工作表请求
*/
public static class SheetRequest {
/**
* 增加工作表操作
*/
private AddSheetRequest addSheet;
/**
* 复制工作表操作
*/
private CopySheetRequest copySheet;
/**
* 删除工作表操作
*/
private DeleteSheetRequest deleteSheet;
/**
* 更新工作表操作
*/
private UpdateSheetRequest updateSheet;
public AddSheetRequest getAddSheet() {
return addSheet;
}
public void setAddSheet(AddSheetRequest addSheet) {
this.addSheet = addSheet;
}
public CopySheetRequest getCopySheet() {
return copySheet;
}
public void setCopySheet(CopySheetRequest copySheet) {
this.copySheet = copySheet;
}
public DeleteSheetRequest getDeleteSheet() {
return deleteSheet;
}
public void setDeleteSheet(DeleteSheetRequest deleteSheet) {
this.deleteSheet = deleteSheet;
}
public UpdateSheetRequest getUpdateSheet() {
return updateSheet;
}
public void setUpdateSheet(UpdateSheetRequest updateSheet) {
this.updateSheet = updateSheet;
}
/**
* 创建添加工作表的请求构建器 用于在电子表格中添加新的工作表
*
* @return 添加工作表的构建器
*/
public static AddSheetBuilder addSheet() {
return new AddSheetBuilder();
}
/**
* 创建复制工作表的请求构建器 用于复制已有的工作表到同一电子表格中
*
* @return 复制工作表的构建器
*/
public static CopySheetBuilder copySheet() {
return new CopySheetBuilder();
}
/**
* 创建删除工作表的请求构建器 用于删除电子表格中的指定工作表
*
* @return 删除工作表的构建器
*/
public static DeleteSheetBuilder deleteSheet() {
return new DeleteSheetBuilder();
}
/**
* 创建更新工作表的请求构建器 用于更新工作表的标题位置显示状态冻结行列保护设置等属性
*
* @return 更新工作表的构建器
*/
public static UpdateSheetBuilder updateSheet() {
return new UpdateSheetBuilder();
}
/**
* 添加工作表的构建器 用于构建添加新工作表的请求
*/
public static class AddSheetBuilder {
private final SheetRequest request;
private final AddSheetRequest addSheet;
public AddSheetBuilder() {
request = new SheetRequest();
addSheet = new AddSheetRequest();
request.setAddSheet(addSheet);
}
/**
* 获取或初始化properties对象
*
* @return properties对象
*/
private SheetProperties getProperties() {
if (addSheet.properties == null) {
addSheet.properties = new SheetProperties();
}
return addSheet.properties;
}
/**
* 设置工作表标题
*
* @param title 工作表标题不能包含特殊字符/ \ ? * [ ] :
* @return 当前构建器
*/
public AddSheetBuilder title(String title) {
getProperties().title = title;
return this;
}
/**
* 设置工作表在电子表格中的位置索引
*
* @param index 位置索引从0开始计数不填默认在第0位置添加
* @return 当前构建器
*/
public AddSheetBuilder index(Integer index) {
getProperties().index = index;
return this;
}
/**
* 构建添加工作表请求
*
* @return 工作表操作请求
*/
public SheetRequest build() {
return request;
}
}
/**
* 复制工作表的构建器 用于构建复制现有工作表的请求
*/
public static class CopySheetBuilder {
private final SheetRequest request;
private final CopySheetRequest copySheet;
public CopySheetBuilder() {
request = new SheetRequest();
copySheet = new CopySheetRequest();
request.setCopySheet(copySheet);
}
/**
* 获取或初始化source对象
*
* @return source对象
*/
private SheetSource getSource() {
if (copySheet.source == null) {
copySheet.source = new SheetSource();
}
return copySheet.source;
}
/**
* 获取或初始化destination对象
*
* @return destination对象
*/
private SheetDestination getDestination() {
if (copySheet.destination == null) {
copySheet.destination = new SheetDestination();
}
return copySheet.destination;
}
/**
* 设置源工作表ID
*
* @param sheetId 要复制的源工作表ID
* @return 当前构建器
*/
public CopySheetBuilder sourceSheetId(String sheetId) {
getSource().sheetId = sheetId;
return this;
}
/**
* 设置目标工作表标题
*
* @param title 复制后的工作表标题不填则默认为"源工作表名称(副本_源工作表的index值)"
* @return 当前构建器
*/
public CopySheetBuilder destinationTitle(String title) {
getDestination().title = title;
return this;
}
/**
* 构建复制工作表请求
*
* @return 工作表操作请求
*/
public SheetRequest build() {
return request;
}
}
/**
* 删除工作表的构建器 用于构建删除工作表的请求
*/
public static class DeleteSheetBuilder {
private final SheetRequest request;
private final DeleteSheetRequest deleteSheet;
public DeleteSheetBuilder() {
request = new SheetRequest();
deleteSheet = new DeleteSheetRequest();
request.setDeleteSheet(deleteSheet);
}
/**
* 设置要删除的工作表ID
*
* @param sheetId 要删除的工作表ID
* @return 当前构建器
*/
public DeleteSheetBuilder sheetId(String sheetId) {
deleteSheet.sheetId = sheetId;
return this;
}
/**
* 构建删除工作表请求
*
* @return 工作表操作请求
*/
public SheetRequest build() {
return request;
}
}
/**
* 更新工作表的构建器 用于构建更新工作表属性的请求
*/
public static class UpdateSheetBuilder {
private final SheetRequest request;
private final UpdateSheetRequest updateSheet;
public UpdateSheetBuilder() {
request = new SheetRequest();
updateSheet = new UpdateSheetRequest();
request.setUpdateSheet(updateSheet);
}
/**
* 获取或初始化properties对象
*
* @return properties对象
*/
private SheetPropertiesUpdate getProperties() {
if (updateSheet.properties == null) {
updateSheet.properties = new SheetPropertiesUpdate();
}
return updateSheet.properties;
}
/**
* 设置要更新的工作表ID
*
* @param sheetId 工作表ID
* @return 当前构建器
*/
public UpdateSheetBuilder sheetId(String sheetId) {
getProperties().sheetId = sheetId;
return this;
}
/**
* 设置工作表标题
*
* @param title 工作表标题不能包含特殊字符/ \ ? * [ ] :
* @return 当前构建器
*/
public UpdateSheetBuilder title(String title) {
getProperties().title = title;
return this;
}
/**
* 设置工作表在电子表格中的位置索引
*
* @param index 位置索引从0开始计数
* @return 当前构建器
*/
public UpdateSheetBuilder index(Integer index) {
getProperties().index = index;
return this;
}
/**
* 设置工作表是否隐藏
*
* @param hidden true表示隐藏false表示显示
* @return 当前构建器
*/
public UpdateSheetBuilder hidden(Boolean hidden) {
getProperties().hidden = hidden;
return this;
}
/**
* 设置冻结行数
*
* @param frozenRowCount 冻结的行数0表示取消冻结
* @return 当前构建器
*/
public UpdateSheetBuilder frozenRowCount(Integer frozenRowCount) {
getProperties().frozenRowCount = frozenRowCount;
return this;
}
/**
* 设置冻结列数
*
* @param frozenColCount 冻结的列数0表示取消冻结
* @return 当前构建器
*/
public UpdateSheetBuilder frozenColCount(Integer frozenColCount) {
getProperties().frozenColCount = frozenColCount;
return this;
}
/**
* 设置用户ID类型
*
* @param userIdType 用户ID类型建议使用open_id或union_id可选值open_idunion_id
* @return 当前构建器
*/
public UpdateSheetBuilder userIdType(String userIdType) {
getProperties().userIdType = userIdType;
return this;
}
/**
* 设置工作表保护
*
* @param lock 是否要保护该工作表可选值LOCK保护UNLOCK取消保护
* @param lockInfo 保护工作表的备注信息
* @param userIDs 有编辑权限的用户ID列表
* @return 当前构建器
*/
public UpdateSheetBuilder protect(String lock, String lockInfo, List<String> userIDs) {
SheetPropertiesUpdate properties = getProperties();
if (properties.protect == null) {
properties.protect = new SheetProtect();
}
properties.protect.lock = lock;
properties.protect.lockInfo = lockInfo;
properties.protect.userIDs = userIDs;
return this;
}
/**
* 构建更新工作表请求
*
* @return 工作表操作请求
*/
public SheetRequest build() {
return request;
}
}
}
/**
* 添加工作表请求
*/
public static class AddSheetRequest {
/**
* 工作表属性
*/
private SheetProperties properties;
public SheetProperties getProperties() {
return properties;
}
public void setProperties(SheetProperties properties) {
this.properties = properties;
}
}
/**
* 工作表属性
*/
public static class SheetProperties {
/**
* 新增工作表的标题 必填字段
*/
private String title;
/**
* 新增工作表的位置 不填默认在工作表的第0索引位置增加工作表
*/
private Integer index;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
}
/**
* 复制工作表请求
*/
public static class CopySheetRequest {
/**
* 需要复制的工作表资源
*/
private SheetSource source;
/**
* 新工作表的属性
*/
private SheetDestination destination;
public SheetSource getSource() {
return source;
}
public void setSource(SheetSource source) {
this.source = source;
}
public SheetDestination getDestination() {
return destination;
}
public void setDestination(SheetDestination destination) {
this.destination = destination;
}
}
/**
* 工作表源
*/
public static class SheetSource {
/**
* 源工作表的ID 必填字段
*/
private String sheetId;
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
}
/**
* 工作表目标
*/
public static class SheetDestination {
/**
* 新工作表名称 不填默认为"源工作表名称"+"(副本_源工作表的index值)""Sheet1(副本_0)"
*/
private String title;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
/**
* 删除工作表请求
*/
public static class DeleteSheetRequest {
/**
* 要删除的工作表的ID 必填字段
*/
private String sheetId;
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
}
/**
* 更新工作表请求
*/
public static class UpdateSheetRequest {
/**
* 工作表属性
*/
private SheetPropertiesUpdate properties;
public SheetPropertiesUpdate getProperties() {
return properties;
}
public void setProperties(SheetPropertiesUpdate properties) {
this.properties = properties;
}
}
/**
* 工作表属性更新
*/
public static class SheetPropertiesUpdate {
/**
* 要更新的工作表的ID 必填字段
*/
private String sheetId;
/**
* 工作表的标题 更新的标题需符合以下规则 长度不超过100个字符 不包含这些特殊字符/ \ ? * [ ] :
*/
private String title;
/**
* 工作表的位置 从0开始计数
*/
private Integer index;
/**
* 是否要隐藏表格 默认值为false
*/
private Boolean hidden;
/**
* 要冻结至指定行的行索引 若填3表示从第一行冻结至第三行 小于或等于工作表的最大行数0表示取消冻结行
*/
private Integer frozenRowCount;
/**
* 要冻结至指定列的列索引 若填3表示从第一列冻结至第三列 小于等于工作表的最大列数0表示取消冻结列
*/
private Integer frozenColCount;
/**
* 工作表保护设置
*/
private SheetProtect protect;
/**
* 用户ID类型 影响protect.userIDs字段的ID类型 用户 ID 类型默认为 lark_id建议选择 open_id union_id了解更多参考用户身份概述可选值
* open_id标识一个用户在某个应用中的身份同一个用户在不同应用中的 Open ID 不同 union_id标识一个用户在某个应用开发商下的身份同一用户在同一开发商下的应用中的 Union ID
* 是相同的在不同开发商下的应用中的 Union ID 是不同的通过 Union ID应用开发商可以把同个用户在多个应用中的身份关联起来
*/
private String userIdType;
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
public Boolean getHidden() {
return hidden;
}
public void setHidden(Boolean hidden) {
this.hidden = hidden;
}
public Integer getFrozenRowCount() {
return frozenRowCount;
}
public void setFrozenRowCount(Integer frozenRowCount) {
this.frozenRowCount = frozenRowCount;
}
public Integer getFrozenColCount() {
return frozenColCount;
}
public void setFrozenColCount(Integer frozenColCount) {
this.frozenColCount = frozenColCount;
}
public SheetProtect getProtect() {
return protect;
}
public void setProtect(SheetProtect protect) {
this.protect = protect;
}
public String getUserIdType() {
return userIdType;
}
public void setUserIdType(String userIdType) {
this.userIdType = userIdType;
}
}
/**
* 工作表保护设置
*/
public static class SheetProtect {
/**
* 是否要保护该工作表 必填字段 可选值 LOCK保护 UNLOCK取消保护
*/
private String lock;
/**
* 保护工作表的备注信息
*/
private String lockInfo;
/**
* 添加除操作用户与所有者外其他用户的ID 为其开通保护范围的编辑权限 ID类型由查询参数user_id_type决定 user_id_type不为空时该字段生效
*/
private List<String> userIDs;
public String getLock() {
return lock;
}
public void setLock(String lock) {
this.lock = lock;
}
public String getLockInfo() {
return lockInfo;
}
public void setLockInfo(String lockInfo) {
this.lockInfo = lockInfo;
}
public List<String> getUserIDs() {
return userIDs;
}
public void setUserIDs(List<String> userIDs) {
this.userIDs = userIDs;
}
}
/**
* 工作表
*/
public static class Sheet {
private String sheetId;
private String title;
private Integer index;
private Integer rowCount;
private Integer columnCount;
private Boolean hidden;
private Integer frozenRowCount;
private Integer frozenColCount;
public String getSheetId() {
return sheetId;
}
public void setSheetId(String sheetId) {
this.sheetId = sheetId;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Integer getIndex() {
return index;
}
public void setIndex(Integer index) {
this.index = index;
}
public Integer getRowCount() {
return rowCount;
}
public void setRowCount(Integer rowCount) {
this.rowCount = rowCount;
}
public Integer getColumnCount() {
return columnCount;
}
public void setColumnCount(Integer columnCount) {
this.columnCount = columnCount;
}
public Boolean getHidden() {
return hidden;
}
public void setHidden(Boolean hidden) {
this.hidden = hidden;
}
public Integer getFrozenRowCount() {
return frozenRowCount;
}
public void setFrozenRowCount(Integer frozenRowCount) {
this.frozenRowCount = frozenRowCount;
}
public Integer getFrozenColCount() {
return frozenColCount;
}
public void setFrozenColCount(Integer frozenColCount) {
this.frozenColCount = frozenColCount;
}
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,235 @@
package cn.isliu.core.utils;
import cn.isliu.core.annotation.TableProperty;
import cn.isliu.core.converters.FieldValueProcess;
import cn.isliu.core.enums.BaseEnum;
import cn.isliu.core.enums.TypeEnum;
import cn.isliu.core.pojo.FieldProperty;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.lang.reflect.InvocationTargetException;
public class ConvertFieldUtil {
private static final Logger log = Logger.getLogger(ConvertFieldUtil.class.getName());
private static final Gson gson = new Gson();
public static Map<String, Object> convertPositionToField(JsonObject jsonObject, Map<String, FieldProperty> fieldsMap) {
Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
String positionKey = entry.getKey();
FieldProperty fieldProperty = fieldsMap.get(positionKey);
if (fieldProperty == null) continue;
String fieldKey = fieldProperty.getField();
TableProperty tableProperty = fieldProperty.getTableProperty();
Object value = getValueByFieldRule(tableProperty, entry.getValue());
if (fieldKey != null) {
// 根据配置获取值
result.put(fieldKey, value);
} else {
// 未找到对应配置项时保持原键可选
result.put(positionKey, value);
}
}
return result;
}
private static Object getValueByFieldRule(TableProperty tableProperty, JsonElement value) {
if (tableProperty == null || value == null || value.isJsonNull()) {
return null;
}
Object result = null;
TypeEnum typeEnum = tableProperty.type();
if (Objects.nonNull(typeEnum)) {
switch (typeEnum) {
case TEXT:
case NUMBER:
case DATE:
// 直接获取值避免额外的引号
result = getJsonValue(value);
break;
case SINGLE_SELECT:
List<String> arr = parseStrToArr(value);
result = conversionValue(tableProperty, arr.get(0));
break;
case MULTI_TEXT:
result = parseStrToArr(value);
break;
case MULTI_SELECT:
List<String> values = parseStrToArr(value);
result = values.stream()
.map(v -> conversionValue(tableProperty, v)).collect(Collectors.toList());
break;
case TEXT_URL:
result = getTextUrl(value);
break;
case TEXT_FILE:
result = conversionValue(tableProperty, getJsonValue(value));
break;
}
}
return result;
}
private static Object getTextUrl(JsonElement value) {
if (value instanceof JsonArray) {
List<String> fileUrls = new ArrayList<>();
JsonArray arr = (JsonArray) value;
for (int i = 0; i < arr.size(); i++) {
JsonElement jsonElement = arr.get(i);
if (jsonElement.isJsonObject()) {
JsonObject jsonObject = jsonElement.getAsJsonObject();
String url = getUrlByTextFile(jsonObject);
if (!url.isEmpty()) {
fileUrls.add(url);
}
}
}
return String.join(",", fileUrls);
} else if (value instanceof JsonObject) {
JsonObject jsb = (JsonObject) value;
return getUrlByTextFile(jsb);
}
return value;
}
/**
* 从JsonElement中提取合适的值避免额外的引号
*/
private static Object getJsonValue(JsonElement value) {
if (value.isJsonPrimitive()) {
// 检查是否为字符串
if (value.getAsJsonPrimitive().isString()) {
String strValue = value.getAsString();
// 检查字符串是否以引号开始和结束如果是则去除引号
if (strValue.length() >= 2 &&
((strValue.startsWith("\"") && strValue.endsWith("\"")) ||
(strValue.startsWith("'") && strValue.endsWith("'")))) {
return strValue.substring(1, strValue.length() - 1);
}
return strValue;
} else if (value.getAsJsonPrimitive().isNumber()) {
return value.getAsNumber();
} else if (value.getAsJsonPrimitive().isBoolean()) {
return value.getAsBoolean();
}
}
return value;
}
private static String getUrlByTextFile(JsonObject jsb) {
String url = "";
String cellType = jsb.get("type").getAsString();
if (cellType.equals("url")) {
String link = jsb.get("link").getAsString();
if (link == null) {
url = jsb.get("text").getAsString();
} else {
url = link;
}
}
return url;
}
public static List<String> parseStrToArr(JsonElement value) {
String result = "";
if (value.isJsonPrimitive()) {
if (value.getAsJsonPrimitive().isString()) {
result = value.getAsString();
// 检查字符串是否以引号开始和结束如果是则去除引号
if (result.length() >= 2 &&
((result.startsWith("\"") && result.endsWith("\"")) ||
(result.startsWith("'") && result.endsWith("'")))) {
result = result.substring(1, result.length() - 1);
}
} else {
result = value.toString();
}
} else if (value.isJsonArray()) {
// 处理数组类型
result = value.toString();
} else {
result = value.toString();
}
String[] split = result.split(",");
return new ArrayList<>(Arrays.asList(split));
}
private static Object conversionValue(TableProperty tableProperty, Object value) {
Object result = value;
if (value != null) {
Class<? extends BaseEnum> enumClass = tableProperty.enumClass();
if (enumClass != null && enumClass != BaseEnum.class) {
BaseEnum baseEnum = BaseEnum.getByDesc(enumClass, value);
if (baseEnum != null) {
result = baseEnum.getCode();
}
}
Class<? extends FieldValueProcess> fieldFormatClass = tableProperty.fieldFormatClass();
if (fieldFormatClass != null && !fieldFormatClass.isInterface()) {
try {
// 使用更安全的实例化方式
FieldValueProcess fieldValueProcess = fieldFormatClass.getDeclaredConstructor().newInstance();
result = fieldValueProcess.process(result);
} catch (InstantiationException e) {
log.log(Level.SEVERE, "无法实例化字段格式化类: " + fieldFormatClass.getName(), e);
} catch (IllegalAccessException e) {
log.log(Level.SEVERE, "无法访问字段格式化类的构造函数: " + fieldFormatClass.getName(), e);
} catch (NoSuchMethodException e) {
log.log(Level.SEVERE, "字段格式化类缺少无参构造函数: " + fieldFormatClass.getName(), e);
} catch (InvocationTargetException e) {
log.log(Level.SEVERE, "字段格式化类构造函数调用异常: " + fieldFormatClass.getName(), e);
} catch (Exception e) {
log.log(Level.SEVERE, "创建字段格式化类实例时发生未知异常: " + fieldFormatClass.getName(), e);
}
}
}
return result;
}
public static Object reverseValueConversion(TableProperty tableProperty, Object value) {
Object result = value;
if (value != null && tableProperty != null) {
Class<? extends BaseEnum> enumClass = tableProperty.enumClass();
if (enumClass != null && enumClass != BaseEnum.class) {
BaseEnum baseEnum = BaseEnum.getByCode(enumClass, value);
if (baseEnum != null) {
result = baseEnum.getDesc();
}
}
Class<? extends FieldValueProcess> fieldFormatClass = tableProperty.fieldFormatClass();
if (fieldFormatClass != null && !fieldFormatClass.isInterface()) {
try {
FieldValueProcess fieldValueProcess = fieldFormatClass.newInstance();
result = fieldValueProcess.reverseProcess(result);
} catch (InstantiationException | IllegalAccessException e) {
log.log(Level.FINE, "format value error", e);
}
}
}
return result;
}
}

@ -0,0 +1,121 @@
package cn.isliu.core.utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.file.Files;
public class FileUtil {
private static final Logger log = LoggerFactory.getLogger(FileUtil.class);
public static final String DEFAULT_FILE_PATH = "fs";
public static byte [] getImageData(String filePath) {
byte[] imageData = new byte[0];
try {
// 如果 filePath URL则使用网络请求获取图片
if (filePath.startsWith("http")) {
URL url = new URL(filePath);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setDoInput(true);
connection.connect();
try (InputStream input = connection.getInputStream()) {
imageData = readStream(input);
}
} else {
// 否则当作本地文件处理
File imageFile = new File(filePath);
imageData = Files.readAllBytes(imageFile.toPath());
}
} catch (IOException e) {
log.error("【巨量广告助手】 读取图片文件异常!参数:{},错误信息:{}", filePath, e.getMessage(), e);
}
return imageData;
}
public static byte[] readStream(InputStream input) throws IOException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = input.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, bytesRead);
}
return byteArrayOutputStream.toByteArray();
}
/**
* 根据 URL 下载文件到本地路径
*
* @param fileURL 文件的 URL 地址
* @param savePath 保存文件的本地路径
* @throws IOException 如果下载或写入过程中发生错误
*/
public static void downloadFile(String fileURL, String savePath) {
HttpURLConnection httpConn = null;
try {
URL url = new URL(fileURL);
// 打开连接
httpConn = (HttpURLConnection) url.openConnection();
int responseCode = httpConn.getResponseCode();
// 检查响应码是否为 HTTP OK
if (responseCode == HttpURLConnection.HTTP_OK) {
// 获取输入流
InputStream inputStream = httpConn.getInputStream();
// 创建文件输出流
FileOutputStream outputStream = new FileOutputStream(savePath);
// 缓冲区
byte[] buffer = new byte[1024];
int bytesRead;
// 写入文件
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
// 关闭流
outputStream.close();
inputStream.close();
log.info("文件下载成功。保存路径: {}", savePath);
} else {
log.error("文件下载失败。响应码: {}", responseCode);
}
} catch (Exception e) {
log.error("文件下载失败。", e);
} finally {
if (httpConn != null) {
httpConn.disconnect();
}
}
}
/**
* 获取系统临时目录路径并确保目录存在
* @return 临时目录路径
*/
public static String getRootPath() {
// 获取系统临时目录
String tempDir = System.getProperty("java.io.tmpdir") + File.separator + DEFAULT_FILE_PATH ;
// 确保路径以文件分隔符结尾
if (!tempDir.endsWith(File.separator)) {
tempDir += File.separator;
}
// 确保目录存在
File dir = new File(tempDir);
if (!dir.exists()) {
dir.mkdirs();
}
return tempDir;
}
}

@ -0,0 +1,488 @@
package cn.isliu.core.utils;
import cn.isliu.core.CopySheet;
import cn.isliu.core.Reply;
import cn.isliu.core.Sheet;
import cn.isliu.core.SheetMeta;
import cn.isliu.core.ValuesBatch;
import cn.isliu.core.client.FeishuClient;
import cn.isliu.core.exception.FsHelperException;
import cn.isliu.core.pojo.ApiResponse;
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 java.io.IOException;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
import java.util.logging.Level;
public class FsApiUtil {
private static final Gson gson = new Gson();
private static final Logger log = Logger.getLogger(FsApiUtil.class.getName());
private static final String REQ_TYPE = "JSON_STR";
public static final int DEFAULT_ROW_NUM = 1000;
public static ValuesBatch getSheetData(String sheetId, String spreadsheetToken, String startPosition, String endPosition, FeishuClient client) {
ValuesBatch valuesBatch = null;
try {
CustomValueService.ValueRequest batchGetRequest = CustomValueService.ValueRequest.batchGetValues()
.addRange(sheetId, startPosition, endPosition)
.valueRenderOption("Formula")
.dateTimeRenderOption("FormattedString")
.build();
CustomValueService.ValueBatchUpdateRequest getBatchRangesRequest = CustomValueService.ValueBatchUpdateRequest.newBuilder()
.addRequest(batchGetRequest)
.build();
ApiResponse batchRangeResp = client.customValues().valueBatchUpdate(spreadsheetToken, getBatchRangesRequest);
if (batchRangeResp.success()) {
valuesBatch = gson.fromJson(gson.toJson(batchRangeResp.getData()), ValuesBatch.class);
} else {
log.log(Level.SEVERE, "【飞书表格】获取Sheet数据失败 错误信息:{0}", gson.toJson(batchRangeResp));
throw new FsHelperException("【飞书表格】获取Sheet数据失败");
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】获取Sheet数据失败 错误信息:{0}", e.getMessage());
throw new FsHelperException("【飞书表格】获取Sheet数据失败");
}
return valuesBatch;
}
public static Sheet getSheetMetadata(String sheetId, FeishuClient client, String spreadsheetToken) {
try {
QuerySpreadsheetSheetReq req = QuerySpreadsheetSheetReq.newBuilder()
.spreadsheetToken(spreadsheetToken)
.build();
// 发起请求
QuerySpreadsheetSheetResp resp = client.sheets().v3().spreadsheetSheet().query(req);
// 处理服务端错误
if (resp.success()) {
// 修复参数转换遗漏问题 - 直接使用添加了注解的类进行转换
SheetMeta sheetMeta = gson.fromJson(gson.toJson(resp.getData()), SheetMeta.class);
List<Sheet> sheets = sheetMeta.getSheets();
AtomicReference<Sheet> sheet = new AtomicReference<>();
sheets.forEach(s -> {
if (s.getSheetId().equals(sheetId)) {
sheet.set(s);
}
});
return sheet.get();
} else {
log.log(Level.SEVERE, "【飞书表格】 获取Sheet元数据异常错误信息{0}", gson.toJson(resp));
throw new FsHelperException("【飞书表格】 获取Sheet元数据异常错误信息" + resp.getMsg());
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 获取Sheet元数据异常错误信息{0}", e.getMessage());
throw new FsHelperException("【飞书表格】 获取Sheet元数据异常");
}
}
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()) {
log.log(Level.SEVERE, "【飞书表格】 合并单元格请求异常!参数:{0},错误信息:{1}", new Object[]{cell, batchMergeResp.getMsg()});
throw new FsHelperException("【飞书表格】 合并单元格请求异常!");
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 合并单元格异常!参数:{0},错误信息:{1}", new Object[]{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()) {
log.log(Level.SEVERE, "【飞书表格】 写入表格头数据异常!错误信息:{0}", apiResponse.getMsg());
throw new FsHelperException("【飞书表格】 写入表格头数据异常!");
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 写入表格头异常!错误信息:{0}", e.getMessage());
throw new FsHelperException("【飞书表格】 写入表格头异常!");
}
}
public static void setTableStyle(String style, String sheetId, FeishuClient client, String spreadsheetToken) {
try {
CustomCellService.CellBatchUpdateRequest batchUpdateRequest = CustomCellService.CellBatchUpdateRequest.newBuilder()
.addRequest(CustomCellService.CellRequest.styleCellsBatch().setReqType(REQ_TYPE)
.setParams(style.replaceAll("%SHEET_ID%", sheetId))
.build())
.build();
ApiResponse apiResponse = client.customCells().cellsBatchUpdate(spreadsheetToken, batchUpdateRequest);
if (!apiResponse.success()) {
log.log(Level.SEVERE, "【飞书表格】 写入表格样式数据异常!参数:{0},错误信息:{1}", new Object[]{style, apiResponse.getMsg()});
throw new FsHelperException("【飞书表格】 写入表格样式数据异常!");
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 写入表格样式异常!参数:{0},错误信息:{1}", new Object[]{style, e.getMessage()});
throw new FsHelperException("【飞书表格】 写入表格样式异常!");
}
}
public static String createSheet(String title, FeishuClient client, String spreadsheetToken) {
String sheetId = null;
try {
// 创建 sheet
CustomSheetService.SheetBatchUpdateRequest addSheetRequest = CustomSheetService.SheetBatchUpdateRequest.newBuilder()
.addRequest(CustomSheetService.SheetRequest.addSheet()
.title(title)
// .index(0) // 在第一个位置添加
.build())
.build();
ApiResponse addResp = client.customSheets().sheetsBatchUpdate(spreadsheetToken, addSheetRequest);
if (addResp.success()) {
log.log(Level.INFO, "【飞书表格】 创建 sheet 成功! {0}", gson.toJson(addResp));
JsonObject jsObj = gson.fromJson(gson.toJson(addResp.getData()), JsonObject.class);
JsonArray replies = jsObj.getAsJsonArray("replies");
JsonObject jsonObject = replies.get(0).getAsJsonObject();
// 使用已有的Reply类
Reply reply = gson.fromJson(jsonObject, Reply.class);
sheetId = reply.getAddSheet().getProperties().getSheetId();
if (sheetId == null || sheetId.isEmpty()) {
log.log(Level.SEVERE, "【飞书表格】 创建 sheet 失败!");
throw new FsHelperException("【飞书表格】创建 sheet 异常SheetId返回为空");
}
} else {
log.log(Level.SEVERE, "【飞书表格】 创建 sheet 失败!错误信息:{0}", gson.toJson(addResp));
throw new FsHelperException("【飞书表格】 创建 sheet 异常!");
}
} catch (Exception e) {
String message = e.getMessage();
log.log(Level.SEVERE, "【飞书表格】 创建 sheet 异常!错误信息:{0}", message);
throw new FsHelperException(message != null && message.contains("403")? "请按照上方操作,当前智投无法操作对应文档哦" : "【飞书表格】 创建 sheet 异常!");
}
return sheetId;
}
public static String copySheet(String sourceSheetId, String title, FeishuClient client, String spreadsheetToken) {
String sheetId = null;
try {
CustomSheetService.SheetBatchUpdateRequest copyRequest = CustomSheetService.SheetBatchUpdateRequest.newBuilder()
.addRequest(CustomSheetService.SheetRequest.copySheet()
.sourceSheetId(sourceSheetId)
.destinationTitle(title)
.build())
.build();
ApiResponse copyResp = client.customSheets().sheetsBatchUpdate(spreadsheetToken, copyRequest);
if (copyResp.success()) {
log.log(Level.INFO, "【飞书表格】 复制 sheet 成功! {0}", gson.toJson(copyResp));
JsonObject jsObj = gson.fromJson(gson.toJson(copyResp.getData()), JsonObject.class);
JsonArray replies = jsObj.getAsJsonArray("replies");
JsonObject jsonObject = replies.get(0).getAsJsonObject();
// 使用已有的Reply类
Reply reply = gson.fromJson(jsonObject, Reply.class);
CopySheet copySheet = reply.getCopySheet();
sheetId = copySheet.getProperties().getSheetId();
if (sheetId == null || sheetId.isEmpty()) {
throw new FsHelperException("【飞书表格】 复制模版异常SheetID 为空!");
}
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 复制模版异常!错误信息:{0}", e.getMessage());
throw new FsHelperException("【飞书表格】 复制模版异常!");
}
return sheetId;
}
public static void setDateType(String sheetId, FeishuClient client, String spreadsheetToken, String conf, Integer headLine) {
JsonObject confObj = gson.fromJson(conf, JsonObject.class);
String position = confObj.get("position").getAsString();
String formatter = confObj.get("formatter").getAsString();
if (position == null || position.trim().isEmpty()) return;
try {
CustomCellService.CellBatchUpdateRequest batchStyleRequest = CustomCellService.CellBatchUpdateRequest.newBuilder()
.addRequest(CustomCellService.CellRequest.styleCells()
.sheetId(sheetId)
.startPosition(position + headLine)
.endPosition(position + DEFAULT_ROW_NUM)
.formatter(formatter)
.build())
.build();
ApiResponse batchStyleResp = client.customCells().cellsBatchUpdate(spreadsheetToken, batchStyleRequest);
if (!batchStyleResp.success()) {
log.log(Level.SEVERE, "【飞书表格】 写入表格样式数据异常!参数:{0},错误信息:{1}", new Object[]{conf, batchStyleResp.getMsg()});
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 写入表格样式数据异常!", e);
}
}
public static void setOptions(String sheetId, FeishuClient client, String spreadsheetToken, boolean isMulti,
String startPosition, String endPosition, List<String> result) {
try {
// 创建设置下拉列表请求
CustomDataValidationService.DataValidationRequest listRequest = CustomDataValidationService.DataValidationRequest.listValidation()
.range(sheetId, startPosition, endPosition) // 设置范围
.addValues(result) // 添加下拉选项
.multipleValues(isMulti) // 设置支持多选
.build();
// 添加到批量请求中
CustomDataValidationService.DataValidationBatchUpdateRequest batchRequest = CustomDataValidationService.DataValidationBatchUpdateRequest.newBuilder()
.addRequest(listRequest)
.build();
// 执行请求
ApiResponse response = client.customDataValidations().dataValidationBatchUpdate(spreadsheetToken, batchRequest);
if (!response.success()) {
log.log(Level.SEVERE, "设置下拉列表失败, sheetId:{0}, startPosition:{1}, endPosition: {2}, 返回信息:{3}", new Object[]{sheetId, startPosition, endPosition, gson.toJson(response)});
}
} catch (Exception e) {
log.log(Level.SEVERE, "设置下拉列表失败sheetId:{0}", new Object[]{sheetId});
}
}
public static void removeSheet(String sheetId, FeishuClient client, String spreadsheetToken) {
try {
CustomSheetService.SheetBatchUpdateRequest deleteRequest = CustomSheetService.SheetBatchUpdateRequest.newBuilder()
.addRequest(CustomSheetService.SheetRequest.deleteSheet()
.sheetId(sheetId)
.build())
.build();
ApiResponse deleteResp = client.customSheets().sheetsBatchUpdate(spreadsheetToken, deleteRequest);
if (!deleteResp.success()) {
log.log(Level.SEVERE, "【飞书表格】 删除 sheet 失败!参数:{0},错误信息:{1}", new Object[]{sheetId, deleteResp.getMsg()});
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 删除 sheet 异常!参数:{0},错误信息:{1}", new Object[]{sheetId, e.getMessage()});
}
}
/**
* 下载素材
*/
public static void downloadMaterial(String fileToken, String outputPath, FeishuClient client, String extra) {
try {
DownloadMediaReq req = DownloadMediaReq.newBuilder()
.fileToken(fileToken)
// .extra("")
.build();
// 发起请求
DownloadMediaResp resp = client.drive().v1().media().download(req);
// 处理服务端错误
if (resp.success()) {
resp.writeFile(outputPath);
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 下载素材异常!参数:{0},错误信息:{1}", new Object[]{fileToken, e.getMessage()});
throw new FsHelperException("【飞书表格】 下载素材异常!");
}
}
public static String downloadTmpMaterialUrl(String fileToken, FeishuClient client) {
String tmpUrl = "";
try {
BatchGetTmpDownloadUrlMediaReq req = BatchGetTmpDownloadUrlMediaReq.newBuilder()
.fileTokens(new String[]{fileToken})
.build();
BatchGetTmpDownloadUrlMediaResp resp = client.drive().v1().media().batchGetTmpDownloadUrl(req);
if (resp.success()) {
return resp.getData().getTmpDownloadUrls()[0].getTmpDownloadUrl();
} else {
log.log(Level.SEVERE, "【飞书表格】 获取临时下载地址失败!参数:{0},错误信息:{1}", new Object[]{fileToken, gson.toJson(resp)});
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 获取临时下载地址异常!参数:{0},错误信息:{1}", new Object[]{fileToken, e.getMessage()});
}
return tmpUrl;
}
public static Object putValues(String spreadsheetToken, CustomValueService.ValueRequest putValuesBuilder, FeishuClient client) {
log.log(Level.INFO, "【飞书表格】 putValues 开始写入数据!参数:{0}", gson.toJson(putValuesBuilder));
// 添加到批量请求中
CustomValueService.ValueBatchUpdateRequest putDataRequest = CustomValueService.ValueBatchUpdateRequest.newBuilder()
.addRequest(putValuesBuilder)
.build();
try {
ApiResponse putResp = client.customValues().valueBatchUpdate(spreadsheetToken, putDataRequest);
if (putResp.success()) {
return putResp.getData();
} else {
log.log(Level.SEVERE, "【飞书表格】 写入表格数据失败!参数:{0},错误信息:{1}", new Object[]{putValuesBuilder, putResp.getMsg()});
throw new FsHelperException("【飞书表格】 写入表格数据失败!");
}
} catch (IOException e) {
log.log(Level.SEVERE, "【飞书表格】 写入表格数据异常!参数:{0},错误信息:{1}", new Object[]{spreadsheetToken, e.getMessage()});
throw new FsHelperException("【飞书表格】 写入表格数据异常!");
}
}
public static Object batchPutValues(String sheetId, String spreadsheetToken,
CustomValueService.ValueRequest batchPutRequest, FeishuClient client) {
log.log(Level.INFO, "【飞书表格】 batchPutValues 开始写入数据!参数:{0}", gson.toJson(batchPutRequest));
try {
CustomValueService.ValueBatchUpdateRequest batchPutDataRequest =
CustomValueService.ValueBatchUpdateRequest.newBuilder()
.addRequest(batchPutRequest)
.build();
ApiResponse batchPutResp = client.customValues().valueBatchUpdate(spreadsheetToken, batchPutDataRequest);
if (batchPutResp.success()) {
return batchPutResp.getData();
} else {
log.log(Level.SEVERE, "【飞书表格】 批量写入数据失败!参数:{0},错误信息:{1}", new Object[]{sheetId, gson.toJson(batchPutResp)});
throw new FsHelperException("【飞书表格】 批量写入数据失败!");
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 批量写入数据异常!参数:{0},错误信息:{1}", new Object[]{sheetId, e.getMessage()});
throw new FsHelperException("【飞书表格】 批量写入数据异常!");
}
}
public static Object addRowColumns(String sheetId, String spreadsheetToken, String type, int length,FeishuClient client) {
CustomDimensionService.DimensionBatchUpdateRequest batchRequest = CustomDimensionService.DimensionBatchUpdateRequest.newBuilder()
.addRequest(CustomDimensionService.DimensionRequest.addDimension()
.sheetId(sheetId)
.majorDimension(type)
.length(length).build())
.build();
try {
ApiResponse batchResp = client.customDimensions().dimensionsBatchUpdate(spreadsheetToken, batchRequest);
if (batchResp.success()) {
return batchResp.getData();
} else {
log.log(Level.SEVERE, "【飞书表格】 添加行列失败!参数:{0},错误信息:{1}", new Object[]{sheetId, gson.toJson(batchResp)});
throw new FsHelperException("【飞书表格】 添加行列失败!");
}
} catch (IOException e) {
log.log(Level.SEVERE, "【飞书表格】 添加行列异常!参数:{0},错误信息:{1}", new Object[]{sheetId, e.getMessage()});
throw new FsHelperException("【飞书表格】 添加行列异常!");
}
}
public static Object getTableInfo(String sheetId, String spreadsheetToken, FeishuClient client) {
try {
// 创建请求对象
GetSpreadsheetReq req = GetSpreadsheetReq.newBuilder()
.spreadsheetToken(spreadsheetToken)
.build();
// 发起请求
GetSpreadsheetResp resp = client.sheets().v3().spreadsheet().get(req);
// 处理服务端错误
if (resp.success()) {
return resp.getData();
} else {
log.log(Level.SEVERE, "【飞书表格】 获取表格信息失败!参数:{0},错误信息:{1}", new Object[]{sheetId, resp.getMsg()});
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 获取表格信息异常!参数:{0},错误信息:{1}", new Object[]{sheetId, e.getMessage()});
}
return null;
}
/**
* 字符串类型 formatter: "@"
*/
public static void setCellType(String sheetId, String formatter, String startPosition, String endPosition, FeishuClient client, String spreadsheetToken) {
try {
CustomCellService.CellBatchUpdateRequest batchUpdateRequest = CustomCellService.CellBatchUpdateRequest.newBuilder()
.addRequest(CustomCellService.CellRequest.styleCells()
.formatter(formatter).sheetId(sheetId).startPosition(startPosition).endPosition(endPosition)
.build())
.build();
ApiResponse apiResponse = client.customCells().cellsBatchUpdate(spreadsheetToken, batchUpdateRequest);
if (!apiResponse.success()) {
log.log(Level.SEVERE, "【飞书表格】 设置单元格类型失败!参数:{0},错误信息:{1}", new Object[]{sheetId, apiResponse.getMsg()});
throw new FsHelperException("【飞书表格】 批量设置单元格类型失败!");
}
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 设置单元格类型失败!参数:{0},错误信息:{1}", new Object[]{sheetId, e.getMessage()});
throw new FsHelperException("【飞书表格】 批量设置单元格类型异常!");
}
}
public static Object imageUpload(String filePath, 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)
.image(imageData)
.name(fileName)
.build();
CustomValueService.ValueBatchUpdateRequest imageWriteRequest = CustomValueService.ValueBatchUpdateRequest.newBuilder()
.addRequest(imageRequest)
.build();
ApiResponse imageResp = client.customValues().valueBatchUpdate(spreadsheetToken, imageWriteRequest);
if (!imageResp.success()) {
log.log(Level.SEVERE, "【飞书表格】 图片上传失败!参数:{0},错误信息:{1}", new Object[]{filePath, gson.toJson(imageResp)});
}
return imageResp.getData();
} catch (Exception e) {
log.log(Level.SEVERE, "【飞书表格】 图片上传异常!参数:{0},错误信息:{1}", new Object[]{filePath, e.getMessage()});
}
return null;
}
}

@ -0,0 +1,35 @@
package cn.isliu.core.utils;
import cn.isliu.core.client.FeishuClient;
public class FsClientUtil {
public static FeishuClient client;
/**
* 获取飞书客户端
*
* @param appId 飞书应用ID
* @param appSecret 飞书应用密钥
* @return 飞书客户端
*/
public static FeishuClient initFeishuClient(String appId, String appSecret) {
client = FeishuClient.newBuilder(appId, appSecret).build();
return client;
}
/**
* 设置飞书客户端
*
* @param appId 飞书应用ID
* @param appSecret 飞书应用密钥
*/
public static void setClient(String appId, String appSecret) {
client = FeishuClient.newBuilder(appId, appSecret).build();
}
public static FeishuClient getFeishuClient() {
return client;
}
}

@ -0,0 +1,323 @@
package cn.isliu.core.utils;
import cn.isliu.core.*;
import cn.isliu.core.annotation.TableProperty;
import cn.isliu.core.config.FsConfig;
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.CustomValueService;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;
public class FsTableUtil {
public static List<FsTableData> getFsTableData(Sheet sheet, String spreadsheetToken) {
// 计算数据范围
GridProperties gridProperties = sheet.getGridProperties();
int totalRow = gridProperties.getRowCount();
int rowCount = Math.min(totalRow, 100); // 每次读取的行数
int colCount = gridProperties.getColumnCount();
int startOffset = 1; // 起始偏移行号
// 实际要读取的数据行数减去偏移量
int actualRows = Math.max(0, totalRow - startOffset);
int batchCount = (actualRows + rowCount - 1) / rowCount;
List<List<Object>> values = new LinkedList<>();
for (int i = 0; i < batchCount; i++) {
int startRowIndex = startOffset + i * rowCount;
int endRowIndex = Math.max(startRowIndex + rowCount - 1, totalRow - 1);
// 3. 获取工作表数据
ValuesBatch valuesBatch = FsApiUtil.getSheetData(sheet.getSheetId(), spreadsheetToken,
"A" + startRowIndex,
getColumnName(colCount - 1) + endRowIndex, FsClientUtil.getFeishuClient());
if (valuesBatch != null) {
List<ValueRange> valueRanges = valuesBatch.getValueRanges();
for (ValueRange valueRange : valueRanges) {
values.addAll(valueRange.getValues());
}
}
}
// 获取飞书表格数据
TableData tableData = processSheetData(sheet, values);
List<FsTableData> dataList = getFsTableData(tableData);
Map<String, String> titleMap = new HashMap<>();
dataList.stream().filter(d -> d.getRow() == (FsConfig.getTitleLine() - 1)).findFirst()
.ifPresent(d -> {
Map<String, String> map = (Map<String, String>) d.getData();
titleMap.putAll(map);
});
return dataList.stream().filter(fsTableData -> fsTableData.getRow() >= FsConfig.getHeadLine()).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);
}
});
item.setData(resultMap);
return item;
}).collect(Collectors.toList());
}
private static List<FsTableData> getFsTableData(TableData tableData) {
return getFsTableData(tableData, new ArrayList<>());
}
private static List<FsTableData> getFsTableData(TableData tableData, List<String> ignoreUniqueFields) {
List<FsTableData> fsTableList = new LinkedList<>();
// 5. 访问补齐后的数据
for (TableRow row : tableData.getRows()) {
FsTableData fsTableData = new FsTableData();
int rowIndex = 0;
Map<String, Object> obj = new HashMap<>();
for (Cell cell : row.getCells()) {
obj.put(getColumnName(cell.getCol()), cell.getValue());
rowIndex = cell.getRow();
}
fsTableData.setRow(rowIndex);
fsTableData.setData(obj);
String jsonStr;
if (!ignoreUniqueFields.isEmpty()) {
Map<String, Object> clone = new HashMap<>(obj);
ignoreUniqueFields.forEach(clone::remove);
jsonStr = StringUtil.mapToJson(clone);
} else {
jsonStr = StringUtil.mapToJson(obj);
}
String uniqueId = StringUtil.getSHA256(jsonStr);
fsTableData.setUniqueId(uniqueId);
fsTableList.add(fsTableData);
}
return fsTableList;
}
/**
* 处理表格数据将合并单元格转换为对象并补齐合并区域的值
*/
public static TableData processSheetData(Sheet metadata, List<List<Object>> values) {
TableData tableData = new TableData();
// 创建单元格网格
int rowCount = values.size();
int colCount = values.stream().mapToInt(List::size).max().orElse(0);
Cell[][] grid = new Cell[rowCount][colCount];
// 1. 初始化网格
for (int i = 0; i < rowCount; i++) {
List<Object> row = values.get(i);
for (int j = 0; j < colCount; j++) {
Object value = (j < row.size()) ? row.get(j) : null;
grid[i][j] = new Cell(i, j, value);
}
}
// 2. 标记合并区域并补齐所有合并单元格的值
if (metadata.getMerges() != null) {
for (Merge merge : metadata.getMerges()) {
int startRow = merge.getStartRowIndex();
int endRow = merge.getEndRowIndex();
int startCol = merge.getStartColumnIndex();
int endCol = merge.getEndColumnIndex();
// 获取合并区域左上角的值
Object topLeftValue = null;
if (startRow < rowCount && startCol < colCount) {
topLeftValue = grid[startRow][startCol].getValue();
}
// 遍历合并区域
for (int i = startRow; i <= endRow; i++) {
for (int j = startCol; j <= endCol; j++) {
if (i < rowCount && j < colCount) {
// 标记合并区域
grid[i][j].setMerge(merge);
// 对于合并区域内除左上角外的所有单元格
if (i != startRow || j != startCol) {
// 补齐值
grid[i][j].setValue(topLeftValue);
}
}
}
}
}
}
// 3. 构建表格数据结构
for (int i = 0; i < rowCount; i++) {
// 检查整行是否都为null
boolean allNull = true;
for (int j = 0; j < colCount; j++) {
if (grid[i][j].getValue() != null) {
allNull = false;
break;
}
}
// 如果整行都为null跳过该行
if (allNull) {
continue;
}
TableRow tableRow = new TableRow();
for (int j = 0; j < colCount; j++) {
Cell cell = grid[i][j];
// 如果是合并区域的左上角
if (cell.getMerge() != null &&
cell.getRow() == cell.getMerge().getStartRowIndex() &&
cell.getCol() == cell.getMerge().getStartColumnIndex()) {
MergedCell mergedCell = new MergedCell();
mergedCell.setValue(cell.getValue());
mergedCell.setRow(cell.getRow());
mergedCell.setCol(cell.getCol());
mergedCell.setRowSpan(cell.getMerge().getRowSpan());
mergedCell.setColSpan(cell.getMerge().getColSpan());
tableRow.getCells().add(mergedCell);
} else {
// 普通单元格或合并区域内的其他单元格
tableRow.getCells().add(cell);
}
}
tableData.getRows().add(tableRow);
}
return tableData;
}
public static String getColumnName(int columnIndex) {
StringBuilder sb = new StringBuilder();
while (columnIndex >= 0) {
char c = (char) ('A' + (columnIndex % 26));
sb.insert(0, c);
columnIndex = (columnIndex / 26) - 1;
if (columnIndex < 0) break;
}
return sb.toString();
}
public static String getColumnNameByNuNumber(int columnNumber) {
StringBuilder columnName = new StringBuilder();
while (columnNumber > 0) {
int remainder = (columnNumber - 1) % 26;
columnName.insert(0, (char) ('A' + remainder));
columnNumber = (columnNumber - 1) / 26;
}
return columnName.toString();
}
public static Map<String, String> getTitlePostionMap(Sheet sheet, String spreadsheetToken) {
GridProperties gridProperties = sheet.getGridProperties();
int colCount = gridProperties.getColumnCount();
Map<String, String> resultMap = new TreeMap<>();
ValuesBatch valuesBatch = FsApiUtil.getSheetData(sheet.getSheetId(), spreadsheetToken,
"A" + FsConfig.getTitleLine(),
getColumnName(colCount - 1) + FsConfig.getTitleLine(), FsClientUtil.getFeishuClient());
if (valuesBatch != null) {
List<ValueRange> valueRanges = valuesBatch.getValueRanges();
if (valueRanges != null && !valueRanges.isEmpty()) {
List<Object> values = valueRanges.get(0).getValues().get(0);
if (values != null && !values.isEmpty()) {
for (int i = 0; i < values.size(); i++) {
Object valObj = values.get(i);
if (valObj == null) {
continue;
}
String value = (String) valObj;
resultMap.put(value.trim(), getColumnName(i));
}
}
}
}
return resultMap;
}
public static void setTableOptions(String spreadsheetToken, List<String> headers, Map<String, FieldProperty> fieldsMap, String sheetId) {
List<Object> list = Arrays.asList(headers.toArray());
fieldsMap.forEach((field, fieldProperty) -> {
TableProperty tableProperty = fieldProperty.getTableProperty();
String position = "";
if (tableProperty != null) {
for (int i = 0; i < list.size(); i++) {
Object obj = list.get(i);
if (obj.toString().equals(field)) {
position = FsTableUtil.getColumnNameByNuNumber(i + 1);
}
}
int line = FsConfig.getTitleLine() + 1;
if (tableProperty.enumClass() != BaseEnum.class) {
FsApiUtil.setOptions(sheetId, FsClientUtil.getFeishuClient(), spreadsheetToken, tableProperty.type() == TypeEnum.MULTI_SELECT, position + line, position + 200,
Arrays.stream(tableProperty.enumClass().getEnumConstants()).map(BaseEnum::getDesc).collect(Collectors.toList()));
}
if (tableProperty.optionsClass() != OptionsValueProcess.class) {
List<String> result;
Class<? extends OptionsValueProcess> optionsClass = tableProperty.optionsClass();
try {
OptionsValueProcess optionsValueProcess = optionsClass.getDeclaredConstructor().newInstance();
result = (List<String>) optionsValueProcess.process();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
FsApiUtil.setOptions(sheetId, FsClientUtil.getFeishuClient(), spreadsheetToken, tableProperty.type() == TypeEnum.MULTI_SELECT, position + line, position + 200,
result);
}
}
});
}
public static CustomValueService.ValueRequest getHeadTemplateBuilder(String sheetId, List<String> headers) {
CustomValueService.ValueRequest.BatchPutValuesBuilder batchPutValuesBuilder = CustomValueService.ValueRequest.batchPutValues();
batchPutValuesBuilder.addRange(sheetId + "!A1:" + FsTableUtil.getColumnNameByNuNumber(headers.size()) + "1");
batchPutValuesBuilder.addRow(headers.toArray());
return batchPutValuesBuilder.build();
}
public static String getDefaultTableStyle(String sheetId, int size) {
String colorTemplate = "{\"data\": [{\"style\": {\"font\": {\"bold\": true, \"clean\": false, \"italic\": false, \"fontSize\": \"10pt/1.5\"}, \"clean\": false, \"hAlign\": 1, \"vAlign\": 1, \"backColor\": \"#000000\", \"foreColor\": \"#ffffff\", \"formatter\": \"\", \"borderType\": \"FULL_BORDER\", \"borderColor\": \"#000000\", \"textDecoration\": 0}, \"ranges\": [\"SHEET_ID!RANG\"]}]}";
colorTemplate = colorTemplate.replace("SHEET_ID", sheetId);
colorTemplate = colorTemplate.replace("RANG", "A1:" + FsTableUtil.getColumnNameByNuNumber(size) + "1");
colorTemplate = colorTemplate.replace("FORE_COLOR", FsConfig.FORE_COLOR).replace("BACK_COLOR", FsConfig.BACK_COLOR);
return colorTemplate;
}
public static void main(String[] args) {
FsConfig.initConfig("cli_a73813628afbd00d", "ouFTDr0Qu5WCgoPS8mgULg6uT0lDEUtX");
String sheetId = "2HokFi";
String spreadsheetToken = "SYRysUIcaheEbNt8KTocxRBinCh";
Sheet sheet = FsApiUtil.getSheetMetadata(sheetId, FsClientUtil.getFeishuClient(), spreadsheetToken);
Map<String, String> headFieldMap = getTitlePostionMap(sheet, spreadsheetToken);
System.out.println(headFieldMap);
}
}

@ -0,0 +1,434 @@
package cn.isliu.core.utils;
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 org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
public class GenerateUtil {
private static final Logger log = Logger.getLogger(GenerateUtil.class.getName());
/**
* 根据配置和数据生成DTO对象通用版本
*/
public static <T> T generateInstance(List<String> fieldPathList, Class<T> clazz, Map<String, Object> dataMap) {
T t;
try {
t = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("创建实例失败: " + clazz.getSimpleName(), e);
}
fieldPathList.forEach(fieldPath -> {
Object value = dataMap.get(fieldPath);
if (value != null) {
try {
setNestedField(t, fieldPath, value);
} catch (Exception e) {
log.log(Level.SEVERE, "【巨量广告助手】 获取字段值异常!参数:{0},异常:{1}", new Object[]{fieldPath, e.getMessage()});
}
}
});
return t;
}
/**
* 递归设置嵌套字段值支持List类型处理
*/
private static void setNestedField(Object target, String fieldPath, Object value)
throws Exception {
String[] parts = fieldPath.split("\\.");
setNestedFieldRecursive(target, parts, 0, value);
}
private static void setNestedFieldRecursive(Object target, String[] parts, int index, Object value)
throws Exception {
if (index >= parts.length - 1) {
// 最后一级字段直接设置值
setFieldValue(target, parts[index], value);
return;
}
String fieldName = parts[index];
Field field = getDeclaredField(target.getClass(), fieldName);
if (field == null) {
// 尝试使用下划线转驼峰的方式查找字段
field = getDeclaredField(target.getClass(), StringUtil.toCamelCase(fieldName));
if (field == null) {
throw new NoSuchFieldException("Field not found: " + fieldName);
}
}
field.setAccessible(true);
Object nestedObj = field.get(target);
// 处理List类型
if (List.class.isAssignableFrom(field.getType())) {
// 确保List存在
if (nestedObj == null) {
nestedObj = new ArrayList<>();
field.set(target, nestedObj);
}
List<Object> list = (List<Object>) nestedObj;
// 确保List中至少有一个元素
if (list.isEmpty()) {
Object newElement = createListElement(field);
list.add(newElement);
}
// 使用List中的第一个元素
nestedObj = list.get(0);
}
// 处理普通对象
else {
// 确保嵌套对象存在
if (nestedObj == null) {
// 通过反射创建嵌套对象实例
try {
nestedObj = field.getType().getDeclaredConstructor().newInstance();
field.set(target, nestedObj);
} catch (Exception e) {
// 如果无法创建实例则记录日志并跳过该字段
log.log(Level.WARNING, "无法创建嵌套对象实例: " + field.getType().getName() + ", 字段: " + fieldName, e);
return;
}
}
}
// 递归处理下一级
setNestedFieldRecursive(nestedObj, parts, index + 1, value);
}
/**
* 创建List元素实例
*/
private static Object createListElement(Field listField) throws Exception {
Type genericType = listField.getGenericType();
if (genericType instanceof ParameterizedType) {
Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments();
if (typeArgs.length > 0 && typeArgs[0] instanceof Class) {
Class<?> elementClass = (Class<?>) typeArgs[0];
// 通过反射创建List元素实例
try {
return elementClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
// 如果无法创建实例则记录日志并返回null
log.log(Level.WARNING, "无法创建List元素实例: " + elementClass.getName(), e);
return null;
}
}
}
throw new InstantiationException("Cannot determine list element type for field: " + listField.getName());
}
/**
* 设置字段值支持基本类型转换
*/
private static void setFieldValue(Object target, String fieldName, Object value)
throws Exception {
Field field = getDeclaredField(target.getClass(), fieldName);
if (field == null) {
// 尝试使用下划线转驼峰的方式查找字段
field = getDeclaredField(target.getClass(), StringUtil.toCamelCase(fieldName));
if (field == null) {
throw new NoSuchFieldException("Field not found: " + fieldName);
}
}
field.setAccessible(true);
Class<?> fieldType = field.getType();
// 简单类型转换
if (value != null) {
if (fieldType == String.class) {
field.set(target, convertStrValue(value));
} else if (fieldType == Integer.class || fieldType == int.class) {
field.set(target, Integer.parseInt(convertValue(value)));
} else if (fieldType == Double.class || fieldType == double.class) {
field.set(target, Double.parseDouble(convertValue(value)));
} else if (fieldType == Boolean.class || fieldType == boolean.class) {
field.set(target, Boolean.parseBoolean(convertValue(value)));
} else if (fieldType == Long.class || fieldType == long.class) {
String stringValue = convertValue(value);
try {
field.set(target, Long.parseLong(stringValue));
} catch (NumberFormatException e) {
try {
field.set(target, ((Double) Double.parseDouble(stringValue)).longValue());
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("无法将值 '" + stringValue + "' 转换为 long 或科学计数法表示的数值", ex);
}
}
} else if (fieldType == List.class) {
// 获取泛型类型
Type genericType = field.getGenericType();
if (!(genericType instanceof ParameterizedType)) {
throw new IllegalArgumentException("无法获取字段的泛型信息:" + fieldName);
}
Type elementType = ((ParameterizedType) genericType).getActualTypeArguments()[0];
if (!(elementType instanceof Class)) {
throw new IllegalArgumentException("不支持非Class类型的泛型" + elementType);
}
Class<?> elementClass = (Class<?>) elementType;
List<Object> convertedList = new ArrayList<>();
if (value instanceof List) {
for (Object item : (List<?>) value) {
convertedList.add(convertValue(item, elementClass));
}
} else {
// 单个值转为单元素List并做类型转换
convertedList.add(convertValue(value, elementClass));
}
field.set(target, convertedList);
} // 新增枚举类型支持
else if (BaseEnum.class.isAssignableFrom(fieldType)) {
if (value instanceof String) {
field.set(target, parseEnum((Class<? extends BaseEnum>) fieldType, (String) value));
}
}
else {
// 其他类型直接设置
field.set(target, value);
}
} else {
field.set(target, null);
}
}
/**
* 获取字段包括父类
*/
private static Field getDeclaredField(Class<?> clazz, String fieldName) {
// 首先尝试直接查找字段驼峰命名
for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
try {
return c.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
// 继续在父类中查找
}
}
// 如果直接查找失败尝试使用下划线转驼峰的方式查找字段
String camelCaseFieldName = StringUtil.toCamelCase(fieldName);
for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
try {
return c.getDeclaredField(camelCaseFieldName);
} catch (NoSuchFieldException e) {
// 继续在父类中查找
}
}
return null;
}
private static String convertStrValue(Object value) {
String result = "";
if (value instanceof BigDecimal) {
BigDecimal bigDecimal = (BigDecimal) value;
result = bigDecimal.stripTrailingZeros().toPlainString();
} else if (value instanceof Double) {
String stringValue = convertValue(value);
try {
result = String.valueOf(Long.parseLong(stringValue));
} catch (NumberFormatException e) {
try {
result = String.valueOf(((Double) Double.parseDouble(stringValue)).longValue());
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("无法将值 '" + stringValue + "' 转换为 long 或科学计数法表示的数值", ex);
}
}
} else {
result = String.valueOf(value);
}
return result.trim();
}
private static String convertValue(Object value) {
String result = "";
if (value instanceof BigDecimal) {
BigDecimal bigDecimal = (BigDecimal) value;
result = bigDecimal.stripTrailingZeros().toPlainString();
} else {
result = String.valueOf(value);
}
return result.trim();
}
private static Object convertValue(Object value, Class<?> targetType) throws Exception {
if (value == null) return null;
if (targetType.isAssignableFrom(value.getClass())) {
return value;
}
if (targetType == String.class) {
return value.toString().trim();
} else if (targetType == Integer.class || targetType == int.class) {
return Integer.parseInt(value.toString().trim());
} else if (targetType == Double.class || targetType == double.class) {
return Double.parseDouble(value.toString().trim());
} else if (targetType == Long.class || targetType == long.class) {
return Long.parseLong(value.toString().trim());
} else if (BaseEnum.class.isAssignableFrom(targetType)) {
if (value instanceof String) {
return parseEnum((Class<? extends BaseEnum>) targetType, (String) value);
} else {
throw new IllegalArgumentException("枚举类型字段只接受字符串类型的值");
}
} else {
throw new IllegalArgumentException("不支持的List元素类型转换" + targetType.getName());
}
}
public static <T extends BaseEnum> T parseEnum(Class<T> enumClass, String value) {
String val = value.trim();
return Arrays.stream(enumClass.getEnumConstants())
.filter(e -> e.getCode().equals(val) || e.getDesc().equals(val))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No matching enum for value: " + val));
}
/**
* 根据实体类以及字段映射关系获取该字段的值 field "field.field2.field3"
*
* @param target 实体类对象
* @param fieldMap 字段映射关系key为注解值value为实际字段路径
* @return 字段值
*/
public static Map<String, Object> getFieldValue(Object target, Map<String, String> fieldMap) {
// 遍历字段映射关系获取字段值
Map<String, Object> result = new HashMap<>();
for (Map.Entry<String, String> entry : fieldMap.entrySet()) {
String fieldName = entry.getKey();
String fieldPath = entry.getValue();
try {
Object value = getNestedFieldValue(target, fieldPath);
if (value != null) {
result.put(fieldName, value);
}
} catch (Exception e) {
log.log(Level.WARNING, "获取字段值异常,字段路径:" + fieldPath, e);
}
}
return result;
}
/**
* 递归获取嵌套字段值支持List类型处理
*/
public static Object getNestedFieldValue(Object target, String fieldPath) throws Exception {
String[] parts = fieldPath.split("\\.");
return getNestedFieldValueRecursive(target, parts, 0);
}
private static Object getNestedFieldValueRecursive(Object target, String[] parts, int index) throws Exception {
if (target == null) {
return null;
}
if (index >= parts.length - 1) {
// 最后一级字段直接获取值
return getFieldValue(target, parts[index]);
}
String fieldName = parts[index];
Field field = getDeclaredField(target.getClass(), fieldName);
if (field == null) {
// 尝试使用下划线转驼峰的方式查找字段
field = getDeclaredField(target.getClass(), StringUtil.toCamelCase(fieldName));
if (field == null) {
throw new NoSuchFieldException("Field not found: " + fieldName);
}
}
field.setAccessible(true);
Object nestedObj = field.get(target);
// 处理List类型
if (nestedObj instanceof List) {
List<?> list = (List<?>) nestedObj;
if (!list.isEmpty()) {
// 使用List中的第一个元素
nestedObj = list.get(0);
} else {
return null;
}
}
// 递归处理下一级
return getNestedFieldValueRecursive(nestedObj, parts, index + 1);
}
/**
* 获取字段值
*/
private static Object getFieldValue(Object target, String fieldName) throws Exception {
Field field = getDeclaredField(target.getClass(), fieldName);
if (field == null) {
// 尝试使用下划线转驼峰的方式查找字段
field = getDeclaredField(target.getClass(), StringUtil.toCamelCase(fieldName));
if (field == null) {
throw new NoSuchFieldException("Field not found: " + fieldName);
}
}
field.setAccessible(true);
Object newObject = field.get(target);
// 处理List类型
if (newObject instanceof List) {
List<?> list = (List<?>) newObject;
if (!list.isEmpty()) {
Field finalField = field;
newObject = list.stream().map(obj -> ConvertFieldUtil.reverseValueConversion(finalField.getAnnotation(TableProperty.class), obj))
.collect(Collectors.toList());
} else {
return null;
}
} else {
newObject = ConvertFieldUtil.reverseValueConversion(field.getAnnotation(TableProperty.class), newObject);
}
return newObject;
}
public static Object getFieldValueList(Object fieldValue) {
Map<String, Object> params = new HashMap<>();
params.put("values", fieldValue);
params.put("type", "multipleValue");
return params;
}
public static <T> @Nullable String getUniqueId(T data) {
String uniqueId = null;
try {
Object uniqueIdObj = GenerateUtil.getNestedFieldValue(data, "uniqueId");
if (uniqueIdObj != null) {
uniqueId = uniqueIdObj.toString();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return uniqueId;
}
}

@ -0,0 +1,49 @@
package cn.isliu.core.utils;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.Map;
public class JSONUtil {
private static final Gson gson = new Gson();
/**
* 手动将HashMap转换为JsonObject避免Gson添加额外引号
* @param data HashMap数据
* @return 转换后的JsonObject
*/
public static JsonObject convertHashMapToJsonObject(HashMap<String, Object> data) {
JsonObject jsonObject = new JsonObject();
for (Map.Entry<String, Object> entry : data.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// 根据值的类型添加到JsonObject中
if (value instanceof String) {
// 检查字符串是否已经包含引号如果是则去除
String strValue = (String) value;
if (strValue.length() >= 2 && strValue.startsWith("\"") && strValue.endsWith("\"")) {
strValue = strValue.substring(1, strValue.length() - 1);
}
jsonObject.addProperty(key, strValue);
} else if (value instanceof Number) {
jsonObject.addProperty(key, (Number) value);
} else if (value instanceof Boolean) {
jsonObject.addProperty(key, (Boolean) value);
} else if (value instanceof Character) {
jsonObject.addProperty(key, (Character) value);
} else if (value == null) {
jsonObject.add(key, null);
} else {
// 对于其他类型使用Gson转换
JsonElement element = gson.toJsonTree(value);
jsonObject.add(key, element);
}
}
return jsonObject;
}
}

@ -0,0 +1,336 @@
package cn.isliu.core.utils;
import cn.isliu.core.annotation.TableProperty;
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 org.jetbrains.annotations.NotNull;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
public class PropertyUtil {
/**
* 获取类及其嵌套类上@TableProperty注解的字段映射关系
* 注解中的值作为keyFieldProperty对象作为value
*
* @param clazz 要处理的类
* @return 包含所有@TableProperty注解字段映射关系的Map嵌套属性使用'.'连接
*/
public static Map<String, FieldProperty> getTablePropertyFieldsMap(Class<?> clazz) {
Map<String, FieldProperty> allFields = new TreeMap<>();
Map<String, String> fieldsWithChildren = new TreeMap<>();
getTablePropertyFieldsMapRecursive(clazz, allFields, "", "", new HashMap<>(), fieldsWithChildren, false);
// 过滤掉有子级的字段
Map<String, FieldProperty> result = new HashMap<>();
for (Map.Entry<String, FieldProperty> entry : allFields.entrySet()) {
if (!fieldsWithChildren.containsKey(entry.getKey())) {
result.put(entry.getKey(), entry.getValue());
}
}
return result;
}
/**
* 递归获取类及其嵌套类上@TableProperty注解的字段映射关系
*
* @param clazz 当前处理的类
* @param result 存储结果的Map
* @param keyPrefix key的前缀使用注解中的值构建
* @param valuePrefix value的前缀使用字段实际名称构建
* @param depthMap 记录每个类的递归深度用于检测循环引用
* @param fieldsWithChildren 记录有子级的字段集合
* @param parentHasAnnotation 父节点是否有注解
*/
private static void getTablePropertyFieldsMapRecursive(Class<?> clazz, Map<String, FieldProperty> result, String keyPrefix, String valuePrefix, Map<Class<?>, Integer> depthMap, Map<String, String> fieldsWithChildren, boolean parentHasAnnotation) {
// 检查类是否在允许的包范围内
if (!isTargetPackageClass(clazz)) {
return;
}
// 检测循环引用限制递归深度
Integer currentDepth = depthMap.getOrDefault(clazz, 0);
if (currentDepth > 5) { // 限制最大递归深度为5
// 遇到可能的循环引用时只添加当前字段路径
if (!keyPrefix.isEmpty()) {
TableProperty dummyAnnotation = createDummyTableProperty(valuePrefix);
result.put(keyPrefix, new FieldProperty(valuePrefix, dummyAnnotation));
}
return;
}
// 增加当前类的递归深度
depthMap.put(clazz, currentDepth + 1);
try {
// 获取所有声明的字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 检查字段是否有@TableProperty注解
if (field.isAnnotationPresent(TableProperty.class)) {
TableProperty tableProperty = field.getAnnotation(TableProperty.class);
String[] propertyValues = tableProperty.value().split("\\."); // 支持多个值
String propertyValue = (propertyValues.length > 0 && !propertyValues[0].isEmpty()) ? propertyValues[0] : field.getName();
processFieldForMap(field, propertyValue, keyPrefix, valuePrefix, result, depthMap, fieldsWithChildren, parentHasAnnotation);
} else {
// 即使字段没有@TableProperty注解也要递归处理复杂类型字段
// 这样可以确保子节点有注解的字段也能被获取到
processFieldWithoutAnnotation(field, keyPrefix, valuePrefix, result, depthMap, fieldsWithChildren, parentHasAnnotation);
}
}
} finally {
// 减少当前类的递归深度
if (currentDepth == 0) {
depthMap.remove(clazz);
} else {
depthMap.put(clazz, currentDepth);
}
}
}
/**
* 判断类是否在目标包范围内
* 只处理用户定义的类避免处理系统类如java.lang.ref.ReferenceQueue
*
* @param clazz 要检查的类
* @return 是否为目标包下的类
*/
private static boolean isTargetPackageClass(Class<?> clazz) {
if (clazz == null) {
return false;
}
String className = clazz.getName();
// 只处理用户自定义的类排除系统类
return !className.startsWith("java.") &&
!className.startsWith("javax.") &&
!className.startsWith("sun.") &&
!className.startsWith("com.sun.") &&
!className.startsWith("jdk.");
}
/**
* 创建一个虚拟的TableProperty注解实例
*
* @param value 注解值
* @return TableProperty实例
*/
private static TableProperty createDummyTableProperty(String value) {
return new TableProperty() {
@Override
public Class<? extends Annotation> annotationType() {
return TableProperty.class;
}
@Override
public String value() {
return value;
}
@Override
public String field() {
return "";
}
@Override
public int order() {
return Integer.MAX_VALUE;
}
@Override
public TypeEnum type() {
return TypeEnum.TEXT;
}
@Override
public Class<? extends BaseEnum> enumClass() {
return BaseEnum.class;
}
@Override
public Class<? extends FieldValueProcess> fieldFormatClass() {
return FieldValueProcess.class;
}
@Override
public Class<? extends OptionsValueProcess> optionsClass() {
return OptionsValueProcess.class;
}
};
}
/**
* 处理带有@TableProperty注解的字段
*
* @param field 当前处理的字段
* @param fieldPropertyName 字段名称注解中的值
* @param keyPrefix key的前缀使用注解中的值构建
* @param valuePrefix value的前缀使用字段实际名称构建
* @param result 存储结果的Map
* @param depthMap 记录每个类的递归深度用于检测循环引用
* @param fieldsWithChildren 有子级的字段集合
* @param parentHasAnnotation 父节点是否有注解
*/
private static void processFieldForMap(Field field, String fieldPropertyName, String keyPrefix, String valuePrefix, Map<String, FieldProperty> result, Map<Class<?>, Integer> depthMap, Map<String, String> fieldsWithChildren, boolean parentHasAnnotation) {
// Key使用注解值构建显示给用户的名称
String fullKey = keyPrefix.isEmpty() ? fieldPropertyName : keyPrefix + "." + fieldPropertyName;
// Value使用字段实际名称构建实际的代码字段路径
String fullValue = valuePrefix.isEmpty() ? field.getName() : valuePrefix + "." + field.getName();
TableProperty tableProperty = field.getAnnotation(TableProperty.class);
// 如果字段是复杂类型则递归处理
Class<?> fieldType = field.getType();
if (isComplexType(fieldType)) {
// 用于收集子字段
Map<String, FieldProperty> subFields = new HashMap<>();
Map<String, String> subFieldsWithChildren = new HashMap<>();
if (Collection.class.isAssignableFrom(fieldType)) {
// 处理集合类型获取泛型参数类型
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class) {
Class<?> elementType = (Class<?>) actualTypeArguments[0];
// 对于集合中的元素类型递归处理其@TableProperty注解
// 父节点有注解所以子节点需要拼接
getTablePropertyFieldsMapRecursive(elementType, subFields, fullKey, fullValue, depthMap, subFieldsWithChildren, true);
}
}
} else {
// 处理普通复杂类型
// 父节点有注解所以子节点需要拼接
getTablePropertyFieldsMapRecursive(fieldType, subFields, fullKey, fullValue, depthMap, subFieldsWithChildren, true);
}
// 将子字段添加到结果中
result.putAll(subFields);
// 如果有子字段则标记当前字段为有子级需要排除
if (!subFields.isEmpty()) {
fieldsWithChildren.put(fullKey, fullValue);
// 同时将子字段中需要排除的也添加到当前需要排除的集合中
fieldsWithChildren.putAll(subFieldsWithChildren);
} else {
// 如果没有子字段如循环引用字段则直接添加该字段
result.put(fullKey, new FieldProperty(fullValue, tableProperty));
}
} else {
// 简单类型直接添加
result.put(fullKey, new FieldProperty(fullValue, tableProperty));
}
}
/**
* 处理没有@TableProperty注解的字段
* 但仍需要递归处理复杂类型字段以确保子节点有注解的字段能被获取到
*
* @param field 当前处理的字段
* @param keyPrefix key的前缀使用注解中的值构建
* @param valuePrefix value的前缀使用字段实际名称构建
* @param result 存储结果的Map
* @param depthMap 记录每个类的递归深度用于检测循环引用
* @param fieldsWithChildren 有子级的字段集合
* @param parentHasAnnotation 父节点是否有注解
*/
private static void processFieldWithoutAnnotation(Field field, String keyPrefix, String valuePrefix, Map<String, FieldProperty> result, Map<Class<?>, Integer> depthMap, Map<String, String> fieldsWithChildren, boolean parentHasAnnotation) {
// 即使字段没有注解也要处理复杂类型字段
Class<?> fieldType = field.getType();
if (isComplexType(fieldType)) {
// 构建新的前缀
String newKeyPrefix;
String newValuePrefix = valuePrefix.isEmpty() ? field.getName() : valuePrefix + "." + field.getName();
// 关键修改如果父节点没有注解则不拼接父节点字段名
if (parentHasAnnotation) {
// 父节点有注解需要拼接
newKeyPrefix = keyPrefix.isEmpty() ? field.getName() : keyPrefix + "." + field.getName();
} else {
// 父节点没有注解不拼接保持空字符串或使用子节点自己的key
newKeyPrefix = "";
}
// 用于收集子字段
Map<String, FieldProperty> subFields = new HashMap<>();
Map<String, String> subFieldsWithChildren = new HashMap<>();
if (Collection.class.isAssignableFrom(fieldType)) {
// 处理集合类型获取泛型参数类型
Type genericType = field.getGenericType();
if (genericType instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) genericType;
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (actualTypeArguments.length > 0 && actualTypeArguments[0] instanceof Class) {
Class<?> elementType = (Class<?>) actualTypeArguments[0];
// 对于集合中的元素类型递归处理其@TableProperty注解
// 父节点没有注解所以子节点不需要拼接
getTablePropertyFieldsMapRecursive(elementType, subFields, newKeyPrefix, newValuePrefix, depthMap, subFieldsWithChildren, false);
}
}
} else {
// 处理普通复杂类型
// 父节点没有注解所以子节点不需要拼接
getTablePropertyFieldsMapRecursive(fieldType, subFields, newKeyPrefix, newValuePrefix, depthMap, subFieldsWithChildren, false);
}
// 将子字段添加到结果中
result.putAll(subFields);
// 如果有子字段则标记当前字段路径为有子级需要排除
if (!subFields.isEmpty()) {
// 注意这里我们不使用注解值作为key因为当前字段没有注解
String currentPath = keyPrefix.isEmpty() ? field.getName() : keyPrefix + "." + field.getName();
fieldsWithChildren.put(currentPath, newValuePrefix);
// 同时将子字段中需要排除的也添加到当前需要排除的集合中
fieldsWithChildren.putAll(subFieldsWithChildren);
}
}
// 简单类型且没有注解的字段不需要处理
}
/**
* 判断是否为复杂类型非基本类型包装类型或String
*
* @param clazz 要判断的类
* @return 是否为复杂类型
*/
private static boolean isComplexType(Class<?> clazz) {
// 基本类型
if (clazz.isPrimitive()) {
return false;
}
// 常见的包装类型和String类型
return !(clazz.equals(String.class) ||
clazz.equals(Integer.class) ||
clazz.equals(Long.class) ||
clazz.equals(Boolean.class) ||
clazz.equals(Double.class) ||
clazz.equals(Float.class) ||
clazz.equals(Character.class) ||
clazz.equals(Byte.class) ||
clazz.equals(Short.class) ||
clazz.equals(java.util.Date.class) ||
clazz.equals(java.time.LocalDate.class) ||
clazz.equals(java.time.LocalDateTime.class));
}
@NotNull
public static List<String> getHeaders(Map<String, FieldProperty> fieldsMap) {
return fieldsMap.entrySet().stream()
.sorted(Comparator.comparingInt(entry -> entry.getValue().getTableProperty().order()))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
}

@ -0,0 +1,128 @@
package cn.isliu.core.utils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.List;
import java.util.Collection;
import java.util.Iterator;
public class StringUtil {
public static String mapToJson (Map<String, Object> map) {
StringBuilder jsonBuilder = new StringBuilder();
jsonBuilder.append("{");
Object[] keys = map.keySet().toArray();
for (int i = 0; i < keys.length; i++) {
String key = (String) keys[i];
Object value = map.get(key);
jsonBuilder.append("\"").append(key).append("\":");
if (value instanceof String) {
jsonBuilder.append("\"").append(value).append("\"");
} else {
jsonBuilder.append(value);
}
if (i < keys.length - 1) {
jsonBuilder.append(",");
}
}
jsonBuilder.append("}");
return jsonBuilder.toString();
}
public static String getSHA256(String str) {
String uniqueId;
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hashBytes = digest.digest(str.getBytes(StandardCharsets.UTF_8));
// 转换为十六进制字符串
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
uniqueId = hexString.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 algorithm not available", e);
}
return uniqueId;
}
/**
* 下划线转驼峰命名
* @param underscoreName 下划线命名的字符串
* @return 驼峰命名的字符串
*/
public static String toCamelCase(String underscoreName) {
if (underscoreName == null || underscoreName.isEmpty()) {
return underscoreName;
}
StringBuilder result = new StringBuilder();
boolean capitalizeNext = false;
for (char c : underscoreName.toCharArray()) {
if (c == '_') {
capitalizeNext = true;
} else if (capitalizeNext) {
result.append(Character.toUpperCase(c));
capitalizeNext = false;
} else {
result.append(Character.toLowerCase(c));
}
}
return result.toString();
}
/**
* 将集合转换为逗号分隔的字符串
* @param collection 集合对象
* @return 逗号分隔的字符串
*/
public static String joinWithComma(Collection<?> collection) {
if (collection == null || collection.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder();
Iterator<?> iterator = collection.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
sb.append(obj != null ? obj.toString() : "");
if (iterator.hasNext()) {
sb.append(",");
}
}
return sb.toString();
}
/**
* 将数组转换为逗号分隔的字符串
* @param array 数组对象
* @return 逗号分隔的字符串
*/
public static String joinWithComma(Object[] array) {
if (array == null || array.length == 0) {
return "";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < array.length; i++) {
sb.append(array[i] != null ? array[i].toString() : "");
if (i < array.length - 1) {
sb.append(",");
}
}
return sb.toString();
}
}