超大量更新

This commit is contained in:
zeaslity
2026-04-29 09:46:36 +08:00
parent ed945abdf1
commit e7c301023c
349 changed files with 83923 additions and 560 deletions

View File

@@ -0,0 +1,646 @@
/**
* ============================================================
* 金山多维表格 AirScript - 完整字段定义导出 + 阶段日志落表
* ============================================================
*
* 目标:
* 1) 导出指定 7 张表的字段定义(含完整 JSON)
* 2) 解决控制台日志分阶段现象: 同步写入日志表
*
* 参考(来自本项目离线文档):
* - Sheet.GetFields()
* - FieldDescriptors / FieldDescriptors.Item()
* - Application.Sheet.CreateSheet()
* - View.RecordRange().Value
*/
// ----------------------------
// 配置区
// ----------------------------
var TARGET_TABLES = [
'项目基本信息表',
'项目本地化部署升级信息表',
'项目本地化部署状态表',
'项目部署环境信息表',
'项目部署网络信息表',
'项目部署中间件信息表',
'项目部署业务信息表',
'持续交付部署表',
'持续交付部署状态表',
];
var EXPORT_SCHEMA_SHEET_NAME = '数据导出-字段定义';
var EXPORT_LOG_SHEET_NAME = '数据导出-执行日志';
var MAX_JSON_CELL_LENGTH = 30000;
// ----------------------------
// 运行态数据
// ----------------------------
var logRows = [];
var schemaRows = [];
var runId = buildRunId();
// ----------------------------
// 工具函数
// ----------------------------
function buildRunId() {
var d = new Date();
function pad(n) { return n < 10 ? '0' + n : '' + n; }
return (
d.getFullYear() +
pad(d.getMonth() + 1) +
pad(d.getDate()) + '_' +
pad(d.getHours()) +
pad(d.getMinutes()) +
pad(d.getSeconds())
);
}
function nowText() {
var d = new Date();
function pad(n) { return n < 10 ? '0' + n : '' + n; }
return (
d.getFullYear() + '-' +
pad(d.getMonth() + 1) + '-' +
pad(d.getDate()) + ' ' +
pad(d.getHours()) + ':' +
pad(d.getMinutes()) + ':' +
pad(d.getSeconds())
);
}
function addLog(stage, level, message, detail) {
var row = {
runId: runId,
time: nowText(),
stage: stage,
level: level,
message: message,
detail: detail || ''
};
logRows.push(row);
var line = '[' + row.time + '][' + row.stage + '][' + row.level + '] ' + row.message;
if (row.detail) line += ' | ' + row.detail;
console.log(line);
}
function safeGet(obj, key) {
try {
return obj[key];
} catch (e) {
return undefined;
}
}
function pickFirst(obj, keys, fallback) {
for (var i = 0; i < keys.length; i++) {
var v = safeGet(obj, keys[i]);
if (v !== undefined && v !== null && v !== '') {
return v;
}
}
return fallback;
}
function safeStringify(value) {
try {
return JSON.stringify(value);
} catch (e) {
try {
return String(value);
} catch (e2) {
return '';
}
}
}
function normalizeCellText(value) {
if (value === undefined || value === null) return '';
var text = '';
if (typeof value === 'string') {
text = value;
} else if (typeof value === 'number' || typeof value === 'boolean') {
text = String(value);
} else {
text = safeStringify(value);
}
if (text.length > MAX_JSON_CELL_LENGTH) {
return text.substring(0, MAX_JSON_CELL_LENGTH) + ' ...(truncated)';
}
return text;
}
function findSheetByName(sheetName) {
var sheets = Application.Sheets;
var count = sheets.Count;
for (var i = 1; i <= count; i++) {
var s = sheets.Item(i);
if (s.Name === sheetName) return s;
}
return null;
}
function extractFieldArrayFromGetFields(rawResult) {
if (!rawResult) return [];
if (Array.isArray(rawResult)) return rawResult;
if (rawResult.fields && Array.isArray(rawResult.fields)) return rawResult.fields;
if (rawResult.Fields && Array.isArray(rawResult.Fields)) return rawResult.Fields;
return [];
}
function snapshotFieldDescriptor(fd) {
return {
Id: pickFirst(fd, ['Id', 'id'], ''),
Name: pickFirst(fd, ['Name', 'name'], ''),
Type: pickFirst(fd, ['Type', 'type'], ''),
Description: pickFirst(fd, ['Description', 'description'], ''),
DefaultVal: pickFirst(fd, ['DefaultVal', 'defaultVal'], ''),
DefaultValType: pickFirst(fd, ['DefaultValType', 'defaultValType'], ''),
NumberFormat: pickFirst(fd, ['NumberFormat', 'numberFormat'], ''),
ValueUnique: pickFirst(fd, ['ValueUnique', 'IsValueUnique'], ''),
SyncField: pickFirst(fd, ['SyncField', 'IsSyncField'], ''),
Width: pickFirst(fd, ['Width', 'width'], ''),
Button: pickFirst(fd, ['Button'], null),
Address: pickFirst(fd, ['Address'], null),
Cascade: pickFirst(fd, ['Cascade'], null),
Contact: pickFirst(fd, ['Contact'], null),
Date: pickFirst(fd, ['Date'], null),
Watch: pickFirst(fd, ['Watch'], null),
Formula: pickFirst(fd, ['Formula'], null),
Lookup: pickFirst(fd, ['Lookup'], null),
Link: pickFirst(fd, ['Link'], null),
Automation: pickFirst(fd, ['Automation'], null),
Attachment: pickFirst(fd, ['Attachment'], null),
Url: pickFirst(fd, ['Url'], null),
Number: pickFirst(fd, ['Number'], null),
Select: pickFirst(fd, ['Select'], null),
Rating: pickFirst(fd, ['Rating'], null)
};
}
function chooseRawField(rawFields, descriptorSnap, index) {
if (!rawFields || rawFields.length === 0) return null;
var id = descriptorSnap.Id;
var name = descriptorSnap.Name;
for (var i = 0; i < rawFields.length; i++) {
var rf = rawFields[i];
var rid = pickFirst(rf, ['id', 'Id', 'fieldId', 'FieldId'], '');
if (id && rid && String(rid) === String(id)) return rf;
}
for (var j = 0; j < rawFields.length; j++) {
var rf2 = rawFields[j];
var rname = pickFirst(rf2, ['name', 'Name', 'fieldName', 'FieldName'], '');
if (name && rname && String(rname) === String(name)) return rf2;
}
if (index >= 0 && index < rawFields.length) return rawFields[index];
return null;
}
function ensureOutputSheet(sheetName, fields) {
var old = findSheetByName(sheetName);
if (old) {
addLog('准备输出表', 'INFO', '删除旧输出表', sheetName);
try {
old.Delete();
} catch (eDel) {
addLog('准备输出表', 'WARN', '删除旧输出表失败', sheetName + ' | ' + eDel.message);
}
}
var createResult = Application.Sheet.CreateSheet({
Name: sheetName,
Views: [{ name: '默认视图', type: 'Grid' }],
Fields: fields
});
var newId = createResult ? createResult.id : '';
addLog('准备输出表', 'INFO', '已创建输出表', sheetName + ' | SheetId=' + newId);
try { Time.sleep(300); } catch (eSleep) { }
var created = findSheetByName(sheetName);
if (!created) {
throw new Error('未找到新创建的输出表: ' + sheetName);
}
return created;
}
function buildFieldSelectors(fields) {
var selectors = [];
for (var i = 0; i < fields.length; i++) {
selectors.push('@' + fields[i].name);
}
return selectors;
}
function writeRowsByCellFallback(view, rows, progressStep) {
var total = rows.length;
var step = progressStep || 20;
for (var i = 0; i < total; i++) {
var row = rows[i];
var rowNo = i + 1;
try {
for (var col = 0; col < row.length; col++) {
view.RecordRange(rowNo, col + 1).Value = row[col];
}
} catch (eWrite) {
addLog('写入输出', 'ERROR', '写入行失败', 'row=' + rowNo + ' | ' + eWrite.message);
}
if ((i + 1) % step === 0 || i === total - 1) {
addLog('写入输出', 'INFO', '写入进度', (i + 1) + '/' + total);
}
}
}
function getViewRecordCount(view) {
try {
var c = view.RecordRange.Count;
if (c === undefined || c === null || c === '') return 0;
return Number(c) || 0;
} catch (e) {
return 0;
}
}
function verifyWriteResult(sheet, expectedRows) {
try {
var view = sheet.Views(1);
try { Time.sleep(300); } catch (eSleep1) { }
var count = getViewRecordCount(view);
if (count < expectedRows) {
try { Time.sleep(800); } catch (eSleep2) { }
count = getViewRecordCount(view);
}
return {
ok: count >= expectedRows,
count: count
};
} catch (e) {
return {
ok: false,
count: 0
};
}
}
function writeRowsToSheet(sheet, fields, rows, progressStep) {
var total = rows.length;
if (total === 0) {
addLog('写入输出', 'INFO', '无数据可写入', sheet.Name);
return;
}
var view = sheet.Views(1);
var selectors = buildFieldSelectors(fields);
var step = progressStep || 20;
var batchSize = 50;
try {
// 某些环境下先激活再写入更稳定
sheet.Activate();
view.Activate();
} catch (eActive) { }
// 方案A: 优先使用 CreateRecords 创建记录(与你现有可用脚本保持一致)
try {
addLog('写入输出', 'INFO', '使用 CreateRecords 批量写入', sheet.Name + ' | rows=' + total);
for (var crStart = 0; crStart < total; crStart += batchSize) {
var crEnd = crStart + batchSize;
if (crEnd > total) crEnd = total;
var recordBatch = [];
for (var r = crStart; r < crEnd; r++) {
var oneRow = rows[r];
var fieldsObj = {};
for (var c = 0; c < fields.length; c++) {
fieldsObj[fields[c].name] = normalizeCellText(oneRow[c]);
}
recordBatch.push({ fields: fieldsObj });
}
var createRes = Application.Record.CreateRecords({
SheetId: sheet.Id,
Records: recordBatch
});
if (createRes && createRes.code !== undefined && createRes.code !== 0) {
var createMsg = pickFirst(createRes, ['Message', 'message'], 'CreateRecords失败');
throw new Error('code=' + createRes.code + ', message=' + createMsg + ', start=' + (crStart + 1));
}
if (crEnd % step === 0 || crEnd === total) {
addLog('写入输出', 'INFO', '写入进度', crEnd + '/' + total);
}
}
var verifyA = verifyWriteResult(sheet, total);
if (verifyA.ok) {
addLog('写入输出', 'INFO', 'CreateRecords 写入校验通过', sheet.Name + ' | count=' + verifyA.count);
return;
}
addLog('写入输出', 'WARN', 'CreateRecords 写入后记录数不足,回退 SetValues', sheet.Name + ' | count=' + verifyA.count + ', expected=' + total);
} catch (eCreateRecords) {
addLog('写入输出', 'WARN', 'CreateRecords 失败,回退 SetValues', eCreateRecords.message);
}
// 方案B: SetValues 批量写入
try {
addLog('写入输出', 'INFO', '使用 SetValues 批量写入', sheet.Name + ' | rows=' + total);
for (var start = 1; start <= total; start += batchSize) {
var end = start + batchSize - 1;
if (end > total) end = total;
var chunk = rows.slice(start - 1, end);
var range = null;
var res = null;
try {
range = view.RecordRange(String(start) + ':' + String(end), selectors);
res = range.SetValues(chunk, true);
} catch (eViewSet) {
// 按官方示例,兼容 Application.RecordRange 方式
range = Application.RecordRange(String(start) + ':' + String(end), selectors);
res = range.SetValues(chunk, true);
}
if (res && res.code !== undefined && res.code !== 0) {
var msg = pickFirst(res, ['Message', 'message'], 'SetValues失败');
throw new Error('code=' + res.code + ', message=' + msg + ', range=' + start + ':' + end);
}
if (end % step === 0 || end === total) {
addLog('写入输出', 'INFO', '写入进度', end + '/' + total);
}
}
var verifyB = verifyWriteResult(sheet, total);
if (verifyB.ok) {
addLog('写入输出', 'INFO', 'SetValues 写入校验通过', sheet.Name + ' | count=' + verifyB.count);
return;
}
addLog('写入输出', 'WARN', 'SetValues 写入后记录数不足,回退逐格写入', sheet.Name + ' | count=' + verifyB.count + ', expected=' + total);
} catch (eSetValues) {
addLog('写入输出', 'WARN', 'SetValues 批量写入失败,回退逐格写入', eSetValues.message);
}
// 方案C: 逐格写入兜底
try {
writeRowsByCellFallback(view, rows, step);
var verifyC = verifyWriteResult(sheet, total);
if (verifyC.ok) {
addLog('写入输出', 'INFO', '逐格写入校验通过', sheet.Name + ' | count=' + verifyC.count);
return;
}
addLog('写入输出', 'ERROR', '逐格写入后记录数仍不足', sheet.Name + ' | count=' + verifyC.count + ', expected=' + total);
} catch (eFallback) {
addLog('写入输出', 'ERROR', '逐格写入失败', eFallback.message);
}
}
function pushSchemaRow(obj) {
schemaRows.push([
obj.runId,
obj.tableName,
obj.sheetId,
obj.fieldIndex,
obj.fieldId,
obj.fieldName,
obj.fieldType,
obj.fieldDescription,
obj.defaultVal,
obj.defaultValType,
obj.numberFormat,
obj.valueUnique,
obj.syncField,
obj.source,
obj.fullDefinitionJson,
obj.rawGetFieldsJson
]);
}
// ----------------------------
// 主流程
// ----------------------------
console.log('======================================================');
console.log('AirScript 完整字段定义导出 + 阶段日志落表');
console.log('RunId: ' + runId);
console.log('======================================================');
addLog('初始化', 'INFO', '脚本启动', '目标表数量=' + TARGET_TABLES.length);
var targetSheets = [];
for (var t = 0; t < TARGET_TABLES.length; t++) {
var tableName = TARGET_TABLES[t];
var sheet = findSheetByName(tableName);
if (sheet) {
targetSheets.push(sheet);
addLog('表扫描', 'INFO', '找到目标表', tableName + ' | SheetId=' + sheet.Id);
} else {
addLog('表扫描', 'WARN', '目标表不存在', tableName);
}
}
addLog('表扫描', 'INFO', '有效目标表统计', String(targetSheets.length));
for (var s = 0; s < targetSheets.length; s++) {
var currentSheet = targetSheets[s];
var currentName = currentSheet.Name;
var currentId = currentSheet.Id;
addLog('字段采集', 'INFO', '开始采集', currentName + ' | SheetId=' + currentId);
var rawGetFieldsResult = null;
var rawFields = [];
var getFieldsErr = '';
try {
rawGetFieldsResult = Application.Field.GetFields({ SheetId: currentId });
rawFields = extractFieldArrayFromGetFields(rawGetFieldsResult);
addLog(
'字段采集',
'INFO',
'GetFields 成功',
currentName + ' | fields=' + rawFields.length
);
} catch (eGet) {
getFieldsErr = eGet.message || String(eGet);
addLog('字段采集', 'WARN', 'GetFields 失败', currentName + ' | ' + getFieldsErr);
}
var descriptors = null;
var descriptorCount = 0;
try {
descriptors = currentSheet.FieldDescriptors;
descriptorCount = descriptors.Count || 0;
addLog(
'字段采集',
'INFO',
'FieldDescriptors 可用',
currentName + ' | count=' + descriptorCount
);
} catch (eDesc) {
addLog('字段采集', 'ERROR', 'FieldDescriptors 失败', currentName + ' | ' + eDesc.message);
}
if (descriptorCount > 0) {
for (var i = 1; i <= descriptorCount; i++) {
try {
var fd = descriptors.Item(i);
var snap = snapshotFieldDescriptor(fd);
var rawMatched = chooseRawField(rawFields, snap, i - 1);
var fullDefObj = {
fromFieldDescriptor: snap,
fromGetFields: rawMatched
};
pushSchemaRow({
runId: runId,
tableName: currentName,
sheetId: String(currentId),
fieldIndex: String(i),
fieldId: normalizeCellText(snap.Id),
fieldName: normalizeCellText(snap.Name),
fieldType: normalizeCellText(snap.Type),
fieldDescription: normalizeCellText(snap.Description),
defaultVal: normalizeCellText(snap.DefaultVal),
defaultValType: normalizeCellText(snap.DefaultValType),
numberFormat: normalizeCellText(snap.NumberFormat),
valueUnique: normalizeCellText(snap.ValueUnique),
syncField: normalizeCellText(snap.SyncField),
source: rawMatched ? 'FieldDescriptors+GetFields' : 'FieldDescriptors',
fullDefinitionJson: normalizeCellText(fullDefObj),
rawGetFieldsJson: normalizeCellText(rawMatched)
});
} catch (eItem) {
addLog(
'字段采集',
'ERROR',
'读取字段描述失败',
currentName + ' | index=' + i + ' | ' + eItem.message
);
}
}
} else if (rawFields.length > 0) {
for (var r = 0; r < rawFields.length; r++) {
var rf = rawFields[r];
var rfId = pickFirst(rf, ['id', 'Id', 'fieldId', 'FieldId'], '');
var rfName = pickFirst(rf, ['name', 'Name', 'fieldName', 'FieldName'], '');
var rfType = pickFirst(rf, ['type', 'Type', 'fieldType', 'FieldType'], '');
pushSchemaRow({
runId: runId,
tableName: currentName,
sheetId: String(currentId),
fieldIndex: String(r + 1),
fieldId: normalizeCellText(rfId),
fieldName: normalizeCellText(rfName),
fieldType: normalizeCellText(rfType),
fieldDescription: '',
defaultVal: '',
defaultValType: '',
numberFormat: '',
valueUnique: '',
syncField: '',
source: 'GetFields',
fullDefinitionJson: normalizeCellText(rf),
rawGetFieldsJson: normalizeCellText(rf)
});
}
} else {
pushSchemaRow({
runId: runId,
tableName: currentName,
sheetId: String(currentId),
fieldIndex: '0',
fieldId: '',
fieldName: '(未获取到字段定义)',
fieldType: '',
fieldDescription: getFieldsErr ? 'GetFields错误: ' + getFieldsErr : '',
defaultVal: '',
defaultValType: '',
numberFormat: '',
valueUnique: '',
syncField: '',
source: 'EMPTY',
fullDefinitionJson: '',
rawGetFieldsJson: ''
});
}
addLog('字段采集', 'INFO', '完成采集', currentName);
}
addLog('汇总', 'INFO', '字段记录总数', String(schemaRows.length));
var schemaFieldDefs = [
{ name: '执行批次', type: 'MultiLineText' },
{ name: '所属表名', type: 'MultiLineText' },
{ name: 'SheetId', type: 'MultiLineText' },
{ name: '字段序号', type: 'MultiLineText' },
{ name: '字段Id', type: 'MultiLineText' },
{ name: '字段名称', type: 'MultiLineText' },
{ name: '字段类型', type: 'MultiLineText' },
{ name: '字段描述', type: 'MultiLineText' },
{ name: '默认值', type: 'MultiLineText' },
{ name: '默认值类型', type: 'MultiLineText' },
{ name: '数字格式', type: 'MultiLineText' },
{ name: '唯一值', type: 'MultiLineText' },
{ name: '同步字段', type: 'MultiLineText' },
{ name: '定义来源', type: 'MultiLineText' },
{ name: '字段完整定义JSON', type: 'MultiLineText' },
{ name: 'GetFields原始JSON', type: 'MultiLineText' }
];
var schemaSheet = ensureOutputSheet(EXPORT_SCHEMA_SHEET_NAME, schemaFieldDefs);
addLog('写入输出', 'INFO', '开始写入字段定义表', EXPORT_SCHEMA_SHEET_NAME);
writeRowsToSheet(schemaSheet, schemaFieldDefs, schemaRows, 25);
addLog('写入输出', 'INFO', '字段定义表写入完成', EXPORT_SCHEMA_SHEET_NAME);
// 在写日志表之前先记一条,确保它也能落表
addLog('写入输出', 'INFO', '准备写入日志表', EXPORT_LOG_SHEET_NAME);
var logFieldDefs = [
{ name: '执行批次', type: 'MultiLineText' },
{ name: '时间', type: 'MultiLineText' },
{ name: '阶段', type: 'MultiLineText' },
{ name: '级别', type: 'MultiLineText' },
{ name: '消息', type: 'MultiLineText' },
{ name: '明细', type: 'MultiLineText' }
];
var logSheet = ensureOutputSheet(EXPORT_LOG_SHEET_NAME, logFieldDefs);
var logRowsForSheet = [];
for (var l = 0; l < logRows.length; l++) {
var lr = logRows[l];
logRowsForSheet.push([
lr.runId,
lr.time,
lr.stage,
lr.level,
lr.message,
lr.detail
]);
}
writeRowsToSheet(logSheet, logFieldDefs, logRowsForSheet, 30);
console.log('');
console.log('======================================================');
console.log('导出完成');
console.log('RunId: ' + runId);
console.log('字段定义表: ' + EXPORT_SCHEMA_SHEET_NAME + ' | 行数=' + schemaRows.length);
console.log('执行日志表: ' + EXPORT_LOG_SHEET_NAME + ' | 行数=' + logRowsForSheet.length);
console.log('======================================================');

