Files
ai-proj-helper/skills-integration/feishu-plugin/demo.py
John Qiu 712063071c refactor: 通用技能按类别拆分为独立目录
skills/ → skills-dev(9), skills-req(10), skills-ops(4),
skills-integration(8), skills-biz(4), skills-workflow(7)

generate-marketplace.py 改为自动扫描所有 skills-* 目录。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 11:31:58 +10:30

410 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
飞书多维表格 Demo
使用 zhiyun.ai 凭证演示多维表格的完整操作流程
"""
import requests
from datetime import datetime, timedelta
from typing import Optional, List, Dict
# ========== 配置 ==========
ZHIYUN_APP_ID = "cli_a9f29dca82b9dbef"
ZHIYUN_APP_SECRET = "sDfhjG7QT1S4gfHiMVYSygmPQPN1R2Ho"
BASE_URL = "https://open.feishu.cn/open-apis"
# ========== 工具类 ==========
class FeishuBitable:
"""飞书多维表格操作工具类"""
def __init__(self, app_id: str = ZHIYUN_APP_ID, app_secret: str = ZHIYUN_APP_SECRET):
self.app_id = app_id
self.app_secret = app_secret
self._token = None
self._token_expires = None
@property
def token(self) -> str:
"""获取或刷新 access token"""
if self._token and self._token_expires and datetime.now() < self._token_expires:
return self._token
url = f"{BASE_URL}/auth/v3/tenant_access_token/internal"
response = requests.post(url, json={
"app_id": self.app_id,
"app_secret": self.app_secret
})
data = response.json()
if data.get("code") != 0:
raise Exception(f"获取 token 失败: {data}")
self._token = data["tenant_access_token"]
self._token_expires = datetime.now() + timedelta(seconds=data.get("expire", 7200) - 60)
print(f"[OK] Token 获取成功,有效期至 {self._token_expires}")
return self._token
@property
def headers(self) -> Dict[str, str]:
return {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
# ========== 多维表格管理 ==========
def create_bitable(self, name: str, folder_token: str = None) -> Dict:
"""
创建新的多维表格
Args:
name: 多维表格名称
folder_token: 文件夹 token可选不指定则创建在根目录
"""
url = f"{BASE_URL}/bitable/v1/apps"
payload = {"name": name}
if folder_token:
payload["folder_token"] = folder_token
response = requests.post(url, headers=self.headers, json=payload)
data = response.json()
if data.get("code") != 0:
raise Exception(f"创建多维表格失败: {data}")
app_info = data["data"]["app"]
print(f"[OK] 多维表格创建成功")
print(f" 名称: {app_info['name']}")
print(f" app_token: {app_info['app_token']}")
print(f" URL: {app_info.get('url', 'N/A')}")
return app_info
def list_tables(self, app_token: str) -> List[Dict]:
"""列出多维表格中的所有数据表"""
url = f"{BASE_URL}/bitable/v1/apps/{app_token}/tables"
response = requests.get(url, headers=self.headers)
data = response.json()
if data.get("code") != 0:
raise Exception(f"获取数据表列表失败: {data}")
tables = data["data"].get("items", [])
print(f"[OK] 找到 {len(tables)} 个数据表")
for t in tables:
print(f" - {t['name']} (table_id: {t['table_id']})")
return tables
def create_table(self, app_token: str, name: str, fields: List[Dict]) -> Dict:
"""
在多维表格中创建数据表
Args:
app_token: 多维表格 app_token
name: 数据表名称
fields: 字段定义列表
"""
url = f"{BASE_URL}/bitable/v1/apps/{app_token}/tables"
payload = {
"table": {
"name": name,
"default_view_name": "默认视图",
"fields": fields
}
}
response = requests.post(url, headers=self.headers, json=payload)
data = response.json()
if data.get("code") != 0:
raise Exception(f"创建数据表失败: {data}")
table_info = data["data"]
print(f"[OK] 数据表创建成功")
print(f" 名称: {name}")
print(f" table_id: {table_info['table_id']}")
return table_info
# ========== 记录操作 ==========
def list_records(self, app_token: str, table_id: str,
filter_str: str = None, page_size: int = 100) -> List[Dict]:
"""列出所有记录(自动分页)"""
url = f"{BASE_URL}/bitable/v1/apps/{app_token}/tables/{table_id}/records"
all_records = []
page_token = None
while True:
params = {"page_size": min(page_size, 500)}
if page_token:
params["page_token"] = page_token
if filter_str:
params["filter"] = filter_str
response = requests.get(url, headers=self.headers, params=params)
data = response.json()
if data.get("code") != 0:
raise Exception(f"查询失败: {data}")
items = data["data"].get("items", [])
all_records.extend(items)
if not data["data"].get("has_more"):
break
page_token = data["data"]["page_token"]
return all_records
def create_record(self, app_token: str, table_id: str, fields: Dict) -> Dict:
"""创建单条记录"""
url = f"{BASE_URL}/bitable/v1/apps/{app_token}/tables/{table_id}/records"
response = requests.post(url, headers=self.headers, json={"fields": fields})
data = response.json()
if data.get("code") != 0:
raise Exception(f"创建失败: {data}")
return data["data"]["record"]
def batch_create(self, app_token: str, table_id: str,
records: List[Dict], batch_size: int = 500) -> List[Dict]:
"""批量创建记录"""
url = f"{BASE_URL}/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create"
created = []
for i in range(0, len(records), batch_size):
batch = [{"fields": r} if "fields" not in r else r for r in records[i:i+batch_size]]
response = requests.post(url, headers=self.headers, json={"records": batch})
data = response.json()
if data.get("code") != 0:
raise Exception(f"批量创建失败: {data}")
created.extend(data["data"]["records"])
return created
def get_fields(self, app_token: str, table_id: str) -> List[Dict]:
"""获取字段定义"""
url = f"{BASE_URL}/bitable/v1/apps/{app_token}/tables/{table_id}/fields"
response = requests.get(url, headers=self.headers)
data = response.json()
if data.get("code") != 0:
raise Exception(f"获取字段失败: {data}")
return data["data"]["items"]
# ========== Demo 函数 ==========
def demo_verify_credentials():
"""验证凭证是否有效"""
print("\n" + "="*50)
print("Step 1: 验证飞书应用凭证")
print("="*50)
bitable = FeishuBitable()
token = bitable.token # 触发 token 获取
print(f" Token 前缀: {token[:20]}...")
return bitable
def demo_create_bitable(bitable: FeishuBitable) -> str:
"""创建新的多维表格"""
print("\n" + "="*50)
print("Step 2: 创建多维表格")
print("="*50)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
name = f"Claude Code Demo - {timestamp}"
app_info = bitable.create_bitable(name)
return app_info["app_token"]
def demo_create_task_table(bitable: FeishuBitable, app_token: str) -> str:
"""创建任务管理数据表"""
print("\n" + "="*50)
print("Step 3: 创建任务管理数据表")
print("="*50)
# 定义字段
fields = [
{
"field_name": "任务名称",
"type": 1 # 文本
},
{
"field_name": "状态",
"type": 3, # 单选
"property": {
"options": [
{"name": "待处理", "color": 0},
{"name": "进行中", "color": 1},
{"name": "已完成", "color": 2},
{"name": "已取消", "color": 3}
]
}
},
{
"field_name": "优先级",
"type": 3, # 单选
"property": {
"options": [
{"name": "", "color": 4},
{"name": "", "color": 5},
{"name": "", "color": 6}
]
}
},
{
"field_name": "负责人",
"type": 1 # 文本
},
{
"field_name": "截止日期",
"type": 5, # 日期
"property": {
"date_formatter": "yyyy/MM/dd"
}
},
{
"field_name": "工时(小时)",
"type": 2 # 数字
},
{
"field_name": "备注",
"type": 1 # 文本
}
]
table_info = bitable.create_table(app_token, "任务看板", fields)
return table_info["table_id"]
def demo_add_sample_data(bitable: FeishuBitable, app_token: str, table_id: str):
"""添加示例数据"""
print("\n" + "="*50)
print("Step 4: 添加示例数据")
print("="*50)
# 准备示例数据
now = datetime.now()
sample_tasks = [
{
"任务名称": "完成产品需求文档",
"状态": "已完成",
"优先级": "",
"负责人": "张三",
"截止日期": int((now - timedelta(days=2)).timestamp() * 1000),
"工时(小时)": 8,
"备注": "PRD 已评审通过"
},
{
"任务名称": "设计系统架构方案",
"状态": "进行中",
"优先级": "",
"负责人": "李四",
"截止日期": int((now + timedelta(days=3)).timestamp() * 1000),
"工时(小时)": 16,
"备注": "正在编写技术方案"
},
{
"任务名称": "开发用户登录模块",
"状态": "待处理",
"优先级": "",
"负责人": "王五",
"截止日期": int((now + timedelta(days=7)).timestamp() * 1000),
"工时(小时)": 24,
"备注": "等待架构方案确定"
},
{
"任务名称": "编写单元测试",
"状态": "待处理",
"优先级": "",
"负责人": "赵六",
"截止日期": int((now + timedelta(days=10)).timestamp() * 1000),
"工时(小时)": 12,
"备注": ""
},
{
"任务名称": "部署测试环境",
"状态": "待处理",
"优先级": "",
"负责人": "钱七",
"截止日期": int((now + timedelta(days=14)).timestamp() * 1000),
"工时(小时)": 4,
"备注": "需要申请服务器资源"
}
]
# 批量创建记录
created = bitable.batch_create(app_token, table_id, sample_tasks)
print(f"[OK] 成功创建 {len(created)} 条示例记录")
# 显示创建的记录
for i, record in enumerate(created, 1):
fields = record["fields"]
print(f" {i}. {fields.get('任务名称')} - {fields.get('状态')} ({fields.get('优先级')}优先级)")
return created
def demo_query_data(bitable: FeishuBitable, app_token: str, table_id: str):
"""查询数据演示"""
print("\n" + "="*50)
print("Step 5: 查询数据演示")
print("="*50)
# 查询所有记录
all_records = bitable.list_records(app_token, table_id)
print(f"[OK] 共 {len(all_records)} 条记录")
# 获取字段定义
fields = bitable.get_fields(app_token, table_id)
print(f"[OK] 共 {len(fields)} 个字段:")
for f in fields:
print(f" - {f['field_name']} (类型: {f['type']})")
return all_records
def main():
"""运行完整 Demo"""
print("\n" + "#"*60)
print("# 飞书多维表格 Demo - zhiyun.ai")
print("#"*60)
try:
# Step 1: 验证凭证
bitable = demo_verify_credentials()
# Step 2: 创建多维表格
app_token = demo_create_bitable(bitable)
# Step 3: 创建数据表
table_id = demo_create_task_table(bitable, app_token)
# Step 4: 添加示例数据
demo_add_sample_data(bitable, app_token, table_id)
# Step 5: 查询数据
demo_query_data(bitable, app_token, table_id)
# 完成
print("\n" + "="*60)
print("Demo 完成!")
print("="*60)
print(f"\n多维表格信息:")
print(f" app_token: {app_token}")
print(f" table_id: {table_id}")
print(f"\n访问地址:")
print(f" https://zhiyun-ai.feishu.cn/base/{app_token}?table={table_id}")
print()
return app_token, table_id
except Exception as e:
print(f"\n[ERROR] {e}")
raise
if __name__ == "__main__":
main()