智慧门店工单复盘全流程
流程概览
一次完整的工单复盘涉及 3个文档,按顺序执行:
Step 0:复盘跟踪表(前置检查)
检查/创建 目标月份记录 → 落数据基线
阶段一:工单数据表(飞书多维表格)
Step 1 → Step 2 → Step 3 → Step 4 → Step 5 → Step 5b
解析月份 → 获取Cookie → 拉取Furcas数据 → 创建/复用月份bitable(模板:J4tawf06GijvEmkBTSCcdyNsnyd)→ 导入CSV → 更新mention-doc链接
阶段二:工单复盘总文档(知识库文档)
Step 6 → Step 7
创建文档(模板:GEqawooPYiDXLWkRKZHcJnLcnFc)→ 填充变量
阶段三:开发复盘文档(知识库文档)
Step 8
创建文档(模板:Secqwa7deiaRwTkUsG6cJ7xpnzj)→ 填充TOP5统计
关键链接
| 类型 | 链接/Token |
|---|---|
| 工单数据Bitable模板 | J4tawf06GijvEmkBTSCcdyNsnyd (table: tblXtjn9QHTcB5oa, view: vew8A2EB9c) |
| 复盘总文档模板 | GEqawooPYiDXLWkRKZHcJnLcnFc |
| 开发复盘文档模板 | Secqwa7deiaRwTkUsG6cJ7xpnzj |
| 复盘跟踪表(前置数据源) | YMQjwQeWCipABCkl8y7ckZlunQe (table: tblpygt9n8dqZcsw,参见 Step 0) |
| Bot App ID | cli_a97b6a0ffc399cc0 |
前置步骤:复盘跟踪表
Step 0: 检查/创建跟踪表记录
在创建任何文档之前,先检查复盘跟踪表是否已有目标月份的记录。
跟踪表链接
https://sqb.feishu.cn/wiki/YMQjwQeWCipABCkl8y7ckZlunQe?table=tblpygt9n8dqZcsw&view=vewme7V2C9
- Bitable App Token:
YMQjwQeWCipABCkl8y7ckZlunQe - Table ID:
tblpygt9n8dqZcsw
⚠️ 核心规则:必须先查存在,绝不复件
这条规则是全局最高优先级。任何创建操作前必须先查,查到已存在则直接复用,绝不允许创建第二条同名记录。
操作步骤
- 查询已有记录(必须执行,不可跳过):
操作:feishu_bitable_app_table_record list
参数:
app_token: "YMQjwQeWCipABCkl8y7ckZlunQe"
table_id: "tblpygt9n8dqZcsw"
filter: {
conjunction: "and",
conditions: [
{field_name: "复盘范围", operator: "is", value: ["2026-05"]}
]
}
注意 field_name 必须用中文名「复盘范围」(是文本类型的主字段),不要用 field_id。
-
判断是否已有目标月份:
- ✅ 找到记录 → 立即停止创建流程,直接使用已有记录的
record_id及其包含的所有数据(工单总数、复盘工单数、各分类统计等),进入阶段一 - ❌ 没找到 → 执行第 3 步创建新记录
绝对禁止:查到已有记录后,再创建第二条同名或同周期的记录。
- ✅ 找到记录 → 立即停止创建流程,直接使用已有记录的
-
新建记录(仅在确认不存在时执行):
操作:feishu_bitable_app_table_record create
参数:
app_token: "YMQjwQeWCipABCkl8y7ckZlunQe"
table_id: "tblpygt9n8dqZcsw"
fields:
复盘范围: "{月份标签}" // 整月如 "2026-05"(不要加 "-整" 后缀)
开始时间: {毫秒时间戳} // 范围起始日期 00:00:00 UTC+8
结束时间: {毫秒时间戳} // 范围结束日期 23:59:59 UTC+8
日期: {毫秒时间戳} // 当前操作日期(执行复盘的当天)
字段说明
| 字段 | 类型 | 填写规则 |
|---|---|---|
复盘范围 | 文本 (主字段) | 整月:2026-05(不要写 2026-05-整);上半月:2026-05-上;下半月:2026-05-下 |
开始时间 | 日期(毫秒时间戳) | 范围起始日 00:00:00 UTC+8,如2026年5月整月 → 1777593600000(对应 2026-05-01T00:00:00+08:00) |
结束时间 | 日期(毫秒时间戳) | 范围结束日 23:59:59 UTC+8,如2026年5月整月 → 1780271999000(对应 2026-05-31T23:59:59+08:00) |
日期 | 日期(毫秒时间戳) | 当前操作日期,即执行复盘的当天,如2026年5月6日 → 1777910400000(2026-05-06T00:00:00+08:00) |
⏰ 时间戳必须二次确认:创建记录后立即读取该记录,检查时间戳对应的年份是否与目标年份一致(2026年)。一个常见的错误是毫秒时间戳算成了2025年的日期。如果时间戳不对,删除该记录,重新计算并创建。
注意:其余字段(工单总数、复盘工单、超时工单数、各解决类别统计等)均为 Lookup/Formula 自动计算字段,无需手动填写,数据导入后会自行更新。
创建后操作
- ✅ 确认记录已成功创建
- ✅ 读取刚创建的记录,验证时间戳是否对应正确的日期(2026年不是2025年)
- 记录下
record_id,后续阶段二填充文档第三节表格时需要 - 进入阶段一,开始拉取工单数据
阶段一:工单数据表
Step 1: 解析用户意图
用户说"做X月工单复盘"或"复盘X月"时:
- 提取目标月份(如
2026-04) - 计算开始/结束日期:
2026-04-01~2026-04-30 - 记录月份标签:
2026-04
如果用户提到"拉取X到X的工单数据",直接使用指定的起止日期。
Step 2: 获取 Cookie
本阶段需要有效的 Furcas Cookie。
- 如果已有可用 cookie,直接使用
- 如果没有,引导用户:
- 登录
https://furcas.shouqianba.com - F12 开发者工具 → Network → 复制 Cookie 请求头
- 粘贴到
scripts/fetch_furcas.py的cookie = ""变量中
- 登录
Step 3: 拉取工单数据
python scripts/fetch_furcas.py -s "2026-04-01" -n "2026-04-30" -o /workspace/furcas.csv
输出字段:问题描述、问题链接、工单状态|修复情况、问题原因、责任人、解决类别、解决模块、超时时间、超时备注
Step 4: 创建/复用月份工单数据 Bitable
⚠️ 必须检查:先确认目标月份目录下是否已有同名 bitable。用 feishu_wiki_space_node list 列出目录内容,看是否已有工单数据表。如有则直接复用,跳过创建。
正确创建方式(仅需一步)
使用 feishu_wiki_space_node copy 将模板节点直接复制到目标月份目录:
操作:feishu_wiki_space_node copy
参数:
node_token: "J4tawf06GijvEmkBTSCcdyNynsd" // 工单数据表模板
space_id: "7510046829784662017"
target_parent_token: "{目标月份目录节点token}"
target_space_id: "7510046829784662017"
为什么必须用 wiki node copy?
- ❌
feishu_bitable_app copy→ 创建独立 bitable(不在 wiki 目录中) - ❌
feishu_wiki_space_node create→ 创建空白 bitable(表名 "Table",字段全英文) - ✅
feishu_wiki_space_node copy→ 直接在 wiki 内创建模板副本,字段/视图全部正确
复制后的处理
- 重命名:bitable 名称 →
智慧门店-2026-5月工单数据 - 删除模板空记录:模板自带 5 条空记录(
fields: {}),用batch_delete清空 - 验证字段结构:应包含以下 9 个字段:
文本(Text, primary)、问题链接(Url)、工单状态|修复情况(Text)、问题原因(Text)责任人(Text)、解决类别(SingleSelect)、解决模块(SingleSelect)超时时间(Text)、超时备注(Text)
关键约束:
工单状态|修复情况含竖线|(CSV中为/,脚本会自动映射)问题链接是 URL 类型,导入时用{"link": "URL", "text": "查看工单"}责任人是文本类型(type=1),不是人员类型
Step 5: 导入数据到多维表格
推荐:使用导入脚本
APP_TOKEN={月份bitable的app_token} \
TABLE_ID={月份bitable的table_id} \
CSV_PATH=/workspace/furcas.csv \
BATCH_SIZE=15 \
node scripts/import_to_bitable.mjs --name "数据表"
⚠️
--name参数必须传:模板复制出来的表名叫数据表,但脚本默认校验名为工单数据。必须用--name "数据表"或TABLE_NAME="数据表"让校验通过。不传的话校验会警告但不阻止导入。
脚本自动:校验表名 → 清空旧数据 → 分批创建记录 → 创建看板视图 → 验证 → 去重检查。
⚠️
batch_delete参数名是record_ids(不是records):这是飞书 API 的规范。如果写错,清空静默失败,新数据追加到旧数据上,导致重复。
⚠️ 去重检查是最后一道防线:即使清空步骤因意外失败(如网络超时、API 限流),去重步骤也会自动删除重复工单,保证每个工单 ID 唯一。
⚠️ 表名校验:脚本在导入前会调用 API 读取目标表的实际名称,与
TABLE_NAME环境变量比对。如果名称不匹配会给出警告,防止数据写入错误的表。
手动替代方案:使用 feishu_bitable_app_table_record 工具 action=batch_create,records 参数用 string="false"。
阶段二:工单复盘总文档
Step 5b: 更新文档 Mention-Doc 链接
导入数据后,立即更新复盘总文档中的 mention-doc 链接,确保指向当前月份的正确表格和开发复盘文档。
为什么必须做: 复盘总文档从模板复制后,mention-doc 链接指向的是模板的占位对象(如旧月份的表格或模板文档)。不更新的话,点击链接会看到错误的数据。
更新步骤
- 获取复盘总文档的文档 ID(从 step 6/7 创建或之前创建的文档获取)
- 找到 mention-doc 所在的 block:
- 遍历文档根级 children,找到包含「月具体工单请查看飞书表格」文本的 block
- 该 block 是 type=2(text),其 elements[] 中包含 mention_doc 类型的 element
- PATCH 更新该 block 的 elements:
工单数据表 mention-doc:token → 当前月份的 bitable token,obj_type → 8(bitable)开发复盘文档 mention-doc:token → 当前月份的 wiki 节点 token,obj_type → 16(wiki)- 不写 text_run 的 content(显示文本由文档标题决定)
- obj_type 必须传整数(8=bitable, 16=wiki),传字符串会报
99992402
脚本参考
// PATCH /docx/v1/documents/{docId}/blocks/{blockId}
// Body: { update_text_elements: { elements: [originalElements] } }
// 只改 mention_doc.token/obj_type/url,保持 text_run 不变
第 34 个经验(2026-05-07):Mention-doc 更新后 token 被自动解析
发送 wiki 节点 token 后,飞书 API 会自动解析为实际文档的 obj_token,类型也会从 wiki(16) 变为具体类型(如 docx=22, bitable=8)。修改后 fetch-doc 验证即可。
Step 6: 创建工单复盘总文档
前置检查
在创建之前,先检查目标月度目录下是否已存在同名的文档。
使用 feishu_wiki_space_node list 列出月度目录下的所有节点。如果已有同名文档,直接进入 Step 7 更新即可,不要重复创建。
创建步骤
- 复制模板:从模板
GEqawooPYiDXLWkRKZHcJnLcnFc复制创建新文档 - 重命名:按月份命名,如
智慧门店-2026-05月-工单复盘 - 放入对应目录:确认文档在正确的年度/月度知识库目录下
复制文档的两种方式:
- 使用
feishu_wiki_space_node的copy操作(如果模板是 wiki 节点),传入space_id和target_parent_token - 或用
feishu_create_doc从模板创建(传入 wiki 节点 token) - 或用
feishu_drive_file的copy操作
Step 7: 填充复盘总文档变量
模板中有大量 x 占位符(橙色高亮),需要按以下规则替换为实际数据。
模板结构要点(非常重要)
不要破坏模板原有的换行和段落结构。 模板中每个变量的位置和间距都有意义。以下从实际模板 GEqawooPYiDXLWkRKZHcJnLcnFc 获取的精确结构:
# 一、会议信息
会议主题:智慧门店-<text color="orange">2026-</text><text color="orange">10</text>月工单复盘
会议时间:<text color="orange">2026.05.07(周四) 16:00 - 16:30</text>
# 二、会议议程
**复盘时间:**<text color="orange">**2026.05.07**</text>
**复盘范围:**<text color="orange">**2026.04.01 00:00:00-2026.04.30 23:59:59**</text>
**参与人员:**
- **开发:朱栋泉、**<text color="orange">**李文茂(28)、付雷(12)、王佳明(10)、赵杭琪(9)**</text>
- **产品:**
- **测试:**
- **技术支持:崔文思**
- **运营:蒋达周**
# 三、工单总计<text color="orange">XX</text>个(按工单数统计)
[Table: 10行×11列,3个月数据(2026-02/03/04),每33格一组=110格]
⚠️ 2026-04 组(最近一个月)使用整体橙色格式,与 02/03 组的逐字橙色不同
<text color="orange">2026-04</text>月具体工单请查看飞书表格:<mention-doc token="J4tawf06GijvEmkBTSCcdyNynsd" type="wiki">2026-xx月-智慧门店工单数据</mention-doc>
开发复盘文档:<mention-doc token="Secqwa7deiaRwTkUsG6cJ7xpnzj" type="wiki">开发复盘文档模板</mention-doc>
# 四、工单总结
## 工单数量分析
- <text color="orange">2026-04</text>月共产生工单<text color="orange">646</text>个,本次复盘筛选出<text color="orange">154</text>个工单。同比<text color="orange">2026-03</text>月<text color="orange">增加25.19%</text>
- 从**解决类别**看,工单主要集中在:<text color="orange">外部原因-无需技术排查(208)、设计如此-无需优化(160)、外部原因-需技术排查(81)</text>
- 从**解决模块**看:工单主要集中在:<text color="orange">收银系统-餐饮(197)、打印机(150)、扫码点单(117)</text>
## 工单时效分析
<text color="orange">90</text>个超时工单(其中产研介入<text color="orange">59</text>个),时效内解决占比<text color="orange">84.67</text>%,较3月<text color="orange">82%</text>有所提高。
主要超时原因:
## 问题总结
立即更改内容跟进:
同步客服&技术支持:
# 五、历史跟进事项
<text color="orange">**2026-03**</text>**月跟进事项**:
...
<text color="orange">**2026-02**</text>**月跟进事项**:
...
📌 模板关键位置标注
| 位置 | 说明 |
|---|---|
| Section 3 标题 | # 三、工单总计<text color="orange">XX</text>个(按工单数统计),无 callout 块 |
| Section 3 末尾 | Mention-doc(工单数据表 bitable + 开发复盘文档 wiki) |
| Section 4: 工单数量分析 | 含同比上月数据行 <text color="orange">同比...增加xx%</text> |
| Section 4: 工单时效分析 | 主要超时原因:后为填空区域 |
| Section 5 | 月份为「目标月-1」和「目标月-2」,如05月复盘则显示04和03月 |
| 表格最后一组(最近月) | 使用整体橙色 <text color="orange">已解决x(产研介x)</text> 格式,与前面两组不同 |
📌 Section 5 月份命名规则
- 表头显示「目标月-1」和「目标月-2」的跟进事项
- 例如:05月复盘 → 显示
**2026-04**月跟进事项和**2026-03**月跟进事项 - 月份部分用橙色标签包裹:
<text color="orange">**2026-03**</text>**月跟进事项**:
🏷️ 命名规范(必须遵守)
创建文档时禁止直接使用模板的名称。每次创建必须按以下规范重命名:
| 文档类型 | 模板节点 | 创建后命名规范 | 示例(2026-05月) |
|---|---|---|---|
| 工单数据跟踪表(多维表格) | J4tawf06GijvEmkBTSCcdyNynsd | 智慧门店-2026-xx月-工单数据 | 智慧门店-2026-05月-工单数据 |
| 工单复盘文档(知识库文档) | GEqawooPYiDXLWkRKZHcJnLcnFc | 智慧门店-2026-xx月-工单复盘 | 智慧门店-2026-05月-工单复盘 |
| 开发复盘文档(知识库文档) | Secqwa7deiaRwTkUsG6cJ7xpnzj | 智慧门店-2026-xx月-开发复盘文档 | 智慧门店-2026-05月-开发复盘文档 |
重命名方式(API 不支持,必须手工):
- 复制模板创建文档后,在飞书页面打开
- 右键左侧目录树中的节点 → 选择「重命名」
- 按上表命名规范输入新名称
- 标题改完后,mention-doc 的显示文字会自动更新(因为 mention 显示的是文档实际标题)
⚠️ Section 3 的表格是
<lark-table>格式(带 rowspan/colspan 的复杂表格),不能用 markdown 表格替代。 使用overwrite模式重写文档时,表格结构会丢失。因此 Section 3 必须保留原始<lark-table>代码。
⚠️ ⚠️ ⚠️ 三大数据来源规则(必须遵守,不要再出错) ⚠️ ⚠️ ⚠️
以下3条规则已经被用户多次重复强调。每次出错都会导致文档数据混乱。阅读此文件时请逐条仔细核对!
规则1:参与人员 → 取开发复盘文档 TOP5
复盘总文档中 # 二、会议议程 → 参与人员: → 开发: 后面的橙色部分,必须填入开发复盘文档 Section 2 中前5名责任人 的姓名和工单数量。
- 不要使用模板中的旧数据(如
李文茂(28)、付雷(12)、王佳明(10)、赵杭琪(9)) - 不要自行编造
- 准确取本月的开发复盘文档中 Section 2 的5位开发人员及其工单数量
- 格式:
李文茂(5)、蒋达周(3)、赵杭琪(2)、金海轩(2)、王金楠(2)
规则2:工单总计 XX + 第三节表格 → 全部来自复盘跟踪表
模板中所有带 x 的数字占位符(包括 Section 3 标题 XX、表格中所有 x)的填充值,必须全部从 复盘跟踪表 Bitable 获取:
复盘跟踪表: https://sqb.feishu.cn/wiki/YMQjwQeWCipABCkl8y7ckZlunQe?table=tblpygt9n8dqZcsw
- Section 3 标题
三、工单总计XX个→ X 填复盘跟踪表中当月记录的工单总数字段 - 表格数据 → 全部填复盘跟踪表中各月份的字段值
- 表格顺序:必须按时间从上到下排列,新数据在下方。例如做 5 月复盘时:
- 第一行(最上方):2026-03(最旧)
- 第二行(中间):2026-04
- 第三行(最下方):2026-05(最新)
- 不要把最新行放在最上面
规则3:Section 4 工单数量分析 + 工单时效分析 → 全部来自复盘跟踪表
# 四、工单总结 下所有带数字的橙色文本,数据来源均为复盘跟踪表(不是工单数据表):
X月共产生工单X个→ 跟踪表工单总数本次复盘筛选出X个工单→ 跟踪表复盘工单同比上月增加/减少XX%→ 对比跟踪表中上月的工单总数解决类别TOP3→ 跟踪表中各解决类别字段(外部原因-无需技术排查、无需优化、外部原因-需技术排查等)解决模块TOP3→ 跟踪表中各模块字段(收银系统-餐饮、扫码点单、打印机等)- 超时工单数/产研介入 → 跟踪表
超时工单数、超时工单数-产研介入 - 时效内解决占比 →
1 - 超时工单数/工单总数 × 100%
数据来源一览
| 数据 | 来源 |
|---|---|
| 会议信息、议程 | 用户提供或从上月文档沿用 |
| 参与人员(开发TOP5) | 开发复盘文档 Section 2 的前5名责任人 |
| 第三节标题 XX | 复盘跟踪表 工单总数 |
| 第三节表格(所有数字) | 复盘跟踪表(全部字段) |
| 工单数量分析(所有数字) | 复盘跟踪表 |
| 工单时效分析(所有数字) | 复盘跟踪表 |
| 历史跟进事项 | 从上月复盘文档复制 |
7.1 填充第三节表格
使用 scripts/fill_review_cells.cjs 填充嵌套表格单元格。详细操作见 references/data_mapping.md。
配置脚本顶部:
const DOC_ID = '...'; // 新创建的复盘总文档 ID
const MONTH = '2026-04'; // 目标月份
const CELL_DATA = {
total: '644',
noOpt: '160(产研介入94)',
prodOpt: '42(20)',
techWork: '0',
techCheck:'79(48)',
noTech: '208(28)',
internal: '138(产研介入87)',
solved: '已解决54(27)',
deferred: '延期处理66(46)',
noRepro: '无法重现:18(14)',
timeout: '88(58)',
};
执行:node scripts/fill_review_cells.cjs
7.2 第三节表格:最近3个月数据
模板中 # 三、工单总计XX个(按工单数统计) 下方自带一个 <lark-table> 格式的嵌套表格。此表格必须保留,不能删除或替换为文本。
表格内容规则
表格内应填充最近3次工单复盘的数据(包含目标月份+前2个月)。例如做 2026-05 月复盘时,表格应包含:
| 行 | 内容 |
|---|---|
| 第一行(表头) | 复盘范围、时间、工单总数等列名 |
| 第一数据行(最旧) | 2026-03 数据(跟踪表记录) |
| 第二数据行(中间) | 2026-04 数据(跟踪表记录) |
| 第三数据行(最新,在最下方) | 2026-05 数据(跟踪表记录) |
⚠️ 顺序规则:
- 模板默认把最近月放在第一行(上方),但正确顺序是旧数据在上、新数据在下
- 因此需要把模板的行标签和数据都修改过来。例如模板中的
2026-02行应改为2026-03数据,2026-03行改为2026-04数据,2026-04行改为2026-05数据 - 是的,需要把所有行的标签和数据都改一遍,不能偷懒只改一行
数据获取
- 从复盘跟踪表
list全部记录 - 找到最近3个月的记录(按月份排序
2026-05/2026-04/2026-03) - 提取各字段值:工单总数、复盘工单、超时工单数、无需优化、待产品优化、内部原因等
填充方式
使用 scripts/fill_review_cells.cjs 逐个填充表格单元格。详细操作见 references/data_mapping.md。
配置脚本顶部:
const DOC_ID = '...'; // 新创建的复盘总文档 ID
const MONTH = '2026-05'; // 目标月份(用于查找表格中的对应单元格)
const CELL_DATA = {
// 以下数据来自复盘跟踪表,key 见 data_mapping.md
total: '21',
noOpt: '11(产研介入7)',
prodOpt: '0',
techWork: '0',
techCheck: '0',
noTech: '0',
internal: '10(产研介入3)',
solved: '已解决4(1)',
deferred: '延期处理6(2)',
noRepro: '无法重现:0',
timeout: '11(3)',
};
如果目标月份在跟踪表中尚无数据(月未结束),表格中该行留空或填 "-",用附注说明。
重要:表格数据来自复盘跟踪表
复盘总文档第三节表格的数据不直接来自工单数据表,而是来自复盘跟踪表。跟踪表中各字段是 Lookup 公式,会自动根据 Step 0 创建的记录中的时间范围和已导入的工单数据计算汇总值。
因此复盘的正常顺序是:
- Step 0 → 创建跟踪表记录
- 阶段一 → 导入工单数据
- 之后 跟踪表各 Lookup 字段会自动计算
- Step 7 → 从跟踪表读取计算结果,填入第三节表格
7.3 填充工单总结
⚠️ 所有数字均来自复盘跟踪表,不是工单数据表
模板中 Section 4 的每一条文本都有橙色标记的数字占位符。所有数值必须从复盘跟踪表获取,不能从刚导入的工单数据表(24条)统计。
工单数量分析需要以下统计数据(全部来自复盘跟踪表 filter 复盘范围=当月):
| 变量 | 跟踪表字段 | 取值方式 |
|---|---|---|
| 当月工单总数 | 工单总数 | 直接取值 |
| 复盘工单数 | 复盘工单 | 直接取值 |
| 同比上月增减% | 对比上月 工单总数 | (本月-上月)/上月 × 100% |
| 解决类别TOP3 | 外部原因-无需技术排查、无需优化、待产品优化、外部原因-需技术排查 等 | 按字段值排序取TOP3 |
| 解决模块TOP3 | 收银系统-餐饮、扫码点单、打印机、收银系统-零售 等 | 按字段值排序取TOP3 |
工单时效分析需要(全部来自跟踪表):
| 变量 | 跟踪表字段 | 取值方式 |
|---|---|---|
| 超时工单数 | 超时工单数 | 直接取值 |
| 超时产研介入 | 超时工单数-产研介入 | 直接取值 |
| 时效内解决占比% | 工单总数 + 超时工单数 | (1 - 超时工单数/工单总数 × 100%),保留两位小数 |
获取方式:feishu_bitable_app_table_record list 查询 复盘范围 = 当月,然后从返回的 fields 中提取所需字段。
注意:Section 4 的文本包含大量橙色标记(<text color="orange">),无法通过简单的 replace_range 或 selection_with_ellipsis 更新(因为标记打断了文本连续性)。
- 超时文本块(如
25个超时工单)可以通过 block PATCH 更新(type 2 块) - 列表项(type 12)和标题块(type 3)无法通过 block API 更新
- 必须通过
feishu_update_doc replace_all(但会破坏 callout)或手动编辑
问题总结和历史跟进事项从上月文档复制,或由用户填写。
阶段三:开发复盘文档
Step 8: 创建开发复盘文档
前置检查
同样先检查目标月度目录下是否已有同名文档。 使用 feishu_wiki_space_node list 确认后再操作。
创建步骤
- 复制模板:从
Secqwa7deiaRwTkUsG6cJ7xpnzj复制创建 - 重命名:按月份命名,如
智慧门店-2026-05月-开发复盘文档 - 放入对应目录
⚠️ 开发复盘文档需要修改/填充的内容(共3项,缺一不可!)
不要破坏模板原有的换行和段落结构。 完整模板结构如下:
# 一、开发复盘文档
- 工单复盘文档:<mention-doc token="..." type="wiki">智慧门店工单复盘模版</mention-doc> ← 需修改 ①
- 复盘表格内容:<mention-doc token="..." type="wiki">2026-xx月-智慧门店工单数据</mention-doc> ← 需修改 ②
# 二、主要人员总结材料
<text color="orange">开发A(</text>工单总数<text color="orange">x</text>个)(超时工单<text color="orange">x</text>个) ← 需修改 ③
[callout] ← 保持模板原样!不要改动!
修改① 替换「工单复盘文档」mention-doc
目标:指向本次创建的复盘总文档 wiki 节点
{ mention_doc: { token: "{复盘总文档的wiki节点token}", obj_type: 16, // wiki
title: "智慧门店-2026-05月-工单复盘",
url: "https://sqb.feishu.cn/wiki/{wiki节点token}" } }
修改② 替换「复盘表格内容」mention-doc
目标:指向当前月份的工单数据 bitable
{ mention_doc: { token: "{月份bitable的app_token}", obj_type: 8, // bitable
title: "智慧门店-2026-5月工单数据",
url: "https://sqb.feishu.cn/base/{app_token}?table={table_id}&view={view_id}" } }
修改③ 替换橙色高亮区的占位符(开发A~E 和 x)
这是最容易出错的地方!严格按照以下步骤:
- 找到文档根 children 中的 type 2 块(5个,对应开发A~E)
- 对每个块的 elements,执行 3 个替换:
| 位置 | 原来 | 改为 | 示例(李文茂) |
|---|---|---|---|
| 第一个 text_run | 开发A( | 责任人姓名+( | 李文茂( |
中间 text_run(内容为x) | x | 该人工单总数 | 5 |
末尾 text_run(内容为x) | x | 该人超时工单数 | 4 |
元素结构参考(以开发A为例,其余开发B~E结构相同但元素数可能不同):
// 开发A 的 elements(8个元素):
[ {text_run: "开发A("}, // 改为人名
{text_run: "工单总"}, // 不动
{text_run: "数"}, // 不动
{text_run: "x"}, // 改为工单总数
{text_run: "个"}, // 不动
{text_run: ")(超时工单"}, // 不动
{text_run: "x"}, // 改为超时工单数
{text_run: "个)"} // 不动
]
// 开发B~E 的 elements(6个元素):
[ {text_run: "开发X("}, // 改为人名
{text_run: "工单总数"}, // 不动
{text_run: "x"}, // 改为工单总数
{text_run: "个)(超时工单"}, // 不动
{text_run: "x"}, // 改为超时工单数
{text_run: "个)"} // 不动
]
// 替换后效果:
[ {text_run: "李文茂("},
{text_run: "工单总"},
{text_run: "数"},
{text_run: "5"}, // 原来是 x
{text_run: "个"},
{text_run: ")(超时工单"},
{text_run: "4"}, // 原来是 x
{text_run: "个)"}
]
✅ 最终橙色高亮区的效果(模板 → 正确):
<text color="orange">开发A(</text>工单总数<text color="orange">x</text>个)(超时工单<text color="orange">x</text>个)
↓ 替换为
<text color="orange">李文茂(</text>工单总数<text color="orange">5</text>个)(超时工单<text color="orange">4</text>个)
❗ 绝对不要做的事(踩坑记录)
| ❌ 错误 | 后果 |
|---|---|
只替换数字 x,不把开发A改成责任人姓名 | 橙色区显示 "开发A(工单总数5个)" 而非 "李文茂(工单总数5个)" |
| 把分析总结填入 callout 内容 | callout 应该保持模板占位符状态,留给用户手动填写 |
写入多行文本到 callout 块(如 工单原因分析:\n1. xxx) | 后续无法一次性清空,需要逐元素删除残留行 |
用 feishu_update_doc replace_all 更新文档内容 | 会破坏 <callout> 块结构 |
责任人的选取规则
- 从工单数据表(阶段一导入的)统计 TOP5 责任人
- 排除
崔文思和蒋达周 - 统计每个责任人:
- 工单总数:该责任人名下的工单数量
- 超时工单数:该责任人名下
超时时间不为空的工单数
数据获取方式:
- 从工单数据表
list全部记录 - 按
责任人字段分组计数 - 排除崔文思、蒋达周
- 取工单数最多的前5名
- 对每个负责人统计超时工单数
填充方式:
- 使用 UAT 调飞书 Docx PATCH API 逐元素更新,不要用
feishu_update_doc(会破坏<callout>块) - 通过 PATCH
update_text_elements替换橙色占位符的x,保留<text color="orange">样式 - 模板已有5个开发者槽位(开发A-E),直接 PATCH 全部5个槽位的占位符,无需 POST 追加
分析总结占位符
每个责任人段落的 callout 区域包含需要用户填写的分析总结(无法自动生成):
分析总结:
超时工单分析:
工单原因分析:
需更改需跟进事项:
需同步产品:
需同步开发:
需同步测试:
需同步客服:
需同步技术支持:
其他:
在填充数据后告知用户这些区域需要手动填写。
脚本说明
scripts/fetch_furcas.py
从 Furcas API 拉取数据。参数:-s 开始日期、-n 结束日期、-o 输出路径。
scripts/import_to_bitable.mjs
CSV → 多维表格导入一体化脚本。环境变量:APP_TOKEN, TABLE_ID, CSV_PATH, BATCH_SIZE, TABLE_NAME。
scripts/generate_batches.py
CSV → JSON 批处理文件。参数:--csv, --output-dir, --batch-size。
scripts/fill_review_cells.cjs
填充复盘总文档第三节嵌套表格单元格。直接调飞书 Docx PATCH API,用用户 OAuth UAT。顶部配置区设置月份和数据。
注意事项
通用
- Cookie 时效性:Furcas Cookie 可能过期,过期后需用户重新拷贝
- 导入长度限制:batch_create 每批 ≤ 15 条
- 看板视图分组:创建后需显式设置
kanban_field_id - 表格嵌套单元格:不能用 markdown flat table 替代,必须用
fill_review_cells.cjs逐个填充 - 替换
x变量:开发复盘文档的<text color="orange">x</text>占位符必须用 UAT PATCH API 逐元素更新(保留橙色样式),禁止用feishu_update_doc replace_all(会破坏<callout>块)。复盘总文档的x同样用 PATCH API 更新。 - 责任人排除:开发复盘 TOP5 统计需排除崔文思、蒋达周
- 文档排版:从模板复制后,确认文档结构完整。当前模板(2026-05起)已无 callout 提示块,无需手动删除
模板结构守则
-
永远不要合并或删除模板中的换行。模板的每个段落、列表都是精心设计的结构。特别是:
- 会议信息的
会议主题/会议时间各占一行 - 会议议程的
**复盘时间:**/**复盘范围:**/**参与人员:**各自独立成段 - 参与人员列表用
-无序列表格式 - 开发复盘文档的 callout 中每个冒号标题独占一行,
>前缀必须保留 - ⚠️ 复盘总文档模板(2026-05 版本)已无 callout 块,Section 3 标题后直接是
<lark-table>
- 会议信息的
-
feishu_update_doc禁止用于含表格的文档:复盘总文档的 Section 3 包含<lark-table>,任何模式(overwrite/replace_all/append/insert_after/replace_range/delete_range)都会摧毁表格属性(rowspan、colspan 丢失,cols 数变化)。- ❌
feishu_update_doc会先序列化文档为 markdown 再反序列化,过程中 lark-table 的 HTML 属性丢失 - ✅ 所有更新必须使用 UAT Token 直接调飞书 PATCH blocks API,逐元素更新
- ✅ 表格单元格:
PATCH /blocks/{cell_child_text_id}用update_text_elements - ✅ 文本段落:
PATCH /blocks/{block_id}用update_text_elements
- ❌
-
feishu_update_doc禁止用于含 callout 块的文档:开发复盘文档不含 lark-table,但含有<callout>块(type=19),feishu_update_doc 的 markdown 序列化/反序列化会:- ❌
<callout>→>blockquote(结构被破坏,callout 框消失) - ❌
<text color="orange">→ 普通文本(颜色标记丢失) - ❌ 可能引入额外段落(如
# 三、总结) - ✅ 正确做法:所有更新必须使用 UAT Token 直接调飞书 PATCH blocks API,逐元素更新
- ❌
开发复盘文档填充经验
-
替换开发 A/B/C/D/E 标记(模板现含5个槽位):
- 模板使用
<text color="orange">开发A(</text>工单总数<text color="orange">x</text>个)(超时工单<text color="orange">x</text>个)(B/C/D/E同理) - 替换时搜索整个带标记的字符串,替换为
**张姝(工单总数12个)(超时工单5个)** - 橙色标记在替换时自动去除,改用加粗(** **)
- 模板使用
-
模板已有 5 个开发者槽位(开发A-E):最新模板
Secqwa7deiaRwTkUsG6cJ7xpnzj已直接包含 5 组(开发A/B/C/D/E),每组含<text color="orange">开发X(工单总数x个)(超时工单x个)</text>+<callout>块。- 直接 PATCH 填满全部 5 个槽位即可(无需 POST 追加)
- TOP5 开发者按工单数从多到少填入开发A→E
- ❌ 模板只有3个槽位时(旧版),才需要 POST
/children追加开发D和E
含表格文档的 PATCH API 更新(关键)
⚠️ 核心规则:feishu_update_doc 不可用于含复杂表格的文档
复盘总文档的 Section 3 包含 <lark-table>(带 rowspan/colspan),feishu_update_doc 的任何模式(replace_all、insert_after、delete_range、replace_range、overwrite)都会摧毁表格属性。
原因:这些模式先将文档序列化为 markdown 再反序列化,反序列化时 lark-table 的 HTML 属性丢失。
后果示例:
cols="11"→cols="10"(colspan=2 的合列丢失)rowspan="3"→ 消失(每月 3 行不再合并)- 表格从 10 行 11 列 → 7 行 10 列
✅ 正确做法:直接调飞书 Docx PATCH API
PATCH /open-apis/docx/v1/documents/{document_id}/blocks/{block_id}
Authorization: Bearer {user_access_token}
Body: {
"update_text_elements": {
"elements": [{
"text_run": {
"content": "新文本内容",
"text_element_style": {}
}
}]
}
}
⚠️ update_text_elements 会丢失格式(bold/orange):新的 text_run 使用空的 text_element_style: {},所以加粗、橙色标记等格式会丢失。如需保留格式,需在 text_element_style 中显式设置 bold: {} 或 inline_code: {color: 25, background_color: 2}(橙色)。
更新顺序不可逆
模板拷贝 → 填表(fill_review_cells.cjs)→ 更新文本(PATCH API)
↑ ↑
必须先做 后做
填表(表格单元格级别的 block 操作)必须在任何文本替换之前完成。如果先做文本替换再填表,feishu_update_doc 已经破坏了表格结构。
Block-type-specific 文本属性
通过 PATCH API 读取/更新文本时,不同 block type 的文本存在不同属性中,不是都在 block.text 下:
| Block Type | 描述 | 文本所在属性 |
|---|---|---|
| 2 | 文本段落 | block.text.elements |
| 3 | 标题1(#) | block.heading1.elements |
| 4 | 标题2(##) | block.heading2.elements |
| 5 | 标题3(###) | block.heading3.elements |
| 12 | 无序列表项(-) | block.bullet.elements |
| 14 | 有序列表项(1.) | block.ordered.elements |
PATCH API 写入统一使用 update_text_elements,不分类型。
Heading 块可以有子节点
飞书文档中,标题块(block_type=3/4/5)可以有 子 children(如 Section 4 的 ## 工单数量分析 就包含 bullet 子节点)。Section 4 的文本不是根级节点,而是 heading 的 children:
// Section 4 的数据需要遍历 heading 的 children 获取:
const rootBlocks = getChildren(document_block_id);
for (const b of rootBlocks) {
// b.block_type === 4 (heading2)
// b.txt === '工单数量分析'
const sectionKids = getChildren(b.block_id); // ← ✅ 能拿到子节点
// sectionKids[0].block_type === 12 (bullet)
// sectionKids[0].txt === '2026-05月共产生工单81个...'
}
但是:表格(type=31)和 callout(type=19)不是任何块的 children,它们始终是文档根层的同级节点。即使它们在视觉上位于某个 heading 下方,API 读取时它们和 heading 是平级关系。
权限与加密
PATCH API 需要 User Access Token(UAT),用 OAuth 方式获取。UAT 加密存储,解密方式:
const d = path.join(process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'), 'openclaw-feishu-uat');
const enc = fs.readFileSync(path.join(d, '{appId}_{userOpenId}.enc'));
const masterKey = fs.readFileSync(path.join(d, 'master.key'));
// AES-256-GCM: IV 12 bytes + tag 16 bytes + ciphertext
const iv = enc.subarray(0, 12), tag = enc.subarray(12, 28), cipher = enc.subarray(28);
const decipher = crypto.createDecipheriv('aes-256-gcm', masterKey, iv);
decipher.setAuthTag(tag);
const uat = JSON.parse(Buffer.concat([decipher.update(cipher), decipher.final()]).toString('utf8')).accessToken;
fill_review_cells.cjs 的使用
位置:scripts/fill_review_cells.cjs
配置顶部变量:
const DOC_ID = '新文档 obj_token';
const APP_ID = 'cli_a97b6a0ffc399cc0';
const SENDER_OPEN_ID = 'ou_229ec432e6d99a781896afb23e386ee1';
const CELL_DATA = {
total: '21',
noOpt: '11(产研介入7)',
prodOpt: '0',
techWork: '0',
techCheck: '0',
noTech: '0',
internal: '10(产研介入3)',
solved: '已解决4(1)',
deferred: '延期处理6(2)',
noRepro: '无法重现:0',
timeout: '11(3)',
};
脚本会自动填充最近 3 个月(本月 + 前 2 个月)的数据到表格中的对应位置。填写规则见 references/data_mapping.md。
文本更新脚本参考
// readText 必须检查所有 block-type 属性
function readText(block) {
const src = block?.text || block?.heading1 || block?.heading2 ||
block?.heading3 || block?.bullet || block?.ordered || block?.todo;
return src?.elements?.map(e => e.text_run?.content || '').join('') || '';
}
// 写入统一用 update_text_elements
async function patchBlock(uat, blockId, newText) {
const body = {
update_text_elements: {
elements: [{ text_run: { content: newText, text_element_style: {} } }]
}
};
const url = `${DOCX_BASE}/${DOC_ID}/blocks/${blockId}`;
return apiRetry('PATCH', url, { Authorization: `Bearer ${uat}` }, JSON.stringify(body));
}
UAT 与 API 调用
-
UAT 解密方式:
- 加密文件路径:
/state/share/openclaw-feishu-uat/{appId}_{userOpenId}.enc - Master key:
/state/share/openclaw-feishu-uat/master.key(32 字节原始密钥,不需要 SHA256 哈希) - 解密算法:AES-256-GCM,IV 12 字节 + tag 16 字节 + ciphertext
- 解密后的 JSON 字段:
accessToken(驼峰,不是access_token) fill_review_cells.cjs已正确实现(fs.readFileSync直接读 Buffer)
- 加密文件路径:
-
mention-doc 的 token 解析:
- 当用
<mention-doc token="wiki节点token" type="wiki">时,飞书 API 会自动解析为实际文档的 obj_token - type 也会自动更新(如 wiki → docx 或 bitable)
- 因此如果后续的 replace_all 索引用的原始 wiki token 找不到内容,很可能已被 API 自动替换
- 应在每次更新后重新 fetch-doc 确认实际内容
- 当用
Import 脚本已知 bug
-
import_to_bitable.mjs看板创建 bug:createKanbanView("按责任人分组")创建的看板视图没有返回正确的 view_id(返回 undefined)- 后续设置
kanban_field_id时会因view_id=undefined而失败 - 变通方案:脚本跑完后,手动创建看板视图或用 UAT 调飞书 API PATCH
/views/{viewId}设置kanban_field_id - 正确字段 ID:
责任人的 field_id(可通过 list_fields 获取)
-
FIELD_MAP 硬编码:CSV 列名 "工单状态/修复情况" → bitable 字段名 "工单状态|修复情况"(竖线)。如果 bitable 字段名用了斜杠,需要修改 FIELD_MAP 或重命名字段。
2026-05-06 完整复盘经验总结
以下为 2026-05 月工单复盘全流程中遇到并解决的问题汇总,下次工单复盘前请先通读,避免重复踩坑。
一、Furcas 数据拉取
❗ Cookie 过期处理
Furcas API 登录态不可持久化。每次执行 fetch_furcas.py 前:
- 打开浏览器 → F12 → Network → 随便请求 Furcas 页面
- 从请求头复制 cookie 请求头完整值(含
acw_tc和furcas字段) - 更新脚本中的
HEADERS["Cookie"]变量 - 不要只更新部分字段,
acw_tc每次都会变
❗ 解决类别 ID 映射
拉取数据后 fetch_furcas.py 会按解决类别分类写入 CSV,ID 映射如下:
| ID | 类别 |
|---|---|
| 1 | 设计如此-无需优化 |
| 2 | 外部原因-无需优化 |
| 3 | 内部原因-已解决 |
| 4 | 内部原因-延期处理 |
| 5 | 内部原因-无法重现 |
二、复盘跟踪表(bitable 操作)
❗ 跟踪表记录创建规范
创建复盘跟踪表记录时:
app_token = YMQjwQeWCipABCkl8y7ckZlunQetable_id = tblpygt9n8dqZcsw- 年份字段填 2026(单选)
- 月份字段填 05(单选)
工单总数= 该月 Furcas 所有工单的总数(从查询结果 count 获取)复盘工单= 筛选后需要复盘的工单数(排除设计如此、外部原因-无需优化后)- 创建后等待 1-2 秒再 PATCH 其他字段
❗ 字段 ID 映射(跟蹤表)
| 字段名 | field_id |
|---|---|
| 工单总数 | fldg7k4Zb |
| 复盘工单 | fldBxBSm3x |
| 无需优化 | fld2Fi7oVD |
| 待产品优化 | fldQ45Kqj8 |
| 外部原因-技术派工 | fldW8sKSSx |
| 外部原因-需技术排查 | fldRerqDIp |
| 外部原因-无需技术排查 | fldVefvMG2 |
| 内部原因 | fldIor3W9R |
| 已解决 | flde2eaOu |
| 延期处理 | fldZqWk3G |
| 无法复现 | fldYplayCE |
| 超时工单数 | fldTQkMXsW |
✅ 工单总数来源
工单总数 字段值来自 Furcas API 返回的总数,不是从导入 bitable 的记录数获取,不是从 CSV 行数获取。导入脚本只导入需要复盘的工单(筛选后的子集),不能当作总数。
三、复盘总文档操作(含表格的文档)
❗ 含 lark-table 的文档禁止用 feishu_update_doc 的任何模式
feishu_update_doc 的 overwrite、replace_all、append 模式都会损坏 lark-table(返回 3000/4000515 等错误)。
- ✅ 正确做法:使用 UAT Token 直接调用 PATCH blocks API,逐格更新单元格文本
- ✅ 使用 PATCH 更新文本块(heading、text、bullet 等)
- ❌ 禁止
feishu_update_doc的任何模式
❗ 表格单元格偏移(110格布局)
每个复盘表格有 110 个单元格:
- 前 11 格 = 表头行
- 之后每 33 格 = 一个月的数据
- Group 0(03月)= cells[11..43]
- Group 1(04月)= cells[44..76]
- Group 2(05月)= cells[77..109]
每个月份组 33 格,单元格偏移:
| 偏移(组内) | 字段 | 行 |
|---|---|---|
| 0 | 月份标签 | row1 |
| 1 | 工单总数 | row1 |
| 2-3 | 无需优化(colspan=2) | row1 |
| 4 | 待产品优化 | row1 |
| 5 | 外部原因-技术派工 | row1 |
| 6 | 外部原因-需技术排查 | row1 |
| 7 | 外部原因-无需技术排查 | row1 |
| 8 | 内部原因 | row1 |
| 9 | 已解决 | row1 |
| 10 | 超时(rowspan=3) | row1 |
| 20 | 延期处理 | row2 |
| 31 | 无法复现 | row3 |
数据数组顺序必须匹配 offset 顺序:
// 正确顺序:total(1), noOpt(2), prodOpt(4), techWork(5), techCheck(6), noTech(7), internal(8), solved(9), timeout(10), deferred(20), noRepro(31)
const data = ['516', '147(产研介入55)', '48(产研介入17)', '3(产研介入3)', '72(产研介入53)', '147(产研介入25)', '96(产研介入56)', '已解决38(产研介入16)', '49(产研介入28)', '延期处理47(产研介入31)', '无法复现:11(产研介入9)'];
❗ 月份标签重命名级联 bug(关键!)
不能按顺序逐个改多个月份标签!
- ❌ 错误做法:依次执行 02→03, 03→04, 04→05
- 改名 02→03 后,原 03 组的标签仍然是 03
- 第二次操作 03→04 会匹配到 刚改好的那组,而不是原 03 组
- 导致一个月份被跳过
- ✅ 正确方案 A:从后往前改(04→05, 03→04, 02→03)
- ✅ 推荐方案 B:一次性全部覆盖写入,无视当前值
- 用已知正确数据直接覆盖所有三组,每次重新写全部数据
- 这是最安全的做法
❗ 表头标题行丢前缀
更新 heading1 的 工单总计XX个(按工单数统计) 后,前缀 三、 会丢失。
- 原因:
update_text_elements完全替换了所有元素,前缀在另一个 element 中 - 修复:更新时要保留完整标题,用
# 三、工单总计x个(按工单数统计)
❗ heading 块用 update_text_elements
- ❌
update_heading1/update_heading2/update_heading3→ 返回1770001 invalid param - ✅
update_text_elements→ 对 heading1/2/3/text/bullet/ordered 都有效 - 通用原则:所有文本元素的更新一律用
update_text_elements: { elements: [...] }
❗ Section 4 子块遍历
Section 4(工单数量分析等)的 bullet 文本是 heading 的 children,不是根级块。 需要:
- 遍历根级块找到 heading(type=4, txt='工单数量分析')
- 获取该 heading 的 children(
gC(docId, heading.block_id)) - 逐个更新这些 bullet 块的文本
✅ 文本更新用 element-level 操作
当 template 有 <text color="orange">x</text> 样式时,不能简单替换整个文本字符串——会丢失颜色样式。
- ✅ 遍历
elements[],只改 content 不变 style - ✅ 如在会议主题中改
10→05,保留其余所有 element 的 style 属性
四、Callout 块处理
❗ Callout 块不可通过 API 删除
- Feishu Docx API 的 DELETE 端点对 type=19(callout/quote)块返回 404
- ❌
DELETE /blocks/{block_id}对 callout 无效 - ✅ 变通方案:只清空子节点的文本内容(将其 text element content 设为空字符串)
- ⚠️ Callout 框本身只能手动在飞书页面删除(选中后按 Delete/Backspace)
五、mention-doc 处理
❗ mention-doc 是内联元素
- mention-doc 不是独立的 type=33 块(那是独立 mention doc block)
- 在文本段落中,mention-doc 是
elements[]数组中的一个 element - 同一 element 可能同时有
text_run和mention_doc两个属性
❗ 更新 mention-doc token 的正确方式
PATCH 时需传入完整 element,必须带的字段:
{
"text_run": { "content": "显示文字", "text_element_style": {} },
"mention_doc": {
"obj_type": 16, // 整數!不能传字符串 "wiki"
"text_element_style": {},
"token": "UJdQwNdkPidcvKkXZzbcL1tDnaf", // wiki 节点 token
"title": "显示标题",
"url": "https://sqb.feishu.cn/wiki/UJdQwNdk..."
}
}
obj_type是整数(16=wiki节点),传字符串会报99992402 field validation failed
❗ token 会被自动解析
发送 wiki 节点 token 后,飞书 API 会自动解析为实际文档的 obj_token:
- ⚡ token:
UJdQwNdk...(wiki) →UtJCdfO5Iotw...(docx obj_token) - ⚡ type: 16 (wiki) → 22 (docx)
- ⚡ url 保持不变(wiki URL)
- ⚡ title 会自动从文档实际标题获取,发送的 title 字段被忽略
❗ 显示文本无法通过 API 修改
mention 在文档中显示的文字由文档标题决定,不是由你写入的 text_run.content 或 title 字段决定。
- ✅ 要改显示文字,必须改文档标题(手工右键重命名)
- ❌ 通过 PATCH 修改 element 中的 text_run.content 或 mention_doc.title 均无效
六、开发复盘文档操作
❗ 旧版模板只有3个开发槽位(已更新为5个)
模板 Secqwa7deiaRwTkUsG6cJ7xpnzj 已在 2026-05-07 更新,直接包含开发A-E 五个槽位。旧版只有3个时:
- 先 PATCH 填满 3 个槽位
- 再通过
POST /blocks/{root_doc_id}/children添加第 4、5 个 - 用 UAT PATCH API 更新 3 个槽位的开发者名称和数量(逐元素更新,保留橙色样式)
- 再通过
POST /blocks/{root_doc_id}/children添加第 4、5 个
❗ insert_after 的正确方式(插入兄弟块)
要插入兄弟块不是 POST 到目标块,而是 POST 到父块(根文档 block_id = doc_id):
// ✅ 正确:POST 到父块(文档本身),指定 index
POST /documents/{docId}/blocks/{docId}/children
{ index: 金海轩的索引+1, children: [{ block_type: 2, text: { elements: [...] } }] }
// ❌ 错误:POST 到目标块自己(会把新内容作为金海轩的子块)
POST /documents/{docId}/blocks/{金海轩的block_id}/children
❗ x 是单字母不是 xxx
模板中的占位符是 x(如 工单总数x个),不是 xxx。替换时注意匹配模式。
❗ 排除特定人员
开发复盘 TOP5 必须排除崔文思(技术支持)和蒋达周(运营)。统计开发者时:
- 先统计每个开发者的工单数
- 去掉崔文思和蒋达周
- 取前 5 个填入
七、文档创建与复制
❗ 模板复制后需验证 obj_token
wiki_space_node action=copy 返回的 obj_token 可能字符错位(如 UtJCdfO5Iotw 与 UtJCdfO5Iotw 中的 5 和 O 互换)。复制后:
- ✅ 立即用
wiki_space_node action=get验证新节点的实际obj_token - ✅ 或用
feishu_fetch_doc直接读节点 token 验证文档是否可访问
❗ 文档创建后有一定延迟
复制创建的文档不会立即就绪。如果立即调用 PATCH 操作,可能返回 4000515 resource not found。
✅ 建议做法:创建后等待 2-3 秒再操作。
❗ 标题改不了(API 限制)
- ❌
PATCH /docx/v1/documents/{docId}→1770001 invalid param(不支持改标题) - ❌
PATCH /wiki/v2/spaces/{spaceId}/nodes/{nodeToken}→ 404(没有 rename 端点) - ✅ 必须手工操作:在飞书页面右键节点 → 重命名
八、UAT 工具链
❗ 标准 UAT 文件路径
const LINUX_UAT_DIR = path.join(
process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share'),
'openclaw-feishu-uat'
);
const encFile = path.join(LINUX_UAT_DIR, `${APP_ID}_${OPEN_ID}.enc`);
const masterKey = fs.readFileSync(path.join(LINUX_UAT_DIR, 'master.key'));
const uatToken = JSON.parse(
decrypt(fs.readFileSync(encFile), masterKey)
).accessToken; // 驼峰!不是 access_token
- 不要硬编码
/root/.local/share/,用XDG_DATA_HOME环境变量 - 加密文件命名规则:
{appId}_{userOpenId}.enc(冒号替换为下划线) accessToken是驼峰,不是下划线格式- 解密算法:AES-256-GCM,IV=12字节 + tag=16字节 + ciphertext
- Master key 是 32 字节原始密钥,不需要 SHA256 哈希
九、复盘范围规范
✅ 复盘范围时间格式
| 复盘类型 | 时间范围 |
|---|---|
| 整月复盘 | 当月1日 00:00:00 - 当月最后一日 23:59:59 |
| 上半月复盘 | — 当月15日 23:59:59 |
| 下半月复盘 | 当月16日 00:00:00 — |
文档中 color="orange" 的复盘范围文本要保持橙底样式,仅改日期文字。
⚠️ 文档命名规范(严格执行,否则重做)
三个文档必须从模板创建,命名必须为:
| 文档类型 | 模板 | 命名规范(5月示例) |
|---|---|---|
| 工单数据跟踪表(Bitable) | J4tawf06GijvEmkBTSCcdyNynsd | 智慧门店-2026-05月-工单数据 |
| 工单复盘总文档 | GEqawooPYiDXLWkRKZHcJnLcnFc (wiki节点) | 智慧门店-2026-05月-工单复盘 |
| 开发复盘文档 | Secqwa7deiaRwTkUsG6cJ7xpnzj (wiki节点) | 智慧门店-2026-05月-开发复盘文档 |
顺序:先建数据表 → 拉数据 → 建复盘文档
常见错误:命名带"模版"后缀、用"05-整"格式、忘记创建在对应月份目录下
十一、文档填充规则
表格更新(重要)
- lark-table文档禁止feishu_update_doc(破坏结构)
- 使用docx PATCH API更新text block的
update_text_elements - 结构:table_cell(31) → 110个type32子节点 → 每个含type2 text block
文本更新
- Feishu文本可能拆成多个text_run元素(每个有独立样式)
- 判断用
elements.map(e=>e.text_run?.content).join('')(完整文本),修改用单个element的content update_text_elements对所有block_type通用- type 12是bullet块,元素在
.bullet.elements中 - mention_doc不可通过API修改
feishu_update_doc禁止用于含 callout(type=19) 的文档(序列化会破坏callout结构)- callout块的内容需通过PATCH其子block的
update_text_elements更新,不要替换整个文档
文档标题/命名
- 创建时在copy request的
title参数设置 - 不能通过API修改已有wiki节点标题
- 需重命名时:copy新文档+删除旧节点
十二、关键常量表
| 项目 | 值 |
|---|---|
| 工单数据(含Furcas) app_token | YMQjwQeWCipABCkl8y7ckZlunQe |
| 工单汇总表 table_id | tblpygt9n8dqZcsw |
| Furcas工单表 table_id | tbln4MvksjBRkGGn |
| 工单数据跟踪表模板(Bitable) | J4tawf06GijvEmkBTSCcdyNynsd |
| 2026目录节点(space 7510046829784662017 下) | MDK5w5CcGi8RK6kRFMKcuBKLnNg |
| 复盘总文档模板 | wiki 节点 GEqawooPYiDXLWkRKZHcJnLcnFc |
| 开发复盘文档模板 | wiki 节点 Secqwa7deiaRwTkUsG6cJ7xpnzj |
| 用户 open_id | ou_229ec432e6d99a781896afb23e386ee1 |
| App ID | cli_a97b6a0ffc399cc0 |
| Space ID | 7510046829784662017 |
| 05月目录节点 | KAXEwRivKinTfTkshDucvSiGn4g |
十三、2026-05-07 追加经验
以下为 2026-05-07 修复 4 月工单数据时发现并解决的问题。
❗ import_to_bitable.mjs 清空步骤有静默失败 bug(致命!)
bug 描述:清空旧数据的 batch_delete API 参数名写错,用的 records 但正确参数是 record_ids。且 try-catch 吞掉了错误,导致清空失败时不停机:
// ❌ 错误(旧代码)
await api("POST", `.../records/batch_delete`, { records: ids });
// 清空失败,错误被 catch 吃掉
// 旧数据继续留在表中,新数据追加写入
// → 每条工单出现 2 遍(304条 = 152 × 2)
修复:
- 参数名改为
record_ids✅ - 去掉 try-catch,让清空失败直接报错停机 ✅
- 导入完成后新增去重步骤(最后一道防线)✅
- 去重逻辑:按工单 ID 分组,每个工单只保留 1 条记录 ✅
❗ 多条同名空表被创建(智慧门店-2026-05月-工单数据 × 4)
原因:首次创建 bitable 时没有检查知识库目录下是否已有同名表格。
修复方式:后续复盘时,在创建新 bitable 前必须:
- 先搜索「工单数据表」目录下是否有同月名称的表格
- 如果有,直接复用,不创建新的
- 搜索方式:
feishu_wiki_space_node list列出目录下所有节点,检查名称是否包含目标月份
❗ Mention-doc 指向错误的表格
原因:从模板复制复盘总文档后,文档内的 mention-doc 指向的是模板的占位对象(4月表或模板),不是当前月份的表。
修复方式(已添加 Step 5b 到流程中):
- 导入数据后立即更新 mention-doc 链接
- 将指向前月份的 bitable mention-doc 改为当前月份的
- 将指向模板的 wiki mention-doc 改为实际开发复盘文档节点
❗ UAT 没有 bitable 读取权限
Raw API 调用 /bitable/v1/apps/{token}/tables/{table_id}/records 返回 99991679 Unauthorized:
- UAT 用户授权不包含
bitable:app:readonly或base:record:read - ✅ feishu_bitable_app_table_record 工具可以正常读取(使用 bot 身份或不同授权路径)
- ✅ 如果脚本需要用 UAT 读 bitable,需要先 check 权限,否则使用
feishu_bitable_app_table_record工具
❗ 单个 DELETE 大量记录会超时
去重脚本一次删 152 条记录(逐条 DELETE)因超时被 SIGTERM:
- 超时阈值约 2 分钟
- ✅ 分批删除 20 条为一个 checkpoint,每次加
await new Promise(r => setTimeout(r, 50))限速 - ✅ 如果超时,再次运行脚本即可继续删剩余的
❗ feishu_update_doc replace_all 破坏 callout 块(生成 "三、总结")
现象:使用 feishu_update_doc replace_all 更新开发复盘文档后:
<callout>块 →>markdown blockquote(callout 框消失)<text color="orange">→ 普通文本(橙色丢失)- 多出
# 三、总结等异常段落
根因:feishu_update_doc 先将文档序列化为 markdown,再反序列化回飞书文档格式。反序列化时:
<callout>被当作>blockquote 处理- 如果 markdown 末尾包含
# 三、总结等标题,会被当作新段落加入 - SKILL.md 第10条之前错误地允许了在含 callout 的文档中使用 feishu_update_doc
修复:
- 从模板重新创建文档 ✅(必须用 copy,不能用 feishu_create_doc 写 markdown)
- 所有更新用 UAT PATCH API 逐元素操作,不用 feishu_update_doc ✅
- SKILL.md 第10条已更正,明确禁止 feishu_update_doc 用于含 callout 的文档 ✅
❗ 开发复盘文档模板更新为5个开发者槽位(2026-05-07)
模板 Secqwa7deiaRwTkUsG6cJ7xpnzj 在 2026-05-07 更新,"主要人员总结材料"下从3个槽位(开发A/B/C)扩展到5个(开发A/B/C/D/E)。
影响:
- ✅ 复盘流程不再需要 POST
/children追加开发D/E - ✅ 直接 PATCH 填充5个槽位即可
- ⚠️ 旧版3槽位模板已废弃,但SKILL.md保留了旧方法作为参考
- SKILL.md 第11条已改为"替换开发 A/B/C/D/E 标记"
- SKILL.md 第12条已更新
❗ 开发复盘文档 Section 1 的 mention-doc 链接需替换(2026-05-07)
复制模板后,开发复盘文档的 Section 1 包含两个指向模板/占位对象的 mention-doc:
# 一、开发复盘文档
- 工单复盘文档:<mention-doc token="GEqawooPYiDXLWkRKZHcJnLcnFc" type="wiki">智慧门店工单复盘模版</mention-doc>
- 复盘表格内容:<mention-doc token="J4tawf06GijvEmkBTSCcdyNynsd" type="wiki">2026-xx月-智慧门店工单数据</mention-doc>
必须替换为:
工单复盘文档mention-doc: token → 本次创建的 复盘总文档 wiki 节点 token复盘表格内容mention-doc: token → 当前月份工单数据 bitable token(含 table_id 和 view_id)
变更方式:PATCH 对应 block 的 update_text_elements,修改 mention_doc 的 token、obj_type、url。
❗ 创建月份工单数据 bitable 的正确方式(2026-05-07)
错误做法(之前踩过的坑):
- ❌
feishu_bitable_app copy从模板复制 → 创建独立 bitable(不在 wiki 目录中) - ❌
feishu_wiki_space_node create传 obj_token → 又新建一个空白 bitable(表名 "Table",字段全英文) → 导致 wiki 链接指向空表,数据在独立表里
正确做法(仅需一步):
- ✅
feishu_wiki_space_node copy直接将模板节点复制到目标月份目录node_token: "J4tawf06GijvEmkBTSCcdyNynsd"(工单数据表模板)target_parent_token: "{目标月份目录节点token}"space_id: "7510046829784662017"→ 直接在 wiki 内创建副本,bitable 结构、字段、视图全部正确
复制后的处理:
- 重命名 bitable(如 "智慧门店-2026-5月工单数据")
- 删除模板自带的 5 条空记录(
batch_delete空记录的record_ids) - 导入 CSV 数据
导入脚本注意事项:
- 表名默认校验为 "工单数据",但模板复制出来的表名是 "数据表"
→ 用
--name "数据表"参数 - 看板视图创建后的
view_id可能返回 undefined(脚本返回值解析 bug) → 视图实际已创建,可通过feishu_bitable_app_table_view list获取view_id→ 手工设置property: { kanban_field_id: "责任人字段ID" }
开发复盘文档人名占位符规则(2026-05-07):
- 模板中
开发A(开发B(等是人名占位符,不是固定标签 - 必须替换为责任人实际姓名(如
李文茂(),不是仅替换数字 - 替换后第一个元素内容从 "开发A(" → "李文茂(",保持橙色样式不变
开发复盘文档 callout 内容规则(2026-05-07):
- callout 内(分析总结:、超时工单分析:等10个字段)不要填充任何内容
- callout 应保持模板的占位符状态,留给用户手动填写
- 如果误填了多行文本,清理时需要:1) 把多余元素全删除 2) 只剩单元素 "分析总结:"