diff --git a/README.md b/README.md
index bbd87ba..9dcdf5e 100644
--- a/README.md
+++ b/README.md
@@ -121,6 +121,7 @@ employees.forEach(emp -> System.out.println(emp.name + " - " + emp.email));
- `headFontColor()`: 表头字体颜色
- `headBackColor()`: 表头背景颜色
- `enableDesc()`: 是否开启字段描述
+- `upsert()`: 是否启用 Upsert 模式
## 依赖
diff --git a/operation_manual.md b/operation_manual.md
new file mode 100644
index 0000000..c0ebe57
--- /dev/null
+++ b/operation_manual.md
@@ -0,0 +1,2669 @@
+# 飞书表格助手操作文档
+
+## 目录
+
+- [1. 项目概述](#1-项目概述)
+- [2. 快速开始](#2-快速开始)
+- [3. 核心概念](#3-核心概念)
+- [4. 注解方式使用(实体类)](#4-注解方式使用实体类)
+- [5. Map 配置方式使用](#5-map-配置方式使用)
+- [6. 高级特性](#6-高级特性)
+- [7. 实际应用场景](#7-实际应用场景)
+- [8. API 参考](#8-api-参考)
+- [9. 最佳实践](#9-最佳实践)
+- [10. 常见问题(FAQ)](#10-常见问题faq)
+
+---
+
+## 1. 项目概述
+
+### 1.1 项目简介
+
+`feishu-table-helper` 是一个简化飞书表格操作的 Java 库。通过使用注解或 Map 配置,开发者可以轻松地将 Java 实体类映射到飞书表格,实现表格的自动创建、数据读取和写入操作,大大简化了与飞书表格 API 的交互。
+
+### 1.2 核心能力
+
+- **注解驱动**:使用 `@TableProperty` 和 `@TableConf` 注解将实体类字段映射到表格列
+- **自动创建表格**:根据实体类结构自动创建飞书表格和设置表头
+- **数据读取**:从飞书表格读取数据并映射到实体类对象
+- **数据写入**:将实体类对象写入飞书表格,支持新增和更新操作(Upsert)
+- **灵活配置**:支持自定义表格样式、单元格格式、下拉选项等
+- **Map 方式**:无需定义实体类,直接使用 Map 配置操作表格
+- **多层级表头**:支持复杂的多层级表头结构
+- **分组表格**:支持分组字段,实现分组数据的创建和读取
+
+### 1.3 适用场景
+
+- **数据同步**:将业务系统的数据同步到飞书表格
+- **报表生成**:自动生成和更新飞书表格报表
+- **数据采集**:通过飞书表格收集数据并回读到系统
+- **数据展示**:将系统数据以表格形式展示给非技术人员
+- **协作办公**:团队成员通过飞书表格协作编辑数据
+
+### 1.4 版本信息
+
+- **当前版本**:0.0.5
+- **最低 Java 版本**:Java 8
+- **主要依赖**:
+ - 飞书开放平台 SDK (oapi-sdk) v2.4.21
+ - OkHttp v4.12.0
+ - Gson v2.8.9
+
+---
+
+## 2. 快速开始
+
+### 2.1 环境准备
+
+#### Maven 依赖
+
+```xml
+
+ cn.isliu
+ feishu-table-helper
+ 0.0.5
+
+```
+
+#### Gradle 依赖
+
+```gradle
+implementation 'cn.isliu:feishu-table-helper:0.0.5'
+```
+
+### 2.2 初始化配置
+
+在使用飞书表格助手之前,需要先初始化飞书客户端。你需要从飞书开放平台获取应用的 `App ID` 和 `App Secret`。
+
+```java
+import cn.isliu.core.client.FsClient;
+
+// 初始化配置
+try (FsClient fsClient = FsClient.getInstance()) {
+ fsClient.initializeClient("your_app_id", "your_app_secret");
+
+ // 后续的表格操作都在这个 try-with-resources 块中进行
+ // 或者在整个应用生命周期中保持 FsClient 实例
+}
+```
+
+**重要提示**:
+- `FsClient.getInstance()` 返回的是单例实例
+- 建议使用 try-with-resources 确保资源正确释放
+- 如果需要在应用生命周期中保持连接,可以在应用启动时初始化一次
+
+### 2.3 第一个示例
+
+让我们通过一个简单的员工信息管理示例来快速上手:
+
+#### 步骤 1:创建实体类
+
+```java
+import cn.isliu.core.BaseEntity;
+import cn.isliu.core.annotation.TableConf;
+import cn.isliu.core.annotation.TableProperty;
+
+@TableConf(headLine = 2, titleRow = 1, enableDesc = true)
+public class Employee extends BaseEntity {
+
+ @TableProperty(value = "员工编号", order = 0, desc = "员工编号不超过20个字符")
+ private String employeeId;
+
+ @TableProperty(value = "姓名", order = 1, desc = "员工姓名不超过20个字符")
+ private String name;
+
+ @TableProperty(value = "部门", order = 2, desc = "员工部门")
+ private String department;
+
+ @TableProperty(value = "邮箱", order = 3, desc = "员工邮箱")
+ private String email;
+
+ // getters and setters
+ public String getEmployeeId() { return employeeId; }
+ public void setEmployeeId(String employeeId) { this.employeeId = employeeId; }
+
+ public String getName() { return name; }
+ public void setName(String name) { this.name = name; }
+
+ public String getDepartment() { return department; }
+ public void setDepartment(String department) { this.department = department; }
+
+ public String getEmail() { return email; }
+ public void setEmail(String email) { this.email = email; }
+}
+```
+
+#### 步骤 2:创建表格
+
+```java
+import cn.isliu.FsHelper;
+
+// 假设你已经初始化了 FsClient
+String spreadsheetToken = "your_spreadsheet_token"; // 电子表格Token
+String sheetId = FsHelper.create("员工表", spreadsheetToken, Employee.class);
+System.out.println("创建的工作表ID: " + sheetId);
+```
+
+#### 步骤 3:写入数据
+
+```java
+List employees = new ArrayList<>();
+Employee emp = new Employee();
+emp.setEmployeeId("E001");
+emp.setName("张三");
+emp.setDepartment("技术部");
+emp.setEmail("zhangsan@company.com");
+employees.add(emp);
+
+Employee emp2 = new Employee();
+emp2.setEmployeeId("E002");
+emp2.setName("李四");
+emp2.setDepartment("产品部");
+emp2.setEmail("lisi@company.com");
+employees.add(emp2);
+
+FsHelper.write(sheetId, spreadsheetToken, employees);
+```
+
+#### 步骤 4:读取数据
+
+```java
+List employees = FsHelper.read(sheetId, spreadsheetToken, Employee.class);
+employees.forEach(emp -> {
+ System.out.println(emp.getName() + " - " + emp.getEmail());
+ System.out.println("唯一ID: " + emp.getUniqueId());
+ System.out.println("行号: " + emp.getRow());
+});
+```
+
+---
+
+## 3. 核心概念
+
+### 3.1 两种使用方式
+
+#### 方式一:注解方式(实体类)
+
+使用 `@TableConf` 和 `@TableProperty` 注解在实体类上定义表格结构,适用于:
+- 表格结构相对固定
+- 需要类型安全
+- 需要 IDE 自动补全
+
+**优点**:
+- 类型安全
+- IDE 支持好
+- 代码可读性强
+
+**缺点**:
+- 需要定义实体类
+- 结构变更需要重新编译
+
+#### 方式二:Map 配置方式
+
+使用 `MapSheetConfig`、`MapTableConfig` 和 `MapFieldDefinition` 配置表格,适用于:
+- 动态字段
+- 表格结构经常变化
+- 不需要定义实体类
+
+**优点**:
+- 灵活,支持动态配置
+- 无需定义实体类
+- 适合配置驱动的场景
+
+**缺点**:
+- 类型安全性较低
+- 需要手动处理类型转换
+
+### 3.2 重要术语
+
+#### spreadsheetToken(电子表格 Token)
+
+飞书电子表格的唯一标识符。你可以在飞书表格的 URL 中找到,例如:
+```
+https://example.feishu.cn/sheets/shtcnxxxxxxxxxxxxxxxxx
+ ^^^^^^^^^^^^^^^^^^^^^^^^
+ 这就是 spreadsheetToken
+```
+
+#### sheetId(工作表 ID)
+
+电子表格中的单个工作表的唯一标识符。通过 `FsHelper.create()` 方法创建表格后会返回这个 ID。
+
+#### titleRow(标题行)
+
+表格中表头所在的行号(从 1 开始计数)。例如:
+- `titleRow = 1` 表示表头在第 1 行
+- `titleRow = 3` 表示表头在第 3 行
+
+#### headLine(数据起始行 - 1)
+
+表格中数据开始的行号(从 1 开始计数)。通常等于 `titleRow + 1`,但如果有描述行,可能需要加 2。
+
+**示例**:
+```
+行1: [表头分组1] [表头分组2] <- 表头分组行(可选)
+行2: [列名1] [列名2] <- 标题行(titleRow = 2)
+行3: [描述1] [描述2] <- 描述行(可选,enableDesc = true)
+行4: [数据1] [数据2] <- 数据起始行(headLine = 3)
+```
+
+#### uniqueId(唯一标识)
+
+系统根据唯一键字段自动计算的唯一标识符。用于:
+- 数据去重
+- Upsert 模式中的数据匹配
+- 数据更新
+
+#### Upsert 模式
+
+Upsert(Update + Insert)模式是一种数据写入策略:
+- **默认开启**(`upsert = true`):根据唯一键匹配,如果数据已存在则更新,不存在则追加
+- **关闭**(`upsert = false`):不匹配唯一键,所有数据直接追加到表格末尾
+
+---
+
+## 4. 注解方式使用(实体类)
+
+### 4.1 实体类定义
+
+#### @TableConf 注解
+
+`@TableConf` 注解用于配置整个表格的全局属性,必须放在实体类上。
+
+**主要参数**:
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `uniKeys` | String[] | {} | 唯一键字段名数组,用于数据更新和去重 |
+| `headLine` | int | 1 | 数据起始行号 |
+| `titleRow` | int | 1 | 标题行行号 |
+| `enableCover` | boolean | false | 是否覆盖已存在的数据 |
+| `isText` | boolean | false | 是否设置表格为纯文本格式 |
+| `enableDesc` | boolean | false | 是否启用字段描述行 |
+| `headFontColor` | String | "#000000" | 表头字体颜色(十六进制) |
+| `headBackColor` | String | "#cccccc" | 表头背景颜色(十六进制) |
+| `upsert` | boolean | true | 是否启用 Upsert 模式 |
+
+**示例**:
+
+```java
+@TableConf(
+ headLine = 4,
+ titleRow = 3,
+ enableDesc = true,
+ uniKeys = {"employeeId"},
+ headFontColor = "#ffffff",
+ headBackColor = "#1890ff"
+)
+public class Employee extends BaseEntity {
+ // ...
+}
+```
+
+#### @TableProperty 注解
+
+`@TableProperty` 注解用于配置实体类字段与表格列的映射关系,必须放在字段上。
+
+**主要参数**:
+
+| 参数 | 类型 | 默认值 | 说明 |
+|------|------|--------|------|
+| `value` | String[] | {} | 表格列名,支持多层级表头 |
+| `desc` | String | "" | 字段描述 |
+| `order` | int | Integer.MAX_VALUE | 字段排序顺序,数值越小越靠前 |
+| `type` | TypeEnum | TEXT | 字段类型(文本、单选、多选等) |
+| `enumClass` | Class | BaseEnum.class | 枚举类(用于单选/多选) |
+| `fieldFormatClass` | Class | FieldValueProcess.class | 字段格式化处理类 |
+| `optionsClass` | Class | OptionsValueProcess.class | 选项处理类 |
+
+**示例**:
+
+```java
+// 单层级表头
+@TableProperty(value = "姓名", order = 0)
+private String name;
+
+// 多层级表头
+@TableProperty(value = {"ID", "员工信息", "员工编号"}, order = 0)
+private String employeeId;
+
+// 单选字段
+@TableProperty(value = "状态", order = 1, type = TypeEnum.SINGLE_SELECT, enumClass = StatusEnum.class)
+private String status;
+
+// 带描述的字段
+@TableProperty(value = "邮箱", order = 2, desc = "员工邮箱地址")
+private String email;
+```
+
+#### BaseEntity 基类
+
+所有使用注解方式的实体类都应该继承 `BaseEntity`,它提供了以下属性:
+
+- `uniqueId`:唯一标识符(自动生成)
+- `row`:数据所在的行号
+- `rowData`:原始行数据(Map格式)
+
+```java
+public class Employee extends BaseEntity {
+ // 你的字段定义
+}
+```
+
+### 4.2 创建表格
+
+#### 基础创建
+
+使用 `FsHelper.create()` 方法创建表格:
+
+```java
+String sheetId = FsHelper.create("员工表", spreadsheetToken, Employee.class);
+```
+
+这个方法会:
+1. 创建新的工作表
+2. 根据实体类注解生成表头
+3. 设置表格样式(字体、背景色等)
+4. 合并多层级表头的单元格
+5. 设置下拉选项(如果配置了枚举类)
+6. 设置字段描述(如果启用了 `enableDesc`)
+
+#### 使用 SheetBuilder 高级配置
+
+`SheetBuilder` 提供了更灵活的配置方式,支持链式调用:
+
+```java
+String sheetId = FsHelper.createBuilder("员工表", spreadsheetToken, Employee.class)
+ .includeFields("employeeId", "name", "email") // 只包含指定字段
+ .excludeFields("department") // 排除指定字段
+ .customProperties(customProps) // 自定义属性
+ .build();
+```
+
+**SheetBuilder 主要方法**:
+
+- `includeFields(String... fields)`:只包含指定的字段
+- `excludeFields(String... fields)`:排除指定的字段
+- `customProperties(Map props)`:自定义属性,用于传递给选项处理类等
+
+#### 多层级表头
+
+多层级表头通过 `@TableProperty` 的 `value` 参数配置,使用字符串数组:
+
+```java
+@TableConf(headLine = 4, titleRow = 3)
+public class Employee extends BaseEntity {
+
+ // 三级表头:ID -> 员工信息 -> 员工编号
+ @TableProperty(value = {"ID", "员工信息", "员工编号"}, order = 0)
+ private String employeeId;
+
+ // 三级表头:ID -> 员工信息 -> 姓名
+ @TableProperty(value = {"ID", "员工信息", "姓名"}, order = 1)
+ private String name;
+
+ // 二级表头:联系方式 -> 邮箱
+ @TableProperty(value = {"联系方式", "邮箱"}, order = 2)
+ private String email;
+
+ // 单级表头:部门
+ @TableProperty(value = "部门", order = 3)
+ private String department;
+}
+```
+
+**表头结构示例**:
+
+```
+行1: [ ID ] [ ] [ ]
+行2: [ 员工信息 ] [联系方式] [ ]
+行3: [员工编号] [ 姓名 ] [ 邮箱 ] [ 部门 ]
+行4: [数据... ] [数据...] [数据...] [数据...]
+```
+
+系统会自动合并相同值的单元格。
+
+#### 分组表格
+
+分组表格允许多个字段组,每个组包含相同的字段集合。例如,在问卷调查中,可能需要为多个受访者记录相同的信息。
+
+**注意**:分组表格功能主要在 Map 配置方式中使用,注解方式通过多层级表头实现类似效果。
+
+### 4.3 写入数据
+
+#### 基础写入
+
+使用 `FsHelper.write()` 方法写入数据:
+
+```java
+List employees = new ArrayList<>();
+// ... 准备数据
+FsHelper.write(sheetId, spreadsheetToken, employees);
+```
+
+**写入逻辑**:
+1. 根据 `uniKeys` 计算每条数据的 `uniqueId`
+2. 在现有数据中查找匹配的 `uniqueId`
+3. 如果找到(且 `upsert = true`),更新对应行
+4. 如果没找到,追加到表格末尾
+
+#### 使用 WriteBuilder 高级配置
+
+```java
+FsHelper.writeBuilder(sheetId, spreadsheetToken, employees)
+ .ignoreUniqueFields("updateTime") // 计算唯一ID时忽略指定字段
+ .build();
+```
+
+**WriteBuilder 主要方法**:
+
+- `ignoreUniqueFields(String... fields)`:计算唯一ID时忽略的字段
+
+#### Upsert 模式
+
+Upsert 模式是默认启用的,可以通过 `@TableConf(upsert = false)` 关闭。
+
+**示例:启用 Upsert 模式**
+
+```java
+@TableConf(uniKeys = {"employeeId"}, upsert = true)
+public class Employee extends BaseEntity {
+ @TableProperty(value = "员工编号", order = 0)
+ private String employeeId;
+ // ...
+}
+
+// 写入数据
+Employee emp1 = new Employee();
+emp1.setEmployeeId("E001");
+emp1.setName("张三");
+employees.add(emp1);
+
+FsHelper.write(sheetId, spreadsheetToken, employees);
+// 如果表格中已存在 employeeId = "E001" 的记录,会更新该记录
+// 如果不存在,会追加新记录
+```
+
+**示例:关闭 Upsert 模式(纯追加)**
+
+```java
+@TableConf(uniKeys = {"employeeId"}, upsert = false)
+public class Employee extends BaseEntity {
+ // ...
+}
+
+// 写入数据
+FsHelper.write(sheetId, spreadsheetToken, employees);
+// 所有数据都会追加到表格末尾,不会检查是否已存在
+```
+
+#### 图片上传
+
+支持将图片上传到飞书表格单元格中:
+
+```java
+import cn.isliu.core.FileData;
+import cn.isliu.core.enums.FileType;
+
+public class Product extends BaseEntity {
+ @TableProperty(value = "产品图片", order = 0)
+ private FileData image;
+
+ // getters and setters
+}
+
+// 写入数据
+Product product = new Product();
+FileData fileData = new FileData();
+fileData.setFileName("product.jpg");
+fileData.setImageData(imageBytes); // 图片的字节数组
+fileData.setFileType(FileType.IMAGE.getType());
+product.setImage(fileData);
+
+List products = new ArrayList<>();
+products.add(product);
+FsHelper.write(sheetId, spreadsheetToken, products);
+```
+
+### 4.4 读取数据
+
+#### 基础读取
+
+使用 `FsHelper.read()` 方法读取数据:
+
+```java
+List employees = FsHelper.read(sheetId, spreadsheetToken, Employee.class);
+```
+
+**读取逻辑**:
+1. 读取表格数据
+2. 根据 `titleRow` 识别表头
+3. 从 `headLine` 开始读取数据行
+4. 将数据映射到实体类对象
+5. 自动计算 `uniqueId` 并设置到 `BaseEntity`
+
+#### 使用 ReadBuilder 高级配置
+
+```java
+List employees = FsHelper.readBuilder(sheetId, spreadsheetToken, Employee.class)
+ .ignoreUniqueFields("updateTime") // 计算唯一ID时忽略指定字段
+ .build();
+```
+
+**ReadBuilder 主要方法**:
+
+- `ignoreUniqueFields(String... fields)`:计算唯一ID时忽略的字段
+
+**读取后的对象属性**:
+
+```java
+for (Employee emp : employees) {
+ // 业务字段
+ System.out.println(emp.getName());
+
+ // BaseEntity 提供的属性
+ System.out.println("唯一ID: " + emp.getUniqueId());
+ System.out.println("行号: " + emp.getRow());
+ System.out.println("原始数据: " + emp.getRowData());
+}
+```
+
+---
+
+## 5. Map 配置方式使用
+
+Map 配置方式适用于不需要定义实体类的场景,提供了更大的灵活性。
+
+### 5.1 MapFieldDefinition 字段定义
+
+`MapFieldDefinition` 用于定义单个字段的所有属性。
+
+#### 快速创建方法
+
+```java
+import cn.isliu.core.config.MapFieldDefinition;
+
+// 创建文本字段
+MapFieldDefinition field1 = MapFieldDefinition.text("字段名", 0);
+
+// 创建文本字段(带描述)
+MapFieldDefinition field2 = MapFieldDefinition.text("字段名", 0, "字段描述");
+
+// 创建单选字段
+MapFieldDefinition field3 = MapFieldDefinition.singleSelect("状态", 1, "启用", "禁用");
+
+// 创建多选字段
+MapFieldDefinition field4 = MapFieldDefinition.multiSelect("标签", 2, "标签1", "标签2", "标签3");
+
+// 使用枚举类创建单选字段
+MapFieldDefinition field5 = MapFieldDefinition.singleSelectWithEnum("状态", 1, StatusEnum.class);
+```
+
+#### 使用 Builder 创建
+
+```java
+MapFieldDefinition field = MapFieldDefinition.builder()
+ .fieldName("字段名")
+ .order(0)
+ .type(TypeEnum.TEXT)
+ .description("字段描述")
+ .required(true)
+ .defaultValue("默认值")
+ .build();
+```
+
+#### 字段类型
+
+支持的类型(`TypeEnum`):
+- `TEXT`:文本
+- `SINGLE_SELECT`:单选
+- `MULTI_SELECT`:多选
+- `NUMBER`:数字
+- `DATE`:日期
+- `TEXT_FILE`:文本文件
+- `MULTI_TEXT`:多个文本(逗号分割)
+- `TEXT_URL`:文本链接
+
+### 5.2 MapSheetConfig 表格配置
+
+`MapSheetConfig` 用于配置表格创建时的属性。
+
+#### 基础配置
+
+```java
+import cn.isliu.core.config.MapSheetConfig;
+
+MapSheetConfig config = MapSheetConfig.sheetBuilder()
+ .titleRow(2) // 标题行
+ .headLine(3) // 数据起始行
+ .headStyle("#ffffff", "#000000") // 字体颜色,背景颜色
+ .isText(false) // 是否纯文本格式
+ .enableDesc(true) // 是否启用描述
+ .addField(MapFieldDefinition.text("字段1", 0))
+ .addField(MapFieldDefinition.text("字段2", 1))
+ .build();
+```
+
+#### 批量添加字段
+
+```java
+List fields = new ArrayList<>();
+fields.add(MapFieldDefinition.text("字段1", 0));
+fields.add(MapFieldDefinition.text("字段2", 1));
+
+MapSheetConfig config = MapSheetConfig.sheetBuilder()
+ .addFields(fields) // 批量添加
+ .build();
+
+// 或者使用可变参数
+MapSheetConfig config2 = MapSheetConfig.sheetBuilder()
+ .addFields(
+ MapFieldDefinition.text("字段1", 0),
+ MapFieldDefinition.text("字段2", 1)
+ )
+ .build();
+```
+
+### 5.3 MapTableConfig 读写配置
+
+`MapTableConfig` 用于配置数据读写时的属性。
+
+```java
+import cn.isliu.core.config.MapTableConfig;
+
+MapTableConfig config = MapTableConfig.builder()
+ .titleRow(2) // 标题行
+ .headLine(3) // 数据起始行
+ .addUniKeyName("字段1") // 添加唯一键
+ .enableCover(false) // 是否覆盖
+ .upsert(true) // 是否启用 Upsert
+ .ignoreNotFound(false) // 是否忽略未找到的数据
+ .build();
+```
+
+### 5.4 创建表格
+
+#### 使用 createMapSheet
+
+```java
+MapSheetConfig config = MapSheetConfig.sheetBuilder()
+ .titleRow(2)
+ .headLine(3)
+ .addField(MapFieldDefinition.text("姓名", 0))
+ .addField(MapFieldDefinition.text("年龄", 1))
+ .build();
+
+String sheetId = FsHelper.createMapSheet("表格名称", spreadsheetToken, config);
+```
+
+#### 使用 MapSheetBuilder
+
+```java
+String sheetId = FsHelper.createMapSheetBuilder("表格名称", spreadsheetToken)
+ .titleRow(2)
+ .headLine(3)
+ .headStyle("#ffffff", "#000000")
+ .isText(false)
+ .enableDesc(true)
+ .addField(MapFieldDefinition.text("姓名", 0))
+ .addField(MapFieldDefinition.text("年龄", 1))
+ .build();
+```
+
+**MapSheetBuilder 主要方法**:
+
+- `titleRow(int row)`:设置标题行
+- `headLine(int line)`:设置数据起始行
+- `headStyle(String fontColor, String backColor)`:设置表头样式
+- `isText(boolean isText)`:是否纯文本格式
+- `enableDesc(boolean enable)`:是否启用描述
+- `addField(MapFieldDefinition field)`:添加单个字段
+- `addFields(List fields)`:批量添加字段
+- `addFields(MapFieldDefinition... fields)`:批量添加字段(可变参数)
+- `fields(List fields)`:设置所有字段(覆盖现有)
+- `groupFields(String... groupFields)`:设置分组字段(用于分组表格)
+
+### 5.5 写入数据
+
+#### 使用 writeMap
+
+```java
+List