超大量更新
This commit is contained in:
30670
18-基础架构及交付部署特战队/1-项目部署-管理/docs/airscript-official-doc.md
Normal file
30670
18-基础架构及交付部署特战队/1-项目部署-管理/docs/airscript-official-doc.md
Normal file
File diff suppressed because it is too large
Load Diff
1013
18-基础架构及交付部署特战队/1-项目部署-管理/docs/airscript-spec-doc.md
Normal file
1013
18-基础架构及交付部署特战队/1-项目部署-管理/docs/airscript-spec-doc.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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('======================================================');
|
||||
@@ -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)
|
||||
├── 是否暴露公网
|
||||
|
||||
BIN
18-基础架构及交付部署特战队/1-项目部署-管理/金山多维表格-结构.xlsx
Normal file
BIN
18-基础架构及交付部署特战队/1-项目部署-管理/金山多维表格-结构.xlsx
Normal file
Binary file not shown.
@@ -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 文件,用于后续的开发参考
|
||||
39
18-基础架构及交付部署特战队/10-飞书多维表格/0-多维表格开发文档.md
Normal file
39
18-基础架构及交付部署特战队/10-飞书多维表格/0-多维表格开发文档.md
Normal 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的渐进式暴露原则,实现文档的转换
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
# 飞书多维表格 AgentSkills(v2 离线文档转换产物)
|
||||
|
||||
本套 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`,避免一次性加载全量文档。
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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 应与路径段一一对应,禁止跨资源复用。
|
||||
|
||||
@@ -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` 是否一致。
|
||||
|
||||
@@ -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` |
|
||||
|
||||
要求:同错误码在不同模块中保持相同语义与排查动作。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 版本与兼容策略
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未给出正式的 API 版本升级策略(deprecate 生命周期、灰度窗口、双写方案)。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 向后兼容判定标准
|
||||
2. 破坏性变更发布流程
|
||||
3. 旧版本下线时间线
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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`。
|
||||
|
||||
@@ -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 接口可用性有直接影响
|
||||
|
||||
@@ -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`。
|
||||
- 发生无访问权限时,应检查高级权限设置中是否为应用主体授权。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 服务端持久化结构
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未提供 App 元数据在业务系统侧的数据库结构。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. App 元数据缓存表是否存在(字段与 TTL)
|
||||
2. 高级权限开关状态是否持久化本地
|
||||
3. App 配置变更的审计日志模型
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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` 需一并保留用于追踪与授权判定。
|
||||
|
||||
@@ -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 三类接入方式。
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
## 验签与解密配置
|
||||
|
||||
- DDS-Section: B.4 处理事件 / B.5 处理回调
|
||||
- DDS-Lines: L16980-L17018, L17294-L17316
|
||||
|
||||
### Extract
|
||||
|
||||
| 参数 | 说明 |
|
||||
|---|---|
|
||||
| `verificationToken` | 用于签名验证 |
|
||||
| `eventEncryptKey` | 用于消息解密 |
|
||||
|
||||
若在开发者后台启用加密策略,则必须传递上述参数。
|
||||
|
||||
@@ -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。
|
||||
- 超时会触发重推,消费侧必须幂等。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 事件存储结构
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未给出事件落库模型(去重表、死信表、重试表)。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. `event_id` 去重存储 TTL
|
||||
2. 失败事件重试与死信策略
|
||||
3. 事件审计字段定义
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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。
|
||||
|
||||
@@ -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] 文档无后端数据库字段字典表定义(字段元数据持久化)。
|
||||
|
||||
@@ -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` 去重 + 字段缓存增量刷新。
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## 字段接口权限模型
|
||||
|
||||
- DDS-Section: 接入指南-鉴权 / 字段相关 OpenAPI security
|
||||
- DDS-Lines: L311-L371, L12108+, L12678+, L13147+
|
||||
|
||||
### Extract
|
||||
|
||||
| 项 | 说明 |
|
||||
|---|---|
|
||||
| token | 支持 tenant/user 两种调用身份 |
|
||||
| 高级权限影响 | 部分无访问权限由高级权限策略导致 |
|
||||
| 用户标识字段 | 某些字段(如人员)涉及 user_id 权限要求 |
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 字段状态机
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未定义字段生命周期状态机(draft/active/deprecated 等)。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 字段启用/停用状态定义
|
||||
2. 变更审批与回滚流程
|
||||
3. 对历史记录的兼容策略
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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` 幂等。
|
||||
|
||||
@@ -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] 文档未给出记录在业务系统持久化时的表结构与索引。
|
||||
|
||||
@@ -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` 的幂等去重。
|
||||
|
||||
@@ -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` 需一致来源 |
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 状态机定义
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未定义 Record 业务状态机(状态枚举、转移守卫、补偿动作)。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 记录业务状态集合(如 draft/active/archived)
|
||||
2. 转移触发条件与角色权限
|
||||
3. 转移失败补偿策略
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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}` | 更新角色 |
|
||||
|
||||
约束:
|
||||
- 角色接口依赖高级权限开启。
|
||||
- 写操作建议串行处理。
|
||||
|
||||
@@ -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}` | 删除协作者 |
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## 高级权限前置与异常
|
||||
|
||||
- DDS-Section: A.7 概述 / A.11 更新元数据
|
||||
- DDS-Lines: L1547-L1565, L2229-L2233, L2286-L2289
|
||||
|
||||
### Extract
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| 开关前置 | Role/Member 调用前必须开启高级权限 |
|
||||
| 生效延迟 | 开启后短时可能报 `OperationTypeError` |
|
||||
| 无权限 | 需在高级权限中加入包含应用的群并授予读写 |
|
||||
|
||||
@@ -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`。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 权限域数据库结构
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未描述 Role/Member 在业务侧持久化结构与审计表。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 角色权限项结构与版本字段
|
||||
2. 成员-角色关系表唯一键
|
||||
3. 权限变更审计日志表
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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 得到)。
|
||||
|
||||
@@ -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。
|
||||
- 不支持并发写接口。
|
||||
|
||||
@@ -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}` | 删除视图 |
|
||||
|
||||
约束:
|
||||
- 写接口不建议并发。
|
||||
- 出现计算超时类错误时可按文档建议重试。
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## 鉴权与权限
|
||||
|
||||
- DDS-Section: 接入指南-鉴权 / 各接口 security 定义
|
||||
- DDS-Lines: L311-L371, L2458+, L5575+
|
||||
|
||||
### Extract
|
||||
|
||||
| 维度 | 说明 |
|
||||
|---|---|
|
||||
| token 类型 | `tenant_access_token` / `user_access_token` |
|
||||
| 权限异常 | 无访问权限常见于高级权限配置未覆盖应用主体 |
|
||||
| 额外要求 | 商店应用场景需关注 `tenant_key` |
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 本地持久化结构
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未提供 Table/View 在业务系统落地时的数据库结构定义。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. 表视图缓存模型(主键、唯一键)
|
||||
2. 软删除或硬删除策略
|
||||
3. 变更审计字段与索引
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`:事件体、回调模式、重推约束
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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 | 事件回调驱动记录或字段消费逻辑 |
|
||||
|
||||
@@ -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. 迁移与回滚方案
|
||||
|
||||
@@ -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`,需重试 |
|
||||
| 权限联动 | 高级权限协作者与云文档协作者不同,必要时同步文档权限 |
|
||||
| 非原子更新 | 更新元数据接口先改名称再开关权限,可能部分成功 |
|
||||
|
||||
@@ -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` | 多租户隔离与重放定位 |
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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。
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
## 高级权限访问模型
|
||||
|
||||
- DDS-Section: A.7 概述 / A.11 更新元数据
|
||||
- DDS-Lines: L1547-L1561, L2229-L2233, L2286-L2289
|
||||
|
||||
### Extract
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| 前置开关 | `is_advanced` 未开启时 Role/Member 不可用 |
|
||||
| 生效延迟 | 开启后可能短时返回 `OperationTypeError` |
|
||||
| 权限联动 | 高级权限协作者与文档协作者是两套体系 |
|
||||
|
||||
@@ -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 生命周期。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 会话/令牌状态机
|
||||
|
||||
- DDS-Section: 全文
|
||||
- DDS-Lines: L1-L18211
|
||||
|
||||
### Extract
|
||||
|
||||
[TBD] 文档未给出统一的 token 状态机与失效传播规范。
|
||||
|
||||
### 最小补充信息清单
|
||||
|
||||
1. user token 刷新触发条件与缓存失效策略
|
||||
2. tenant token 刷新提前量与失败降级策略
|
||||
3. 多实例 token 缓存一致性策略
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -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。
|
||||
|
||||
@@ -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`。
|
||||
|
||||
@@ -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 |
|
||||
|
||||
注意:长连接是集群分发,不是广播。
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
## SDK 鉴权选项
|
||||
|
||||
- DDS-Section: 3.3 请求级选项 / B.3 请求选项
|
||||
- DDS-Lines: L65-L72, L16706-L16722
|
||||
|
||||
### Extract
|
||||
|
||||
| 选项 | 用途 |
|
||||
|---|---|
|
||||
| `WithUserAccessToken` | 用户态请求 |
|
||||
| `WithTenantAccessToken` | 应用态请求 |
|
||||
| `WithTenantKey` | 商店应用租户隔离 |
|
||||
| `WithHeaders` | 自定义请求头 |
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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`
|
||||
@@ -0,0 +1,4 @@
|
||||
# Skeleton Example
|
||||
|
||||
- 仅提供调用骨架,不包含完整业务实现。
|
||||
- 实际编码前先对照 `reference/` 校验参数、权限、错误码与频控限制。
|
||||
@@ -0,0 +1,15 @@
|
||||
## 接口频控与批量上限
|
||||
|
||||
- DDS-Section: 接入指南-限制 / 各 OpenAPI 描述
|
||||
- DDS-Lines: L377-L384, L1954+, L2233+, L8529+, L10629+, L11217+
|
||||
|
||||
### Extract
|
||||
|
||||
| 维度 | 约束 |
|
||||
|---|---|
|
||||
| 读接口 QPS | 常见 20 QPS |
|
||||
| 写接口 QPS | 常见 10 QPS |
|
||||
| 批量写 | 单次最多 500 条记录 |
|
||||
|
||||
说明:具体值以目标接口章节为准。
|
||||
|
||||
@@ -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)写请求串行化。
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
## 幂等键规则
|
||||
|
||||
- DDS-Section: 记录/字段批量写相关错误码与参数
|
||||
- DDS-Lines: L9343, L9483, L10707, L10847, L10893, L12777, L12965, L13005
|
||||
|
||||
### Extract
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| key 字段 | `client_token` |
|
||||
| 格式 | 标准 UUID |
|
||||
| 冲突处理 | 重新生成幂等键后重试 |
|
||||
|
||||
不满足格式会返回幂等键格式错误。
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
## 重试策略依赖
|
||||
|
||||
- DDS-Section: 接口错误说明 / 事件重推说明
|
||||
- DDS-Lines: L3654, L3913, L4896, L5356, L5734, L6058, L18209
|
||||
|
||||
### Extract
|
||||
|
||||
| 错误类型 | 建议 |
|
||||
|---|---|
|
||||
| 服务器计算超时 | 可有限重试 |
|
||||
| 并发写冲突 | 串行化后重试 |
|
||||
| 参数/权限错误 | 不重试,直接修正 |
|
||||
| 回调超时重推 | 消费端按 event_id 去重 |
|
||||
|
||||
@@ -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 ]
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
220
18-基础架构及交付部署特战队/10-飞书多维表格/developing-feishu-bitable/SKILL.md
Normal file
220
18-基础架构及交付部署特战队/10-飞书多维表格/developing-feishu-bitable/SKILL.md
Normal file
@@ -0,0 +1,220 @@
|
||||
---
|
||||
name: developing-feishu-bitable
|
||||
description: >
|
||||
飞书多维表格(Bitable)Go SDK 全栈开发指导 Skill(Full-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 是飞书多维表格(Bitable)Go 后端开发的唯一全栈向导。基于 `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. **批量操作策略**:批量 API(batch_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 1:SDK 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 2:Record 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` |
|
||||
@@ -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
|
||||
}
|
||||
@@ -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()) // 阻塞主线程
|
||||
}
|
||||
@@ -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`)。
|
||||
@@ -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` 必须指定凭证类型
|
||||
@@ -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/` | 精选拷贝 |
|
||||
@@ -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
Reference in New Issue
Block a user