View File

@@ -7,8 +7,8 @@
### 项目基本信息表 <行业组人员填写> todo: 需要能够通过项目名称搜索)
1. 项目名称
2. 行业组人员名称
3. 行业组人电话
2. 行业组接口
3. 行业组接口人电话
4. 省份 <下拉选择>
5. 城市 <下拉选择>
6. 部署飞服平台 <选择>
@@ -92,11 +92,18 @@
上面每个中间件都具备如下的属性
1. 是否暴露公网
2. 公网端口
3. 内网IP
4. 网端口
5. 用户名
6. 密码 <不允许填写>
2. 公网IP<唯一>
3. 内网IP<唯一>
2. 网端口A
4. 内网端口A
5. 公网端口B
6. 内网端口B
7. 公网端口C
8. 内网端口C
9. 公网端口D
10. 内网端口D
11. 用户名
12. 密码 <不允许填写>
### 项目部署业务信息表 <交付部署特战队填写> (todo: 此表如何与项目进行关联)
1. 微服务名称
@@ -129,12 +136,12 @@
├── 项目部署环境信息表(每台主机一行)
├── 项目部署网络信息表(每次部署一行)
├── 项目部署中间件信息表每个中间件一行共7行
└── 项目部署业务信息表(每个微服务一行)
└── 项目部署业务信息表(每个微服务一行,不同平台的微服务不同
同时在「项目本地化部署状态表」里对每个子表建立双向关联的反向字段,这样在部署状态表中可以直接看到这条部署关联了哪些主机、哪些中间件 。
项目部署中间件信息表
├── 所属部署 ← 单向关联字段(→ 部署状态表),这是你的"projectID"
├── 所属部署 ← 单向关联字段(→ 部署状态表)
├── 项目名称 ← 引用字段(从部署状态表引用,只读自动填入)
├── 中间件类型 ← 单选MySQL / Redis / RabbitMQ / EMQX / NACOS / K8S Dashboard / MINIO
├── 是否暴露公网

View File

@@ -25,4 +25,12 @@
[金山多维表格开发文档](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/dbsheet-standard)
[AirScript文档](https://airsheet.wps.cn/docs/guide/summary.html)
整理得到一份可以执行落地的API脚本用于创建上述的表格 以及人工需要进行操作补充的相关说明
整理得到一份可以执行落地的API脚本用于创建上述的表格 以及人工需要进行操作补充的相关说明
请你分析根据金山多维表格的的API文档
[金山多维表格开发文档](https://365.kdocs.cn/3rd/open/documents/app-integration-dev/guide/dbsheet/dbsheet-standard)
[AirScript文档](https://airsheet.wps.cn/docs/guide/summary.html)
要求完整的获取关于AirScript的相关文档如果遇到需要递归的字段和网页 请递归查找
需要整理一份离线的功能完备的markdown格式的 airscript-spec-doc.md 文件,用于后续的开发参考

View File

@@ -0,0 +1,39 @@
### V1
你是一名优秀的飞书多维表格开发专家,你非常善于阅读并搜集在线的资料,并根据资料完成开发工作。
请你阅读 [飞书多维表格开发文档](https://open.feishu.cn/document/server-docs/docs/bitable-v1/bitable-overview) 将飞书多维表格的开发文档,包含所有子页面,整理成一份完整的参考文档.
提示:飞书文档页面存在复制页面的button, 可以直接复制为Markdown格式提供给大模型参考,请熟练使用
注意:
1. 先拉取完整的开发文档,注意不同子页面之间的关联关系
2. 后期我会将此部分开发文档制作为AgentSkills
3. 将完整的文档输出到 18-基础架构及交付部署特战队\10-飞书多维表格 目录下
### V2
你完成的很好
1. 现在实现的参考文档中,存在大量的在线链接的内容,我希望的是可以完全离线使用的完整文档
2. 请你将在线内容全部下载到本地,并整理成一份完整的参考文档
3. 需要将Go SDK的示例代码也一并下载一份
4. 请研究[飞书API参考文档](https://feishu.apifox.cn/) 将GolangSDK调用的使用说明 也一并写入文档中
请你按照上面的要求,重新生成一份完整的参考文档v2版本
### V3
请你详细的阅读飞书的官方文档,完成如下要求的深度研究
1. 飞书能够实现的功能有哪些
2. 飞书API接口实现的功能有哪些
3. 飞书GO SDK能够实现的功能有哪些
4. 飞书所有的功能是否都支持API接口修改
5. 飞书能否作为项目协同的前端页面平台
6. 飞书免费版能否畅快的使用,不受限制
### V4
使用dds-to-skill技能你现在需要根据 [飞书多维表格开发文档-完整参考-v2.md](18-基础架构及交付部署特战队\10-飞书多维表格\飞书多维表格开发文档-完整参考-v2.md) 为大模型制作一份可以参考使用的AgentSkills
要求如下:
1. 输出目录为 18-基础架构及交付部署特战队\10-飞书多维表格
2. 需要按照AgentSkills的渐进式暴露原则,实现文档的转换

View File

@@ -0,0 +1,20 @@
# 全局自检结果
## 结构完整性
- ✅ S1 PASS: 系统级 Skill = 1
- ✅ S2 PASS: 模块级 Skills = 6
- ✅ S3 PASS: 横切 Skills = 4>=3
- ✅ S4 PASS: 所有 Skill 含 scripts/verify.sh
- ✅ S5 PASS: 已执行 11 个 verify.sh全部通过
## 内容质量
- ✅ C1 PASS: 所有 SKILL.md < 500
- C2 PASS: 所有 SKILL.md Plan/Verify/Execute/Pitfalls
- C3 PASS: 所有 SKILL.md 动态注入命令 >= 2
## Reference 质量
- ✅ R1 PASS: 所有 Skill reference 含 DDS-Section 与 DDS-Lines
- ✅ R2 PASS: 模块级 Skill 均含 API + 安全/依赖 + [TBD] 缺失标注文件
## 总计: 10 PASS / 0 FAIL

View File

@@ -0,0 +1,58 @@
# 飞书多维表格 AgentSkillsv2 离线文档转换产物)
本套 Skills 基于 `飞书多维表格开发文档-完整参考-v2.md` 生成,遵循「渐进式暴露」:
1. 先进入系统级 Skill 判断变更范围。
2. 再进入对应模块级 Skill 执行实现/改造。
3. 叠加横切 Skill 处理契约、鉴权、并发、SDK 接入。
4. 最后下钻 `reference/` 读取可溯源设计细节(含 DDS 章节与行号)。
## Skill 清单(最终命名)
### 系统级1
- `developing-feishu-bitable-system`
### 模块级6
- `developing-bitable-app-metadata`
- `developing-bitable-table-view`
- `developing-bitable-record`
- `developing-bitable-field`
- `developing-bitable-role-member`
- `developing-bitable-event-callback`
### 横切4
- `designing-bitable-contracts`
- `implementing-bitable-auth-access`
- `managing-bitable-write-concurrency`
- `implementing-bitable-go-sdk`
## 命名候选与选择理由
| Skill | 候选名 | 选择 | 理由 |
|---|---|---|---|
| 系统级 | `developing-bitable-system` / `developing-feishu-bitable-system` / `developing-lark-bitable-system` | `developing-feishu-bitable-system` | 同时保留平台域feishu和产品域bitable检索命中更稳定。 |
| App | `developing-bitable-app` / `developing-bitable-app-metadata` / `developing-bitable-app-core` | `developing-bitable-app-metadata` | 文档对 App 主要是元数据与高级权限开关,`metadata` 语义更准确。 |
| Table/View | `developing-bitable-table` / `developing-bitable-view` / `developing-bitable-table-view` | `developing-bitable-table-view` | 视图严格依附数据表,合并后可减少重复规则。 |
| Record | `developing-bitable-record` / `developing-bitable-row` / `developing-bitable-record-crud` | `developing-bitable-record` | 与 OpenAPI 资源命名一致,便于映射接口。 |
| Field | `developing-bitable-field` / `developing-bitable-schema-field` / `developing-bitable-column` | `developing-bitable-field` | 与文档“字段 Field”章节一致。 |
| Role/Member | `developing-bitable-role` / `developing-bitable-role-member` / `developing-bitable-advanced-permission` | `developing-bitable-role-member` | 将角色与协作者放在同一模块,贴合高级权限操作闭环。 |
| Event/Callback | `developing-bitable-event` / `developing-bitable-event-callback` / `developing-bitable-webhook` | `developing-bitable-event-callback` | 同时覆盖事件与回调两条接入路径。 |
| 契约 | `designing-bitable-contracts` / `designing-bitable-openapi` / `managing-bitable-contracts` | `designing-bitable-contracts` | 以设计导向统一 API/错误码/资源 ID 契约。 |
| 鉴权 | `implementing-bitable-auth` / `implementing-bitable-auth-access` / `managing-bitable-token-auth` | `implementing-bitable-auth-access` | 同时包含 token 与权限模型。 |
| 并发 | `managing-bitable-concurrency` / `managing-bitable-write-concurrency` / `implementing-bitable-retry` | `managing-bitable-write-concurrency` | 明确聚焦写操作并发与幂等。 |
| Go SDK | `implementing-bitable-sdk` / `implementing-bitable-go-sdk` / `developing-bitable-sdk-integration` | `implementing-bitable-go-sdk` | 技术栈指向清晰,避免多语言混淆。 |
## 目录约定
每个 Skill 目录均包含:
- `SKILL.md`
- `reference/`(按章节分层,含 `DDS-Section` + `DDS-Lines`
- `examples/skeleton.md`
- `scripts/verify.sh`
## 渐进式使用建议
1. 先读 `developing-feishu-bitable-system/SKILL.md`
2. 根据变更资源进入对应模块 Skill。
3. 若涉及跨模块约束,再叠加横切 Skill。
4. 编码或审查时仅按需展开目标 `reference/*.md`,避免一次性加载全量文档。

View File

@@ -0,0 +1,79 @@
---
name: designing-bitable-contracts
description: 统一飞书多维表格 API 契约设计与审查Guides cross-module contract design for Feishu Bitable。包含资源 ID 规范、OpenAPI 路径方法、请求响应字段与错误码一致性。触发场景 Trigger: 新增接口、变更字段、排查上下游契约不一致、做跨模块发布评审。关键词 Keywords: contract, openapi, path, error code, app_token, table_id, bitable。
argument-hint: "<scope> <change> - e.g., 'record api add field', 'role api error-code align'"
allowed-tools:
- Read
- Glob
- Grep
- Bash
---
# Designing Bitable Contracts
用于跨模块统一 API 契约,重点防止路径、参数、错误码与资源 ID 在不同模块实现中产生漂移。
## Quick Context
```bash
# 汇总 Bitable 相关接口定义
!`rg -n "OpenAPI Specification|/bitable/v1/apps|错误码|requestBody|responses" -S . | head -n 150`
# 扫描资源 ID 使用位置
!`rg -n "app_token|table_id|view_id|record_id|field_id|role_id|member_id" -S . | head -n 150`
```
## Plan
### 产物清单
- [ ] 资源 ID 与路径映射表
- [ ] 请求/响应字段差异清单
- [ ] 错误码冲突清单
- [ ] 兼容性评审结论
### 决策点
1. 变更是向后兼容还是破坏性变更?
2. 是否需要引入新错误码或复用现有错误码?
3. 是否影响多模块消费方SDK、回调、下游服务
## Verify
### 资源一致性
- [ ] 资源 ID 语义统一,见 `reference/01-resource-ids/dependencies.md`
- [ ] 路径层级与资源归属无冲突
### OpenAPI 一致性
- [ ] 方法、路径、参数、响应字段对齐 `reference/02-openapi-contract/apis.md`
- [ ] 请求体可选项与默认语义明确
### 错误模型一致性
- [ ] 同错误码语义一致,见 `reference/03-error-model/apis.md`
- [ ] 失败场景有明确排查建议
## Execute
### 1. 建立契约基线
1. 汇总当前模块接口矩阵。
2. 锁定资源 ID 与路径层级。
### 2. 比较差异
1. 对请求字段做新增/删除/语义变化对比。
2. 对响应字段做兼容性对比。
3. 对错误码做含义冲突检查。
### 3. 输出决策
1. 明确兼容级别(兼容/有条件兼容/不兼容)。
2. 给出迁移步骤与回滚策略。
## Pitfalls
1. **同一资源使用多个命名**:如 token/id 混用导致歧义,见 `reference/01-resource-ids/dependencies.md`
2. **只改请求不改响应示例**:上下游生成代码不一致,见 `reference/02-openapi-contract/apis.md`
3. **错误码复用语义冲突**:排障不可控,见 `reference/03-error-model/apis.md`
## Related References
- `reference/01-resource-ids/dependencies.md`
- `reference/02-openapi-contract/apis.md`
- `reference/03-error-model/apis.md`
- `reference/04-compatibility/state-machine.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,19 @@
## 资源 ID 规范
- DDS-Section: 接入指南-参数说明 / A.1 概述
- DDS-Lines: L399-L468, L111-L257
### Extract
| 资源 | ID 字段 |
|---|---|
| App | `app_token` |
| Table | `table_id` |
| View | `view_id` |
| Record | `record_id` |
| Field | `field_id` |
| Role | `role_id` |
| Member | `member_id` |
规则ID 应与路径段一一对应,禁止跨资源复用。

View File

@@ -0,0 +1,17 @@
## OpenAPI 契约骨架
- DDS-Section: A.10~A.49 OpenAPI Specification
- DDS-Lines: L1938-L16470
### Extract
- 路径命名统一前缀:`/bitable/v1/apps/{app_token}`
- 子资源按层次展开:`tables -> views/records/fields``roles -> members`
- 请求体和响应体均以标准 JSON schema 描述,含 code/msg/data 包装。
## 字段级审查要点
- 请求字段是否含示例值与必填标注。
- 响应字段是否覆盖新增字段。
- 分页字段 `page_token`/`has_more` 是否一致。

View File

@@ -0,0 +1,16 @@
## 错误码模型
- DDS-Section: 各 OpenAPI 的错误码章节
- DDS-Lines: L1957+, L2239+, L2461+, L4723+, L7594+
### Extract
| 类别 | 示例 |
|---|---|
| 参数错误 | `WrongRequestJson`, `WrongRequestBody`, `WrongFieldId` |
| 资源错误 | `WrongBaseToken`, `app_token 不存在` |
| 并发/处理冲突 | `OperationTypeError`、并发写限制 |
| 幂等冲突 | `client token and try again` |
要求:同错误码在不同模块中保持相同语义与排查动作。

View File

@@ -0,0 +1,15 @@
## 版本与兼容策略
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未给出正式的 API 版本升级策略deprecate 生命周期、灰度窗口、双写方案)。
### 最小补充信息清单
1. 向后兼容判定标准
2. 破坏性变更发布流程
3. 旧版本下线时间线

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,82 @@
---
name: developing-bitable-app-metadata
description: 指导飞书多维表格 App 元数据模块开发Guides Bitable app metadata module development。包含获取/更新元数据、高级权限开关、参数与错误码校验。触发场景 Trigger: 新增或修改 app 级配置、切换 advanced permission、排查 app_token 级接口失败。关键词 Keywords: bitable, app, metadata, app_token, advanced permission, 高级权限。
argument-hint: "<action> <app_token> - e.g., 'get metadata appbxxx', 'update app name+permission appbxxx'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Developing Bitable App Metadata
用于处理 `app_token` 级别能力:读取/更新多维表格元数据与高级权限开关,确保调用身份、前置权限、错误码处理一致。
## Quick Context
```bash
# 定位 app 级接口定义与调用处
!`rg -n "bitable/v1/apps/\{app_token\}|app/update|app/get" -S . | head -n 80`
# 定位高级权限相关逻辑
!`rg -n "advanced.*permission|roles|OperationTypeError|app_token" -S . | head -n 80`
```
## Plan
### 产物清单
- [ ] App 元数据读取与更新调用点
- [ ] 参数映射(`app_token`, `name`, `is_advanced`
- [ ] 错误码与重试策略
- [ ] 权限前置检查
### 决策点
1. 本次更新是仅改名称,还是同时改高级权限开关?
2. 是否接受“非原子更新”带来的部分成功状态?
3. 调用身份选择用户态还是应用态 token
## Verify
### API 契约
- [ ] 路径与方法与 `reference/02-app-openapi/apis.md` 一致
- [ ] 请求体字段只包含本次所需字段,避免误改
### 权限与鉴权
- [ ] token 身份与权限范围匹配,见 `reference/03-security/security-model.md`
- [ ] 高级权限开关后续 Role/Member 流程已准备
### 风险控制
- [ ] 已处理接口“先改名后开关权限”的部分成功场景
- [ ]`app_token` 错误、无访问权限、未开启高级权限错误有明确分支
## Execute
### 1. 读取当前状态
1. 调用 `GET /bitable/v1/apps/{app_token}`,确认当前名称与权限开关。
2. 记录变更前快照,作为回滚基线。
### 2. 执行更新
1. 调用 `PUT /bitable/v1/apps/{app_token}`
2. 若同时变更多项,先按接口语义评估“部分成功”影响。
3. 对已知可重试错误(如权限生效延迟)实施有限重试。
### 3. 验证结果
1. 再次读取元数据核对结果。
2. 若开启高级权限,立即验证 Role/Member 接口是否可用。
## Pitfalls
1. **把元数据更新当作原子事务**:接口本身可能部分成功,先读 `reference/02-app-openapi/apis.md`
2. **开启高级权限后立即操作角色失败**:存在生效延迟,见 `reference/03-security/security-model.md`
3. **调用身份不匹配**:用户态与应用态 token 混用会造成权限异常,见 `reference/03-security/security-model.md`
4. **遗漏 app_token 来源校验**URL/块信息提取错误会导致全链路失败,见 `reference/01-resource-overview/dependencies.md`
## Related References
- `reference/01-resource-overview/dependencies.md`
- `reference/02-app-openapi/apis.md`
- `reference/03-security/security-model.md`
- `reference/04-db-schema/db-schema.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,22 @@
## App 资源定位与依赖
- DDS-Section: 资源:多维表格应用 App / 接入指南-参数说明 app_token
- DDS-Lines: L111-L128, L399-L423
### Extract
| 项 | 内容 |
|---|---|
| 主标识 | `app_token` |
| 获取方式 | 多维表格 URL、文档 block token 拆分、相关 API 返回 |
| 下游依赖 | Table/View/Record/Field/Role/Member 均依赖 `app_token` |
## 调用链路
- DDS-Section: 接入指南-内容
- DDS-Lines: L294-L303
### Extract
`App -> Table -> (View / Record / Field)`,以及 `App -> Role -> Member`

View File

@@ -0,0 +1,31 @@
## 获取多维表格元数据
- DDS-Section: A.10 获取多维表格元数据
- DDS-Lines: L1932-L2205
### Extract
| 方法 | 路径 | 关键参数 |
|---|---|---|
| GET | `/bitable/v1/apps/{app_token}` | `app_token` |
关键约束:
- 支持 20 QPS以接口描述为准
- 错误码含 `WrongBaseToken``OperationTypeError`
## 更新多维表格元数据
- DDS-Section: A.11 更新多维表格元数据
- DDS-Lines: L2206-L2430
### Extract
| 方法 | 路径 | 请求体关键字段 |
|---|---|---|
| PUT | `/bitable/v1/apps/{app_token}` | `name`, `is_advanced` |
关键约束:
- 接口非原子:先改名,后切换高级权限
- 写接口常见 10 QPS且不建议并发写
- 高级权限开关对后续 Role/Member 接口可用性有直接影响

View File

@@ -0,0 +1,23 @@
## token 与访问身份
- DDS-Section: 接入指南-鉴权
- DDS-Lines: L311-L371
### Extract
| 方式 | 适用 |
|---|---|
| `user_access_token` | 用户身份调用 |
| `tenant_access_token` | 应用身份调用 |
## 高级权限开关前置
- DDS-Section: A.7 概述 / A.11 更新多维表格元数据
- DDS-Lines: L1547-L1561, L2229-L2233, L2286-L2289
### Extract
- 飞书文档/飞书表格/知识库中的多维表格不支持开启高级权限。
- 开启高级权限存在生效延迟,可能返回 `OperationTypeError`
- 发生无访问权限时,应检查高级权限设置中是否为应用主体授权。

View File

@@ -0,0 +1,15 @@
## 服务端持久化结构
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未提供 App 元数据在业务系统侧的数据库结构。
### 最小补充信息清单
1. App 元数据缓存表是否存在(字段与 TTL
2. 高级权限开关状态是否持久化本地
3. App 配置变更的审计日志模型

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,87 @@
---
name: developing-bitable-event-callback
description: 指导飞书多维表格事件与回调模块开发Guides Bitable event and callback integration development。包含事件类型、长连接/HTTP 回调模式、验签解密参数、3 秒响应与重推去重策略。触发场景 Trigger: 新增事件订阅、接入回调服务、排查重复推送与签名校验失败。关键词 Keywords: event, callback, websocket, webhook, verificationToken, eventEncryptKey, bitable。
argument-hint: "<mode> <event-type> - e.g., 'websocket bitable_record_changed', 'http callback card action'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Developing Bitable Event & Callback
用于搭建与维护飞书事件/回调接入通道,统一消息投递语义、幂等消费、验签解密配置与故障处理。
## Quick Context
```bash
# 定位事件处理器、回调路由与 SDK dispatcher
!`rg -n "EventDispatcher|OnP2|OnCustomizedEvent|webhook/event|callback" -S . | head -n 120`
# 定位 3 秒响应、重推、去重逻辑
!`rg -n "3 秒|重推|event_id|idempot|retry|verificationToken|eventEncryptKey" -S . | head -n 120`
```
## Plan
### 产物清单
- [ ] 事件订阅清单与处理器映射
- [ ] 回调模式(长连接/HTTP决策
- [ ] 验签解密配置与密钥管理
- [ ] 重推去重与失败告警策略
### 决策点
1. 采用长连接模式还是 HTTP 回调模式?
2. 是否开启加密策略(需要 verificationToken/eventEncryptKey
3. 幂等去重键是否统一为 `event_id`
## Verify
### 事件契约
- [ ] 事件类型与字段对齐 `reference/01-event-types/events-topics.md`
- [ ] 处理器注册方法与事件版本匹配v1/v2
### 接入模式
- [ ] 模式选择与网络前置条件对齐 `reference/02-callback-modes/dependencies.md`
- [ ] HTTP 模式已提供稳定回调地址与快速响应路径
### 安全与语义
- [ ] 验签/解密参数配置对齐 `reference/03-security/security-model.md`
- [ ] 3 秒响应与重推语义对齐 `reference/04-delivery-semantics/state-machine.md`
## Execute
### 1. 建立接入通道
1. 长连接模式:初始化 SDK ws client 并注册 dispatcher。
2. HTTP 模式:暴露 `/webhook/event` 路由并接入 handler。
### 2. 注册事件处理器
1. 按事件版本选择 `OnCustomizedEvent``OnP2*` 方法。
2. 在处理器内抽取 `event_id`, `tenant_key`, 资源 ID。
### 3. 落地幂等与超时控制
1. 处理链路控制在 3 秒内返回 ACK。
2. 业务处理异步化,核心字段入队。
3.`event_id` 去重,避免重推重复消费。
### 4. 异常处理
1. 验签/解密失败:快速拒绝并记录上下文。
2. 下游不可用:入队重试并上报告警。
## Pitfalls
1. **使用错误的事件版本处理器**v1/v2 结构不同,见 `reference/01-event-types/events-topics.md`
2. **处理超时超过 3 秒**:平台会重推,见 `reference/04-delivery-semantics/state-machine.md`
3. **开启加密后仍传空密钥**:会导致解密失败,见 `reference/03-security/security-model.md`
4. **长连接当广播使用**:实际是集群随机分发,见 `reference/02-callback-modes/dependencies.md`
## Related References
- `reference/01-event-types/events-topics.md`
- `reference/02-callback-modes/dependencies.md`
- `reference/03-security/security-model.md`
- `reference/04-delivery-semantics/state-machine.md`
- `reference/05-db-schema/db-schema.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,14 @@
## Bitable 事件清单
- DDS-Section: A.8 多维表格字段变更 / A.9 多维表格记录变更
- DDS-Lines: L1599-L1712, L1815-L1915
### Extract
| 事件类型 | 场景 | 关键字段 |
|---|---|---|
| `drive.file.bitable_field_changed_v1` | 字段变更 | `event_id`, `event_type`, `table_id`, `field_id` |
| `drive.file.bitable_record_changed_v1` | 记录变更 | `event_id`, `event_type`, `table_id`, `record_id` |
字段:`tenant_key`, `token`, `subscriber_id_list` 需一并保留用于追踪与授权判定。

View File

@@ -0,0 +1,21 @@
## 回调接入模式
- DDS-Section: 3.5 事件与回调处理 / B.4 处理事件 / B.5 处理回调
- DDS-Lines: L86-L92, L16849-L17083, L17147-L17397
### Extract
| 模式 | 特点 | 前置条件 |
|---|---|---|
| 长连接WebSocket | 接入快、免内网穿透、集群非广播 | 运行环境可访问公网 |
| HTTP 回调 | 传统 webhook平台主动 POST | 需要公网可达地址 |
## 框架集成
- DDS-Section: B.4/B.5 示例代码
- DDS-Lines: L17028-L17081, L17358-L17397
### Extract
支持原生 HTTP、Gin、Hertz 三类接入方式。

View File

@@ -0,0 +1,14 @@
## 验签与解密配置
- DDS-Section: B.4 处理事件 / B.5 处理回调
- DDS-Lines: L16980-L17018, L17294-L17316
### Extract
| 参数 | 说明 |
|---|---|
| `verificationToken` | 用于签名验证 |
| `eventEncryptKey` | 用于消息解密 |
若在开发者后台启用加密策略,则必须传递上述参数。

View File

@@ -0,0 +1,16 @@
## 投递语义状态流
- DDS-Section: 事件/回调处理说明 / FAQ
- DDS-Lines: L89, L16871, L17196, L18207-L18209
### Extract
```text
RECEIVED -> ACK(<3s) -> DONE
RECEIVED -> TIMEOUT(>=3s) -> RETRY_PUSH -> DEDUPE_BY_EVENT_ID -> DONE
```
规则:
- 必须在 3 秒内完成 ACK。
- 超时会触发重推,消费侧必须幂等。

View File

@@ -0,0 +1,15 @@
## 事件存储结构
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未给出事件落库模型(去重表、死信表、重试表)。
### 最小补充信息清单
1. `event_id` 去重存储 TTL
2. 失败事件重试与死信策略
3. 事件审计字段定义

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,86 @@
---
name: developing-bitable-field
description: 指导飞书多维表格 Field 模块开发Guides Bitable field module development。包含字段 CRUD、字段类型 property 配置、全量更新语义、字段变更事件处理。触发场景 Trigger: 新增/更新/删除字段、扩展字段类型配置、排查 field_id 与 property 不匹配问题。关键词 Keywords: field, field_id, property, option, relation, bitable。
argument-hint: "<action> <app_token table_id field_id?> - e.g., 'create field appbxxx tblxxx', 'update field property appbxxx tblxxx fldxxx'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Developing Bitable Field
用于字段资源开发:字段列表与增删改、字段类型 `property` 配置、全量更新风险控制,以及字段变更事件消费。
## Quick Context
```bash
# 定位字段 API 与字段类型处理
!`rg -n "app-table-field|fields/\{field_id\}|property|options|table_id" -S . | head -n 120`
# 定位字段事件与幂等逻辑
!`rg -n "bitable_field_changed|field_changed|client_token|幂等" -S . | head -n 100`
```
## Plan
### 产物清单
- [ ] Field CRUD 调用封装
- [ ] 各字段类型 `property` 映射
- [ ] 更新语义防护(全量覆盖)
- [ ] 字段变更事件消费链路
### 决策点
1. 本次是新增字段还是全量更新字段?
2. 是否涉及关联字段(单向/双向)与跨表依赖?
3. 字段变更是否需要同步下游读写映射?
## Verify
### 契约与结构
- [ ] API 路径、方法对齐 `reference/01-field-openapi/apis.md`
- [ ] 字段类型与 `property` 结构对齐 `reference/02-field-types/db-schema.md`
### 事件与一致性
- [ ] 字段变更事件字段对齐 `reference/03-field-events/events-topics.md`
- [ ] 更新后已验证字段列表与读写兼容
### 权限
- [ ] token 与权限范围对齐 `reference/04-security/security-model.md`
## Execute
### 1. 字段读取
1.`list fields` 获取当前字段与类型。
2. 基于目标类型准备 `property` 请求体。
### 2. 字段写操作
1. 新增字段:调用 `create`,最小化初始属性。
2. 更新字段:调用 `update` 前先合并旧属性,避免误删。
3. 删除字段:确认下游表达式/关联/报表依赖后执行。
### 3. 关联字段处理
1. 单向/双向关联字段需显式绑定目标 `table_id`
2. 双向关联应检查返回的反向字段信息。
### 4. 事件消费
1. 消费 `bitable_field_changed`
2.`event_id` 去重,刷新本地字段缓存。
## Pitfalls
1. **把字段更新当成增量 patch**:接口是全量更新,易误删旧选项,见 `reference/02-field-types/db-schema.md`
2. **关联字段只改一侧**:双向关联会产生不一致,见 `reference/02-field-types/db-schema.md`
3. **删字段前未清理筛选或公式依赖**:可能导致查询失败,先查 `reference/01-field-openapi/apis.md`
4. **字段变更事件重复消费**:需按 event_id 去重,见 `reference/03-field-events/events-topics.md`
## Related References
- `reference/01-field-openapi/apis.md`
- `reference/02-field-types/db-schema.md`
- `reference/03-field-events/events-topics.md`
- `reference/04-security/security-model.md`
- `reference/05-state-machine/state-machine.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,18 @@
## Field API 矩阵
- DDS-Section: A.37~A.40(列出/新增/更新/删除字段)
- DDS-Lines: L12086-L13934
### Extract
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/fields` | 列出字段 |
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/fields` | 新增字段 |
| PUT | `/bitable/v1/apps/{app_token}/tables/{table_id}/fields/{field_id}` | 更新字段 |
| DELETE | `/bitable/v1/apps/{app_token}/tables/{table_id}/fields/{field_id}` | 删除字段 |
约束:
- 写接口不建议并发。
- 常见写接口频控 10 QPS。

View File

@@ -0,0 +1,39 @@
## 字段基础结构
- DDS-Section: A.3 数据结构 / A.5 字段编辑指南
- DDS-Lines: L571-L580, L719-L738
### Extract
| 字段 | 类型 | 说明 |
|---|---|---|
| `field_id` | string | 字段 ID |
| `field_name` | string | 字段名 |
| `type` | int | 字段类型编码 |
| `property` | object | 类型相关属性 |
## 关键类型 property
- DDS-Section: A.5 字段编辑指南(数字、单选/多选、日期、人员、关联、公式、自动编号)
- DDS-Lines: L738-L1344
### Extract
| 类型 | 关键 property |
|---|---|
| 单选/多选 | `options[]` |
| 单向/双向关联 | `table_id`, `table_name`, `back_field_*` |
| 自动编号 | `type`, `options[]`, `rule_option_type` |
更新语义:
- 字段更新为全量更新,`property` 会被完全覆盖。
## DB Schema
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档无后端数据库字段字典表定义(字段元数据持久化)。

View File

@@ -0,0 +1,21 @@
## 字段变更事件
- DDS-Section: A.8 多维表格字段变更
- DDS-Lines: L1595-L1712
### Extract
| 事件类型 | 关键字段 |
|---|---|
| `drive.file.bitable_field_changed_v1` | `event_id`, `event_type`, `tenant_key`, `table_id`, `field_id` |
## 消费策略
- DDS-Section: 事件回调说明
- DDS-Lines: L89, L18209
### Extract
- 3 秒内响应,否则重推。
- 建议 `event_id` 去重 + 字段缓存增量刷新。

View File

@@ -0,0 +1,13 @@
## 字段接口权限模型
- DDS-Section: 接入指南-鉴权 / 字段相关 OpenAPI security
- DDS-Lines: L311-L371, L12108+, L12678+, L13147+
### Extract
| 项 | 说明 |
|---|---|
| token | 支持 tenant/user 两种调用身份 |
| 高级权限影响 | 部分无访问权限由高级权限策略导致 |
| 用户标识字段 | 某些字段(如人员)涉及 user_id 权限要求 |

View File

@@ -0,0 +1,15 @@
## 字段状态机
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未定义字段生命周期状态机draft/active/deprecated 等)。
### 最小补充信息清单
1. 字段启用/停用状态定义
2. 变更审批与回滚流程
3. 对历史记录的兼容策略

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,88 @@
---
name: developing-bitable-record
description: 指导飞书多维表格 Record 模块开发Guides Bitable record module development。包含记录 CRUD、批量写入、筛选表达式、附件字段协同与变更事件消费。触发场景 Trigger: 新增/更新/删除记录、批量写入、按条件检索、排查 record_id 或 fields 映射问题。关键词 Keywords: record, fields, batch_update, filter, file_token, bitable。
argument-hint: "<action> <app_token table_id record_id?> - e.g., 'batch_update appbxxx tblxxx', 'get record appbxxx tblxxx recxxx'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Developing Bitable Record
用于记录资源生命周期开发:单条与批量 CRUD、`fields` 结构映射、筛选表达式、附件字段联动与记录变更事件处理。
## Quick Context
```bash
# 定位记录接口与批量操作
!`rg -n "app-table-record|records/batch_|/records/\{record_id\}|filter" -S . | head -n 100`
# 定位幂等键、重试与附件 file_token 处理
!`rg -n "client_token|幂等|file_token|batch_get_tmp_download_url|OperationTypeError" -S . | head -n 100`
```
## Plan
### 产物清单
- [ ] 记录 CRUD 与批量接口封装
- [ ] `fields` 映射层(字段类型到值结构)
- [ ] 幂等与重试控制
- [ ] 记录变更事件消费逻辑
### 决策点
1. 写入是单条还是批量(最多 500 条)?
2. 是否需要 `client_token` 幂等保护?
3. 是否要同时处理附件上传/下载链路?
## Verify
### API 与参数
- [ ] 路径、方法、分页参数与 `reference/01-record-openapi/apis.md` 对齐
- [ ] `fields` 结构映射与 `reference/02-record-structure/db-schema.md` 对齐
### 事件与幂等
- [ ] 记录变更事件消费字段对齐 `reference/03-events/events-topics.md`
- [ ] 写接口幂等与重试策略明确(含 UUID client_token
### 安全与权限
- [ ] 鉴权模式、字段级敏感信息权限对齐 `reference/04-security/security-model.md`
## Execute
### 1. 读取与检索
1.`list/get` 获取基线数据和分页状态。
2. 按需增加筛选表达式,避免全量扫描。
### 2. 写入与批量
1. 单条场景使用 `create/update/delete`
2. 批量场景使用 `batch_create/batch_update/batch_delete`,每次不超过 500。
3. 对写请求设置串行化策略,必要时附 `client_token`
### 3. 附件协同
1. 先上传素材拿到 `file_token`
2. 再写入记录附件字段。
3. 下载时先从记录读取 `file_token` 再调用下载接口。
### 4. 事件消费
1. 处理 `bitable_record_changed` 事件。
2. 使用 `event_id` 去重,避免超时重推导致重复处理。
## Pitfalls
1. **批量写超出上限**:单次最多 500 条,见 `reference/01-record-openapi/apis.md`
2. **fields 结构硬编码错误**:不同字段类型值结构不同,见 `reference/02-record-structure/db-schema.md`
3. **未使用幂等键导致重复写入**:重试场景可能产生重复副作用,见 `reference/01-record-openapi/apis.md`
4. **附件流程顺序错误**:必须先拿 file_token 再写记录,见 `reference/02-record-structure/db-schema.md`
5. **事件重复消费**3 秒超时会重推,见 `reference/03-events/events-topics.md`
## Related References
- `reference/01-record-openapi/apis.md`
- `reference/02-record-structure/db-schema.md`
- `reference/03-events/events-topics.md`
- `reference/04-security/security-model.md`
- `reference/05-state-machine/state-machine.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,23 @@
## Record API 矩阵
- DDS-Section: A.29~A.36(检索/列出/新增/更新/删除/批量)
- DDS-Lines: L7926-L12085
### Extract
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}` | 检索记录 |
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/records` | 列出记录 |
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/records` | 新增记录 |
| PUT | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}` | 更新记录 |
| DELETE | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/{record_id}` | 删除记录 |
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create` | 批量新增 |
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update` | 批量更新 |
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_delete` | 批量删除 |
关键约束:
- 批量接口单次最多 500。
- 写接口不建议并发。
- 部分批量接口支持 `client_token` 幂等。

View File

@@ -0,0 +1,34 @@
## 记录数据结构
- DDS-Section: A.3 数据结构(记录/fields/value
- DDS-Lines: L475-L571
### Extract
| 字段 | 类型 | 说明 |
|---|---|---|
| `record_id` | string | 记录唯一标识 |
| `fields` | map | 字段名到值的映射 |
`fields` 值结构按字段类型变化(文本、数字、人员、附件、关联等)。
## 附件字段协同
- DDS-Section: A.6 附件字段说明
- DDS-Lines: L1370-L1534
### Extract
1. 先调用上传素材接口获取 `file_token`
2. 再通过记录写接口将附件写入字段。
3. 下载时先通过记录读取 `file_token`,再调用下载接口。
## 本地 DB Schema
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未给出记录在业务系统持久化时的表结构与索引。

View File

@@ -0,0 +1,23 @@
## 记录变更事件
- DDS-Section: A.9 多维表格记录变更
- DDS-Lines: L1811-L1915
### Extract
| 事件 | 说明 |
|---|---|
| `drive.file.bitable_record_changed_v1` | 记录变更事件 |
关键字段:`event_id`, `event_type`, `tenant_key`, `table_id`, `record_id`
## 投递语义
- DDS-Section: 事件与回调处理说明
- DDS-Lines: L89, L18209
### Extract
- 接收端需在 3 秒内响应,否则重推。
- 消费端需做基于 `event_id` 的幂等去重。

View File

@@ -0,0 +1,13 @@
## Record 接口鉴权
- DDS-Section: 接入指南-鉴权 / 记录 API security 声明
- DDS-Lines: L311-L371, L8529+, L9268+, L9842+
### Extract
| 维度 | 说明 |
|---|---|
| token | `tenant_access_token``user_access_token` |
| 权限失败 | 高级权限或文档协作者权限未覆盖时会拒绝 |
| ID 字段 | `app_token` + `table_id` + `record_id` 需一致来源 |

View File

@@ -0,0 +1,15 @@
## 状态机定义
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未定义 Record 业务状态机(状态枚举、转移守卫、补偿动作)。
### 最小补充信息清单
1. 记录业务状态集合(如 draft/active/archived
2. 转移触发条件与角色权限
3. 转移失败补偿策略

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,86 @@
---
name: developing-bitable-role-member
description: 指导飞书多维表格高级权限 Role/Member 模块开发Guides Bitable advanced permission role/member development。包含角色管理、协作者管理、前置开关检查与权限联动。触发场景 Trigger: 配置高级权限角色、批量增删协作者、排查 role_id/member_id 相关鉴权失败。关键词 Keywords: role, member, advanced permission, role_id, member_id, bitable。
argument-hint: "<action> <app_token role_id?> - e.g., 'create role appbxxx', 'add members appbxxx rolxxx'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Developing Bitable Role & Member
用于高级权限域开发角色Role与协作者Member的增删改查及其与元数据开关、文档权限的联动处理。
## Quick Context
```bash
# 定位角色与协作者 API
!`rg -n "app-role|roles/\{role_id\}|role-member|members" -S . | head -n 120`
# 定位高级权限前置与无权限错误处理
!`rg -n "is_advanced|OperationTypeError|无访问权限|permission-member" -S . | head -n 100`
```
## Plan
### 产物清单
- [ ] Role API 封装
- [ ] Member API 封装(含批量)
- [ ] 高级权限开关前置检查
- [ ] 权限联动处理(文档权限)
### 决策点
1. 当前 app 是否已开启高级权限?
2. 是否需要同时同步云文档协作者权限?
3. 成员操作是否采用批量接口?
## Verify
### API 与参数
- [ ] 角色接口对齐 `reference/01-role-openapi/apis.md`
- [ ] 协作者接口对齐 `reference/02-member-openapi/apis.md`
### 前置与权限
- [ ] 开关前置、延迟生效、无权限分支对齐 `reference/03-security/security-model.md`
- [ ] 与文档权限联动策略对齐 `reference/04-dependencies/dependencies.md`
### 风险控制
- [ ] 批量操作有容量与失败处理策略
- [ ] 删除角色前已确认成员迁移或清理方案
## Execute
### 1. 前置检查
1. 调用 app 元数据接口确认高级权限已开启。
2. 若未开启,先走 app metadata 更新流程。
### 2. 角色管理
1. `list/create/update/delete role` 按资源策略执行。
2. 变更后回读角色详情,校验权限范围。
### 3. 协作者管理
1. 通过 `list/create/delete` 或批量接口维护成员。
2. 视场景同步云文档权限,避免“高级权限已加但文档无权限”。
### 4. 错误恢复
1. 对延迟生效错误(`OperationTypeError`)实施短周期重试。
2. 对权限缺失错误直接中止并返回修复建议。
## Pitfalls
1. **未开启高级权限就调用 Role/Member**:必然失败,见 `reference/03-security/security-model.md`
2. **只加高级权限不加文档权限**:用户仍可能不可访问,见 `reference/04-dependencies/dependencies.md`
3. **角色删除前未迁移成员**:造成权限漂移,需先清理成员,见 `reference/02-member-openapi/apis.md`
4. **忽略开启延迟**:开关后立即调用可能报错,见 `reference/03-security/security-model.md`
## Related References
- `reference/01-role-openapi/apis.md`
- `reference/02-member-openapi/apis.md`
- `reference/03-security/security-model.md`
- `reference/04-dependencies/dependencies.md`
- `reference/05-db-schema/db-schema.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,18 @@
## Role API 矩阵
- DDS-Section: A.41~A.44(列出/新增/删除/更新自定义角色)
- DDS-Lines: L13935-L15372
### Extract
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/bitable/v1/apps/{app_token}/roles` | 列出角色 |
| POST | `/bitable/v1/apps/{app_token}/roles/{role_id}` | 新增角色 |
| DELETE | `/bitable/v1/apps/{app_token}/roles/{role_id}` | 删除角色 |
| PUT | `/bitable/v1/apps/{app_token}/roles/{role_id}` | 更新角色 |
约束:
- 角色接口依赖高级权限开启。
- 写操作建议串行处理。

View File

@@ -0,0 +1,15 @@
## Member API 矩阵
- DDS-Section: A.45~A.49(批量删/批量增/列出/新增/删除协作者)
- DDS-Lines: L15373-L16470
### Extract
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/bitable/v1/apps/{app_token}/roles/{role_id}/members/batch_delete` | 批量删除协作者 |
| POST | `/bitable/v1/apps/{app_token}/roles/{role_id}/members/batch_create` | 批量新增协作者 |
| GET | `/bitable/v1/apps/{app_token}/roles/{role_id}/members` | 列出协作者 |
| POST | `/bitable/v1/apps/{app_token}/roles/{role_id}/members` | 新增协作者 |
| DELETE | `/bitable/v1/apps/{app_token}/roles/{role_id}/members/{member_id}` | 删除协作者 |

View File

@@ -0,0 +1,13 @@
## 高级权限前置与异常
- DDS-Section: A.7 概述 / A.11 更新元数据
- DDS-Lines: L1547-L1565, L2229-L2233, L2286-L2289
### Extract
| 规则 | 说明 |
|---|---|
| 开关前置 | Role/Member 调用前必须开启高级权限 |
| 生效延迟 | 开启后短时可能报 `OperationTypeError` |
| 无权限 | 需在高级权限中加入包含应用的群并授予读写 |

View File

@@ -0,0 +1,19 @@
## 权限联动依赖
- DDS-Section: A.7 概述
- DDS-Lines: L1559-L1561
### Extract
- 高级权限协作者与云文档协作者是两套身份体系。
- 新增高级权限协作者后,建议通过 drive 权限接口同步文档权限。
## 上下游依赖
- DDS-Section: 接入指南-内容
- DDS-Lines: L294-L303
### Extract
`App(is_advanced=true) -> Role -> Member`

View File

@@ -0,0 +1,15 @@
## 权限域数据库结构
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未描述 Role/Member 在业务侧持久化结构与审计表。
### 最小补充信息清单
1. 角色权限项结构与版本字段
2. 成员-角色关系表唯一键
3. 权限变更审计日志表

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,83 @@
---
name: developing-bitable-table-view
description: 指导飞书多维表格 Table 与 View 模块开发Guides Bitable table and view module development。包含数据表管理、视图管理、分页与错误处理、写接口并发约束。触发场景 Trigger: 新增/删除数据表、创建/删除视图、调整视图属性、排查 table_id/view_id 相关错误。关键词 Keywords: table, view, table_id, view_id, bitable, openapi。
argument-hint: "<action> <app_token/table_id/view_id> - e.g., 'create table appbxxx', 'delete view tblxxx vewxxx'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Developing Bitable Table & View
聚焦 `table_id``view_id` 资源生命周期,包括列表、创建、删除、更新,以及上游 App 与下游 Record/Field 的依赖影响。
## Quick Context
```bash
# 定位表和视图相关接口
!`rg -n "app-table|tables/\{table_id\}|views|app-table-view" -S . | head -n 100`
# 定位写接口并发与限流防护逻辑
!`rg -n "QPS|batch|并发|OperationTypeError|retry|client_token" -S . | head -n 80`
```
## Plan
### 产物清单
- [ ] Table API 调用点与参数封装
- [ ] View API 调用点与参数封装
- [ ] 分页遍历/幂等/重试策略
- [ ] 资源删除后的下游影响评估
### 决策点
1. 是否需要批量建表/删表能力?
2. 视图变更是否影响既有筛选与展示语义?
3. 是否有并发写风险(同 app 同时发多个写请求)?
## Verify
### API 对齐
- [ ] Table 接口路径与方法对齐 `reference/02-table-openapi/apis.md`
- [ ] View 接口路径与方法对齐 `reference/03-view-openapi/apis.md`
### 约束检查
- [ ] QPS、并发写限制已按 `reference/02-table-openapi/apis.md``reference/03-view-openapi/apis.md` 落地
- [ ] 删除类操作前已验证依赖关系Record/Field/业务视图)
### 安全检查
- [ ] token 模式与权限范围匹配,见 `reference/04-security/security-model.md`
## Execute
### 1. 数据表操作
1.`list tables` 确认目标是否已存在。
2. 执行 `create / batch_create / delete / batch_delete`
3. 对写请求串行化,避免同 app 并发写。
### 2. 视图操作
1.`list views` 获取目标 `view_id`
2. 执行 `create / update / delete / get`
3. 修改后做一次读取回放,确认属性生效。
### 3. 失败处理
1. 对参数类错误立即失败并回传上下文。
2. 对可恢复错误(冲突、短暂不可用)使用有限重试。
## Pitfalls
1. **删除表前未处理下游依赖**Record/Field/业务配置悬空,先看 `reference/01-resource-model/dependencies.md`
2. **同一 app 并发写表/视图**:会触发并发写限制,先看 `reference/02-table-openapi/apis.md`
3. **view_id 获取方式错误**:不同形态下取值路径不同,先看 `reference/01-resource-model/dependencies.md`
4. **分页处理缺失**list 接口有 `page_token`,漏处理会丢数据,见 `reference/02-table-openapi/apis.md`
## Related References
- `reference/01-resource-model/dependencies.md`
- `reference/02-table-openapi/apis.md`
- `reference/03-view-openapi/apis.md`
- `reference/04-security/security-model.md`
- `reference/05-db-schema/db-schema.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,16 @@
## 资源关系
- DDS-Section: 资源:数据表 Table / 资源:视图 View / 接入指南-参数说明
- DDS-Lines: L130-L170, L442-L452
### Extract
| 资源 | 标识 | 关系 |
|---|---|---|
| Table | `table_id` | 归属 `app_token` |
| View | `view_id` | 归属 `table_id` |
获取建议:
- `table_id` 可通过列出数据表接口获取。
- `view_id` 可通过列出视图接口获取(部分场景不可直接从 doc block 得到)。

View File

@@ -0,0 +1,20 @@
## 数据表接口矩阵
- DDS-Section: A.12~A.17(更新/列出/新增/批量新增/删除/批量删除数据表)
- DDS-Lines: L2431-L4013
### Extract
| 方法 | 路径 | 说明 |
|---|---|---|
| PUT | `/bitable/v1/apps/{app_token}/tables/{table_id}` | 更新数据表 |
| GET | `/bitable/v1/apps/{app_token}/tables` | 列出数据表 |
| POST | `/bitable/v1/apps/{app_token}/tables` | 新增数据表 |
| POST | `/bitable/v1/apps/{app_token}/tables/batch_create` | 批量新增 |
| DELETE | `/bitable/v1/apps/{app_token}/tables/{table_id}` | 删除数据表 |
| POST | `/bitable/v1/apps/{app_token}/tables/batch_delete` | 批量删除 |
约束:
- 写接口常见 10 QPS读接口常见 20 QPS。
- 不支持并发写接口。

View File

@@ -0,0 +1,19 @@
## 视图接口矩阵
- DDS-Section: A.20~A.24(更新/检索/列出/新增/删除视图)
- DDS-Lines: L4700-L6501
### Extract
| 方法 | 路径 | 说明 |
|---|---|---|
| PUT | `/bitable/v1/apps/{app_token}/tables/{table_id}/views/{view_id}` | 更新视图 |
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/views/{view_id}` | 检索视图 |
| GET | `/bitable/v1/apps/{app_token}/tables/{table_id}/views` | 列出视图 |
| POST | `/bitable/v1/apps/{app_token}/tables/{table_id}/views` | 新增视图 |
| DELETE | `/bitable/v1/apps/{app_token}/tables/{table_id}/views/{view_id}` | 删除视图 |
约束:
- 写接口不建议并发。
- 出现计算超时类错误时可按文档建议重试。

View File

@@ -0,0 +1,13 @@
## 鉴权与权限
- DDS-Section: 接入指南-鉴权 / 各接口 security 定义
- DDS-Lines: L311-L371, L2458+, L5575+
### Extract
| 维度 | 说明 |
|---|---|
| token 类型 | `tenant_access_token` / `user_access_token` |
| 权限异常 | 无访问权限常见于高级权限配置未覆盖应用主体 |
| 额外要求 | 商店应用场景需关注 `tenant_key` |

View File

@@ -0,0 +1,15 @@
## 本地持久化结构
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未提供 Table/View 在业务系统落地时的数据库结构定义。
### 最小补充信息清单
1. 表视图缓存模型(主键、唯一键)
2. 软删除或硬删除策略
3. 变更审计字段与索引

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,114 @@
---
name: developing-feishu-bitable-system
description: 指导飞书多维表格系统级开发与跨模块一致性Guides system-level development for Feishu Bitable。包含模块边界、全局约束、鉴权模型、事件交付和变更路由。触发场景 Trigger: 跨模块改造、资源模型调整、接口策略统一、事件接入方案选择。关键词 Keywords: feishu, bitable, system, architecture, app, table, record, field, role, event, callback。
argument-hint: "<change-scope> <goal> - e.g., 'record+field add batch update', 'role enable advanced permission'"
allowed-tools:
- Read
- Glob
- Grep
- Bash
---
# Developing Feishu Bitable System
用于统一飞书多维表格的系统级决策:先判定变更影响域,再路由到对应模块 Skill并叠加横切约束契约、鉴权、并发、SDK
## Quick Context
```bash
# 扫描仓库中的 Bitable 资源路径与调用热点
!`rg -n "bitable/v1/apps|tables|views|records|fields|roles|members" -S . | head -n 80`
# 扫描鉴权与事件接入相关实现
!`rg -n "WithUserAccessToken|WithTenantAccessToken|WithTenantKey|EventDispatcher|verificationToken|eventEncryptKey" -S . | head -n 80`
```
## Architecture Overview
```text
Client/Job
-> App Metadata
-> Table/View
-> Record
-> Field
-> Role/Member (advanced permission)
-> Event/Callback Inbound
Cross-cut: Contracts / Auth / Write-Concurrency / Go SDK
```
## Module Registry
| 模块 | 职责 | 对应 Skill |
|---|---|---|
| App Metadata | app 元数据与高级权限开关 | `developing-bitable-app-metadata` |
| Table & View | 数据表与视图管理 | `developing-bitable-table-view` |
| Record | 记录 CRUD 与批量操作 | `developing-bitable-record` |
| Field | 字段定义、类型与属性变更 | `developing-bitable-field` |
| Role & Member | 高级权限角色与协作者 | `developing-bitable-role-member` |
| Event & Callback | 事件订阅、回调处理、3 秒响应约束 | `developing-bitable-event-callback` |
## Plan
### 产物清单
- [ ] 变更影响模块清单(主模块 + 受影响模块)
- [ ] 需要叠加的横切 Skill 清单
- [ ] 契约变更面(路径/字段/错误码/权限)
- [ ] 风险与回滚点(重点是写并发和幂等)
### 决策点
1. 变更是否跨越两个及以上资源模块?
2. 是否触及写接口(需执行串行化与幂等策略)?
3. 是否涉及高级权限开关或角色策略?
4. 是否需要事件/回调通道而非轮询读取?
## Verify
### 架构边界
- [ ] 变更只在目标模块内落地,跨模块依赖已在 `reference/01-architecture-overview/dependencies.md` 对齐
- [ ] 新增能力没有绕过 App/Table 资源层级
### 全局约束
- [ ] QPS 与批量上限已对齐 `reference/02-global-constraints/apis.md`
- [ ] 写接口并发冲突风险已评估并规避(串行或幂等键)
### 安全与权限
- [ ] token 模式与调用身份已对齐 `reference/03-auth-and-permissions/security-model.md`
- [ ] 高级权限开启前置条件与延迟影响已覆盖
### 事件与回调
- [ ] 事件订阅/回调路径满足 3 秒响应约束,见 `reference/04-event-delivery/events-topics.md`
## Execute
### 1. 识别变更类型
1. 标注主资源:`app/table/view/record/field/role/member/event`
2. 判断是否含写操作:`create/update/delete/batch_*`
3. 判断是否含权限变化:高级权限开关、角色或协作者变更。
### 2. 路由到模块 Skill
1. 单模块改动:直接进入对应模块 Skill。
2. 跨模块改动:按调用链顺序执行(上游契约先变更、下游消费后变更)。
3. 若涉及 SDK 接入方式或调用风格,叠加 `implementing-bitable-go-sdk`
### 3. 叠加横切校验
1. 契约:执行 `designing-bitable-contracts` 检查路径、参数、错误码。
2. 鉴权:执行 `implementing-bitable-auth-access` 检查 token 与权限。
3. 并发:执行 `managing-bitable-write-concurrency` 检查 QPS、幂等、重试。
### 4. 输出变更包
1. 给出变更影响矩阵(模块 × 接口 × 权限 × 风险)。
2. 明确灰度步骤与回滚触发条件。
## Pitfalls
1. **把 Role/Member 当作普通 CRUD 资源处理**:高级权限开关未完成时直接调用会失败,先查 `reference/03-auth-and-permissions/security-model.md`
2. **多写接口并发提交**:文档明确不支持并发写接口,需先查 `reference/02-global-constraints/apis.md`
3. **事件通道没有 3 秒响应保障**:会触发重推与重复消费,先查 `reference/04-event-delivery/events-topics.md`
4. **跨模块修改只改调用方不改契约源**:导致路径或字段漂移,先对照 `reference/01-architecture-overview/dependencies.md`
## Related References
- `reference/01-architecture-overview/dependencies.md`:资源层次、模块依赖、调用链
- `reference/02-global-constraints/apis.md`QPS、批量上限、写并发、幂等约束
- `reference/03-auth-and-permissions/security-model.md`token 模型与高级权限前置条件
- `reference/04-event-delivery/events-topics.md`:事件体、回调模式、重推约束

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,30 @@
## 资源分层与模块边界
- DDS-Section: A.1 概述 / A.2 接入指南(形态、内容)
- DDS-Lines: L111-L257, L272-L303
### Extract
| 层级 | 资源 | 核心标识 | 说明 |
|---|---|---|---|
| L1 | App | `app_token` | 多维表格应用根资源 |
| L2 | Table | `table_id` | App 下的数据容器 |
| L2 | Role | `role_id` | App 下高级权限角色 |
| L3 | View | `view_id` | Table 下视图 |
| L3 | Record | `record_id` | Table 下记录 |
| L3 | Field | `field_id` | Table 下字段 |
| L3 | Member | `member_id` | Role 下协作者 |
## 跨模块依赖链路
- DDS-Section: A.2 接入指南 / A.6 附件字段说明 / A.7 高级权限概述
- DDS-Lines: L302-L303, L1410-L1534, L1547-L1588
### Extract
| 源模块 | 目标模块 | 协议/接口 | 依赖说明 |
|---|---|---|---|
| Record | Drive Media | upload/download API | 附件字段依赖 file_token |
| RoleMember | Drive Permission | permission-member/create | 高级权限协作者建议同步文档权限 |
| EventCallback | 业务模块 | WebSocket/HTTP callback | 事件回调驱动记录或字段消费逻辑 |

View File

@@ -0,0 +1,38 @@
## 全局频控与批量约束
- DDS-Section: 接入指南-限制 / 各 OpenAPI Specification 描述
- DDS-Lines: L377-L384, L1954+, L2233+, L2635+, L8529+, L10629+
### Extract
| 规则 | 约束 |
|---|---|
| QPS | 多数读接口 20 QPS写接口常见 10 QPS以具体接口描述为准 |
| 批量记录操作 | 单次最多 500 条batch create/update/delete |
| 批量结果 | 接口语义为整体成功或失败,不提供部分成功结果 |
## 写并发与幂等
- DDS-Section: 接入指南注意事项 / 各接口错误码说明
- DDS-Lines: L309, L2067+, L3050+, L3339+, L9483+, L10847+, L12965+
### Extract
| 规则 | 说明 |
|---|---|
| 写并发 | 同一多维表格不建议并发调用写接口 |
| 幂等键 | 部分写接口支持 `client_token`,要求 UUID 格式 |
| 幂等冲突 | 发生冲突时需重生成幂等键再重试 |
## DB Schema
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未给出服务端数据库表结构、索引、迁移策略。若需要落地后端存储,请补充:
1. 业务表与映射表 DDL
2. 索引与唯一键策略
3. 迁移与回滚方案

View File

@@ -0,0 +1,27 @@
## token 鉴权模型
- DDS-Section: 接入指南-鉴权 / Golang SDK 调用说明
- DDS-Lines: L311-L371, L39-L41, L67-L69, L16503
### Extract
| 身份 | token | 说明 |
|---|---|---|
| 用户身份 | `user_access_token` | 用户态调用,刷新后旧 token 失效 |
| 应用身份 | `tenant_access_token` | 应用态调用SDK 可托管生命周期 |
| 商店应用补充 | `tenant_key` | ISV 场景调用需显式传入 |
## 高级权限模型
- DDS-Section: A.7 概述 / 更新多维表格元数据
- DDS-Lines: L1547-L1565, L2226-L2233, L2286-L2289
### Extract
| 规则 | 说明 |
|---|---|
| 开启前置 | 调用 Role/Member 接口前需先开启高级权限 |
| 延迟生效 | 开启后短时间可能返回 `OperationTypeError`,需重试 |
| 权限联动 | 高级权限协作者与云文档协作者不同,必要时同步文档权限 |
| 非原子更新 | 更新元数据接口先改名称再开关权限,可能部分成功 |

View File

@@ -0,0 +1,38 @@
## Bitable 事件定义
- DDS-Section: A.8 多维表格字段变更 / A.9 多维表格记录变更
- DDS-Lines: L1599-L1712, L1815-L1915
### Extract
| 事件类型 | 关键字段 |
|---|---|
| `drive.file.bitable_field_changed_v1` | `event_id`, `event_type`, `tenant_key`, `table_id`, `field_id` |
| `drive.file.bitable_record_changed_v1` | `event_id`, `event_type`, `tenant_key`, `table_id`, `record_id` |
## 回调交付约束
- DDS-Section: Golang SDK-处理事件 / 处理回调
- DDS-Lines: L89, L16853-L16873, L17196-L17263, L18209
### Extract
| 约束 | 说明 |
|---|---|
| 响应时限 | 接收端需在 3 秒内响应,否则会触发重推 |
| 长连接模式 | SDK 支持 WebSocket 长连接;集群模式非广播 |
| HTTP 回调模式 | 需公网地址,接收 POST 推送 |
| 验签/解密 | 若启用加密策略需提供 `verificationToken``eventEncryptKey` |
## 消费幂等
- DDS-Section: 事件与回调说明
- DDS-Lines: L1706, L1846, L18209
### Extract
| 建议 | 原因 |
|---|---|
| 以 `event_id` 做去重 | 重推场景下避免重复处理 |
| 记录 `tenant_key + resource_id` | 多租户隔离与重放定位 |

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,84 @@
---
name: implementing-bitable-auth-access
description: 指导飞书多维表格鉴权与访问控制实现Guides authentication and access control for Feishu Bitable。包含 tenant/user token 选择、商店应用 tenant_key、高级权限开关与协作者联动。触发场景 Trigger: 鉴权失败排查、身份模式切换、权限策略改造、Role/Member 接口不可用问题。关键词 Keywords: auth, access, tenant_access_token, user_access_token, tenant_key, advanced permission。
argument-hint: "<auth-scope> <scenario> - e.g., 'tenant token app-level api', 'role-member no-permission'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Implementing Bitable Auth & Access
用于统一 Bitable 的鉴权和权限控制实现确保调用身份、token 生命周期和高级权限策略在跨模块场景下保持一致。
## Quick Context
```bash
# 扫描 token 与身份调用代码
!`rg -n "WithUserAccessToken|WithTenantAccessToken|WithTenantKey|tenant_access_token|user_access_token" -S . | head -n 120`
# 扫描高级权限开关与无权限处理
!`rg -n "is_advanced|OperationTypeError|无访问权限|roles|members" -S . | head -n 120`
```
## Plan
### 产物清单
- [ ] 身份模式选择矩阵user/tenant
- [ ] token 获取、刷新、缓存策略
- [ ] 高级权限前置校验
- [ ] 权限异常处理流程
### 决策点
1. 场景是否必须用户身份user token
2. 是否为商店应用,需要补 `tenant_key`
3. 是否涉及高级权限域Role/Member
## Verify
### token 模型
- [ ] token 类型选择与接口权限匹配,见 `reference/01-token-model/security-model.md`
- [ ] user token 刷新后旧 token 失效逻辑已处理
### 高级权限
- [ ] app 高级权限开关前置检查已落地,见 `reference/02-advanced-permission/security-model.md`
- [ ] Role/Member 与文档协作者权限联动策略已覆盖
### SDK 选项
- [ ] SDK 请求级 auth 选项使用正确,见 `reference/03-sdk-auth-options/dependencies.md`
## Execute
### 1. 身份选择
1. 先按接口需求判断 user/tenant 身份。
2. 商店应用场景补传 `tenant_key`
### 2. token 生命周期
1. tenant token 优先使用 SDK 托管缓存。
2. user token 自建刷新与失效处理。
### 3. 权限联动
1. 高级权限接口前确认 `is_advanced=true`
2. 必要时同步云文档权限协作者。
### 4. 错误收敛
1. 将鉴权失败细分为身份不匹配、token 失效、权限未开通。
2. 每类返回明确修复动作。
## Pitfalls
1. **把 user token 当 tenant token 用**:调用语义错误,见 `reference/01-token-model/security-model.md`
2. **商店应用漏传 tenant_key**:跨租户调用失败,见 `reference/03-sdk-auth-options/dependencies.md`
3. **高级权限未开启就调角色接口**:直接失败,见 `reference/02-advanced-permission/security-model.md`
4. **只配高级权限不配文档权限**:用户仍无访问权,见 `reference/02-advanced-permission/security-model.md`
## Related References
- `reference/01-token-model/security-model.md`
- `reference/02-advanced-permission/security-model.md`
- `reference/03-sdk-auth-options/dependencies.md`
- `reference/04-session-lifecycle/state-machine.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,17 @@
## 身份与 token 模型
- DDS-Section: 接入指南-鉴权
- DDS-Lines: L311-L371
### Extract
| 身份 | token | 语义 |
|---|---|---|
| 用户身份 | `user_access_token` | 刷新后旧 token 失效 |
| 应用身份 | `tenant_access_token` | 有效期 2 小时,可刷新并短时新旧并存 |
## token 选择建议
- 用户行为归因场景优先 user token。
- 平台级批处理场景优先 tenant token。

View File

@@ -0,0 +1,13 @@
## 高级权限访问模型
- DDS-Section: A.7 概述 / A.11 更新元数据
- DDS-Lines: L1547-L1561, L2229-L2233, L2286-L2289
### Extract
| 规则 | 说明 |
|---|---|
| 前置开关 | `is_advanced` 未开启时 Role/Member 不可用 |
| 生效延迟 | 开启后可能短时返回 `OperationTypeError` |
| 权限联动 | 高级权限协作者与文档协作者是两套体系 |

View File

@@ -0,0 +1,16 @@
## SDK 鉴权选项
- DDS-Section: 3.3 请求级选项 / B.3 调用服务端 API
- DDS-Lines: L65-L72, L16706-L16722
### Extract
| 选项 | 用途 |
|---|---|
| `WithUserAccessToken(...)` | 用户态调用 |
| `WithTenantAccessToken(...)` | 手动传租户 token |
| `WithTenantKey(...)` | 商店应用指定租户 |
| `WithHeaders(...)` | 透传自定义头 |
SDK 可托管 tenant token 生命周期,但不托管 user token 生命周期。

View File

@@ -0,0 +1,15 @@
## 会话/令牌状态机
- DDS-Section: 全文
- DDS-Lines: L1-L18211
### Extract
[TBD] 文档未给出统一的 token 状态机与失效传播规范。
### 最小补充信息清单
1. user token 刷新触发条件与缓存失效策略
2. tenant token 刷新提前量与失败降级策略
3. 多实例 token 缓存一致性策略

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,86 @@
---
name: implementing-bitable-go-sdk
description: 指导飞书多维表格 Go SDK 集成实现Guides Feishu Bitable integration with Go SDK。包含 Client 初始化、强类型 API 调用、raw API 兜底、事件/回调接入与请求级选项。触发场景 Trigger: 新接入 Go SDK、补齐未封装 API、接入事件回调、排查 SDK 调用失败。关键词 Keywords: go sdk, larksuite, bitable v1, client.Do, dispatcher, websocket。
argument-hint: "<task> <api-or-scenario> - e.g., 'typed call list records', 'raw call unsupported api', 'event websocket setup'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Implementing Bitable Go SDK
用于建立 Go SDK 的统一调用与接入模式覆盖初始化、API 访问、事件回调与未封装接口兜底。
## Quick Context
```bash
# 扫描 Go SDK 初始化与调用方式
!`rg -n "lark.NewClient|client\.Bitable\.V1|client\.Do\(|WithUserAccessToken|WithTenantKey" -S . | head -n 150`
# 扫描事件回调 dispatcher 与路由
!`rg -n "dispatcher\.NewEventDispatcher|OnP2|OnCustomizedEvent|webhook/event" -S . | head -n 150`
```
## Plan
### 产物清单
- [ ] SDK Client 初始化模板
- [ ] 强类型 API 调用模板
- [ ] Raw API 调用兜底模板
- [ ] 事件/回调接入模板
### 决策点
1. 目标接口是否有 SDK 强类型方法?
2. 身份调用是否需要 user token 或 tenant key
3. 事件接入选长连接还是 HTTP 回调?
## Verify
### SDK 初始化
- [ ] 初始化参数与缓存策略对齐 `reference/01-sdk-bootstrap/dependencies.md`
- [ ] 请求级选项使用正确
### API 调用策略
- [ ] 强类型调用优先,兜底 raw 调用,见 `reference/02-typed-vs-raw/apis.md`
- [ ] 失败分支统一输出 code/msg/logid
### 事件回调
- [ ] dispatcher 注册方式与模式选择对齐 `reference/03-event-callback/events-topics.md`
- [ ] 验签解密参数对齐 `reference/04-auth/security-model.md`
## Execute
### 1. 初始化 Client
1. 使用 `lark.NewClient(appId, appSecret, ...)`
2. 按场景配置 token cache、tenant key、请求超时。
### 2. 强类型 API 调用
1. 使用 `client.Bitable.V1.<Resource>.<Action>`
2. 统一封装 `resp.Success()` 与错误输出。
### 3. Raw API 兜底
1. 对未封装或历史版本接口使用 `client.Do()`
2. 明确 HTTP 方法、路径、path/query/body 参数。
### 4. 事件与回调
1. 使用 dispatcher 注册处理器。
2. 根据接入模式挂接 ws 或 HTTP handler。
3. 控制 3 秒内应答并落地幂等去重。
## Pitfalls
1. **所有接口都走 raw 调用**:失去类型安全与可维护性,见 `reference/02-typed-vs-raw/apis.md`
2. **user/tenant token 混用**:会触发权限异常,见 `reference/04-auth/security-model.md`
3. **事件处理器注册版本不匹配**:导致解析失败,见 `reference/03-event-callback/events-topics.md`
4. **忽略 logid 输出**:线上排障困难,见 `reference/02-typed-vs-raw/apis.md`
## Related References
- `reference/01-sdk-bootstrap/dependencies.md`
- `reference/02-typed-vs-raw/apis.md`
- `reference/03-event-callback/events-topics.md`
- `reference/04-auth/security-model.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,15 @@
## SDK 初始化与能力
- DDS-Section: 3.1 安装与初始化 / B.1 服务端 SDK / B.2 开发前准备
- DDS-Lines: L25-L41, L16479-L16520, L16572-L16580
### Extract
| 项 | 内容 |
|---|---|
| 安装 | `go get -u github.com/larksuite/oapi-sdk-go/v3@latest` |
| 初始化 | `lark.NewClient(appID, appSecret, ...)` |
| token 托管 | SDK 可托管 tenant token 生命周期 |
扩展:支持自定义缓存、超时与 HTTP Client。

View File

@@ -0,0 +1,14 @@
## 强类型调用与 raw 兜底
- DDS-Section: 3.2 Bitable v1 调用范式 / 3.4 历史或未封装 API 原生调用 / B.3 调用服务端 API
- DDS-Lines: L43-L63, L72-L84, L16594-L16820
### Extract
| 模式 | 方式 | 适用 |
|---|---|---|
| 强类型 | `client.Bitable.V1.<Resource>.<Action>` | 常规已封装接口 |
| Raw | `client.Do(context, ApiReq{...})` | 历史版本/未封装接口 |
统一错误处理建议:输出 `code + msg + request/log id`

View File

@@ -0,0 +1,15 @@
## SDK 事件与回调接入
- DDS-Section: 3.5 事件与回调处理 / B.4 / B.5
- DDS-Lines: L86-L92, L16843-L17397
### Extract
| 能力 | 说明 |
|---|---|
| Event Dispatcher | `dispatcher.NewEventDispatcher` 注册事件处理器 |
| 长连接模式 | SDK 提供 WebSocket 通道接收事件/回调 |
| HTTP 模式 | 可集成原生 HTTP/Gin/Hertz |
注意:长连接是集群分发,不是广播。

View File

@@ -0,0 +1,14 @@
## SDK 鉴权选项
- DDS-Section: 3.3 请求级选项 / B.3 请求选项
- DDS-Lines: L65-L72, L16706-L16722
### Extract
| 选项 | 用途 |
|---|---|
| `WithUserAccessToken` | 用户态请求 |
| `WithTenantAccessToken` | 应用态请求 |
| `WithTenantKey` | 商店应用租户隔离 |
| `WithHeaders` | 自定义请求头 |

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,85 @@
---
name: managing-bitable-write-concurrency
description: 指导飞书多维表格写并发与可靠性治理Guides write concurrency and reliability management for Feishu Bitable。包含 QPS 限制、同资源串行写、幂等键策略、超时重试与失败回退。触发场景 Trigger: 批量写入优化、并发写冲突排查、重试机制设计、高频任务稳定性治理。关键词 Keywords: concurrency, qps, idempotency, client_token, retry, batch, bitable。
argument-hint: "<write-path> <load-profile> - e.g., 'records/batch_update high-throughput', 'fields/update conflict-fix'"
allowed-tools:
- Read
- Write
- Edit
- Glob
- Grep
- Bash
---
# Managing Bitable Write Concurrency
用于治理 Bitable 写路径的稳定性:限流、串行化、幂等、重试和回滚,避免在高频调用时出现冲突或重复写。
## Quick Context
```bash
# 扫描写接口与批量调用点
!`rg -n "create|update|delete|batch_create|batch_update|batch_delete|client_token" -S . | head -n 150`
# 扫描并发冲突、频控、重试逻辑
!`rg -n "QPS|并发|OperationTypeError|retry|timeout|backoff|idempot" -S . | head -n 150`
```
## Plan
### 产物清单
- [ ] 写接口分类与容量画像
- [ ] 串行化/限流策略
- [ ] 幂等键策略
- [ ] 重试与失败回退方案
### 决策点
1. 哪些接口必须串行(同 app/table
2. 哪些接口必须携带 `client_token`
3. 哪些错误可重试、重试上限是多少?
## Verify
### 限流与容量
- [ ] QPS 与批量上限对齐 `reference/01-qps-limits/apis.md`
- [ ] 批量接口单次容量未超过文档约束
### 冲突与幂等
- [ ] 同资源写请求串行化,见 `reference/02-write-conflict/state-machine.md`
- [ ] 幂等键规则与冲突处理对齐 `reference/03-idempotency/apis.md`
### 重试策略
- [ ] 重试只用于可恢复错误,见 `reference/04-retry-policy/dependencies.md`
- [ ] 不可恢复错误直接失败并回传定位信息
## Execute
### 1. 写路径分层
1. 按资源拆分写队列app/table/record/field/role。
2. 同队列内串行,跨队列受全局限流控制。
### 2. 幂等落地
1. 对支持幂等的接口统一生成 UUID `client_token`
2. 幂等冲突时不盲目重放,先刷新 token 再重试。
### 3. 重试与退避
1. 可重试错误采用指数退避 + 上限次数。
2. 达到上限后进入人工排查通道。
### 4. 观测与告警
1. 记录冲突率、重试率、最终失败率。
2. 关键阈值越界时触发告警。
## Pitfalls
1. **把所有写接口并发放开**:同资源并发写会触发冲突,见 `reference/02-write-conflict/state-machine.md`
2. **幂等键格式不规范**:非 UUID 会直接失败,见 `reference/03-idempotency/apis.md`
3. **把不可恢复错误也重试**:放大故障面,见 `reference/04-retry-policy/dependencies.md`
4. **批量写超限**:单次超过 500 条会失败,见 `reference/01-qps-limits/apis.md`
## Related References
- `reference/01-qps-limits/apis.md`
- `reference/02-write-conflict/state-machine.md`
- `reference/03-idempotency/apis.md`
- `reference/04-retry-policy/dependencies.md`

View File

@@ -0,0 +1,4 @@
# Skeleton Example
- 仅提供调用骨架,不包含完整业务实现。
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。

View File

@@ -0,0 +1,15 @@
## 接口频控与批量上限
- DDS-Section: 接入指南-限制 / 各 OpenAPI 描述
- DDS-Lines: L377-L384, L1954+, L2233+, L8529+, L10629+, L11217+
### Extract
| 维度 | 约束 |
|---|---|
| 读接口 QPS | 常见 20 QPS |
| 写接口 QPS | 常见 10 QPS |
| 批量写 | 单次最多 500 条记录 |
说明:具体值以目标接口章节为准。

View File

@@ -0,0 +1,15 @@
## 写冲突状态流
- DDS-Section: 接入指南注意事项 / 各 API 错误码
- DDS-Lines: L309, L2067+, L3050+, L3339+
### Extract
```text
READY -> WRITE_REQUEST
WRITE_REQUEST + parallel_same_resource -> CONFLICT(OperationTypeError)
WRITE_REQUEST + serialized -> SUCCESS
```
策略:同 app更细可到 table写请求串行化。

View File

@@ -0,0 +1,15 @@
## 幂等键规则
- DDS-Section: 记录/字段批量写相关错误码与参数
- DDS-Lines: L9343, L9483, L10707, L10847, L10893, L12777, L12965, L13005
### Extract
| 规则 | 说明 |
|---|---|
| key 字段 | `client_token` |
| 格式 | 标准 UUID |
| 冲突处理 | 重新生成幂等键后重试 |
不满足格式会返回幂等键格式错误。

View File

@@ -0,0 +1,14 @@
## 重试策略依赖
- DDS-Section: 接口错误说明 / 事件重推说明
- DDS-Lines: L3654, L3913, L4896, L5356, L5734, L6058, L18209
### Extract
| 错误类型 | 建议 |
|---|---|
| 服务器计算超时 | 可有限重试 |
| 并发写冲突 | 串行化后重试 |
| 参数/权限错误 | 不重试,直接修正 |
| 回调超时重推 | 消费端按 event_id 去重 |

View File

@@ -0,0 +1,47 @@
#!/bin/bash
# verify.sh - skill structure/content quick validation
set -e
PASS=0
FAIL=0
pass() {
echo "PASS: $1"
PASS=$((PASS+1))
}
fail() {
echo "FAIL: $1"
FAIL=$((FAIL+1))
}
check_cmd() {
local desc="$1"
shift
if "$@"; then
pass "$desc"
else
fail "$desc"
fi
}
SKILL_DIR="$(cd "$(dirname "$0")/.." && pwd)"
check_cmd "SKILL.md exists" test -f "$SKILL_DIR/SKILL.md"
check_cmd "reference dir exists" test -d "$SKILL_DIR/reference"
line_count=$(wc -l < "$SKILL_DIR/SKILL.md")
if [ "$line_count" -lt 500 ]; then
pass "SKILL.md < 500 lines"
else
fail "SKILL.md < 500 lines"
fi
check_cmd "has Plan" grep -q '^## Plan' "$SKILL_DIR/SKILL.md"
check_cmd "has Verify" grep -q '^## Verify' "$SKILL_DIR/SKILL.md"
check_cmd "has Execute" grep -q '^## Execute' "$SKILL_DIR/SKILL.md"
check_cmd "has Pitfalls" grep -q '^## Pitfalls' "$SKILL_DIR/SKILL.md"
check_cmd "reference has DDS-Section" grep -rq 'DDS-Section:' "$SKILL_DIR/reference"
check_cmd "reference has DDS-Lines" grep -rq 'DDS-Lines:' "$SKILL_DIR/reference"
echo "RESULT: $PASS PASS / $FAIL FAIL"
[ $FAIL -eq 0 ]

View File

@@ -0,0 +1,134 @@
[
{
"Skill": "designing-bitable-contracts",
"Lines": 79,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 4,
"DDSLines": 4
},
{
"Skill": "developing-bitable-app-metadata",
"Lines": 82,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 7,
"DDSLines": 7
},
{
"Skill": "developing-bitable-event-callback",
"Lines": 87,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 6,
"DDSLines": 6
},
{
"Skill": "developing-bitable-field",
"Lines": 86,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 8,
"DDSLines": 8
},
{
"Skill": "developing-bitable-record",
"Lines": 88,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 8,
"DDSLines": 8
},
{
"Skill": "developing-bitable-role-member",
"Lines": 86,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 6,
"DDSLines": 6
},
{
"Skill": "developing-bitable-table-view",
"Lines": 83,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 5,
"DDSLines": 5
},
{
"Skill": "developing-feishu-bitable-system",
"Lines": 114,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 10,
"DDSLines": 10
},
{
"Skill": "implementing-bitable-auth-access",
"Lines": 84,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 4,
"DDSLines": 4
},
{
"Skill": "implementing-bitable-go-sdk",
"Lines": 86,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 4,
"DDSLines": 4
},
{
"Skill": "managing-bitable-write-concurrency",
"Lines": 85,
"VerifyExists": true,
"HasPlan": true,
"HasVerify": true,
"HasExecute": true,
"HasPitfalls": true,
"CmdCount": 2,
"DDSSection": 4,
"DDSLines": 4
}
]

View File

@@ -0,0 +1,220 @@
---
name: developing-feishu-bitable
description: >
飞书多维表格BitableGo SDK 全栈开发指导 SkillFull-stack development guide for Feishu/Lark Bitable using Go SDK。涵盖 oapi-sdk-go 初始化、App/Table/View/Record/Field CRUD API、字段类型与数据结构、记录筛选 Filter 语法、事件订阅(字段变更/记录变更)、长连接/Webhook 事件处理、高级权限Role/Member管理。触发场景 Trigger: 使用 Go 开发飞书多维表格集成时、调用 Bitable API 时、处理多维表格事件回调时、操作字段/记录/视图时。关键词 Keywords: feishu, lark, bitable, 多维表格, Go SDK, oapi-sdk-go, app_token, table_id, record, field, view, filter, event, webhook, 长连接, tenant_access_token, 高级权限。
argument-hint: "<action> <target> - e.g., 'create record', 'list fields', 'subscribe event', 'filter records'"
allowed-tools:
- Read
- Glob
- Grep
---
# Developing Feishu Bitable
本 Skill 是飞书多维表格BitableGo 后端开发的唯一全栈向导。基于 `oapi-sdk-go/v3` SDK覆盖
App/Table/View/Record/Field/Role/Member 全部资源的 CRUD API、字段类型系统、记录筛选公式、
事件订阅(字段变更 + 记录变更)、长连接/HTTP 回调模式、以及高级权限管理。
**核心技术栈**Go ≥ 1.18 · `github.com/larksuite/oapi-sdk-go/v3` · REST API · WebSocket 长连接
## Quick Context
```bash
# 动态注入:查看项目中已有的 Bitable 相关代码
!`grep -rn "larkbitable\|Bitable\|bitable\|AppTableRecord\|AppTableField" --include="*.go" . | head -30`
# 动态注入:查看 SDK 依赖版本
!`grep "oapi-sdk-go" go.mod 2>/dev/null || echo "SDK not in go.mod"`
# 动态注入:查看事件处理相关代码
!`grep -rn "EventDispatcher\|OnP2\|larkws\|webhook" --include="*.go" . | head -20`
```
## Plan
### 产物清单
- [ ] SDK Client 初始化模块(含 token 缓存配置)
- [ ] Bitable CRUD 服务层Record/Field/View/Table 操作封装)
- [ ] 字段类型映射与序列化/反序列化逻辑
- [ ] 记录筛选 Filter 构建器
- [ ] 事件订阅处理器(记录变更 / 字段变更)
- [ ] 高级权限管理Role/Member如需要
### 决策点
1. **凭证模式选择**`tenant_access_token`纯后端服务vs `user_access_token`代用户操作。SDK 自动管理 tenant token 的获取与缓存,但不管理 user token。
2. **事件接收模式**推荐长连接WebSocket免内网穿透、无需解密验签生产集群部署时注意集群模式为随机分发而非广播。
3. **批量操作策略**:批量 APIbatch_create/batch_update/batch_delete单次限 500 条,结果全成功或全失败,无部分结果。
## Verify
> 验证按 SDK 初始化 → API 调用 → 字段处理 → 事件 → 权限 顺序。
### SDK 初始化验证
- [ ] `lark.NewClient(appID, appSecret)` 中 appID/appSecret 来自开发者后台,不硬编码
- [ ] `lark.WithEnableTokenCache(true)` 已启用(默认开启)
- [ ] 商店应用调用时附带 `larkcore.WithTenantKey(tenantKey)`
### API 调用验证
- [ ] 所有 API 调用统一使用 Builder 模式:`NewXxxReqBuilder().XxxField(val).Build()`
- [ ] 分页查询使用 `PageToken` + `PageSize`(单次最多 500 条记录)
- [ ] API 路径中的 `app_token``table_id` 来自配置或动态获取,不硬编码
- [ ] 响应处理先检查 `resp.Success()`,失败时记录 `resp.Code``resp.Msg``resp.RequestId()`
### 字段类型验证
- [ ] 字段 type 枚举与 `reference/02-data-model/field-types.md` 一致1=文本, 2=数字, 3=单选...
- [ ] 写入日期字段使用 **毫秒级 Unix 时间戳**number
- [ ] 写入人员字段使用 `[{"id": "ou_xxx"}]` 格式
- [ ] 写入单向关联字段使用 `["record_id_1", "record_id_2"]` 格式
- [ ] 数字字段写入用 number 类型,读取时返回 string需类型转换
### 事件处理验证
- [ ] 长连接 `dispatcher.NewEventDispatcher("", "")` 两个参数填**空字符串**
- [ ] HTTP 回调模式需传入正确的 `verificationToken``eventEncryptKey`
- [ ] 事件处理在 **3 秒内**完成,否则触发超时重推
- [ ] 记录变更事件类型与 `reference/04-events/record-changed.md` 中 action 枚举一致
- [ ] 公式字段值变化**不会触发**记录变更事件
### 权限验证
- [ ] 使用 `tenant_access_token` 前,确认应用已加入目标多维表格的协作者列表
- [ ] 高级权限 API 调用前,确认多维表格已开启高级权限
## Execute
> 开发顺序SDK 初始化 → CRUD API → 字段处理 → 筛选 → 事件 → 权限
### Step 1SDK Client 初始化
```go
import lark "github.com/larksuite/oapi-sdk-go/v3"
client := lark.NewClient(appID, appSecret,
lark.WithEnableTokenCache(true), // 自动管理 tenant_access_token
)
// 方法映射client.Bitable.V1.<资源>.<动作>(ctx, req)
```
**关键约束**
- SDK **仅**托管 `tenant_access_token``user_access_token` 需自行获取与刷新
- 未封装的 API 可用 `client.Do()` 原生调用(参考 `reference/01-sdk-guide/raw-api-call.md`
### Step 2Record CRUD 实现
`reference/03-api-reference/record-apis.md` 中的 OpenAPI Spec 实现:
```go
// 列出记录(分页)
req := larkbitable.NewListAppTableRecordReqBuilder().
AppToken(appToken).TableId(tableId).
PageSize(500). // 单次最多 500
Filter(filterExpression). // 可选筛选
Build()
resp, err := client.Bitable.V1.AppTableRecord.List(ctx, req)
// 新增记录
fields := map[string]interface{}{
"文本字段": "value",
"数字字段": 42, // 写入用 number
"日期字段": 1672531200000, // 毫秒 Unix 时间戳
"人员字段": []map[string]string{{"id": "ou_xxx"}},
"单选字段": "选项A",
"多选字段": []string{"选项A", "选项B"},
}
```
### Step 3字段类型处理
根据 `reference/02-data-model/field-types.md``reference/02-data-model/field-edit-guide.md`
| type 值 | 类型 | 写入格式 | 读取格式 |
|---------|------|---------|---------|
| 1 | 多行文本 | `string` | `string` |
| 2 | 数字 | `number` | `string`(需转换!) |
| 3 | 单选 | `string`(选项名) | `string` |
| 4 | 多选 | `[]string` | `[]string` |
| 5 | 日期 | `number`(ms timestamp) | `number` |
| 7 | 复选框 | `boolean` | `boolean` |
| 11 | 人员 | `[{"id":"ou_xxx"}]` | `[{name,id,email}]` |
| 17 | 附件 | 需先上传获取 file_token | `[{file_token,name,url}]` |
| 18 | 单向关联 | `["record_id"]` | `["record_id"]` |
**注意**:双向关联(21)、公式(20)、查找引用(19) **不支持写入**
### Step 4记录筛选 Filter 构建
根据 `reference/03-api-reference/filter-guide.md`
```
// 语法CurrentValue.[字段名] 运算符 值
filter := `CurrentValue.[状态] = "进行中"`
filter := `AND(CurrentValue.[类型]="需求", CurrentValue.[优先级]>=3)`
filter := `CurrentValue.[创建时间] > TODAY()`
// ⚠️ URL 编码注意:+ 号须编码为 %2B
```
### Step 5事件订阅处理
根据 `reference/04-events/record-changed.md``reference/01-sdk-guide/event-handling.md`
```go
// 推荐:长连接模式(免内网穿透)
eventHandler := dispatcher.NewEventDispatcher("", ""). // 必须空串
OnCustomizedEvent("drive.file.bitable_record_changed_v1",
func(ctx context.Context, event *larkevent.EventReq) error {
// event.Body 中 action_list 包含变更详情
// action: record_added / record_edited / record_deleted
return nil // 3 秒内返回
})
cli := larkws.NewClient(appID, appSecret,
larkws.WithEventHandler(eventHandler),
larkws.WithLogLevel(larkcore.LogLevelDebug),
)
cli.Start(ctx) // 阻塞主线程
```
### Step 6高级权限管理如适用
根据 `reference/05-permissions/role-member-apis.md`
1. 先通过更新元数据 API 开启高级权限
2. 创建自定义角色(`AppRole.Create`
3. 为角色添加协作者(`AppRoleMember.Create`
4. 开启后可能有延迟,遇到 `OperationTypeError` 需重试
## Pitfalls
1. **数字字段读写类型不一致**:写入时是 `number`,但列出记录时返回 `string`,需做类型转换。忽略此差异会导致 JSON 反序列化失败。(参考 `reference/02-data-model/field-types.md`
2. **日期字段用毫秒而非秒**:日期字段的 value 是**毫秒级** Unix 时间戳。若误传秒级时间戳,结果日期将是 1970 年附近。(参考 `reference/02-data-model/field-types.md`
3. **长连接 EventDispatcher 参数必须为空串**:使用长连接模式时 `dispatcher.NewEventDispatcher("", "")`传非空值会导致签名验证失败。HTTP 回调模式才需要传 verificationToken 和 encryptKey。参考 `reference/01-sdk-guide/event-handling.md`
4. **批量操作全成功或全失败**`batch_create`/`batch_update`/`batch_delete` 不存在部分成功的结果。若其中一条记录有问题,整个请求全部失败。(参考 `reference/03-api-reference/record-apis.md`
5. **公式字段变化不触发事件**:记录变更事件 `drive.file.bitable_record_changed_v1` 不会因公式字段值的变化而触发。如果业务依赖公式结果变化,需通过轮询方式检测。(参考 `reference/04-events/record-changed.md`
6. **同一时刻对同一多维表格仅一次写操作**:并发写入同一张多维表格可能导致数据冲突。需用串行写入队列或乐观锁策略。(参考 `reference/03-api-reference/record-apis.md`
7. **字段更新是全量覆盖**:更新字段的 `property` 是全量替换而非增量 merge。更新单选/多选字段时必须传入完整的 options 列表,否则未传入的选项会被删除。(参考 `reference/02-data-model/field-edit-guide.md`
8. **Filter 中 + 号须 URL 编码为 %2B**:在日期表达式如 `TODAY()+1` 中,`+` 需编码为 `%2B`,否则服务端会将其解析为空格。(参考 `reference/03-api-reference/filter-guide.md`
## Related References
| 需要了解... | 查阅... |
|------------|--------|
| Go SDK 安装初始化、Client 构建 | `reference/01-sdk-guide/sdk-setup.md` |
| 原生调用未封装 API | `reference/01-sdk-guide/raw-api-call.md` |
| 事件/回调处理(长连接+HTTP | `reference/01-sdk-guide/event-handling.md` |
| Bitable 资源模型App/Table/View/Record/Field | `reference/02-data-model/resource-model.md` |
| 字段类型系统type 枚举、读写格式) | `reference/02-data-model/field-types.md` |
| 字段 Property 编辑指南 | `reference/02-data-model/field-edit-guide.md` |
| Record CRUD API含 OpenAPI Spec | `reference/03-api-reference/record-apis.md` |
| Table/View/Field/App API | `reference/03-api-reference/table-view-field-apis.md` |
| 记录筛选 Filter 公式语法 | `reference/03-api-reference/filter-guide.md` |
| 附件上传/下载 | `reference/03-api-reference/attachment.md` |
| 记录变更事件action + 回调体) | `reference/04-events/record-changed.md` |
| 字段变更事件 | `reference/04-events/field-changed.md` |
| 高级权限 Role/Member API | `reference/05-permissions/role-member-apis.md` |
| 鉴权模式与 QPS 限制 | `reference/05-permissions/auth-and-limits.md` |
| SDK 常见问题与排错 | `reference/06-faq/common-issues.md` |

View File

@@ -0,0 +1,129 @@
// bitable_client.go - 飞书多维表格 SDK Client 初始化骨架
//
// 设计决策:
// 1. 使用 tenant_access_token纯后端服务场景
// 2. 启用 token 缓存SDK 自动管理 token 生命周期)
// 3. 通过环境变量注入 App ID/Secret避免硬编码
package bitable
import (
"context"
"fmt"
"os"
lark "github.com/larksuite/oapi-sdk-go/v3"
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
larkbitable "github.com/larksuite/oapi-sdk-go/v3/service/bitable/v1"
)
// Config 存储多维表格连接配置
type Config struct {
AppID string // 飞书应用 App ID
AppSecret string // 飞书应用 App Secret
AppToken string // 目标多维表格的 app_token
TableID string // 目标数据表的 table_id
}
// Client 封装飞书 Bitable SDK Client
type Client struct {
larkClient *lark.Client
config Config
}
// NewClient 创建 Bitable Client
func NewClient(cfg Config) *Client {
client := lark.NewClient(cfg.AppID, cfg.AppSecret,
lark.WithEnableTokenCache(true), // 自动管理 tenant_access_token
)
return &Client{
larkClient: client,
config: cfg,
}
}
// NewClientFromEnv 从环境变量创建 Client
func NewClientFromEnv() *Client {
return NewClient(Config{
AppID: os.Getenv("FEISHU_APP_ID"),
AppSecret: os.Getenv("FEISHU_APP_SECRET"),
AppToken: os.Getenv("FEISHU_APP_TOKEN"),
TableID: os.Getenv("FEISHU_TABLE_ID"),
})
}
// ListRecords 列出记录(带分页)
// filter: 筛选表达式,如 `CurrentValue.[状态]="进行中"`
func (c *Client) ListRecords(ctx context.Context, filter string, pageToken string) ([]*larkbitable.AppTableRecord, string, error) {
builder := larkbitable.NewListAppTableRecordReqBuilder().
AppToken(c.config.AppToken).
TableId(c.config.TableID).
PageSize(500) // 单次最多 500
if filter != "" {
builder.Filter(filter)
}
if pageToken != "" {
builder.PageToken(pageToken)
}
resp, err := c.larkClient.Bitable.V1.AppTableRecord.List(ctx, builder.Build())
if err != nil {
return nil, "", fmt.Errorf("list records: %w", err)
}
if !resp.Success() {
return nil, "", fmt.Errorf("list records failed: code=%d msg=%s requestId=%s",
resp.Code, resp.Msg, resp.RequestId())
}
nextPageToken := ""
if resp.Data.PageToken != nil {
nextPageToken = *resp.Data.PageToken
}
return resp.Data.Items, nextPageToken, nil
}
// CreateRecord 新增单条记录
// fields: map[string]interface{} 字段名 -> 值
func (c *Client) CreateRecord(ctx context.Context, fields map[string]interface{}) (string, error) {
req := larkbitable.NewCreateAppTableRecordReqBuilder().
AppToken(c.config.AppToken).
TableId(c.config.TableID).
AppTableRecord(larkbitable.NewAppTableRecordBuilder().
Fields(fields).
Build()).
Build()
resp, err := c.larkClient.Bitable.V1.AppTableRecord.Create(ctx, req)
if err != nil {
return "", fmt.Errorf("create record: %w", err)
}
if !resp.Success() {
return "", fmt.Errorf("create record failed: code=%d msg=%s requestId=%s",
resp.Code, resp.Msg, resp.RequestId())
}
return *resp.Data.Record.RecordId, nil
}
// UpdateRecord 更新单条记录
// fields: 需要更新的字段(增量更新)
func (c *Client) UpdateRecord(ctx context.Context, recordID string, fields map[string]interface{}) error {
req := larkbitable.NewUpdateAppTableRecordReqBuilder().
AppToken(c.config.AppToken).
TableId(c.config.TableID).
RecordId(recordID).
AppTableRecord(larkbitable.NewAppTableRecordBuilder().
Fields(fields).
Build()).
Build()
resp, err := c.larkClient.Bitable.V1.AppTableRecord.Update(ctx, req)
if err != nil {
return fmt.Errorf("update record: %w", err)
}
if !resp.Success() {
return fmt.Errorf("update record failed: code=%d msg=%s requestId=%s",
resp.Code, resp.Msg, resp.RequestId())
}
return nil
}

View File

@@ -0,0 +1,87 @@
// event_handler.go - 飞书多维表格事件处理骨架
//
// 设计决策:
// 1. 使用长连接模式(推荐):免内网穿透、无需解密验签
// 2. EventDispatcher 参数必须为空字符串(长连接要求)
// 3. 事件处理需在 3 秒内完成,耗时操作应异步化
package bitable
import (
"context"
"encoding/json"
"fmt"
"log"
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
larkevent "github.com/larksuite/oapi-sdk-go/v3/event"
"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
larkws "github.com/larksuite/oapi-sdk-go/v3/ws"
)
// RecordAction 记录变更事件中的单个操作
type RecordAction struct {
Action string `json:"action"` // record_added / record_edited / record_deleted
RecordID string `json:"record_id"`
BeforeValue []FieldValue `json:"before_value"`
AfterValue []FieldValue `json:"after_value"`
}
// FieldValue 字段值
type FieldValue struct {
FieldID string `json:"field_id"`
FieldValue string `json:"field_value"` // JSON 序列化后的字符串
}
// RecordChangedEvent 记录变更事件体
type RecordChangedEvent struct {
Event struct {
FileToken string `json:"file_token"` // 多维表格 app_token
FileType string `json:"file_type"` // 固定 "bitable"
TableID string `json:"table_id"`
ActionList []RecordAction `json:"action_list"`
OperatorID struct {
OpenID string `json:"open_id"`
UnionID string `json:"union_id"`
UserID string `json:"user_id"`
} `json:"operator_id"`
} `json:"event"`
}
// RecordChangeHandler 记录变更回调函数类型
type RecordChangeHandler func(ctx context.Context, event RecordChangedEvent) error
// StartEventListener 启动长连接事件监听器
//
// appID, appSecret: 飞书应用凭证
// handler: 记录变更事件处理函数
//
// ⚠️ 此函数会阻塞主线程
func StartEventListener(appID, appSecret string, handler RecordChangeHandler) error {
// ⚠️ 长连接模式:两个参数必须为空字符串
eventHandler := dispatcher.NewEventDispatcher("", "").
OnCustomizedEvent("drive.file.bitable_record_changed_v1",
func(ctx context.Context, event *larkevent.EventReq) error {
var parsed RecordChangedEvent
if err := json.Unmarshal(event.Body, &parsed); err != nil {
log.Printf("parse event failed: %v", err)
return nil // 解析失败也要返回 nil避免重推
}
// 委托给业务处理器
if err := handler(ctx, parsed); err != nil {
log.Printf("handle event failed: %v", err)
// 不返回 error避免触发重推
// 如需重试,在 handler 内部实现异步重试
}
return nil // 3 秒内返回
})
cli := larkws.NewClient(appID, appSecret,
larkws.WithEventHandler(eventHandler),
larkws.WithLogLevel(larkcore.LogLevelDebug),
)
fmt.Println("starting bitable event listener...")
return cli.Start(context.Background()) // 阻塞主线程
}

View File

@@ -0,0 +1,101 @@
# 事件与回调处理
- **DDS-Section**: 3.5 事件与回调处理 / B.4 处理事件 / B.5 处理回调
- **DDS-Lines**: L86-L91, L16843-L17528
## Extract
### 两种事件接收模式
| 模式 | 优势 | 限制 |
|------|------|------|
| **长连接WebSocket** | 免内网穿透、无需解密验签、SDK 内置鉴权 | 仅限企业自建应用,每应用最多 50 连接 |
| **HTTP 回调Webhook** | 支持所有应用类型 | 需公网地址、需处理解密验签 |
### 关键约束
- 长连接模式是**集群分发**,不是广播;同应用多实例时由随机一个实例消费
- 无论哪种模式,事件处理**需在 3 秒内完成**,否则触发超时重推
- HTTP 回调模式服务端需在 3 秒内返回 HTTP 200 状态码
### 长连接示例(推荐)
```go
package main
import (
"context"
"fmt"
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
larkevent "github.com/larksuite/oapi-sdk-go/v3/event"
"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
larkws "github.com/larksuite/oapi-sdk-go/v3/ws"
)
func main() {
// ⚠️ 长连接模式:两个参数必须填空字符串
eventHandler := dispatcher.NewEventDispatcher("", "").
OnCustomizedEvent("drive.file.bitable_record_changed_v1",
func(ctx context.Context, event *larkevent.EventReq) error {
fmt.Printf("record changed: %s\n", string(event.Body))
return nil // 3 秒内返回
})
cli := larkws.NewClient("YOUR_APP_ID", "YOUR_APP_SECRET",
larkws.WithEventHandler(eventHandler),
larkws.WithLogLevel(larkcore.LogLevelDebug),
)
// 启动客户端,主线程阻塞
err := cli.Start(context.Background())
if err != nil {
panic(err)
}
}
```
### HTTP 回调模式示例
```go
// HTTP 模式:需传入 verificationToken 和 eventEncryptKey
handler := dispatcher.NewEventDispatcher("verificationToken", "eventEncryptKey")
handler = handler.OnCustomizedEvent("drive.file.bitable_record_changed_v1",
func(ctx context.Context, event *larkevent.EventReq) error {
fmt.Println(string(event.Body))
return nil
})
http.HandleFunc("/webhook/event", httpserverext.NewEventHandlerFunc(handler,
larkevent.WithLogLevel(larkcore.LogLevelDebug)))
http.ListenAndServe(":9999", nil)
```
### Gin 框架集成
```bash
go get -u github.com/larksuite/oapi-sdk-gin
```
```go
gin.POST("/webhook/event", sdkginext.NewEventHandlerFunc(handler))
```
### 事件版本说明
| 版本 | 注册方法 | 说明 |
|------|---------|------|
| v2.0 | `OnP2XxxV1(...)` | P2 前缀 = v2.0 版本事件结构 |
| v1.0 | `OnCustomizedEvent("event_key", ...)` | 自定义事件 key |
### 回调处理(卡片交互等)
长连接模式下处理卡片回传交互:
```go
eventHandler := dispatcher.NewEventDispatcher("", "").
OnP2CardActionTrigger(func(ctx context.Context, event *callback.CardActionTriggerEvent) (*callback.CardActionTriggerResponse, error) {
fmt.Println(larkcore.Prettify(event))
return nil, nil
})
```
HTTP 模式下处理旧版卡片回调(使用 `larkcard.NewCardActionHandler`)。

View File

@@ -0,0 +1,30 @@
# 原生调用未封装 API
- **DDS-Section**: 3.4 历史/未封装 API 的原生调用
- **DDS-Lines**: L72-L84
## Extract
当 SDK 暂未提供强类型方法时,可用 `client.Do()` 进行原生调用:
```go
resp, err := client.Do(context.Background(), &larkcore.ApiReq{
HttpMethod: http.MethodGet,
ApiPath: "https://open.feishu.cn/open-apis/contact/v3/users/:user_id",
PathParams: larkcore.PathParams{"user_id": "ou_xxx"},
QueryParams: larkcore.QueryParams{"user_id_type": []string{"open_id"}},
SupportedAccessTokenTypes: []larkcore.AccessTokenType{larkcore.AccessTokenTypeUser},
})
```
### 使用场景
- SDK 版本落后于 API 更新
- 历史版本接口调用
- 特殊参数需求(如自定义 Header
### 注意事项
- `ApiPath` 使用完整的 API URL
- `PathParams` 中的键名对应 URL 中的 `:key` 占位符
- `SupportedAccessTokenTypes` 必须指定凭证类型

View File

@@ -0,0 +1,73 @@
# Go SDK 安装与初始化
- **DDS-Section**: 3. Golang SDK 调用说明 / B.2 开发前准备
- **DDS-Lines**: L23-L97, L16549-L16586
## Extract
### 安装
```bash
go get -u github.com/larksuite/oapi-sdk-go/v3@latest
```
### Client 初始化
```go
import lark "github.com/larksuite/oapi-sdk-go/v3"
client := lark.NewClient(appID, appSecret,
// lark.WithMarketplaceApp(), // 商店应用时启用
lark.WithEnableTokenCache(true), // 默认启用,自动管理 tenant_access_token
)
```
### 关键约束
| 约束 | 说明 |
|------|------|
| Token 托管 | SDK 自动获取和缓存 `tenant_access_token``app_access_token`,无需手动管理 |
| user_access_token | SDK **不**托管,需自行实现获取/刷新逻辑 |
| 商店应用 | 调用时需补 `larkcore.WithTenantKey(tenantKey)` |
### Bitable API 调用范式
SDK 方法映射规则:`client.Bitable.V1.<资源>.<动作>(ctx, req)`
```go
req := larkbitable.NewListAppTableRecordReqBuilder().
AppToken("app_token").
TableId("table_id").
PageSize(20).
Build()
resp, err := client.Bitable.V1.AppTableRecord.List(context.Background(), req)
if err != nil {
// 网络错误
panic(err)
}
if !resp.Success() {
// 业务错误
fmt.Println(resp.Code, resp.Msg, resp.RequestId())
return
}
fmt.Println(larkcore.Prettify(resp.Data))
```
### 请求级选项
| 选项 | 用途 |
|------|------|
| `larkcore.WithUserAccessToken(...)` | 用户态调用 |
| `larkcore.WithTenantAccessToken(...)` | 手动传租户 token |
| `larkcore.WithTenantKey(...)` | 商店应用必须设置 |
| `larkcore.WithHeaders(...)` | 透传自定义 Header |
### 本地示例代码位置
| 路径 | 内容 |
|------|------|
| `offline-docs-v2/go-sdk-examples/oapi-sdk-go/sample/apiall/bitablev1/` | Bitable v1 全量 API 样例 |
| `offline-docs-v2/go-sdk-examples/oapi-sdk-go/sample/api/bitable2.go` | 复杂字段示例 |
| `offline-docs-v2/go-sdk-examples/oapi-sdk-go-demo/` | 官方场景化 Demo |
| `offline-docs-v2/go-sdk-examples/bitablev1-curated/` | 精选拷贝 |

View File

@@ -0,0 +1,136 @@
# 字段 Property 编辑指南
- **DDS-Section**: A.5 字段编辑指南 - Property 说明
- **DDS-Lines**: L734-L1370
## Extract
> ⚠️ **关键约束**:更新字段时为**全量更新**`property` 会被完全覆盖。更新单选/多选字段时,必须传入完整的 options 列表。
### 数字字段 Property
| 名称 | 类型 | 说明 | 默认值 |
|------|------|------|--------|
| `formatter` | string | 数字显示格式 | `"0.0"` |
**formatter 对照表**
| 格式 | formatter 值 |
|------|-------------|
| 整数 | `"0"` |
| 保留1位小数 | `"0.0"` |
| 保留2位小数 | `"0.00"` |
| 千分位 | `"1,000"` |
| 千分位(小数) | `"1,000.00"` |
| 百分比 | `"%"` |
| 百分比(小数) | `"0.00%"` |
| 人民币 | `"¥"` |
| 人民币(小数) | `"¥0.00"` |
| 美元 | `"$"` |
| 美元(小数) | `"$0.00"` |
```json
{
"field_name": "数字",
"type": 2,
"property": {
"formatter": "0.00"
}
}
```
### 单选/多选字段 Property
| 名称 | 类型 | 说明 |
|------|------|------|
| `options` | array | 选项列表 |
| `options[].name` | string | 选项名 |
| `options[].id` | string | 选项 ID更新时如果保留已有选项需传入 |
| `options[].color` | int | 选项颜色0-54 |
**⚠️ 更新时必须传入完整 options 列表**
```json
// 新增字段:只需 name 和 color
{
"field_name": "任务状态",
"type": 3,
"property": {
"options": [
{"name": "待处理", "color": 0},
{"name": "进行中", "color": 1}
]
}
}
// 更新字段:保留已有选项需带上 id
{
"field_name": "任务状态",
"type": 3,
"property": {
"options": [
{"name": "待处理", "color": 0, "id": "optXXX1"},
{"name": "进行中", "color": 1, "id": "optXXX2"},
{"name": "已完成", "color": 2} // 新增选项不需要 id
]
}
}
```
### 日期字段 Property
| 名称 | 类型 | 说明 | 默认值 |
|------|------|------|--------|
| `date_formatter` | string | 日期显示格式 | `"yyyy/MM/dd"` |
| `auto_fill` | boolean | 新记录自动填写创建时间 | `false` |
### 人员字段 Property
| 名称 | 类型 | 说明 | 默认值 |
|------|------|------|--------|
| `multiple` | boolean | 允许添加多个成员 | `false` |
### 单向关联字段 Property
| 名称 | 类型 | 说明 |
|------|------|------|
| `table_id` | string | 关联的数据表 ID必填 |
| `multiple` | boolean | 允许关联多条记录 |
### 双向关联字段 Property
| 名称 | 类型 | 说明 |
|------|------|------|
| `table_id` | string | 关联的数据表 ID必填 |
| `back_field_name` | string | 对向关联字段名(必填) |
| `multiple` | boolean | 允许关联多条记录 |
### 公式字段 Property
| 名称 | 类型 | 说明 |
|------|------|------|
| `formula_expression` | string | 公式表达式(⚠️ 创建/更新字段时不支持设置公式表达式) |
| `formatter` | string | 显示格式 |
### 创建时间/最后更新时间字段 Property
| 名称 | 类型 | 说明 |
|------|------|------|
| `date_formatter` | string | 日期显示格式 |
### 自动编号字段 Property
| 名称 | 类型 | 说明 |
|------|------|------|
| `auto_serial` | object | 编号规则配置 |
| `auto_serial.type` | string | 类型:`auto_increment_number`(自增数字), `custom`(自定义) |
| `auto_serial.options` | array | 自定义规则列表 |
| `auto_serial.options[].type` | string | 规则项类型:`system_number`(自增编号), `fixed_text`(固定字符), `created_time`(创建时间) |
| `auto_serial.options[].value` | string | 与类型对应的取值 |
### 各字段类型的 API 支持情况
| 操作 | 支持的字段类型 | 不支持 |
|------|-------------|--------|
| 新增/更新记录 | 文本、单选、多选、日期、人员、附件、复选框、超链接、数字、单向关联、电话、地理位置 | 双向关联、公式、查找引用 |
| 新增/更新字段 | 文本、单选、多选、日期、数字、人员、附件、单向/双向关联、复选框、超链接、公式(不支持设表达式)、创建人、修改人、创建时间、更新时间、自动编号、电话、地理位置 | 查找引用 |

Some files were not shown because too many files have changed in this diff Show More