#!/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()