commit e6e4cd8119568eb49f1b9b2273d9f5b59cbfda06 Author: 0Xiao0 <511201264@qq.com> Date: Mon Mar 23 10:45:02 2026 +0800 first commit diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..41fa906 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.dockerignore +.gitignore +.git +README.md \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..5ddfeda --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# 核心凭证配置 (必填) +APP_ID=your_app_id +APP_SECRET=your_app_secret +LARK_HOST=https://open.feishu.cn + +# 单表查询配置 (get_records, batch_get_records) +DEFAULT_APP_TOKEN=your_default_app_token +DEFAULT_TABLE_ID=your_default_table_id +BATCH_TABLE_ID=your_batch_table_id + +# 导出网页视图配置 (严格 JSON 数组格式) +WEB_VIEW_TABS=[{"name": "默认分组", "app_token": "your_app_token", "table_id": "your_table_id"}] + +# AI 总结配置 (可选,用于 outline_view.html 生成摘要) +AI_API_KEY=your_ai_api_key +AI_BASE_URL=https://api.openai.com/v1 +AI_MODEL=gpt-4o + +# 多表合并功能配置 (可选) +MERGE_SOURCE_APP_TOKEN_1=source_token_1 +MERGE_SOURCE_TABLE_ID_1=source_id_1 +MERGE_SOURCE_APP_TOKEN_2=source_token_2 +MERGE_SOURCE_TABLE_ID_2=source_id_2 +MERGE_TARGET_APP_TOKEN=target_token diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..110b0a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Python-generated files +__pycache__/ +*.py[oc] +build/ +dist/ +wheels/ +*.egg-info + +# Virtual environments +.venv +.env diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..24ee5b1 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.13 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..bfe13bd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM python:3 + +RUN apt-get update + +WORKDIR /home/app + +# If we add the requirements and install dependencies first, docker can use cache if requirements don't change +ADD requirements.txt /home/app +RUN pip install --no-cache-dir -r requirements.txt + +ADD . /home/app + +# Run the command on container startup +CMD python3 bitable_calendar.py + diff --git a/README.md b/README.md new file mode 100644 index 0000000..0cc3989 --- /dev/null +++ b/README.md @@ -0,0 +1,70 @@ +# 基于多维表格的敏捷项目周期管理 + +[使用说明](https://open.feishu.cn/document/home/agile-project-cycle-management-based-on-bitable/introduction) + + +## 配置说明 + +本项目依赖环境变量来进行飞书应用授权和数据表定位。你需要根据 `.env.example`(或直接新建一个 `.env` 文件),配置以下环境变量: + +### 核心凭证配置 (必填) +* `APP_ID`: 你的飞书应用的 App ID。 +* `APP_SECRET`: 你的飞书应用的 App Secret。 +* `LARK_HOST`: 飞书开放平台 API 地址,默认为 `https://open.feishu.cn`。 + +### 单表查询配置 (get_records, batch_get_records) +* `DEFAULT_APP_TOKEN`: 默认读取的飞书多维表格的 App Token。 +* `DEFAULT_TABLE_ID`: `get_records.py` 默认查询的 Table ID。 +* `BATCH_TABLE_ID`: `batch_get_records.py` 默认批量查询的 Table ID。 + +### 导出可视化视图配置 (export_web_view) +* `WEB_VIEW_TABS`: 导出 HTML 时使用的多标签页配置,必须为严格的 **JSON 数组字符串**。例如: + `[{"name": "开发组", "app_token": "token1", "table_id": "id1"}, {"name": "测试组", "app_token": "token2", "table_id": "id2"}]` +* **AI 总结配置**(可选,用于网页自带的 AI 分板): + * `AI_API_KEY`: 大模型 API Key。 + * `AI_BASE_URL`: 大模型 API 请求 Base URL。 + * `AI_MODEL`: 大模型模型名称(如 `gpt-4o`, `MiniMaxAI` 等)。 + +### 多表合并功能配置 (merge_bitables, append_bitables) +* `MERGE_SOURCE_APP_TOKEN_1` / `MERGE_SOURCE_TABLE_ID_1`: 第一个数据源。 +* `MERGE_SOURCE_APP_TOKEN_2` / `MERGE_SOURCE_TABLE_ID_2`: 第二个数据源。 +* `MERGE_TARGET_APP_TOKEN`: 数据合并后的目标 App Token。 + +## 快速开始模板 (`.env` 文件配置) +你可以直接在项目根目录创建一个 `.env` 文件,然后复制以下内容并替换成你的真实配置: + +```env +# 核心凭证配置 (必填) +APP_ID=your_app_id +APP_SECRET=your_app_secret +LARK_HOST=https://open.feishu.cn + +# 单表查询配置 (get_records, batch_get_records) +DEFAULT_APP_TOKEN=your_default_app_token +DEFAULT_TABLE_ID=your_default_table_id +BATCH_TABLE_ID=your_batch_table_id + +# 导出网页视图配置 (严格 JSON 数组格式) +WEB_VIEW_TABS=[{"name": "默认分组", "app_token": "your_app_token", "table_id": "your_table_id"}] + +# AI 总结配置 (可选,用于 outline_view.html 生成摘要) +AI_API_KEY=your_ai_api_key +AI_BASE_URL=https://api.openai.com/v1 +AI_MODEL=gpt-4o + +# 多表合并功能配置 (可选) +MERGE_SOURCE_APP_TOKEN_1=source_token_1 +MERGE_SOURCE_TABLE_ID_1=source_id_1 +MERGE_SOURCE_APP_TOKEN_2=source_token_2 +MERGE_SOURCE_TABLE_ID_2=source_id_2 +MERGE_TARGET_APP_TOKEN=target_token +``` +## 快速开始 +```bash +uv sync +uv run export_web_view.py +uv run server.py +``` + +服务启动后,可以通过浏览器直接访问查看交互式数据看板: +🔗 **[http://localhost:18080](http://localhost:18080)** diff --git a/api.py b/api.py new file mode 100644 index 0000000..82b3e5a --- /dev/null +++ b/api.py @@ -0,0 +1,237 @@ +# -*- coding: UTF-8 -*- +import datetime +from typing import List +from utils import request +import sys +if sys.version_info.minor >= 8: + from typing import Literal +else: + from typing_extensions import Literal + + +class Client(object): + def __init__(self, lark_host): + self._host = lark_host + + def get_tenant_access_token(self, app_id, app_secret): + url = self._host+"/open-apis/auth/v3/app_access_token/internal/" + headers = { + 'Content-Type': 'application/json; charset=utf-8' + } + payload = { + 'app_id': app_id, + 'app_secret': app_secret + } + resp = request("POST", url, headers, payload) + return resp['tenant_access_token'] + + def get_records(self, access_token: str, app_token: str, table_id: str, view_id: str=None): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + + params = {} + if view_id: params['view_id'] = view_id + resp = request("GET", url, headers, params=params) + return resp['data']['items'] + + + def create_calendar( + self, + access_token: str, + summary: str=None, + description: str=None, + permissions: Literal["private", "show_only_free_busy", "public"]=None, + color: int=None, + summary_alias: str=None): + url = f"{self._host}/open-apis/calendar/v4/calendars" + + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + payload = {} + if summary: payload['summary'] = summary + if description: payload['description'] = description + if permissions: payload['permissions'] = permissions + if color: payload['color'] = color + if summary_alias: payload['summary_alias'] = summary_alias + + resp = request("POST", url, headers, payload) + return resp['data']['calendar'] + + def create_event( + self, + access_token: str, + calendar_id: str, + start_time: datetime.datetime, + end_time: datetime.datetime, + vchat=None, + location=None, + reminders=None, + schemas=None, + summary: str=None, + description: str=None, + need_notification: bool=None, + visibility: Literal['default', 'public', 'private']=None, + attendee_ability: Literal['none', 'can_see_others', 'can_invite_others', 'can_modify_event']=None, + free_busy_status: Literal['busy', 'free']=None, + color: int=None, + recurrence: str=None, + ): + url = f"{self._host}/open-apis/calendar/v4/calendars/{calendar_id}/events" + + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + payload = { + 'start_time': {'timestamp': int(start_time.timestamp())}, + 'end_time': {'timestamp': int(end_time.timestamp())} + } + if vchat: payload['vchat'] = vchat + if location: payload['location'] = location + if reminders: payload['reminders'] = reminders + if schemas: payload['schemas'] = schemas + if summary: payload['summary'] = summary + if description: payload['description'] = description + if need_notification: payload['need_notification'] = need_notification + if visibility: payload['visibility'] = visibility + if attendee_ability: payload['attendee_ability'] = attendee_ability + if free_busy_status: payload['free_busy_status'] = free_busy_status + if color: payload['color'] = color + if recurrence: payload['recurrence'] = recurrence + resp = request("POST", url, headers, payload) + return resp['data']['event'] + + def batch_create_records(self, access_token: str, app_token: str, table_id: str, records: List[dict]): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_create" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + payload = { + 'records': records + } + resp = request("POST", url, headers, payload) + return resp['data']['records'] + + def get_fields_list(self, access_token: str, app_token: str, table_id: str, view_id: str=None, page_token: str=None, page_size: int=None): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/fields" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + params = {} + if view_id: params['view_id'] = view_id + if page_token: params['page_token'] = page_token + if page_size: params['page_size'] = page_size + resp = request("GET", url, headers, params=params) + return resp['data'] + + def update_field(self, access_token: str, app_token: str, table_id: str, field_id: str, field_name: str, type: int, property: dict=None): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/fields/{field_id}" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + payload = { + 'field_name': field_name, + 'type': type, + } + if property: payload['property'] = property + resp = request("PUT", url, headers, payload) + return resp['data']['field'] + + def get_records_list(self, access_token: str, app_token: str, table_id: str, view_id: str=None, page_token: str=None, page_size: int=None): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + params = {} + if view_id: params['view_id'] = view_id + if page_token: params['page_token'] = page_token + if page_size: params['page_size'] = page_size + resp = request("GET", url, headers, params=params) + return resp['data'] + + def get_tables_list(self, access_token: str, app_token: str, page_token: str=None, page_size: int=None): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + params = {} + if page_token: params['page_token'] = page_token + if page_size: params['page_size'] = page_size + resp = request("GET", url, headers, params=params) + return resp.get('data', {}) + + + def batch_update_records(self, access_token: str, app_token: str, table_id: str, records: List[dict], user_id_type: str=None): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_update" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + payload = { + 'records': records + } + resp = request("POST", url, headers, payload) + return resp['data']['records'] + + def create_table(self, access_token: str, app_token: str, table_name: str=None) -> str: + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + payload = {} + if table_name: payload['table'] = {'name': table_name} + resp = request("POST", url, headers, payload) + return resp['data']['table_id'] + + def add_field(self, access_token: str, app_token: str, table_id: str, field_name: str, type: int, property: dict=None): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/fields" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + payload = { + 'field_name': field_name, + 'type': type + } + if property: payload['property'] = property + resp = request("POST", url, headers, payload) + return resp['data']['field'] + + def batch_get_records(self, access_token: str, app_token: str, table_id: str, record_ids: List[str], user_id_type: str=None, with_shared_url: bool=None, automatic_fields: bool=None): + url = f"{self._host}/open-apis/bitable/v1/apps/{app_token}/tables/{table_id}/records/batch_get" + headers = { + 'Content-Type': 'application/json; charset=utf-8', + 'Authorization': 'Bearer '+access_token, + } + + all_records = [] + # The API supports a maximum of 100 record IDs per request. + for i in range(0, len(record_ids), 100): + chunk = record_ids[i:i + 100] + payload = { + 'record_ids': chunk + } + if user_id_type is not None: payload['user_id_type'] = user_id_type + if with_shared_url is not None: payload['with_shared_url'] = with_shared_url + if automatic_fields is not None: payload['automatic_fields'] = automatic_fields + + resp = request("POST", url, headers, payload) + if resp and 'data' in resp and 'records' in resp['data']: + all_records.extend(resp['data']['records']) + + return all_records + + + + diff --git a/append_bitables.py b/append_bitables.py new file mode 100644 index 0000000..0145c52 --- /dev/null +++ b/append_bitables.py @@ -0,0 +1,130 @@ +# -*- coding: UTF-8 -*- +import api +import config +import logging +import utils +import copy + +LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" +logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) + +def get_all_records(client: api.Client, access_token: str, app_token: str, table_id: str): + records = [] + page_token = None + while True: + resp = client.get_records_list(access_token, app_token, table_id, page_token=page_token) + items = resp.get('items', []) + if items: + records.extend(items) + if resp.get('has_more'): + page_token = resp.get('page_token') + else: + break + return records + + +def get_all_fields(client: api.Client, access_token: str, app_token: str, table_id: str): + fields = [] + page_token = None + while True: + resp = client.get_fields_list(access_token, app_token, table_id, page_token=page_token) + items = resp.get('items', []) + if items: + fields.extend(items) + if resp.get('has_more'): + page_token = resp.get('page_token') + else: + break + return fields + + +def append_to_existing_table(client: api.Client, access_token: str, source_tables: list, target_app_token: str, target_table_id: str): + """ + Appends data from multiple source tables into one existing target table. + Missing fields in the target table will be automatically added. + """ + logging.info(f"Appending records from {len(source_tables)} source tables to target {target_app_token} / {target_table_id}") + + # 1. Read existing Target Table schema + target_fields = get_all_fields(client, access_token, target_app_token, target_table_id) + target_field_map = {f['field_name']: f['field_id'] for f in target_fields} + + # Check if "Source" field exists, if not, create it + if "Source" not in target_field_map: + try: + resp = client.add_field(access_token, target_app_token, target_table_id, "Source", 1, None) + target_field_map["Source"] = resp['field_id'] + except utils.LarkException as e: + logging.error(f"Failed to create 'Source' field: {e}") + + for source_app_token, source_table_id in source_tables: + logging.info(f"Processing source table {source_app_token} / {source_table_id}...") + + # 2. Merge Missing Fields + source_fields = get_all_fields(client, access_token, source_app_token, source_table_id) + for f in source_fields: + name = f['field_name'] + if name not in target_field_map: + ftype = f['type'] + fprop = copy.deepcopy(f.get('property')) + # Clean up option IDs to prevent insertion errors in target table + if fprop and 'options' in fprop: + for opt in fprop['options']: + if 'id' in opt: del opt['id'] + # Sometimes a field might be a reference to another table that isn't copied, so we might want to catch creation errors + try: + resp = client.add_field(access_token, target_app_token, target_table_id, name, ftype, fprop) + target_field_map[name] = resp['field_id'] + except utils.LarkException as e: + logging.error(f"Failed to create field '{name}' in target: {e}") + + # 3. Read & Insert Records + records = get_all_records(client, access_token, source_app_token, source_table_id) + batch = [] + source_name = f"{source_app_token}/{source_table_id}" + for r in records: + original_fields = r.get('fields', {}) + new_fields = {} + for fname, fvalue in original_fields.items(): + if fname in target_field_map: + new_fields[fname] = fvalue + new_fields['Source'] = source_name + batch.append({'fields': new_fields}) + + # Feishu limit is usually 500 per request + CHUNK_SIZE = 500 + for i in range(0, len(batch), CHUNK_SIZE): + chunk = batch[i:i + CHUNK_SIZE] + if chunk: + try: + client.batch_create_records(access_token, target_app_token, target_table_id, chunk) + except utils.LarkException as e: + logging.error(f"Failed to insert chunk of records: {e}") + + logging.info(f"Appended records from {source_name} successfully.") + +if __name__ == "__main__": + client = api.Client(config.LARK_HOST) + + try: + access_token = client.get_tenant_access_token(config.APP_ID, config.APP_SECRET) + except Exception as e: + logging.error(f"Could not get access token: {e}") + exit(1) + + logging.info(f"Using App ID: {config.APP_ID}") + + SOURCE_TABLES = [ + (config.MERGE_SOURCE_APP_TOKEN_1, config.MERGE_SOURCE_TABLE_ID_1), + (config.MERGE_SOURCE_APP_TOKEN_2, config.MERGE_SOURCE_TABLE_ID_2), + # Add more source tables as needed: ("app_token", "table_id") + ] + + TARGET_APP_TOKEN = config.MERGE_TARGET_APP_TOKEN + TARGET_TABLE_ID = "target_table_id_to_append_to" # PLEASE UPDATE THIS TO THE EXISTING MERGED TABLE ID + + is_placeholder = (config.MERGE_SOURCE_APP_TOKEN_1 == "source_app_token_1") + if is_placeholder: + logging.warning("Please update config.py or environment variables with valid MERGE_* tokens to execute.") + else: + append_to_existing_table(client, access_token, SOURCE_TABLES, TARGET_APP_TOKEN, TARGET_TABLE_ID) diff --git a/batch_get_records.py b/batch_get_records.py new file mode 100644 index 0000000..4b829fa --- /dev/null +++ b/batch_get_records.py @@ -0,0 +1,58 @@ +# -*- coding: UTF-8 -*- +import api +import config +import logging +import json + +LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" +logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) + +def main(): + client = api.Client(config.LARK_HOST) + + # Get tenant access token + try: + access_token = client.get_tenant_access_token(config.APP_ID, config.APP_SECRET) + except Exception as e: + logging.error(f"Could not get access token: {e}") + return + + # Use parameters from config or environment + APP_TOKEN = config.DEFAULT_APP_TOKEN + TABLE_ID = config.BATCH_TABLE_ID + + # First, let's get some record IDs to demonstrate batch retrieval + # In a real scenario, you might already have these IDs. + logging.info(f"Fetching some record IDs from App Token: {APP_TOKEN}, Table ID: {TABLE_ID}") + try: + resp = client.get_records_list(access_token, APP_TOKEN, TABLE_ID, page_size=10) + records = resp.get('items', []) + record_ids = [r['record_id'] for r in records] + + if not record_ids: + logging.warning("No records found in the table to perform batch retrieval.") + return + + logging.info(f"Found {len(record_ids)} record IDs. Performing batch retrieval...") + + # Performance batch retrieval + batch_records = client.batch_get_records( + access_token, + APP_TOKEN, + TABLE_ID, + record_ids, + with_shared_url=True, + automatic_fields=True + ) + + logging.info(f"Successfully retrieved {len(batch_records)} records via batch_get.") + + if batch_records: + print("\nExample Batch Retrieved Record (First item):") + print(json.dumps(batch_records[0], ensure_ascii=False, indent=2)) + + except Exception as e: + logging.error(f"Error during batch record retrieval: {e}") + +if __name__ == "__main__": + main() diff --git a/bitable_calendar.py b/bitable_calendar.py new file mode 100644 index 0000000..303a906 --- /dev/null +++ b/bitable_calendar.py @@ -0,0 +1,108 @@ +# -*- coding: UTF-8 -*- +import api +import config +import logging +from datetime import datetime +import utils +from mock import schema, raw_data, name_map + +LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" +logging.basicConfig(format=LOG_FORMAT, level=logging.ERROR) + +import os +logging.info(os.getcwd()) + +def rgb(r: int, g: int, b: int): return (r<<16) + (g<<8) + b + + +def sync_records_to_calendar(client: api.Client, access_token, calendar_id, app_token, table_id): + '''generate calendar events from bitable records''' + + records = client.get_records(access_token, app_token, table_id) + for record in records: + try: + fields = record.get('fields') + version = fields.get(config.KEY_VERSION) + proposal_date = datetime.fromtimestamp(fields.get(config.KEY_STARTUP) / 1000) + delivery_date = datetime.fromtimestamp(fields.get(config.KEY_SUBMIT) / 1000) + expected_online_time = datetime.fromtimestamp(fields.get(config.KEY_GREY) / 1000) + online_time = datetime.fromtimestamp(fields.get(config.KEY_GA) / 1000) + print(version, proposal_date, delivery_date, expected_online_time, online_time) + print(client.create_event(access_token, calendar_id, start_time=proposal_date, end_time=delivery_date, summary=config.FORMATTER_DEV.format(version), color=rgb(221, 88, 24))) + print(client.create_event(access_token, calendar_id, start_time=delivery_date, end_time=expected_online_time, summary=config.FORMATTER_TEST.format(version), color=rgb(85, 220, 101))) + print(client.create_event(access_token, calendar_id, start_time=expected_online_time, end_time=online_time, summary=config.FORMATTER_GREY.format(version), color=rgb(118, 244, 244))) + except TypeError: + pass + + + +def write_bitable(client: api.Client, access_token, raw_data, schema, name_map) -> str: + '''write project release date info to bitable''' + table_id = client.create_table(access_token, config.APP_TOKEN, config.TABLE_NAME) + resp = client.get_fields_list(access_token, config.APP_TOKEN, table_id) + current_fields = resp['items'] + + for index, (field_name, field_type) in enumerate(schema.items()): + try: + if index >= len(current_fields): + print(client.add_field(access_token, config.APP_TOKEN, table_id, name_map[field_name], field_type)) + else: + print(client.update_field( + access_token, + config.APP_TOKEN, + table_id, + current_fields[index]['field_id'], + name_map[field_name], + field_type)) + except utils.LarkException as e: + if e.code == 1254606: # DataNotChange + pass + else: + raise + + resp = client.get_records_list(access_token, config.APP_TOKEN, table_id) + current_records = resp['items'] + if current_records is None: + current_records = [] + + updated_records = [] + created_records = [] + + for index, version_info in enumerate(raw_data): + fields = {} + for field_name, field_value in version_info.items(): + if schema[field_name] == 5: + field_value = datetime.strptime(field_value, '%Y-%m-%d').timestamp() * 1000 + fields[name_map[field_name]] = field_value + if index >= len(current_records): + created_records.append({'fields': fields}) + else: + updated_records.append({'record_id': current_records[index]['record_id'], 'fields': fields}) + if updated_records: + resp = client.batch_update_records(access_token, config.APP_TOKEN, table_id, updated_records) + if created_records: + resp = client.batch_create_records(access_token, config.APP_TOKEN, table_id, created_records) + return table_id + +def bitable_to_calendar(): + '''write project release date info to bitable and sync it to calendar''' + # init api client + client = api.Client(config.LARK_HOST) + + # get tenant access token + access_token = client.get_tenant_access_token(config.APP_ID, config.APP_SECRET) + + # create a new table and push local json-like version iteration data to it + table_id = write_bitable(client, access_token, raw_data, schema, name_map) + + # create new calendar and get its id + calendar = client.create_calendar(access_token, permissions='public', summary=config.SUMMARY) + print(calendar) + calendar_id = calendar['calendar_id'] + + # sync records to calendar + sync_records_to_calendar(client, access_token, calendar_id, config.APP_TOKEN, table_id) + + +if __name__ == "__main__": + bitable_to_calendar() diff --git a/config.py b/config.py new file mode 100644 index 0000000..d45c5cc --- /dev/null +++ b/config.py @@ -0,0 +1,54 @@ +# -*- coding: UTF-8 -*- +import os +from dotenv import load_dotenv, find_dotenv + +load_dotenv(find_dotenv()) + +# load from env +APP_ID = os.getenv("APP_ID") +APP_SECRET = os.getenv("APP_SECRET") +LARK_HOST = os.getenv("LARK_HOST") +APP_TOKEN=os.getenv("APP_TOKEN") + +# AI API Configurations +AI_API_KEY = os.getenv("AI_API_KEY", "sk-orez64WkG1NkfksB5j_hGA") +AI_BASE_URL = os.getenv("AI_BASE_URL", "https://oai.bwgdi.com/v1") +AI_MODEL = os.getenv("AI_MODEL", "MiniMaxAI") + +# merge source and target configs (placeholders) +MERGE_SOURCE_APP_TOKEN_1 = os.getenv("MERGE_SOURCE_APP_TOKEN_1", "source_app_token_1") +MERGE_SOURCE_TABLE_ID_1 = os.getenv("MERGE_SOURCE_TABLE_ID_1", "source_table_id_1") +MERGE_SOURCE_APP_TOKEN_2 = os.getenv("MERGE_SOURCE_APP_TOKEN_2", "source_app_token_2") +MERGE_SOURCE_TABLE_ID_2 = os.getenv("MERGE_SOURCE_TABLE_ID_2", "source_table_id_2") +MERGE_TARGET_APP_TOKEN = os.getenv("MERGE_TARGET_APP_TOKEN", "target_app_token") + +# localization +SUMMARY = '项目版本迭代计划日历' +TABLE_NAME = '项目版本迭代计划表' +KEY_VERSION='迭代版本' +KEY_STARTUP='启动时间' +KEY_SUBMIT='提测时间' +KEY_GREY='灰度时间' +KEY_GA='全量时间' + +FORMATTER_DEV = '版本{}开工' +FORMATTER_TEST = '版本{}测试' +FORMATTER_GREY = '版本{}灰度' + + +import json + +# Web View Tabs Configuration +WEB_VIEW_TABS_ENV = os.getenv("WEB_VIEW_TABS") +if WEB_VIEW_TABS_ENV: + try: + WEB_VIEW_TABS = json.loads(WEB_VIEW_TABS_ENV) + except Exception: + WEB_VIEW_TABS = [] +else: + WEB_VIEW_TABS = [] + +# Default table configuration for single table operations +DEFAULT_APP_TOKEN = os.getenv("DEFAULT_APP_TOKEN", "") +DEFAULT_TABLE_ID = os.getenv("DEFAULT_TABLE_ID", "") +BATCH_TABLE_ID = os.getenv("BATCH_TABLE_ID", "") diff --git a/exec.ps1 b/exec.ps1 new file mode 100644 index 0000000..60033f3 --- /dev/null +++ b/exec.ps1 @@ -0,0 +1,2 @@ +docker build -t bitable-calendar . +docker run --env-file .env -it bitable-calendar diff --git a/exec.sh b/exec.sh new file mode 100644 index 0000000..60033f3 --- /dev/null +++ b/exec.sh @@ -0,0 +1,2 @@ +docker build -t bitable-calendar . +docker run --env-file .env -it bitable-calendar diff --git a/export_web_view.py b/export_web_view.py new file mode 100644 index 0000000..dbf0d32 --- /dev/null +++ b/export_web_view.py @@ -0,0 +1,971 @@ +import api +import config +import logging +import json +from get_records import get_all_records + +LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" + +TABS = config.WEB_VIEW_TABS + + +logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) + +HTML_TEMPLATE = r""" + + + + +Bitable Records - Interactive View + + + +
+

ALL IN AI

+ + +
+ +
+ +
+ Date: + + - + +
+ + + + +
+ +
+
+ +
+
+ + + + +""" + +def build_html(client, access_token): + tab_data_list = [] + + for tab in TABS: + logging.info(f"Fetching records for tab [{tab['name']}]...") + records = get_all_records(client, access_token, tab['app_token'], tab['table_id']) + logging.info(f"Tab [{tab['name']}] retrieved {len(records)} records.") + tab_data_list.append({ + "name": tab['name'], + "records": records + }) + + # Dump records to json + records_json = json.dumps(tab_data_list, ensure_ascii=False) + + final_html = HTML_TEMPLATE.replace('{RECORDS_JSON}', records_json) + final_html = final_html.replace('{DEFAULT_AI_KEY}', config.AI_API_KEY or '') + final_html = final_html.replace('{DEFAULT_AI_URL}', config.AI_BASE_URL or 'https://api.openai.com/v1') + final_html = final_html.replace('{DEFAULT_AI_MODEL}', config.AI_MODEL or 'gpt-4o') + return final_html + +def main(): + client = api.Client(config.LARK_HOST) + + try: + access_token = client.get_tenant_access_token(config.APP_ID, config.APP_SECRET) + except Exception as e: + logging.error(f"Could not get access token: {e}") + return + + final_html = build_html(client, access_token) + + output_file = "outline_view.html" + with open(output_file, "w", encoding="utf-8") as f: + f.write(final_html) + + logging.info(f"Successfully generated HTML view at {output_file}") + print(f"\n✅ HTML view generated! You can open '/home/verachen/Workspace/feishu/bitable_calendar/python/{output_file}' in your browser.") + +if __name__ == "__main__": + main() diff --git a/get_records.py b/get_records.py new file mode 100644 index 0000000..509ccfc --- /dev/null +++ b/get_records.py @@ -0,0 +1,59 @@ +# -*- coding: UTF-8 -*- +import api +import config +import logging +import json + +LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" +logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) + +def get_all_records(client: api.Client, access_token: str, app_token: str, table_id: str): + """Retrieve all records from a given table, handling pagination if necessary.""" + records = [] + page_token = None + while True: + resp = client.get_records_list(access_token, app_token, table_id, page_token=page_token) + items = resp.get('items', []) + if items: + records.extend(items) + if resp.get('has_more'): + page_token = resp.get('page_token') + else: + break + return records + + +if __name__ == "__main__": + client = api.Client(config.LARK_HOST) + + # Get tenant access token + try: + access_token = client.get_tenant_access_token(config.APP_ID, config.APP_SECRET) + except Exception as e: + logging.error(f"Could not get access token: {e}") + exit(1) + + logging.info(f"Using App ID: {config.APP_ID}") + + # Please replace these with your actual app_token and table_id + APP_TOKEN = config.DEFAULT_APP_TOKEN + TABLE_ID = config.DEFAULT_TABLE_ID + + if APP_TOKEN == "your_app_token_here" or TABLE_ID == "your_table_id_here": + logging.warning("Please specify APP_TOKEN and TABLE_ID in the script to get records.") + logging.info("For example, you can edit this script to set APP_TOKEN and TABLE_ID, then run it again.") + else: + logging.info(f"Fetching records for App Token: {APP_TOKEN}, Table ID: {TABLE_ID}") + records = get_all_records(client, access_token, APP_TOKEN, TABLE_ID) + logging.info(f"Successfully retrieved {len(records)} records.") + + # Print the first record as an example of the structure + if records: + print("\nExample Record (First item):") + print(json.dumps(records[0], ensure_ascii=False, indent=2)) + + # Example to use batch_get_records if you have specific record IDs: + # record_ids = [r['record_id'] for r in records[:5]] # get first 5 ids + # if record_ids: + # batch_records = client.batch_get_records(access_token, APP_TOKEN, TABLE_ID, record_ids) + # logging.info(f"Batch GET returned {len(batch_records)} records.") diff --git a/import_bitables.py b/import_bitables.py new file mode 100644 index 0000000..8ef02c1 --- /dev/null +++ b/import_bitables.py @@ -0,0 +1,158 @@ +# -*- coding: UTF-8 -*- +import api +import config +import logging +import utils +import copy +import time + +LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" +logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) + +def get_all_records(client: api.Client, access_token: str, app_token: str, table_id: str): + records = [] + page_token = None + while True: + resp = client.get_records_list(access_token, app_token, table_id, page_token=page_token) + items = resp.get('items', []) + if items: + records.extend(items) + if resp.get('has_more'): + page_token = resp.get('page_token') + else: + break + return records + + +def get_all_fields(client: api.Client, access_token: str, app_token: str, table_id: str): + fields = [] + page_token = None + while True: + resp = client.get_fields_list(access_token, app_token, table_id, page_token=page_token) + items = resp.get('items', []) + if items: + fields.extend(items) + if resp.get('has_more'): + page_token = resp.get('page_token') + else: + break + return fields + + +def get_all_tables(client: api.Client, access_token: str, app_token: str): + tables = [] + page_token = None + while True: + resp = client.get_tables_list(access_token, app_token, page_token=page_token) + items = resp.get('items', []) + if items: + tables.extend(items) + if resp.get('has_more'): + page_token = resp.get('page_token') + else: + break + return tables + + +def copy_single_table(client: api.Client, access_token: str, source_app_token: str, source_table_id: str, target_app_token: str, table_name: str): + logging.info(f"Copying table '{table_name}' ({source_table_id}) from {source_app_token} to {target_app_token}") + + fields = get_all_fields(client, access_token, source_app_token, source_table_id) + + # Create Target Table + target_table_id = client.create_table(access_token, target_app_token, table_name) + + # Get initial default fields set by Feishu + target_initial_fields = get_all_fields(client, access_token, target_app_token, target_table_id) + initial_field_names = {f['field_name']: f['field_id'] for f in target_initial_fields} + + target_field_map = {} + + for f in fields: + name = f['field_name'] + ftype = f['type'] + fprop = copy.deepcopy(f.get('property')) + + # Clean up IDs from options to avoid errors + if fprop and 'options' in fprop: + for opt in fprop['options']: + if 'id' in opt: + del opt['id'] + + if name in initial_field_names: + field_id = initial_field_names[name] + try: + client.update_field(access_token, target_app_token, target_table_id, field_id, name, ftype, fprop) + target_field_map[name] = field_id + except utils.LarkException as e: + logging.error(f"Failed to update default field '{name}': {e}") + else: + try: + resp = client.add_field(access_token, target_app_token, target_table_id, name, ftype, fprop) + target_field_map[name] = resp['field_id'] + except utils.LarkException as e: + logging.error(f"Failed to create field '{name}': {e}") + + # Read Records from Source Table + records = get_all_records(client, access_token, source_app_token, source_table_id) + + target_records = [] + for r in records: + original_fields = r.get('fields', {}) + new_fields = {} + for fname, fvalue in original_fields.items(): + if fname in target_field_map: + new_fields[fname] = fvalue + target_records.append({'fields': new_fields}) + + CHUNK_SIZE = 500 + for i in range(0, len(target_records), CHUNK_SIZE): + chunk = target_records[i:i + CHUNK_SIZE] + if chunk: + try: + client.batch_create_records(access_token, target_app_token, target_table_id, chunk) + except utils.LarkException as e: + logging.error(f"Failed to insert chunk of records: {e}") + + logging.info(f"Successfully copied table '{table_name}' to {target_table_id}!") + return target_table_id + + +def import_all_tables(client: api.Client, access_token: str, source_app_tokens: list, target_app_token: str): + """ + Imports all tables from a list of source bitable Apps into the target App. + """ + for source_app_token in source_app_tokens: + logging.info(f"Fetching tables from source app: {source_app_token}") + try: + tables = get_all_tables(client, access_token, source_app_token) + for table in tables: + source_table_id = table['table_id'] + table_name = table['name'] + copy_single_table(client, access_token, source_app_token, source_table_id, target_app_token, table_name) + except Exception as e: + logging.error(f"Failed to copy tables from {source_app_token}: {e}") + +if __name__ == "__main__": + client = api.Client(config.LARK_HOST) + + try: + access_token = client.get_tenant_access_token(config.APP_ID, config.APP_SECRET) + except Exception as e: + logging.error(f"Could not get access token: {e}") + exit(1) + + logging.info(f"Using App ID: {config.APP_ID}") + + # Setup source and target tokens. You can put multiple source app tokens in a list. + SOURCE_APP_TOKENS = [ + config.MERGE_SOURCE_APP_TOKEN_1, # Or replace with an actual App token string + # "another_source_app_token", + ] + TARGET_APP_TOKEN = config.MERGE_TARGET_APP_TOKEN + + is_placeholder = (config.MERGE_SOURCE_APP_TOKEN_1 == "source_app_token_1") + if is_placeholder: + logging.warning("Please update config.py or environment variables with valid MERGE_* tokens to execute.") + else: + import_all_tables(client, access_token, SOURCE_APP_TOKENS, TARGET_APP_TOKEN) diff --git a/list_test.py b/list_test.py new file mode 100644 index 0000000..9f00738 --- /dev/null +++ b/list_test.py @@ -0,0 +1,39 @@ +import json + +import lark_oapi as lark +from lark_oapi.api.bitable.v1 import * + +APP_ID="cli_a92c04f105f8dcb0" +APP_SECRET="5ogsQbtBQ1fPDvwdsykdecpVLT2EyXZc" + +# SDK 使用说明: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/server-side-sdk/python--sdk/preparations-before-development +# 以下示例代码默认根据文档示例值填充,如果存在代码问题,请在 API 调试台填上相关必要参数后再复制代码使用 +# 复制该 Demo 后, 需要将 "YOUR_APP_ID", "YOUR_APP_SECRET" 替换为自己应用的 APP_ID, APP_SECRET. +def main(): + # 创建client + client = lark.Client.builder() \ + .app_id("APP_ID") \ + .app_secret("APP_SECRET") \ + .log_level(lark.LogLevel.DEBUG) \ + .build() + + # 构造请求对象 + request: ListAppTableRequest = ListAppTableRequest.builder() \ + .page_size(20) \ + .build() + + # 发起请求 + response: ListAppTableResponse = client.bitable.v1.app_table.list(request) + + # 处理失败返回 + if not response.success(): + lark.logger.error( + f"client.bitable.v1.app_table.list failed, code: {response.code}, msg: {response.msg}, log_id: {response.get_log_id()}, resp: \n{json.dumps(json.loads(response.raw.content), indent=4, ensure_ascii=False)}") + return + + # 处理业务结果 + lark.logger.info(lark.JSON.marshal(response.data, indent=4)) + + +if __name__ == "__main__": + main() diff --git a/main.py b/main.py new file mode 100644 index 0000000..7d486fd --- /dev/null +++ b/main.py @@ -0,0 +1,6 @@ +def main(): + print("Hello from python!") + + +if __name__ == "__main__": + main() diff --git a/merge_bitables.py b/merge_bitables.py new file mode 100644 index 0000000..a0e2952 --- /dev/null +++ b/merge_bitables.py @@ -0,0 +1,221 @@ +# -*- coding: UTF-8 -*- +import api +import config +import logging +import utils + +LOG_FORMAT = "%(asctime)s - %(levelname)s - %(message)s" +logging.basicConfig(format=LOG_FORMAT, level=logging.INFO) + +def get_all_records(client: api.Client, access_token: str, app_token: str, table_id: str): + """Retrieve all records from a given table, handling pagination if necessary.""" + records = [] + page_token = None + while True: + resp = client.get_records_list(access_token, app_token, table_id, page_token=page_token) + items = resp.get('items', []) + if items: + records.extend(items) + if resp.get('has_more'): + page_token = resp.get('page_token') + else: + break + return records + + +def get_all_fields(client: api.Client, access_token: str, app_token: str, table_id: str): + """Retrieve all fields from a given table, handling pagination if necessary.""" + fields = [] + page_token = None + while True: + resp = client.get_fields_list(access_token, app_token, table_id, page_token=page_token) + items = resp.get('items', []) + if items: + fields.extend(items) + if resp.get('has_more'): + page_token = resp.get('page_token') + else: + break + return fields + + +def merge_schema(fields1: list, fields2: list): + """ + Merge two lists of field definitions into one unified schema list. + If fields have the same name, we assume they are the same field. + We'll keep the definition from fields1 if there's a collision. + """ + merged_fields = {} + + # Process fields from first table + for f in fields1: + # We use a deep copy-like approach or just assign, but we will mutate properties later + import copy + merged_fields[f['field_name']] = { + 'field_name': f['field_name'], + 'type': f['type'], + 'property': copy.deepcopy(f.get('property')) + } + + # Process fields from second table (add if not exists) + for f in fields2: + import copy + if f['field_name'] not in merged_fields: + merged_fields[f['field_name']] = { + 'field_name': f['field_name'], + 'type': f['type'], + 'property': copy.deepcopy(f.get('property')) + } + else: + # Merge options if both are SingleSelect (3) or MultiSelect (4) + existing = merged_fields[f['field_name']] + if existing['type'] in (3, 4) and existing.get('property', {}).get('options') and f.get('property', {}).get('options'): + existing_names = {opt['name'] for opt in existing['property']['options']} + for opt in f['property']['options']: + if opt['name'] not in existing_names: + existing['property']['options'].append(copy.deepcopy(opt)) + existing_names.add(opt['name']) + + # Clean up option IDs to prevent insertion errors in the target table + for f_name, f_def in merged_fields.items(): + if f_def.get('property') and 'options' in f_def['property']: + for opt in f_def['property']['options']: + if 'id' in opt: + del opt['id'] + + # Add a custom "Source" field + merged_fields['Source'] = { + 'field_name': 'Source', + 'type': 1, # Text type + 'property': None + } + + return list(merged_fields.values()) + + +def merge_bitables( + client: api.Client, + access_token: str, + source_app_token_1: str, + source_table_id_1: str, + source_app_token_2: str, + source_table_id_2: str, + target_app_token: str, + target_table_name: str = "Merged Table" +): + """ + Core function to merge two bitables into a single one. + """ + logging.info(f"Merging {source_app_token_1}/{source_table_id_1} and {source_app_token_2}/{source_table_id_2} into {target_app_token}") + + # 1. Read schema from both tables + logging.info("Reading schema from source tables...") + fields1 = get_all_fields(client, access_token, source_app_token_1, source_table_id_1) + fields2 = get_all_fields(client, access_token, source_app_token_2, source_table_id_2) + + # 2. Merge schema + unified_schema = merge_schema(fields1, fields2) + + # 3. Create Target Table + logging.info(f"Creating target table '{target_table_name}'...") + target_table_id = client.create_table(access_token, target_app_token, target_table_name) + + # The new table will have some default columns. + # Let's get them, so we can update them or ignore them. + target_initial_fields = client.get_fields_list(access_token, target_app_token, target_table_id).get('items', []) + initial_field_names = {f['field_name']: f['field_id'] for f in target_initial_fields} + + # 4. Create Fields in Target Table + logging.info("Creating fields in target table...") + target_field_map = {} # Maps field name to its new field_id in the target table + + for field_def in unified_schema: + name = field_def['field_name'] + ftype = field_def['type'] + fprop = field_def['property'] + + # If the default table creation already made this field (e.g. initial '多行文本' / Text), we can update it or skip + if name in initial_field_names: + field_id = initial_field_names[name] + # Optionally update it the type is different (often default is just 'Text') + client.update_field(access_token, target_app_token, target_table_id, field_id, name, ftype, fprop) + target_field_map[name] = field_id + else: + try: + resp = client.add_field(access_token, target_app_token, target_table_id, name, ftype, fprop) + target_field_map[name] = resp['field_id'] + except utils.LarkException as e: + logging.error(f"Failed to create field '{name}': {e}") + + # 5. Read Records from Source Tables + logging.info("Reading records from source tables...") + records1 = get_all_records(client, access_token, source_app_token_1, source_table_id_1) + records2 = get_all_records(client, access_token, source_app_token_2, source_table_id_2) + + # 6. Map and batch insert records + logging.info("Mapping and inserting records...") + + def process_records(source_records, source_name): + batch = [] + for r in source_records: + original_fields = r.get('fields', {}) + new_fields = {} + for fname, fvalue in original_fields.items(): + if fname in target_field_map: + # In bitable API, it's safer to use the new field_names (or their ids depending on the specific endpoint). + # The get_records endpoint returns field names as keys. The batch_create_records also accepts field names as keys if no 'user_id_type' strictly enforces IDs. + # We'll use the mapped names to be safe. + new_fields[fname] = fvalue + + # Add Source + new_fields['Source'] = source_name + batch.append({'fields': new_fields}) + return batch + + target_records = [] + target_records.extend(process_records(records1, f"{source_app_token_1}/{source_table_id_1}")) + target_records.extend(process_records(records2, f"{source_app_token_2}/{source_table_id_2}")) + + # Feishu batch insert has a limit of 500 per request usually, so we chunk it just in case + CHUNK_SIZE = 500 + for i in range(0, len(target_records), CHUNK_SIZE): + chunk = target_records[i:i + CHUNK_SIZE] + if chunk: + try: + client.batch_create_records(access_token, target_app_token, target_table_id, chunk) + except utils.LarkException as e: + logging.error(f"Failed to insert chunk of records: {e}") + + logging.info(f"Successfully merged into {target_table_id}!") + return target_table_id + +if __name__ == "__main__": + # Example execution using vars from config.py + + client = api.Client(config.LARK_HOST) + + # Get tenant access token + try: + access_token = client.get_tenant_access_token(config.APP_ID, config.APP_SECRET) + except Exception as e: + logging.error(f"Could not get access token: {e}") + exit(1) + + logging.info(f"Using App ID: {config.APP_ID}") + + # These need to be valid app_tokens and table_ids for this script to actually work + is_placeholder = (config.MERGE_SOURCE_APP_TOKEN_1 == "source_app_token_1") + + if is_placeholder: + logging.warning("Please update config.py or environment variables with valid MERGE_* tokens to execute.") + else: + merge_bitables( + client=client, + access_token=access_token, + source_app_token_1=config.MERGE_SOURCE_APP_TOKEN_1, + source_table_id_1=config.MERGE_SOURCE_TABLE_ID_1, + source_app_token_2=config.MERGE_SOURCE_APP_TOKEN_2, + source_table_id_2=config.MERGE_SOURCE_TABLE_ID_2, + target_app_token=config.MERGE_TARGET_APP_TOKEN, + target_table_name="Merged Data" + ) diff --git a/mock.py b/mock.py new file mode 100755 index 0000000..53e0277 --- /dev/null +++ b/mock.py @@ -0,0 +1,40 @@ +from config import KEY_SUBMIT, KEY_GREY, KEY_GA, KEY_STARTUP, KEY_VERSION +schema = { + "version": 1, + "proposal": 5, + "delivery": 5, + "expected": 5, + "online": 5, +} + +name_map = { + "version": KEY_VERSION, + "proposal": KEY_STARTUP, + "delivery": KEY_SUBMIT, + "expected": KEY_GREY, + "online": KEY_GA, +} + +raw_data = [ + { + "version": "1.0.0", + "proposal": '2022-07-20', + "delivery": '2022-7-27', + "expected": '2022-07-28', + "online": '2022-07-29', + }, + { + "version": "1.1.0", + "proposal": '2022-07-30', + "delivery": '2022-08-04', + "expected": '2022-08-05', + "online": '2022-08-07', + }, + { + "version": "1.2.0", + "proposal": '2022-08-09', + "delivery": '2022-08-16', + "expected": '2022-08-16', + "online": '2022-08-16', + } +] \ No newline at end of file diff --git a/outline_view.html b/outline_view.html new file mode 100644 index 0000000..e1d6d5e --- /dev/null +++ b/outline_view.html @@ -0,0 +1,915 @@ + + + + + +Bitable Records - Interactive View + + + +
+

ALL IN AI

+ + +
+ +
+ +
+ Date: + + - + +
+ + + + +
+ +
+
+ +
+
+ + + + diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9c1532e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "python" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "python-dotenv>=1.2.2", + "requests>=2.32.5", +] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..323ef8c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +python-dotenv +requests \ No newline at end of file diff --git a/server.py b/server.py new file mode 100644 index 0000000..2281353 --- /dev/null +++ b/server.py @@ -0,0 +1,76 @@ +# -*- coding: UTF-8 -*- +import http.server +import socketserver +import json +import logging +import time +import os +import sys + +# Import functions from existing scripts +from export_web_view import HTML_TEMPLATE, build_html +from get_records import get_all_records +import api +import config + +PORT = int(os.getenv("PORT", 18080)) +CACHE_DURATION = 60 # Cache data for 60 seconds + +# Globals to store cached HTML +cached_html = None +last_fetch_time = 0 + +class ThreadingHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer): + daemon_threads = True + allow_reuse_address = True + +class FeishuHandler(http.server.SimpleHTTPRequestHandler): + def do_GET(self): + global cached_html, last_fetch_time + + # Serve dynamic HTML at root or outline_view.html + if self.path == '/' or self.path == '/outline_view.html': + self.send_response(200) + self.send_header('Content-type', 'text/html; charset=utf-8') + self.end_headers() + + current_time = time.time() + if cached_html is None or (current_time - last_fetch_time) > CACHE_DURATION: + logging.info("Fetching fresh records from API (Cache expired or missing)...") + try: + client = api.Client(config.LARK_HOST) + access_token = client.get_tenant_access_token(config.APP_ID, config.APP_SECRET) + + final_html = build_html(client, access_token) + + cached_html = final_html.encode('utf-8') + last_fetch_time = current_time + logging.info(f"Successfully generated new HTML view. Cached for {CACHE_DURATION}s.") + except Exception as e: + logging.error(f"Error fetching data: {e}") + if cached_html is None: + error_msg = f"

Internal Server Error: Could not fetch data

{e}

".encode('utf-8') + self.wfile.write(error_msg) + return + + # Write response + self.wfile.write(cached_html) + else: + # Fallback to serving static files for any other paths + super().do_GET() + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") + + server_address = ("0.0.0.0", PORT) + try: + httpd = ThreadingHTTPServer(server_address, FeishuHandler) + logging.info(f"🚀 Server successfully started at http://localhost:{PORT}") + logging.info("You can now access the viewer directly through the browser.") + logging.info("Press Ctrl+C to stop the server.") + httpd.serve_forever() + except KeyboardInterrupt: + logging.info("Shutting down the server...") + sys.exit(0) + except Exception as e: + logging.error(f"Could not start server on port {PORT}: {e}") diff --git a/test_batch_get.py b/test_batch_get.py new file mode 100644 index 0000000..3c3c9ff --- /dev/null +++ b/test_batch_get.py @@ -0,0 +1,44 @@ +# -*- coding: UTF-8 -*- +import unittest +from unittest.mock import MagicMock, patch +import api + +class TestBatchGetChunking(unittest.TestCase): + @patch('api.request') + def test_batch_get_records_chunking(self, mock_request): + # Setup mock client + client = api.Client("https://open.feishu.cn") + access_token = "fake_token" + app_token = "fake_app" + table_id = "fake_table" + + # Create 250 record IDs (should result in 3 chunks: 100, 100, 50) + record_ids = [f"rec_{i}" for i in range(250)] + + # Mock responses + mock_request.side_effect = [ + {'code': 0, 'data': {'records': [{'record_id': f'rec_{i}'} for i in range(0, 100)]}}, + {'code': 0, 'data': {'records': [{'record_id': f'rec_{i}'} for i in range(100, 200)]}}, + {'code': 0, 'data': {'records': [{'record_id': f'rec_{i}'} for i in range(200, 250)]}}, + ] + + # Call the method + results = client.batch_get_records(access_token, app_token, table_id, record_ids) + + # Verify + self.assertEqual(len(results), 250) + self.assertEqual(mock_request.call_count, 3) + + # Verify the chunks were passed correctly + first_call_payload = mock_request.call_args_list[0][0][3] # payload is the 4th positional arg + self.assertEqual(len(first_call_payload['record_ids']), 100) + self.assertEqual(first_call_payload['record_ids'][0], "rec_0") + self.assertEqual(first_call_payload['record_ids'][99], "rec_99") + + last_call_payload = mock_request.call_args_list[2][0][3] + self.assertEqual(len(last_call_payload['record_ids']), 50) + self.assertEqual(last_call_payload['record_ids'][0], "rec_200") + self.assertEqual(last_call_payload['record_ids'][49], "rec_249") + +if __name__ == '__main__': + unittest.main() diff --git a/utils.py b/utils.py new file mode 100644 index 0000000..495e278 --- /dev/null +++ b/utils.py @@ -0,0 +1,34 @@ +# -*- coding: UTF-8 -*- +import json +import logging as logger +import requests +class LarkException(Exception): + def __init__(self, code=0, msg=None): + self.code = code + self.msg = msg + + def __str__(self) -> str: + return "{}:{}".format(self.code, self.msg) + + __repr__ = __str__ + +def request(method, url, headers, payload={}, params=None): + response = requests.request(method, url, headers=headers, json=payload, params=params) + logger.info("URL: " + url) + logger.info("X-Tt-Logid: " + response.headers['X-Tt-Logid']) + logger.info("headers:\n"+json.dumps(headers,indent=2, ensure_ascii=False)) + logger.info("payload:\n"+json.dumps(payload,indent=2, ensure_ascii=False)) + resp = {} + if response.text[0] == '{': + resp = response.json() + logger.info("response:\n"+json.dumps(resp,indent=2, ensure_ascii=False)) + else: + logger.info("response:\n"+response.text) + code = resp.get("code", -1) + if code == -1: + code = resp.get("StatusCode", -1) + if code == -1 and response.status_code != 200: + response.raise_for_status() + if code != 0: + raise LarkException(code=code, msg=resp.get("msg", "")) + return resp \ No newline at end of file diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..1b81676 --- /dev/null +++ b/uv.lock @@ -0,0 +1,110 @@ +version = 1 +revision = 2 +requires-python = ">=3.13" + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "python" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "python-dotenv" }, + { name = "requests" }, +] + +[package.metadata] +requires-dist = [ + { name = "python-dotenv", specifier = ">=1.2.2" }, + { name = "requests", specifier = ">=2.32.5" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] diff --git a/列出数据表.md b/列出数据表.md new file mode 100644 index 0000000..3da2a82 --- /dev/null +++ b/列出数据表.md @@ -0,0 +1,117 @@ +# 列出数据表 + +列出多维表格中的所有数据表,包括其 ID、版本号和名称。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables +HTTP Method | GET +接口频率限制 | [20 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 获取数据表信息(base:table:read)
查看、评论、编辑和管理多维表格(bitable:app)
查看、评论和导出多维表格(bitable:app:readonly) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +app_token | string | 多维表格 App 的唯一标识。不同形态的多维表格,其 `app_token` 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值才是多维表格的 `app_token`。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
**示例值**:"appbcbWCzen6D8dezhoCH2RpMAh" + +### 查询参数 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +page_token | string | 否 | 分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token,下次遍历可采用该 page_token 获取查询结果
**示例值**:tblsRc9GRRXKqhvW +page_size | int | 否 | 分页大小
**示例值**:10
**默认值**:`20`
**数据校验规则**:
- 最大值:`100` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +has_more | boolean | 是否还有更多项 +page_token | string | 分页标记,当 has_more 为 true 时,会同时返回新的 page_token,否则不返回 page_token +total | int | 总数 +items | app.table\[\] | 数据表信息 +table_id | string | 数据表 ID +revision | int | 数据表的版本号。对数据表进行修改时更新,如新增、删除记录,修改数据表名称等,初始为 1,每次更新+1 +name | string | 数据表名称 + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "has_more": false, + "page_token": "tblKz5D60T4JlfcT", + "total": 1, + "items": [ + { + "table_id": "tblKz5D60T4JlfcT", + "revision": 1, + "name": "数据表1" + } + ] + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +200 | 1254000 | WrongRequestJson | 请求体错误 +200 | 1254001 | WrongRequestBody | 请求体错误 +200 | 1254002 | Fail | 内部错误,有疑问可咨询客服 +200 | 1254003 | WrongBaseToken | app_token 错误 +200 | 1254004 | WrongTableId | table_id 错误 +200 | 1254005 | WrongViewId | view_id 错误 +200 | 1254006 | WrongRecordId | 检查 record_id +200 | 1254007 | EmptyValue | 空值 +200 | 1254008 | EmptyView | 空视图 +200 | 1254009 | WrongFieldId | 字段 id 错误 +200 | 1254010 | ReqConvError | 请求错误 +400 | 1254011 | Page size must greater than 0. | 确认page_size参数的值符合要求。 +200 | 1254030 | TooLargeResponse | 响应体过大 +400 | 1254036 | Base is copying, please try again later. | 多维表格副本复制中,稍后重试 +200 | 1254040 | BaseTokenNotFound | app_token 不存在 +200 | 1254041 | TableIdNotFound | table_id 不存在 +200 | 1254042 | ViewIdNotFound | view_id 不存在 +200 | 1254043 | RecordIdNotFound | record_id 不存在 +200 | 1254044 | FieldIdNotFound | field_id 不存在 +200 | 1254060 | TextFieldConvFail | 多行文本字段错误 +200 | 1254061 | NumberFieldConvFail | 数字字段错误 +200 | 1254062 | SingleSelectFieldConvFail | 单选字段错误 +200 | 1254063 | MultiSelectFieldConvFail | 多选字段错误 +200 | 1254064 | DatetimeFieldConvFail | 日期字段错误 +200 | 1254065 | CheckboxFieldConvFail | 复选框字段错误 +200 | 1254066 | UserFieldConvFail | 人员字段有误。原因可能是:
- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配
- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组
- 跨应用传入了 `open_id`。如果跨应用传入 ID,建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用 +200 | 1254067 | LinkFieldConvFail | 关联字段错误 +200 | 1254100 | TableExceedLimit | 数据表或仪表盘数量超限。每个多维表格中,数据表加仪表盘的数量最多为 100 个 +200 | 1254101 | ViewExceedLimit | 视图数量超限, 限制200个 +200 | 1254102 | FileExceedLimit | 超限 +200 | 1254103 | RecordExceedLimit | 记录数量超限, 限制20,000条 +200 | 1254104 | RecordAddOnceExceedLimit | 单次添加记录数量超限, 限制500条 +200 | 1254130 | TooLargeCell | 格子内容过大 +200 | 1254290 | TooManyRequest | 请求过快,稍后重试 +200 | 1254291 | Write conflict | 同一个数据表(table) 不支持并发调用写接口,请检查是否存在并发调用写接口。写接口包括:新增、修改、删除记录;新增、修改、删除字段;修改表单;修改视图等。 +200 | 1254301 | OperationTypeError | 多维表格未开启高级权限或不支持开启高级权限 +403 | 1254302 | The role has no permissions. | 无访问权限, 常由表格开启了高级权限造成, 如果是用应用请求的话,目前有两种方法对应用赋予高级权限,第一种方法为在表格中添加应用为协作者并将应用设置为管理员,第二种方法为在一个用户群中将应用添加为机器人, 并在高级权限的角色中添加该用户群,从而赋予对应的权限。 +200 | 1255001 | InternalError | 内部错误,有疑问可咨询客服 +200 | 1255002 | RpcError | 内部错误,有疑问可咨询客服 +200 | 1255003 | MarshalError | 序列化错误,有疑问可咨询客服 +200 | 1255004 | UmMarshalError | 反序列化错误 +200 | 1255005 | ConvError | 内部错误,有疑问可咨询客服处 +504 | 1255040 | Request timed out, please try again later. | 请求超时,请进行重试 diff --git a/创建多维表格.md b/创建多维表格.md new file mode 100644 index 0000000..3625362 --- /dev/null +++ b/创建多维表格.md @@ -0,0 +1,124 @@ +# 创建多维表格 + +在指定文件夹中创建一个多维表格,包含一个空白的数据表。 + +**注意事项**:要基于模板创建多维表格,可先获取模板多维表格 `app_token` 作为文件 token,再调用[复制文件](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/file/copy)接口创建多维表格。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps +HTTP Method | POST +接口频率限制 | [20 次/分钟](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 创建多维表格(base:app:create)
查看、评论、编辑和管理多维表格(bitable:app) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) +Content-Type | string | 是 | **固定值**:"application/json; charset=utf-8" + +### 请求体 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +name | string | 否 | 多维表格 App 名称。最长为 255 个字符。
**示例值**:"一篇新的多维表格" +folder_token | string | 否 | 多维表格 App 归属文件夹。默认为空,表示多维表格将被创建在云空间根目录。了解如何获取文件夹 Token,参考[如何获取云文档资源相关 Token](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#08bb5df6)。
**注意**:
请确保调用身份拥有在该文件夹中的编辑权限。若应用使用的是 `tenant_access_token` 权限,此处仅可指定应用创建的文件夹。详情参考[如何为应用开通云文档相关资源的权限](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-add-permissions-to-app)。
**示例值**:"fldcnqquW1svRIYVT2Np6Iabcef" +time_zone | string | 否 | 文档时区,详情参考[文档时区介绍](https://feishu.feishu.cn/docx/YKRndTM7VoyDqpxqqeEcd67MnEf)。
**示例值**:"Asia/Macau" + +### 请求体示例 +```json +{ + "name":"一篇新的多维表格", + "folder_token": "fldcnqquW1svRIYVT2Np6Iabcef" +} +``` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +app | app | 返回响应体 +app_token | string | 多维表格的唯一标识 app_token +name | string | 多维表格的名称 +folder_token | string | 多维表格 App 归属文件夹 +url | string | 多维表格 App 的 URL 链接 +default_table_id | string | 默认创建的数据表 ID +time_zone | string | 文档时区 + +### 响应体示例 +```json +{ + "code": 0, + "data": { + "app": { + "app_token": "S404b*****e9PQsYDWYcNryFn0g", + "default_table_id": "tbl********oumSQ", + "folder_token": "fldcnqquW1svRIYVT2Np6Iabcef", + "name": "一篇新的多维表格", + "url": "https://example.feishu.cn/base/S404b*****e9PQsYDWYcNryFn0g" + } + }, + "msg": "success" +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +200 | 1254000 | WrongRequestJson | 请求体错误 +200 | 1254001 | WrongRequestBody | 请求体错误 +200 | 1254002 | Fail | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1254003 | WrongBaseToken | app_token 错误 +200 | 1254004 | WrongTableId | table_id 错误 +200 | 1254005 | WrongViewId | view_id 错误 +200 | 1254006 | WrongRecordId | 检查 record_id +200 | 1254007 | EmptyValue | 空值 +200 | 1254008 | EmptyView | 空视图 +200 | 1254009 | WrongFieldId | 字段 id 错误 +200 | 1254010 | ReqConvError | 请求错误 +200 | 1254025 | InvalidCopyTypes | Copy Type参数无效 +200 | 1254030 | TooLargeResponse | 响应体过大 +400 | 1254031 | InvalidAppName | 名称不能超过255个字符 +400 | 1254036 | Base is copying, please try again later. | 复制多维表格为异步操作,该错误码表示当前多维表格仍在复制中,在复制期间无法操作当前多维表格。需要等待复制完成后再操作 +200 | 1254040 | BaseTokenNotFound | app_token 不存在 +200 | 1254041 | TableIdNotFound | table_id 不存在 +200 | 1254042 | ViewIdNotFound | view_id 不存在 +200 | 1254043 | RecordIdNotFound | record_id 不存在 +200 | 1254044 | FieldIdNotFound | field_id 不存在 +200 | 1254060 | TextFieldConvFail | 多行文本字段错误 +200 | 1254061 | NumberFieldConvFail | 数字字段错误 +200 | 1254062 | SingleSelectFieldConvFail | 单选字段错误 +200 | 1254063 | MultiSelectFieldConvFail | 多选字段错误 +200 | 1254064 | DatetimeFieldConvFail | 日期字段错误 +200 | 1254065 | CheckboxFieldConvFail | 复选框字段错误 +200 | 1254066 | UserFieldConvFail | 人员字段有误。原因可能是:
- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配
- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组
- 跨应用传入了 `open_id`。如果跨应用传入 ID,建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用 +200 | 1254067 | LinkFieldConvFail | 关联字段错误 +200 | 1254100 | TableExceedLimit | 数据表或仪表盘数量超限。每个多维表格中,数据表加仪表盘的数量最多为 100 个 +200 | 1254101 | ViewExceedLimit | 视图数量超限,限制 200 个,包括公共视图、锁定视图和个人视图(即只有视图所有者才能看到的视图) +200 | 1254102 | FileExceedLimit | 文档数量超限 +200 | 1254103 | RecordExceedLimit | 记录数量超限,限制 20,000 条 +200 | 1254104 | RecordAddOnceExceedLimit | 单次添加记录数量超限,限制 500 条 +200 | 1254130 | TooLargeCell | 格子内容过大 +200 | 1254290 | TooManyRequest | 请求过快,稍后重试 +200 | 1254291 | Write conflict | 同一个数据表 (table) 不支持并发调用写接口,请检查是否存在并发调用写接口。写接口包括:新增、修改、删除记录;新增、修改、删除字段;修改表单;修改视图等。 +200 | 1254301 | OperationTypeError | 多维表格未开启高级权限或不支持开启高级权限 +200 | 1254304 | PermNotAllow | 仅企业版和旗舰版飞书支持行列权限 +403 | 1254701 | DriveNodePermNotAllow | 对目标云空间节点没有权限 +404 | 1254702 | DriveNodeNotExist | 云空间节点不存在,检查参数正确性 +400 | 1254800 | InvalidParameter | 参数错误,请根据msg修正后重试 +200 | 1255001 | InternalError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255002 | RpcError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255003 | MarshalError | 序列化错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255004 | UmMarshalError | 反序列化错误 +200 | 1255005 | ConvError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +504 | 1255040 | 请求超时 | 进行重试 diff --git a/复制多维表格.md b/复制多维表格.md new file mode 100644 index 0000000..a210f7b --- /dev/null +++ b/复制多维表格.md @@ -0,0 +1,108 @@ +# 复制多维表格 + +复制一个多维表格,可以指定复制到某个有权限的文件夹下。 + +**注意事项**:当多维表格记录数超 50,000 条可复制上限时,仅可复制多维表格结构。 + +## 前提条件 + +调用此接口前,请确保当前调用身份(tenant_access_token 或 user_access_token)已有多维表格和目标文件夹的阅读、编辑等文档权限,否则接口将返回 HTTP 403 或 400 状态码。了解更多,参考[如何为应用或用户开通云文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/copy +HTTP Method | POST +接口频率限制 | [20 次/分钟](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 复制多维表格(base:app:copy)
查看、评论、编辑和管理多维表格(bitable:app) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) +Content-Type | string | 是 | **固定值**:"application/json; charset=utf-8" + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +app_token | string | 要复制的多维表格 App 的唯一标识。不同形态的多维表格,其 app_token 的获取方式不同,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview)获取。
**示例值**:"AW3Qbtr2cakCnesXzXVbbsrIcVT " + +### 请求体 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +name | string | 否 | 多维表格 App 的名称
**示例值**:"一篇新的多维表格" +folder_token | string | 否 | 了解如何获取文件夹 Token,参考[如何获取云文档资源相关 Token](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#08bb5df6)。
**注意**:
请确保调用身份拥有在该文件夹中的编辑权限。若应用使用的是 `tenant_access_token` 权限,此处仅可指定应用创建的文件夹。详情参考[如何为应用开通云文档相关资源的权限](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-add-permissions-to-app)。
**示例值**:"fldcnqquW1svRIYVT2Np6Iabcef" +without_content | boolean | 否 | 是否复制多维表格中的内容,默认 false,即复制多维表格中的内容。可取值:
* true:不复制
* false:复制
**示例值**:false +time_zone | string | 否 | 文档时区,详情参考[文档时区介绍](https://feishu.feishu.cn/docx/YKRndTM7VoyDqpxqqeEcd67MnEf)。
**示例值**:"Asia/Shanghai" + +### 请求体示例 +```json +{ + "name": "一篇新的多维表格", + "folder_token": "fldcnqquW1svRIYVT2Np6Iabcef", + "without_content": false, + "time_zone": "Asia/Shanghai" +} +``` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +app | app | 返回响应体 +app_token | string | 多维表格的唯一标识 app_token +name | string | 多维表格的名称 +folder_token | string | 多维表格 App 归属文件夹 +url | string | 多维表格 App 的 URL 链接 +time_zone | string | 文档时区 + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "app": { + "app_token": "S404b*****e9PQsYDWYcNryFn0g", + "name": "一篇新的多维表格", + "folder_token": "fldbco*****CIMltVc", + "url": "https://example.feishu.cn/base/S404b*****e9PQsYDWYcNryFn0g", + "time_zone": "" + } + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +400 | 1254000 | WrongRequestJson | 请求体错误 +400 | 1254001 | WrongRequestBody | 请求体错误 +400 | 1254002 | Fail | 内部错误,有疑问可咨询客服 +400 | 1254003 | WrongBaseToken | app_token 错误 +400 | 1254031 | InvalidAppName | 多维表格名称格式错误,长度不超过 100 个字符,不能包含 ? / \ * : [ ] +400 | 1254036 | Base is copying, please try again later. | 多维表格副本复制中,稍后重试 +404 | 1254040 | BaseTokenNotFound | app_token 不存在 +400 | 1254290 | TooManyRequest | 请求过快,稍后重试 +400 | 1254291 | Write conflict | 同一个数据表(table) 不支持并发调用写接口,请检查是否存在并发调用写接口。写接口包括:新增、修改、删除记录;新增、修改、删除字段;修改表单;修改视图等。 +403 | 1254304 | PermNotAllow | 仅企业版和旗舰版飞书支持行列权限 +403 | 1254701 | DriveNodePermNotAllow | 目标文件夹没有权限 +404 | 1254702 | DriveNodeNotExist | 目标文件夹不存在 +400 | 1254800 | InvalidParameter | 参数错误,请根据msg修正后重试 +500 | 1255001 | InternalError | 内部错误,有疑问可咨询客服 +500 | 1255002 | RpcError | 内部错误,有疑问可咨询客服 +500 | 1255003 | MarshalError | 序列化错误,有疑问可咨询客服 +500 | 1255004 | UmMarshalError | 反序列化错误 +500 | 1255005 | ConvError | 内部错误,有疑问可咨询客服处 +504 | 1255040 | 请求超时 | 进行重试 diff --git a/多维表格常见问题.md b/多维表格常见问题.md new file mode 100644 index 0000000..62a77ed --- /dev/null +++ b/多维表格常见问题.md @@ -0,0 +1,194 @@ +# 多维表格常见问题 + +## 1. 如何在多维表格中上传附件? + +**第 1 步**:调用[上传素材](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/upload_all)或[分片上传素材](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/upload_prepare)接口将附件上传至多维表格,获取文件的 file_token。 + +**第 2 步**:调用[新增记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/create)或[更新记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/update)接口,将 file_token 的值传入多维表格中。请求示例如下所示: +file_token 仅支持在当前多维表格中使用,要上传附件至其他多维表格,你仍需要重新上传素材获取新的 file_token。 +```json +{ + "records": [ + { + "fields": { + "附件": [ + { + "file_token": "boxbcCFb2dBwMK9S8kDILk1tayh" + }, + { + "file_token": "boxbcCFb2dBwMK9S8kDILk1tayh" + } + ] + } + }, + { + "fields": { + "附件": [ + { + "file_token": "boxbcCFb2dBwMK9S8kDILk1tayh" + }, + { + "file_token": "boxbcCFb2dBwMK9S8kDILk1tayh" + } + ] + } + } + ] +} +``` + +## 2. 如何下载多维表格中的附件? + +**第 1 步**:调用[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)获取多维表格中附件的 file_token。 + +**第 2 步**:调用[下载素材](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/download)或者[获取素材临时下载链接](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/media/batch_get_tmp_download_url)接口下载附件。 + +若多维表格开启了高级权限,你需要添加 extra 参数作为 URL 查询参数鉴权。你可通过以下方式获取 extra 参数: + +1. 调用[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)接口,响应示例中会返回附件的下载链接,如下所示: + ```json + { + "code": 0, + "data": { + "has_more": false, + "items": [ + { + "fields": { + "附件": [ + { + "file_token": "RSkabsaphoy6yVxK0mGcggabcef", + "name": "74d2c703524489dbabcef.png", + "size": 87123, + "tmp_url": "https://open.feishu.cn/open-apis/drive/v1/medias/batch_get_tmp_download_url?file_tokens=RSkabsaphoy6yVxK0mGcggabcef&extra={"bitablePerm":{"tableId":"tblz8ExGaVhuiSD1","rev":32}}", // 素材临时下载链接对应的 URL 值,需要对此字符串进行 URL 编码 + "type": "image/png", + "url": "https://open.feishu.cn/open-apis/drive/v1/medias/RSkabsaphoy6yVxK0mGcggabcef/download?extra={"bitablePerm":{"tableId":"tblz8ExGaVhuiSD1","rev":32}}" // 下载素材对应的 URL 值,需要对此字符串进行 URL 编码 + } + ] + }, + "record_id": "recbMzD0zT" + } + ], + "total": 1 + }, + "msg": "success" + } + ``` + +2. 构建示例如下所示: + +```JSON + {"bitablePerm":{"tableId":"tblO6OeNZxfabcef","attachments":{"fld32zZi5I":{"rec0BuOHq":["boxbcsQNT0JsmrztOnX530abcef"]}}}} + // 转义后 + https://open.feishu.cn/open-apis/drive/v1/medias/boxbcsQNT0JsmrztOnX530abcef/download?extra=%7B%22bitablePerm%22%3A%7B%22tableId%22%3A%22tblO6OeNZxfabcef%22%2C%22attachments%22%3A%7B%22fld32zZi5I%22%3A%7B%22rec0BuOHq%22%3A%5B%22boxbcsQNT0JsmrztOnX530abcef%22%5D%7D%7D%7D%7D + ``` + +## 3. 调用多维表格相关接口,返回 1254040、1254041、1254042、1254043、1254044 等 NotFound 错误码,该如何解决? + +上述这些错误码表示未找到到标识各类 ID 对应的资源。你可参考以下方法解决: + +1. 确认 ID 是否正确。你可参考[多维表格概述](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview)标获取各类资源的 ID +2. 如果 ID 对应的资源刚创建完成后出现此类报错,可能是遇到了服务器的主从延迟问题。你可尝试等待几秒之后重试。 + +## 4. 调用查询记录接口成功,数据返回为空,但实际多维表格存在数据,该如何解决? + +这一般是由于多维表格开通了高级权限,导致调用身份权限不足。你需给予调用身份数据表的 **可管理** 权限或多维表格的 **可管理** 等权限,再重新调用。具体步骤如下所示: + +- 对用户授予可管理权限,你可在 **多维表格高级权限设置** 中添加用户,为用户开通足够权限。具体可参考飞书帮助中心文档[使用多维表格高级权限](https://www.feishu.cn/hc/zh-CN/articles/588604550568-%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%BB%B4%E8%A1%A8%E6%A0%BC%E9%AB%98%E7%BA%A7%E6%9D%83%E9%99%90)。 + +- 对应用授予可管理权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&lazyload=true&maxWidth=550&width=3278) + +![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?height=728&lazyload=true&maxWidth=550&width=890) + +**注意**: + 在 **添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。 + +- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。 + +## 5. 调用查询记录接口,复选框返回数据为空,如何解决? + +在多维表格中,如果字段为空值,则查询记录接口不返回数据。相应地,如果复选框字段为空值(即用户没有选中和取消选中过该复选框),则查询记录接口也不返回数据。在此场景下,由于空值效果与复选框为 `false` 效果相同,开发者需自行兼容该空值场景。 +## 6. 如何筛选自定义编号类型的自动编号字段? + +要筛选含有固定字符的自动编号字段,需将自定义的固定字符去除,再筛选,否则将返回空数据。如下图,自定义的固定字符为 2024,在调用查询记录接口时,需确保仅 value 的值为自增部分数字,不包含自定义的 2024。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/a8fc72c1c50b1621a18886aba56c0008_PMPNNaXEXe.png?height=488&lazyload=true&maxWidth=350&width=495) + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/0dd311da6db2fa91ad4e3ab2425ad3d7_myNxGtTNGr.png?height=428&lazyload=true&maxWidth=650&width=1594) + +## 7. 调用查询记录接口,如何筛选人员字段? + +对于人员字段,你需在 value 中传入人员的用户 ID。用户 ID 的类型与查询记录接口中查询参数 `user_id_type` 指定的类型一致,默认为 Open ID 类型。以下为筛选人员 ID 为 `ou_00d9ea2d7bcd6b6aa7d71dd88deabcef` 和 `ou_5fdfc3d312b24d28224769baf52abcef` 的请求示例: + +```json +{ + "view_id": "vewfrk8iX4", + "field_names": [ + "创建人" + ], + "filter": { + "conjunction": "and", + "conditions": [ + { + "field_name": "创建人", + "operator": "contains", + "value": [ + "ou_00d9ea2d7bcd6b6aa7d71dd88deabcef", + "ou_5fdfc3d312b24d28224769baf52abcef" + ] + } + ] + } +} +``` +## 8. 查询记录接口是否支持查询特定行,如数据表第 10 行~20 行的数据? + +1. 在多维表格中新增一个自动编号字段,编号类型选择自增数字。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/036152cbc21520eb106eae1e3329bdec_haAdeKitl7.png?height=561&lazyload=true&maxWidth=350&width=518) + +2. 筛选该自动编号。例如要查询第 10~20 行数据,则筛选小于 21、大于 9 的编号,然后调用[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)接口,请求体如下所示: + +```json + { + "view_id": "vewfrk8iX4", + "field_names": [], + "filter": { + "conjunction": "and", + "conditions": [ + { + "field_name": "编号", + "operator": "isGreater", + "value": [ + "9" + ] + }, + { + "field_name": "编号", + "operator": "isLess", + "value": [ + "21" + ] + } + ] + } + } + ``` + +你也可以调用[批量获取记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/batch_get)接口,使用记录的 Record ID 来查询,获取多条记录的数据。 + +## 9. 如何获取多维表格指定数据表的总记录数(或总行数)? + +你可以调用[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)接口,在请求体中将 `conditions` 字段设为空或不设置,以下为请求体示例: + +```json +{ + "view_id": "vewfrk8iX4", // 请替换为实际的 view_id + "filter": { + "conjunction": "and", + "conditions": [] + } +} +``` +若请求成功,响应体中将返回 `total` 字段,即为记录总数。 \ No newline at end of file diff --git a/多维表格概述.md b/多维表格概述.md new file mode 100644 index 0000000..1125b1b --- /dev/null +++ b/多维表格概述.md @@ -0,0 +1,272 @@ +# 多维表格概述 +多维表格(Base)是全新的业务管理工具,帮助用户重构工作应用和团队协同模式,高效在线协同数据,随心构建个性化应用,轻松掌控全盘业务数据,和团队一起创造效率的无限可能。多维表格可以是一个表格,也可以是无数个应用。它拥有强大的底层开放能力,你可以通过多维表格 API 轻松打通内部其他业务系统,让业务数据通畅流转,实时同步。 +## 典型案例 + +开放平台提供了集成多维表格能力的客户实践案例: + +- [芝麻开门,在飞书五天打造门禁管理系统](https://open.feishu.cn/solutions/detail/wiseasy) +- [飞书集成平台,让企业服务行业项目管理与销售体验升级](https://open.feishu.cn/solutions/detail/cloudwise) +## 接入流程 + +接入多维表格 OpenAPI 的流程如下图所示。了解更多,参考[云文档概述](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/docs-overview) 的 **接入流程** 一节。 +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/2bf59a67945d4fa8ec04de95d7e60fc9_hpiU9OyoGD.png?height=546&lazyload=true&width=6382) + +## 开发教程 +体验以下多维表格相关教程,了解如何运用多维表格 API 助力企业高效协作。 + +- [快速调用一个服务端 API(以创建多维表格接口为例)](https://open.feishu.cn/document/uAjLw4CM/uMzNwEjLzcDMx4yM3ATM/call-a-server-api-base-example/introduction) +- [快速接入多维表格](https://open.feishu.cn/document/home/quick-access-to-base/preparation) +- [多维表格管理敏捷项目](https://open.feishu.cn/document/home/agile-project-cycle-management-based-on-bitable/introduction) + +## 鉴权说明 +使用 tenant_access_token 访问多维表格资源之前,请确保你的应用已经是多维表格的所有者或者协作者,否则会调用失败。 +你可通过添加文档应用的方式将应用添加为协作者,详情参考[开通文档、电子表格等其它云文档资源权限](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-add-permissions-to-app#223459af);或通过应用身份创建一篇多维表格,再使用 tenant_access_token 来调用接口。 + +## 使用限制 + +使用多维表格接口,整体有以下限制或说明: +- 对于接口的批量操作,单次最高为 1,000 条记录,且响应状态是全部成功或者失败,不存在部分成功或失败的结果。 +- 为保证稳定性,建议对单一多维表格同时只请求**一次** API 写操作。 +单一多维表格中,各个资源的数量限制如下所示: + +| **资源** | **最大支持数量** | +| --------- | ---------- | +| 记录 | 不同租户的最大支持数量不同,开放平台没有额外限制。你可以在多维表格数据表 UI 中点击查看。 ![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/5bafd33990c42fcc8ef5d8c4c5760375_FzucbOdzz5.png?height=583&lazyload=true&width=1028) | +| 字段 | 300,对于公式类型的字段,最多支持 100 个 | | +| 视图 | 200 | +| 数据表+仪表盘 | 100 | +| 高级权限自定义角色 | 30 | +| 高级权限协作者 | 200 | + +## 资源介绍 + +多维表格开放了多维表格 App、数据表、视图、记录、字段、仪表盘、高级权限等多种资源的接口。本小节介绍这几类资源的含义。了解更多多维表格的概念和使用说明,可参考飞书帮助中心文档[快速上手多维表格](https://www.feishu.cn/hc/zh-CN/articles/697278684206-%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B%E5%A4%9A%E7%BB%B4%E8%A1%A8%E6%A0%BC)。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/c0e8e232f2057b25462b2f7704b2626c_MfUbOGXKne.png?height=7223&lazyload=true&maxWidth=700&width=12841) + +### 多维表格 app + +一个多维表格可以理解成是一个应用(app,但不是在开发者后台创建的应用),标记该应用的唯一标识为 `app_token`。作为一个应用,多维表格有多种形态:可以作为一个独立应用,也可以作为一个模块(block)与文档、电子表格结合。 + +#### 多维表格形态 + +| **多维表格形态** | **资源定义** | **含义** | +| ---------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 文件夹中的多维表格 | Base app | 存储在飞书云空间(云盘)文件夹中的多维表格。URL 以 **feishu.cn/base** 开头![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_Xc87lb1mIE.png?height=766&lazyload=true&width=3004) | +| 知识库下的多维表格 | Base app 和 wiki node | 放置在知识库中的多维表格。URL 以 **feishu.cn/wiki** 开头![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/bb2e4afcd9a67d6fac88dd959efbf8f5_W8ma47dqcz.png?height=408&lazyload=true&width=1410) | +| 文档嵌入多维表格 | Base docx block | 即在"文档"中插入的多维表格,URL 以 **feishu.cn/docx** 开头![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/d6a6091dbd3db056d1e4126651116f00_rLR5ryVX5P.png?height=808&lazyload=true&width=1620) | +| 电子表格嵌入多维表格 | Base sheet block | 在电子表格中嵌入的多维表格,URL 以 **feishu.cn/sheets** 开头![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/3859f7bdf1f00878d2077175e34a09c1_O5wDuyRIFB.png?height=496&lazyload=true&width=1689) | + +#### 多维表格 app_token 获取方式 + +不同形态的多维表格,其 `app_token` 的获取方式不同,具体如下所示。 + +##### **文件夹中的多维表格** + +该类多维表格的 app_token 为 URL 下图高亮部分: + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_sTn7sVvhOB.png?height=766&lazyload=true&maxWidth=700&width=3004) + +##### **知识库下的多维表格** + +需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取该类多维表格的 app_token。如下返回示例,当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值 `AW3Qbtr2cakCnesXzXVbbsrIcVT` 是多维表格的 `app_token`: +```json +{ + "node":{ + "space_id":"6946843325487912356", + "node_token":"wikcnKQ1k3p******8Vabcef", + "obj_token":"AW3Qbtr2cakCnesXzXVbbsrIcVT", // 多维表格的 app_token + "obj_type":"bitable", + "parent_node_token":"wikcnKQ1k3p******8Vabcef", + "node_type":"origin", + "origin_node_token":"wikcnKQ1k3p******8Vabcef", + "origin_space_id":"6946843325487912356", + "has_child":false, + "title":"标题", + "obj_create_time":"1642402428", + "obj_edit_time":"1642402428", + "node_create_time":"1642402428", + "creator":"ou_xxxxx", + "owner":"ou_xxxxx" + } +} +``` + +##### **文档嵌入多维表格** + +文档中嵌入的多维表格,需要调用文档相关接口获取多维表格的 `app_token`。 +调用[获取文档所有块](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/document-docx/docx-v1/document-block/list),在返回结果中检索,其中 `bitable.token` 字段的值 `AW3Qbtr2cakCnesXzXVbbsrIcVT_tblkIYhz52o6G5nx`是用 `_` 隔开的 `app_token` 和 `table_id`; +```json +{ + "bitable": { + "token": "AW3Qbtr2cakCnesXzXVbbsrIcVT_tblkIYhz52o6G5nx" + }, + "block_id": "Mgeadqo4CoeoOGxI7Lgb4GNicEd", + "block_type": 18, + "parent_id": "YUqpdO2eLo7xJdxy5RQbuQBdctf" +} +``` + +##### **电子表格嵌入多维表格** + +电子表格中嵌入的多维表格,需要调用电子表格相关接口获取多维表格的 `app_token`。 +若电子表格中嵌有多维表格,需调用[获取表格元数据](https://open.feishu.cn/document/ukTMukTMukTM/uETMzUjLxEzM14SMxMTN),在返回结果中将返回 `blockInfo`。其中,当 `blockType` 的值为 `BITABLE_BLOCK` 时,blockToken 字段的值`AW3Qbtr2cakCnesXzXVbbsrIcVT_tblkIYhz52o6G5nx` 是用 `_` 隔开的 `app_token` 和 `table_id`。 +```json +{ + "blockInfo": { + "blockToken": "AW3Qbtr2cakCnesXzXVbbsrIcVT_tblkIYhz52o6G5nx", + "blockType": "BITABLE_BLOCK" + }, + "columnCount": 0, + "frozenColCount": 0, + "frozenRowCount": 0, + "index": 0, + "rowCount": 0, + "sheetId": "***", + "title": "*** " +} +``` + +### 数据表 table + +多维表格的数据容器,一个多维表格中至少有一个数据表(table),也可能有多个数据表。每个数据表都有唯一标识 `table_id`。`table_id` 在一个多维表格 App 中唯一,在全局不一定唯一。 +你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的唯一标识。你也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976) + +### 视图 view + +指数据的汇总和展现形式。视图有多种类型,包括表格视图、看板视图、画册视图、甘特视图和表单视图等,可参考飞书帮助中心文档[视图类型](https://www.feishu.cn/hc/zh-CN/articles/360049067931-%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%BB%B4%E8%A1%A8%E6%A0%BC%E8%A7%86%E5%9B%BE#tabs0|lineguid-6kjqG)。一个数据表至少有一个视图,可能有多个视图。每个视图都有唯一标识 `view_id`,`view_id` 在一个多维表格中唯一,在全局不一定唯一。 +你可通过多维表格 URL 获取 `view_id`,下图高亮部分即为当前视图的唯一标识。你也可通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取 `view_id`。暂时无法获取到嵌入到文档中的多维表格的 `view_id`。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_c76RMwZAgW.png?height=748&lazyload=true&maxWidth=700&width=2998) + +#### **表单视图 form** + +表单视图是多维表格的一种视图类型,形式类似于问卷,可以用来收集信息和数据。每个表单都有唯一标识 `form_id`,即当前视图的 `view_id`。`form_id` 的获取方式和 `view_id` 的获取方式相同。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/b8d9bd2d7e1ca7d0cc955ef7f1f4fe16_zDd5TqOh3Q.png?height=942&lazyload=true&maxWidth=600&width=1327) + +### 记录 record + +数据表中的每一行数据都是一条记录(record)。每条记录都有唯一标识 `record_id`,`record_id` 在一个多维表格中唯一,在全局不一定唯一。`record_id` 需要通过[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)接口获取。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/abc84b39be159ccdcafa707ee141144d_3fcTz7mMP5.png?height=503&lazyload=true&maxWidth=700&width=1536) + +### 字段 field + +即多维表格的“列”,多维表格提供丰富的字段类型。每个字段都有唯一标识 `field_id`,`field_id` 在一个多维表格内唯一,在全局不一定唯一。`field_id` 需要通过[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)接口获取。了解更多字段说明,参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6fb2359552ed15433289fbd0d9fc53c1_mGnTo91OJa.png?height=656&lazyload=true&maxWidth=700&width=1170) + +### 仪表盘 block + +仪表盘与数据看板类似,可以从不同的维度统计对多维表格中的数据进行统计。了解更多,参考飞书帮助中心文档[使用多维表格仪表盘](https://www.feishu.cn/hc/zh-CN/articles/161059314076-%E4%BD%BF%E7%94%A8%E5%A4%9A%E7%BB%B4%E8%A1%A8%E6%A0%BC%E4%BB%AA%E8%A1%A8%E7%9B%98)。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/fae3c8a886a0075fdeeefe5b74f274e5_WepjCfS7Hx.png?height=1490&lazyload=true&maxWidth=600&width=2794) + +仪表盘的唯一标识为 `block_id`,以 `blk` 开头,你可通过多维表格 URL 获取 `block_id`,下图高亮部分即为当前仪表盘的唯一标识,也可通过[列出仪表盘](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-dashboard/list)接口获取。 + +![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/a966d15323ee73c66b1e9a31d34ae6c7_x3ctncH2nO.png?height=575&lazyload=true&maxWidth=600&width=1397) + +### 高级权限 + +高级权限允许用户针对单一数据表设置哪些用户可以查看、编辑指定的行,或是设置针对某用户可以编辑的列。高级权限接口分为 **自定义角色** 和 **协作者** 两部分,多维表格的 **所有者** 或者 **有可管理权限** 的用户可通过接口设置高级权限,管理高级权限的协作者。了解更多,参考[高级权限概述](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/advanced-permission-guide)。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9a6f4194ee269de14c8ee1f077e5f782_mp94yLqGBE.png?height=760&lazyload=true&maxWidth=500&width=871) + +#### **自定义角色 role** + +在高级权限中添加角色并设置权限,该角色即为自定义角色。每个自定义角色都有唯一标识 `role_id`。`role_id` 需要通过[列出自定义角色](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/list)接口获取。 + +![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/3be8cd2e40f0f981370bc87251dafebd_i1xBoWPh2Z.png?height=761&lazyload=true&maxWidth=500&width=880) + +#### **协作者 member** + +高级权限设置中,一个自定义角色(role)中的成员,即为协作者(member)。每个协作者都有唯一标识 `member_id`。`member_id` 需要通过[列出协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/list)接口获取。 + +### 自动化流程 workflows + +自动化流程是用户给多维表格设定的自动运行规则。设定“触发条件”和“执行操作”以后,多维表格会根据数据变更,自动执行下一步操作。你可通过[列出自动化流程](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-workflow/list)获取自动化流程的 ID,即 `workflow_id`。 + +## 方法列表 +以下为多维表格的方法列表。其中,“商店”代表[商店应用](https://open.feishu.cn/document/home/app-types-introduction/overview);“自建”代表[企业自建应用](https://open.feishu.cn/document/home/app-types-introduction/overview),了解更多应用相关信息,参考[应用类型简介](https://open.feishu.cn/document/home/app-types-introduction/overview)。了解调用服务端 API 的流程,参考[流程概述](https://open.feishu.cn/document/uMzNwEjLzcDMx4yM3ATM/ugzNwEjL4cDMx4CO3ATM)。 + +### 多维表格 app + +**[方法 (API)](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)** | 权限要求(满足任一) | **[访问凭证](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)(选择其一)** | 商店 | 自建 +---|---|---|---|--- +[创建多维表格](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/create)
`POST` /open-apis/bitable/v1/apps | 创建多维表格(base:app:create)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[复制多维表格](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/copy)
`POST` /open-apis/bitable/v1/apps/:app_token/copy | 复制多维表格(base:app:copy)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[获取多维表格元数据](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/get)
`GET` /open-apis/bitable/v1/apps/:app_token | 查看、评论和导出多维表格(bitable:app:readonly)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[更新多维表格元数据](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/update)
`PUT` /open-apis/bitable/v1/apps/:app_token | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** + +### 数据表 table + +**[方法 (API)](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)** | 权限要求(满足任一) | **[访问凭证](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)(选择其一)** | 商店 | 自建 +---|---|---|---|--- +[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)
`GET` /open-apis/bitable/v1/apps/:app_token/tables | 查看、评论和导出多维表格(bitable:app:readonly)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[新增数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/create)
`POST` /open-apis/bitable/v1/apps/:app_token/tables | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[新增多个数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/batch_create)
`POST` /open-apis/bitable/v1/apps/:app_token/tables/batch_create | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[删除数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/delete)
`DELETE` /open-apis/bitable/v1/apps/:app_token/tables/:table_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[删除多个数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/batch_delete)
`POST` /open-apis/bitable/v1/apps/:app_token/tables/batch_delete | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** + +### 视图 view + +**[方法 (API)](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)** | 权限要求(满足任一) | **[访问凭证](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)(选择其一)** | 商店 | 自建 +---|---|---|---|--- +[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)
`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views | 查看、评论和导出多维表格(bitable:app:readonly)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[新增视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/create)
`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[更新视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/patch)
`PATCH` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views/:view_id | 查看、评论和导出多维表格(bitable:app:readonly)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[检索视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/get)
`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views/:view_id | 检索视图(base:view:read)
查看、评论、编辑和管理多维表格(bitable:app)
查看、评论和导出多维表格(bitable:app:readonly) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[删除视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/delete)
`DELETE` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views/:view_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** + +### 记录 record + +**[方法 (API)](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)** | 权限要求(满足任一) | **[访问凭证](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)(选择其一)** | 商店 | 自建 +---|---|---|---|--- +[列出记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/list)
`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records | 查看、评论和导出多维表格(bitable:app:readonly)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[检索记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/get)
`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/:record_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[新增记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/create)
`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[新增多条记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/batch_create)
`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_create | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[更新记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/update)
`PUT` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/:record_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[更新多条记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/batch_update)
`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_update | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[删除记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/delete)
`DELETE` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/:record_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[删除多条记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/batch_delete)
`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_delete | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** + +### 字段 field + +**[方法 (API)](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)** | 权限要求(满足任一) | **[访问凭证](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)(选择其一)** | 商店 | 自建 +---|---|---|---|--- +[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)
`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/fields | 查看、评论和导出多维表格(bitable:app:readonly)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[新增字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/create)
`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/fields | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[更新字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/update)
`PUT` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/fields/:field_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[删除字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/delete)
`DELETE` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/fields/:field_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** + +### 仪表盘 dashboard + +**[方法 (API)](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)** | 权限要求(满足任一) | **[访问凭证](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)(选择其一)** | 商店 | 自建 +---|---|---|---|--- +[复制仪表盘](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-dashboard/copy)
`POST` /open-apis/bitable/v1/apps/:app_token/dashboards/:block_id/copy | 复制仪表盘(base:dashboard:copy)
查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[列出仪表盘](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-dashboard/list)
`GET` /open-apis/bitable/v1/apps/:app_token/dashboards | 获取仪表盘信息(base:dashboard:read)
查看、评论、编辑和管理多维表格(bitable:app)
查看、评论和导出多维表格(bitable:app:readonly) | `tenant_access_token`
`user_access_token` | **✓** | **✓** + +### 自定义角色 role + +**[方法 (API)](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)** | 权限要求(满足任一) | **[访问凭证](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)(选择其一)** | 商店 | 自建 +---|---|---|---|--- +[列出自定义权限](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/list)
`GET` /open-apis/bitable/v1/apps/:app_token/roles | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[新增自定义权限](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/create)
`POST` /open-apis/bitable/v1/apps/:app_token/roles/:role_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[更新自定义权限](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/update)
`PUT` /open-apis/bitable/v1/apps/:app_token/roles/:role_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[删除自定义权限](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/delete)
`DELETE` /open-apis/bitable/v1/apps/:app_token/roles/:role_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** + +### 协作者 member + +高级权限下的协作者。 + +**[方法 (API)](https://open.feishu.cn/document/ukTMukTMukTM/uITNz4iM1MjLyUzM)** | 权限要求(满足任一) | **[访问凭证](https://open.feishu.cn/document/ukTMukTMukTM/uMTNz4yM1MjLzUzM)(选择其一)** | 商店 | 自建 +---|---|---|---|--- +[列出协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/list)
`GET` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[新增协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/create)
`POST` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[删除协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/delete)
`DELETE` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/:member_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[批量新增协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/batch_create)
`POST` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/batch_create | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** +[批量删除协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/batch_delete)
`DELETE` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/batch_delete | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`
`user_access_token` | **✓** | **✓** diff --git a/批量获取记录.md b/批量获取记录.md new file mode 100644 index 0000000..829efbf --- /dev/null +++ b/批量获取记录.md @@ -0,0 +1,296 @@ +# 批量获取记录 + +通过多个记录 ID 查询记录信息。该接口最多支持查询 100 条记录。 + +## 注意事项 + +若多维表格开启了高级权限,你需确保调用身份拥有多维表格的可管理权限,否则可能出现调用成功但返回数据为空的情况。了解具体步骤,参考[如何为应用或用户开通文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_get +HTTP Method | POST +接口频率限制 | [20 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 检索特定记录(base:record:read)
查看、评论、编辑和管理多维表格(bitable:app)
查看、评论和导出多维表格(bitable:app:readonly) +字段权限要求 | **注意事项**:该接口返回体中存在下列敏感字段,仅当开启对应的权限后才会返回;如果无需获取这些字段,则不建议申请
获取用户基本信息(contact:user.base:readonly)
以应用身份访问通讯录(contact:contact:access_as_app)
读取通讯录(contact:contact:readonly)
以应用身份读取通讯录(contact:contact:readonly_as_app) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) +Content-Type | string | 是 | **固定值**:"application/json; charset=utf-8" + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +app_token | string | 多维表格 App 的唯一标识。不同形态的多维表格,其 `app_token` 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值才是多维表格的 `app_token`。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
**示例值**:"NQRxbRkBMa6OnZsjtERcxhabcef"
**数据校验规则**:
- 长度范围:`0` ~ `100` 字符 +table_id | string | 多维表格数据表的唯一标识。获取方式:
- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`
- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976)
**示例值**:"tbl0xe5g8PPabcef"
**数据校验规则**:
- 长度范围:`0` ~ `50` 字符 + +### 请求体 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +record_ids | string\[\] | 是 | 记录 ID 列表。调用[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)获取。
**示例值**:["recyO3299N"]
**数据校验规则**:
- 长度范围:`1` ~ `100` +user_id_type | string | 否 | 此次调用中使用的用户 id 的类型
**示例值**:"open_id"
**可选值有**:
- user_id:以user_id来识别用户
- union_id:以union_id来识别用户
- open_id:以open_id来识别用户 +with_shared_url | boolean | 否 | 是否返回记录的分享链接。可选值:
- true:返回分享链接
- false:不返回分享链接
**默认值**:false
**示例值**:true +automatic_fields | boolean | 否 | 是否返回自动计算的字段。可选值:
- true:返回自动计算的字段
- false:不返回自动计算的字段
**默认值**:false
**示例值**:true + +### 请求体示例 +```json +{ + "record_ids": [ + "recyOaMB2F", + "rec111111", + "recyOaMB2F" + ], + "user_id_type": "open_id", + "with_shared_url": true, + "automatic_fields": true +} +``` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +records | app.table.record\[\] | 记录列表 +fields | map<string, union> | 记录字段 +record_id | string | 记录 ID +created_by | person | 创建人信息 +id | string | 创建人 ID。ID 类型与请求体中的 `user_id_type` 指定的类型一致。 +name | string | 创建人中文姓名 +en_name | string | 创建人英文姓名 +email | string | 创建人邮箱 +avatar_url | string | 创建人头像链接
**字段权限要求(满足任一)**:
获取用户基本信息(contact:user.base:readonly)
以应用身份访问通讯录(contact:contact:access_as_app)
读取通讯录(contact:contact:readonly)
以应用身份读取通讯录(contact:contact:readonly_as_app) +created_time | int | 创建时间。毫秒级时间戳。 +last_modified_by | person | 修改人信息 +id | string | 修改人 ID。ID 类型与请求体中的 `user_id_type` 指定的类型一致。 +name | string | 修改人中文姓名 +en_name | string | 修改人英文姓名 +email | string | 修改人邮箱 +avatar_url | string | 修改人头像链接
**字段权限要求(满足任一)**:
获取用户基本信息(contact:user.base:readonly)
以应用身份访问通讯录(contact:contact:access_as_app)
读取通讯录(contact:contact:readonly)
以应用身份读取通讯录(contact:contact:readonly_as_app) +last_modified_time | int | 最近更新时间。毫秒级时间戳。 +shared_url | string | 记录分享链接 +record_url | string | 记录链接(检索记录接口将返回该字段,本接口不返回) +forbidden_record_ids | string\[\] | 禁止访问的记录列表(针对开启了高级权限的多维表格) +absent_record_ids | string\[\] | 不存在的记录列表 + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "forbidden_record_ids": [ + "recyOaMB2F" + ], + "absent_record_ids": [ + "rec111111" + ], + "records": [ + { + "created_by": { + "avatar_url": "https://internal-api-lark-file.feishu.cn/static-resource/v1/06d568cb-f464-4c2e-bd03-76512c545c5j~?image_size=72x72&cut_type=default-face&quality=&format=jpeg&sticker_format=.webp", + "email": "", + "en_name": "Min Zhang", + "id": "ou_92945f86a98bba075174776959c90eda", + "name": "张敏" + }, + "created_time": 1691049973000, + "fields": { + "人员": [ + { + "avatar_url": "https://internal-api-lark-file.feishu.cn/static-resource/v1/b2-7619-4b8a-b27b-c72d90b06a2j~?image_size=72x72&cut_type=default-face&quality=&format=jpeg&sticker_format=.webp", + "email": "minzhang.leben@bytedance.com", + "en_name": "Min Zhang", + "id": "ou_2910013f1e6456f16a0ce75ede950a0a", + "name": "张敏" + }, + { + "avatar_url": "https://internal-api-lark-file.feishu.cn/static-resource/v1/v2_q86-fcb6-4f18-85c7-87ca8881e50j~?image_size=72x72&cut_type=default-face&quality=&format=jpeg&sticker_format=.webp", + "email": "minzhang.00@bytedance.com", + "en_name": "Min Zhang", + "id": "ou_e04138c9633dd0d2ea166d79f548ab5d", + "name": "张敏" + } + ], + "修改人": [ + { + "avatar_url": "https://internal-api-lark-file.feishu.cn/static-resource/v1/06d568cb-f464-4c2e-bd03-76512c545c5j~?image_size=72x72&cut_type=default-face&quality=&format=jpeg&sticker_format=.webp", + "email": "", + "en_name": "Min Zhang", + "id": "ou_92945f86a98bba075174776959c90eda", + "name": "张敏" + } + ], + "创建人": [ + { + "avatar_url": "https://internal-api-lark-file.feishu.cn/static-resource/v1/06d568cb-f464-4c2e-bd03-76512c545c5j~?image_size=72x72&cut_type=default-face&quality=&format=jpeg&sticker_format=.webp", + "email": "", + "en_name": "Min Zhang", + "id": "ou_92945f86a98bba075174776959c90eda", + "name": "张敏" + } + ], + "创建时间": 1691049973000, + "单向关联": { + "link_record_ids": [ + "recnVYsuqV" + ] + }, + "单选": "选项1", + "双向关联": { + "link_record_ids": [ + "recqLvMaXT", + "recrdld32q" + ] + }, + "地理位置": { + "address": "东长安街", + "adname": "东城区", + "cityname": "北京市", + "full_address": "天安门广场,北京市东城区东长安街", + "location": "116.397755,39.903179", + "name": "天安门广场", + "pname": "北京市" + }, + "复选框": true, + "多行文本": [ + { + "text": "多行文本内容1", + "type": "text" + }, + { + "mentionNotify": false, + "mentionType": "User", + "name": "张敏", + "text": "@张敏", + "token": "ou_2910013f1e6456f16a0ce75ede950a0a", + "type": "mention" + } + ], + "多选": [ + "选项1", + "选项2" + ], + "数字": 2323.2323, + "日期": 1690992000000, + "最后更新时间": 1702455191000, + "条码": [ + { + "text": "123", + "type": "text" + } + ], + "电话号码": "131xxxx6666", + "自动编号": "17", + "群组": [ + { + "avatar_url": "https://internal-api-lark-file.feishu-boe.cn/static-resource/v1/v2_c8d2cd50-ba29-476f-b7f1-5b5917cb18ej~?image_size=72x72&cut_type=&quality=&format=jpeg&sticker_format=.webp", + "id": "oc_cd07f55f14d6f4a4f1b51504e7e97f48", + "name": "武侠聊天组" + } + ], + "评分": 3, + "货币": 1, + "超链接": { + "link": "https://bitable.feishu.cn", + "text": "飞书多维表格官网" + }, + "进度": 0.66, + "附件": [ + { + "file_token": "Vl3FbVkvnowlgpxpqsAbBrtFcrd", + "name": "飞书.jpeg", + "size": 32975, + "tmp_url": "https://open.feishu.cn/open-apis/drive/v1/medias/batch_get_tmp_download_url?file_tokens=Vl3FbVk11owlgpxpqsAbBrtFcrd&extra=%7B%22bitablePerm%22%3A%7B%22tableId%22%3A%22tblBJyX6jZteblYv%22%2C%22rev%22%3A90%7D%7D", + "type": "image/jpeg", + "url": "https://open.feishu.cn/open-apis/drive/v1/medias/Vl3FbVk11owlgpxpqsAbBrtFcrd/download?extra=%7B%22bitablePerm%22%3A%7B%22tableId%22%3A%22tblBJyX6jZteblYv%22%2C%22rev%22%3A90%7D%7D" + } + ] + }, + "last_modified_by": { + "avatar_url": "https://internal-api-lark-file.feishu.cn/static-resource/v1/06d568cb-f464-4c2e-bd03-76512c545c5j~?image_size=72x72&cut_type=default-face&quality=&format=jpeg&sticker_format=.webp", + "email": "", + "en_name": "Min Zhang", + "id": "ou_92945f86a98bba075174776959c90eda", + "name": "张敏" + }, + "last_modified_time": 1702455191000, + "record_id": "recyOaMB2F", + "shared_url": "https://example.feishu.cn/record/KBcNrNtpWePAlscCvdmb6ZcSc5b" + } + ] + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +400 | 1254000 | WrongRequestJson | 请求体错误。请检查请求参数 +400 | 1254001 | WrongRequestBody | 请求体错误。请检查请求参数 +500 | 1254002 | Fail | 导致报 1254002 错误码的场景较多,请参考以下建议排查:
- 如果单次操作的内容变更较大,请尝试在单次操作中减少数据量
- 如果你并发调用了接口,请尝试控制请求间隔,稍后重试
- 如果在知识库(wiki)中创建多维表格,请检查你是否使用了知识库[创建知识空间节点](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space-node/create)接口创建多维表格。在此场景下不能使用[创建多维表格](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/create)接口
- 请检查接口参数是否有误。例如,在分页查询多维表格时,传递了无效的 page_token,或传递了错误的数据表的 table_id
- 如果该报错偶尔发生,可能是服务器超时或不稳定,请重试解决 +400 | 1254003 | WrongBaseToken | app_token 错误。app_token 是多维表格 App 的唯一标识。不同形态的多维表格,其 `app_token` 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值才是多维表格的 `app_token`。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。 +400 | 1254004 | WrongTableId | table_id 错误。table_id 是多维表格数据表的唯一标识。获取方式:
- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`
- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976) +400 | 1254005 | WrongViewId | view_id 错误。view_id 是多维表格中视图的唯一标识。获取方式:
- 在多维表格的 URL 地址栏中,`view_id` 是下图中高亮部分:
![view_id.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_DJMgVH9x2S.png?height=748&lazyload=true&width=2998)
- 通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取。暂时无法获取到嵌入到云文档中的多维表格的 `view_id`。
**注意**:
当 `filter` 参数 或 `sort` 参数不为空时,请求视为对数据表中的全部数据做条件过滤,指定的 `view_id` 会被忽略。 +400 | 1254006 | WrongRecordId | record_id 错误。record_id 是数据表中一条记录的唯一标识。通过[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)接口获取 +400 | 1254007 | EmptyValue | 空值 +400 | 1254008 | EmptyView | 空视图 +400 | 1254009 | WrongFieldId | field_id 错误。field_id 是数据表中一个字段的唯一标识。通过[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)接口获取 +400 | 1254010 | ReqConvError | 请求错误 +400 | 1254011 | Page size must greater than 0. | page_size参数非法 +400 | 1254016 | InvalidSort | Sort参数错误 +400 | 1254018 | InvalidFilter | filter 参数错误。请参考[记录过滤参数填写指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/record-filter-guide)了解如何填写 filter 参数。 +400 | 1254024 | InvalidFieldNames | field_names 参数错误。请检查接口中字段名称和多维表格中的字段名称是否完全匹配。如果难以排查,建议你调用[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)接口获取字段名称,因为根据表格页面的 UI 名称可能会忽略空格、换行或特殊符号等差异 +400 | 1254030 | InvalidPageToken | 分页游标错误 +400 | 1254036 | Bitable is copying, please try again later. | 复制多维表格为异步操作,该错误码表示当前多维表格仍在复制中,在复制期间无法操作当前多维表格。需要等待复制完成后再操作 +404 | 1254040 | BaseTokenNotFound | app_token 不存在。不同形态的多维表格,其 `app_token` 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值才是多维表格的 `app_token`。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。 +404 | 1254041 | TableIdNotFound | table_id 不存在。获取方式:
- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`
- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976) +404 | 1254042 | ViewIdNotFound | view_id 不存在。获取方式:
- 在多维表格的 URL 地址栏中,`view_id` 是下图中高亮部分:
![view_id.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_DJMgVH9x2S.png?height=748&lazyload=true&width=2998)
- 通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取。暂时无法获取到嵌入到云文档中的多维表格的 `view_id`。
**注意**:
当 `filter` 参数 或 `sort` 参数不为空时,请求视为对数据表中的全部数据做条件过滤,指定的 `view_id` 会被忽略。 +404 | 1254043 | RecordIdNotFound | record_id 不存在。record_id 是数据表中一条记录的唯一标识。请通过[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)接口获取。 +404 | 1254044 | FieldIdNotFound | field_id 不存在。field_id 是数据表中一个字段的唯一标识。通过[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)接口获取。 +404 | 1254045 | FieldNameNotFound | 字段名称不存在。请检查:
- 接口中字段名称和多维表格中的字段名称是否完全匹配。如果难以排查,建议你调用[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)接口获取字段名称,因为根据表格页面的 UI 名称可能会忽略空格、换行或特殊符号等差异。
- 表格是否开启了高级权限但调用身份缺少对应字段的权限。你需要为调用身份授予高级权限:
- 对用户授予高级权限,你需要在多维表格页面右上方 **分享** 入口为当前用户添加可管理权限。![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/df3911b4f747d75914f35a46962d667d_dAsfLjv3QC.png?height=546&lazyload=true&maxWidth=550)
- 对应用授予高级权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&maxWidth=550&width=3278)
![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?maxWidth=550)
**注意**:
在 **添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。
- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。 +500 | 1254060 | TextFieldConvFail | 多行文本字段错误 +500 | 1254061 | NumberFieldConvFail | 数字字段错误 +500 | 1254062 | SingleSelectFieldConvFail | 单选字段错误 +500 | 1254063 | MultiSelectFieldConvFail | 多选字段错误 +500 | 1254064 | DatetimeFieldConvFail | 日期字段错误 +500 | 1254065 | CheckboxFieldConvFail | 复选框字段错误 +500 | 1254066 | UserFieldConvFail | 人员字段有误。原因可能是:
- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配
- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组
- 跨应用传入了 `open_id`。如果跨应用传入 ID,建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用
- 若想对人员字段传空,可传 null +500 | 1254067 | LinkFieldConvFail | 关联字段错误 +500 | 1254068 | URLFieldConvFail | 超链接字段错误 +500 | 1254069 | AttachFieldConvFail | 附件字段错误 +400 | 1254072 | Failed to convert phone field, please make sure it is correct. | 电话字段错误 +400 | 1254100 | TableExceedLimit | 数据表或仪表盘数量超限。每个多维表格中,数据表加仪表盘的数量最多为 100 个 +400 | 1254101 | ViewExceedLimit | 视图数量超限, 限制 200 个 +400 | 1254102 | FileExceedLimit | 文件数量超限 +400 | 1254103 | RecordExceedLimit | 记录数量超限, 限制 20,000 条 +400 | 1254104 | RecordAddOnceExceedLimit | 单次添加记录数量超限, 单次调用最多更新 1,000 条记录 +400 | 1254107 | FilterLengthExceedLimit | Filter长度超限, 限制 2,000 个字符 +400 | 1254108 | SortLengthExceedLimit | Sort 长度超限, 限制 1,000 个字符 +400 | 1254109 | FormulaTableSizeExceedLimit | 公式表大小超限 +400 | 1254130 | TooLargeCell | 格子内容过大 +400 | 1254290 | TooManyRequest | 请求过快,稍后重试 +400 | 1254291 | LockNotObtainedError | 在同一个数据表中,并发调用了读写接口或请求过快,出现冲突。请参考以下建议解决:
- 确保没有并发调用多维表格读写相关接口
- 若操作量较大,建议在接口与接口之间增加 0.5 或 1 秒的延迟,也可在报错中增加重试逻辑,确保业务的稳定性
- 对于写接口,可以将接口中的查询参数 `ignore_consistency_check` 设置为 true,表示在读写操作时,暂时忽略一致性检查,以提高性能 +400 | 1254301 | OperationTypeError | 多维表格未开启高级权限或不支持开启高级权限 +400 | 1254302 | RolePermNotAllow | 无访问权限,常由表格开启了高级权限造成。请确保当前调用身份具有高级权限或多维表格的管理权限:
- 对于应用身份,你需要通过云文档网页页面右上方 「**...**」->「**...更多**」-> 「**添加文档应用**」入口添加并授予应用可管理权限,或在高级权限设置中添加一个包含应用的群组,给予这个群读写权限
- 对于用户身份,你需要通过云文档网页的「**分享**」入口授予用户管理权限
了解更多,参考[如何为应用或用户开通云文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。 +400 | 1254303 | AttachPermNotAllow | 附件无权限 +500 | 1255001 | InternalError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +500 | 1255002 | RpcError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +500 | 1255003 | MarshalError | 序列化错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +500 | 1255004 | UmMarshalError | 反序列化错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +500 | 1255005 | ConvError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +504 | 1255040 | Request timed out, please try again later | 请求超时 +500 | 1254607 | Data not ready, please try again later | 该报错一般是由于前置操作未执行完成,或本次操作数据太大,服务器计算超时导致。遇到该错误码时,建议等待一段时间后重试。通常有以下几种原因:
- **编辑操作频繁**:开发者对多维表格的编辑操作非常频繁。可能会导致由于等待前置操作处理完成耗时过长而超时的情况。多维表格底层对数据表的处理基于版本维度的串行方式,不支持并发。因此,并发请求时容易出现此类错误,不建议开发者对单个数据表进行并发请求。
- **批量操作负载重**:开发者在多维表格中进行批量新增、删除等操作时,如果数据表的数据量非常大,可能会导致单次请求耗时过长,最终导致请求超时。建议开发者适当降低批量请求的 page_size 以减少请求耗时。
- **资源分配与计算开销**:资源分配是基于单文档维度的,如果读接口涉及公式计算、排序等计算逻辑,会占用较多资源。例如,并发读取一个文档下的多个数据表也可能导致该文档阻塞。 diff --git a/批量获取评论.md b/批量获取评论.md new file mode 100644 index 0000000..f9b816e --- /dev/null +++ b/批量获取评论.md @@ -0,0 +1,159 @@ +# 批量获取评论 + +该接口用于根据评论 ID 列表批量获取云文档评论信息,包括评论和回复 ID、回复的内容、评论人和回复人的用户 ID 等。支持返回全局评论以及局部评论(可通过 is_whole 字段区分)。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/drive/v1/files/:file_token/comments/batch_query +HTTP Method | POST +接口频率限制 | [100 次/分钟](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 获取云文档中的评论(docs:document.comment:read)
查看、评论、编辑和管理云空间中所有文件(drive:drive)
查看、评论和下载云空间中所有文件(drive:drive:readonly) +字段权限要求 | **注意事项**:该接口返回体中存在下列敏感字段,仅当开启对应的权限后才会返回;如果无需获取这些字段,则不建议申请
获取用户 user ID(contact:user.employee_id:readonly) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) +Content-Type | string | 是 | **固定值**:"application/json; charset=utf-8" + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +file_token | string | 文档 Token
**示例值**:"doxbcdl03Vsxhm7Qmnj110abcef" + +### 查询参数 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +file_type | string | 是 | 云文档类型
**示例值**:docx
**可选值有**:
- doc:旧版文档类型,已不推荐使用
- docx:新版文档类型
- sheet:电子表格类型
- file:文件类型
- slides:幻灯片 +user_id_type | string | 否 | 用户 ID 类型
**示例值**:open_id
**可选值有**:
- open_id:标识一个用户在某个应用中的身份。同一个用户在不同应用中的 Open ID 不同。[了解更多:如何获取 Open ID](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-openid)
- union_id:标识一个用户在某个应用开发商下的身份。同一用户在同一开发商下的应用中的 Union ID 是相同的,在不同开发商下的应用中的 Union ID 是不同的。通过 Union ID,应用开发商可以把同个用户在多个应用中的身份关联起来。[了解更多:如何获取 Union ID?](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-union-id)
- user_id:标识一个用户在某个租户内的身份。同一个用户在租户 A 和租户 B 内的 User ID 是不同的。在同一个租户内,一个用户的 User ID 在所有应用(包括商店应用)中都保持一致。User ID 主要用于在不同的应用间打通用户数据。[了解更多:如何获取 User ID?](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-user-id)
**默认值**:`open_id`
**当值为 `user_id`,字段权限要求**:
获取用户 user ID(contact:user.employee_id:readonly) + +### 请求体 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +comment_ids | string\[\] | 是 | 需要获取数据的评论 ID ,可通过调用获取云文档所有评论接口获取 comment_id
**示例值**:["1654857036541812356"] + +### 请求体示例 +```json +{ + "comment_ids": [ + "1654857036541812356" + ] +} +``` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +items | file.comment\[\] | 评论的相关信息、回复的信息、回复分页的信息 +comment_id | string | 评论 ID +user_id | string | 用户 ID +create_time | int | 创建时间 +update_time | int | 更新时间 +is_solved | boolean | 是否已解决 +solved_time | int | 解决评论时间 +solver_user_id | string | 解决评论者的用户 ID +has_more | boolean | 是否有更多回复 +page_token | string | 回复分页标记 +is_whole | boolean | 是否是全文评论 +quote | string | 局部评论的引用字段 +reply_list | reply_list | 评论里的回复列表 +replies | file.comment.reply\[\] | 回复列表 +content | reply_content | 回复内容 +elements | reply_element\[\] | 回复的内容 +type | string | 回复的内容元素
**可选值有**:
- text_run:普通文本
- docs_link:at 云文档链接
- person:at 联系人 +text_run | text_run | 文本内容 +text | string | 回复 普通文本 +docs_link | docs_link | 添加云文档链接 +url | string | 回复 at 云文档 +person | person | 添加用户的 user_id +user_id | string | 添加用户的 user_id 以@用户 +reply_id | string | 回复 ID +user_id | string | 用户 ID +create_time | int | 创建时间 +update_time | int | 更新时间 +extra | reply_extra | 回复的其他内容,图片 Token 等 +image_list | string\[\] | 评论中的图片 Token list + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "items": [ + { + "comment_id": "6916106822734512356", + "user_id": "ou_cc19b2bfb93f8a44db4b4d6eababcef", + "create_time": 1610281603, + "update_time": 1610281603, + "is_solved": false, + "solved_time": 1610281603, + "solver_user_id": "null", + "has_more": false, + "page_token": "6916106822734512356", + "is_whole": true, + "quote": "划词评论引用内容", + "reply_list": { + "replies": [ + { + "content": { + "elements": [ + { + "type": "text_run", + "text_run": { + "text": "comment text" + }, + "docs_link": { + "url": "https://example.feishu.cn/docs/doccnHh7U87HOFpii5u5Gabcef" + }, + "person": { + "user_id": "ou_cc19b2bfb93f8a44db4b4d6eababcef" + } + } + ] + }, + "reply_id": "6916106822734512356", + "user_id": "ou_cc19b2bfb93f8a44db4b4d6eab2abcef", + "create_time": 1610281603, + "update_time": 1610281603, + "extra": { + "image_list": [ + "xfsfseewewabcef" + ] + } + } + ] + } + } + ] + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +400 | 1069301 | fail | 重试,若稳定失败请联系相关业务方oncall人员 +400 | 1069302 | param error | 检查参数有效性 +403 | 1069303 | forbidden | 检查是否有待评论云文档的评论权限 +400 | 1069304 | docs had been deleted | 检查待评论云文档是否已被删除 +400 | 1069305 | docs not exist | 检查待评论云文档是否能正常访问 +400 | 1069306 | content review not pass | 排查评论内容是否存在不合法内容 +404 | 1069307 | not exist | 检查待评论云文档是否能正常访问、检查评论内容at人或云文档是否存在 +400 | 1069308 | exceeded limit | 评论数据超过上限限制,详情请咨询客服 +400 | 1069399 | internal error | 重试,若稳定失败请联系相关业务方oncall人员 +400 | 1064230 | locked for data migration | 数据迁移中,暂时无法上传。 diff --git a/数据结构概述.md b/数据结构概述.md new file mode 100644 index 0000000..9b297b2 --- /dev/null +++ b/数据结构概述.md @@ -0,0 +1,80 @@ +# 数据结构概述 + +本文档介绍多维表格数据表中记录、字段和视图等的数据结构。多维表格中的数据表由记录(record)和字段(field)组成, 同时可以拥有多个视图(view)。 + +## 记录 +记录由 record 和 fields 两个结构组成。 + +### record 结构 + +record 是一个 object 结构类型。 +| 参数 | 数据类型 | 描述 | +| --------- | ------- | --------- | +|`record_id`| string | 记录的 ID | +|`fields`| map | 记录的字段 | + +### fields 结构 + +fields 字段为 map 型,由字段名称和其具体内容的键值对组成。了解 fields 详细结构和参数描述,参考[多维表格记录数据结构](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/bitable-record-data-structure-overview)。 +```json +{ + "任务情况总结": [ + { + "text": "网站更新任务由黄泡泡负责,正在进行中", + "type": "text" + } + ] +} +``` + +参数 | 数据类型 | 描述 | 示例值 +---|---|---|--- +key | string | 多维表格数据表中的字段名称。 | "任务情况总结" +value | union | 某个字段的具体内容,其结构可以是数字、字符串、布尔型、字符串列表或对象列表。详情参考下文。 | 该示例值为对象列表,更多示例,参考[多维表格记录数据结构](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/bitable-record-data-structure-overview)。
```json
[
{
"text": "网站更新任务正在进行中",
"type": "text"
}
]
``` + +## 字段 + +字段即多维表格数据表中的“列”,是一个`object`结构类型。字段的基本结构如下所示。了解字段详细结构和参数描述,参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。 + +```json +{ + "field_id": "fldYWaldeW", // 字段的 ID + "field_name": "文本", // 字段名称 + "type": 1, // 字段的类型,详情参考下文 + "description": "字段的描述", // 对字段的更多说明 + "is_primary": true, // 该字段是否是初始的索引字段 + "property": null, // 字段的属性,详情参考下文 + "ui_type": "Text", // 字段在界面上的展示类型,例如进度字段是数字的一种展示形态 + "is_hidden": false // 字段是否是隐藏字段 +} +``` +参数描述如下所示: + +名称 | 类型 | 描述 +---|---|--- +field_id | string | 字段 ID +field_name | string | 字段名称 +type | int | 字段类型:相同类型用ui_type区分:
- 1:文本(默认值)、条码(需声明 "ui_type": "Barcode")、邮箱(需声明"ui_type": "Email")
- 2:数字(默认值)、进度(需声明 "ui_type": "Progress")、货币(需声明 "ui_type": "Currency")、评分(需声明 "ui_type": "Rating")
- 3:单选
- 4:多选
- 5:日期
- 7:复选框
- 11:人员
- 13:电话号码
- 15:超链接
- 17:附件
- 18:单向关联
- 19:查找引用
- 20:公式
- 21:双向关联
- 22:地理位置
- 23:群组
- 24:流程(不支持通过写接口新增或编辑,仅支持读接口)
- 1001:创建时间
- 1002:最后更新时间
- 1003:创建人
- 1004:修改人
- 1005:自动编号
- 3001:按钮(不支持通过写接口新增或编辑,仅支持读接口) +description | 字段的描述 | 对字段的更多说明。 +is_primary | true/false | 该字段是否是初始的索引字段。 +property | object | 字段属性,因字段类型而异。详情参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。 +ui_type | string | 字段的 UI 类型:
- "Text":文本
- "Email":邮箱
- "Barcode":条码
- "Number":数字
- "Progress":进度
- "Currency":货币
- "Rating":评分
- "SingleSelect":单选
- "MultiSelect":多选
- "DateTime":日期
- "Checkbox":复选框
- "User":人员
- "GroupChat":群组
- "Phone":电话号码
- "Url":超链接
- "Attachment":附件
- "SingleLink":单向关联
- "Formula":公式
- "Lookup": 查找引用
- "DuplexLink":双向关联
- "Location":地理位置
- "CreatedTime":创建时间
- "ModifiedTime":最后更新时间
- "CreatedUser":创建人
- "ModifiedUser":修改人
- "AutoNumber":自动编号
- "Button":按钮 +is_hidden | true/false | 字段是否是隐藏字段。 + +## 视图 + +视图是一个 object 结构类型。 + +参数 | 类型 | 描述 +---|---|--- +view_id | string | 视图 ID。`view_id` 在一个多维表格中唯一,在全局不一定唯一。获取方式:
- 你可通过多维表格 URL 获取 `view_id`,下图高亮部分即为当前视图的唯一标识。
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_c76RMwZAgW.png?height=748&lazyload=true&maxWidth=700&width=2998)
- 你也可通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取 `view_id`。暂时无法获取到嵌入到文档中的多维表格的 `view_id`。 +view_name | string | 视图名称 +view_type | string | 视图类型,支持以下类型,默认为 grid 类型。
- grid:表格视图
- kanban:看板视图
- gallery:画册视图
- gantt:甘特视图
- form:表单视图 + +## 自定义数据结构 + +### delete_record +| 参数 | 数据类型 | 描述 | +| --------- | --------------- | ----------- | +|`deleted` | `boolean` | 是否删除成功 | +|`record_id` | `string` | 单条记录的 ID | \ No newline at end of file diff --git a/新增一个数据表.md b/新增一个数据表.md new file mode 100644 index 0000000..7f183e7 --- /dev/null +++ b/新增一个数据表.md @@ -0,0 +1,189 @@ +# 新增一个数据表 + +新增一个数据表,支持传入数据表名称、视图名称和字段。 + +## 前提条件 + +调用此接口前,请确保当前调用身份(tenant_access_token 或 user_access_token)已有多维表格的编辑等文档权限,否则接口将返回 HTTP 403 或 400 状态码。了解更多,参考[如何为应用或用户开通文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。 + +## 使用限制 + +每个多维表格中,数据表与仪表盘的总数量上限为 100。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables +HTTP Method | POST +接口频率限制 | [10 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 新增数据表(base:table:create)
查看、评论、编辑和管理多维表格(bitable:app) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) +Content-Type | string | 是 | **固定值**:"application/json; charset=utf-8" + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +app_token | string | 多维表格 App 的唯一标识。不同形态的多维表格,其 app_token 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 app_token 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 obj_type 的值为 bitable 时,obj_token 字段的值才是多维表格的 app_token。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
**示例值**:"appbcbWCzen6D8dezhoCH2RpMAh"
**数据校验规则**:
- 最小长度:`1` 字符 + +### 请求体 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +table | req_table | 否 | 数据表 +name | string | 否 | 数据表名称。该字段必填。
**注意**:
- 名称中的首尾空格将会被默认去除
- 数据表名称不可以包含 `/ \ ? * : [ ]` 等特殊字符
**示例值**:"一个新的数据表"
**数据校验规则**:
- 长度范围:`1` ~ `100` 字符 +default_view_name | string | 否 | 默认表格视图的名称。
注意:
- 名称中的首尾空格将会被去除
- 名称中不允许包含 [ ] 两个字符
**示例值**:"表格视图" +fields | app.table.create_header\[\] | 否 | 数据表的初始字段。了解如何填写字段,参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。
**注意**:
- 如果传入了 `default_view_name` 字段,则必须传入 `fields` 字段
- 如果不传 `default_view_name` 字段,则 `fields` 字段为可选字段
- 若 `default_view_name` 字段和 `fields` 字段都不传,将会创建一个仅包含索引字段的空数据表。
- 数据表的第一个字段为索引字段。索引字段仅支持以下类型:
- 1:多行文本
- 2:数字
- 5:日期
- 13:电话号码
- 15:超链接
- 20:公式
- 22:地理位置
**数据校验规则**:
- 长度范围:`1` ~ `300` +field_name | string | 是 | 字段名称
**示例值**:"问题描述" +type | int | 是 | 字段类型。不支持新增 19 查找引用字段类型。
**示例值**:1
**可选值有**:
- 1:文本
- 2:数字
- 3:单选
- 4:多选
- 5:日期
- 7:复选框
- 11:人员
- 13:电话号码
- 15:超链接
- 17:附件
- 18:单向关联
- 20:公式
- 21:双向关联
- 22:地理位置
- 23:群组
- 1001:创建时间
- 1002:最后更新时间
- 1003:创建人
- 1004:修改人
- 1005:自动编号 +ui_type | string | 否 | 字段在界面上的展示类型,例如 Progress 进度字段是数字的一种展示形态
**示例值**:"Progress"
**可选值有**:
- Text:文本
- Barcode:条码
- Number:数字
- Progress:进度
- Currency:货币
- Rating:评分
- SingleSelect:单选
- MultiSelect:多选
- DateTime:日期
- Checkbox:复选框
- User:人员
- GroupChat:群组
- Phone:电话号码
- Url:超链接
- Attachment:附件
- SingleLink:单向关联
- Formula:公式
- DuplexLink:双向关联
- Location:地理位置
- CreatedTime:创建时间
- ModifiedTime:最后更新时间
- CreatedUser:创建人
- ModifiedUser:修改人
- AutoNumber:自动编号 +property | app.table.field.property | 否 | 字段属性 +options | app.table.field.property.option\[\] | 否 | 单选、多选字段的选项信息 +name | string | 否 | 选项名
**示例值**:"红色" +id | string | 否 | 选项 ID,创建时不可指定 ID
**示例值**:"optKl35lnG" +color | int | 否 | 选项颜色,详情参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。
**示例值**:0
**数据校验规则**:
- 取值范围:`0` ~ `54` +formatter | string | 否 | 数字、公式字段的显示格式。详情参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。
**示例值**:"0" +date_formatter | string | 否 | 日期、创建时间、最后更新时间字段的显示格式。默认为 "yyyy/MM/dd",详情参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。
**示例值**:"2021/01/30" +auto_fill | boolean | 否 | 日期字段中新纪录自动填写创建时间。默认为 false
**示例值**:false +multiple | boolean | 否 | 人员字段中允许添加多个成员,单向关联、双向关联中允许添加多个记录
**示例值**:false +table_id | string | 否 | 单向关联、双向关联字段中关联的数据表的id
**示例值**:"tblsRc9GRRXKqhvW" +table_name | string | 否 | 单向关联、双向关联字段中关联的数据表的名字
**示例值**:"table2" +back_field_name | string | 否 | 双向关联字段中关联的数据表中对应的双向关联字段的名字
**示例值**:"table1-双向关联" +auto_serial | app.field.property.auto_serial | 否 | 自动编号类型 +type | string | 是 | 自动编号类型
**示例值**:"auto_increment_number"
**可选值有**:
- custom:自定义编号
- auto_increment_number:自增数字 +options | app.field.property.auto_serial.options\[\] | 否 | 自动编号规则列表 +type | string | 是 | 自动编号的可选规则项类型
**示例值**:"created_time"
**可选值有**:
- system_number:自增数字位,value范围1-9
- fixed_text:固定字符,最大长度:20
- created_time:创建时间,支持格式 "yyyyMMdd"、"yyyyMM"、"yyyy"、"MMdd"、"MM"、"dd" +value | string | 是 | 与自动编号的可选规则项类型相对应的取值
**示例值**:"yyyyMMdd" +location | app.field.property.location | 否 | 地理位置输入方式 +input_type | string | 是 | 地理位置输入限制
**示例值**:"not_limit"
**可选值有**:
- only_mobile:只允许移动端上传
- not_limit:无限制 +formula_expression | string | 否 | 公式字段的表达式
**示例值**:"bitable::$table[tblNj92WQBAasdEf].$field[fldMV60rYs]*2" +allowed_edit_modes | allowed_edit_modes | 否 | 字段支持的编辑模式 +manual | boolean | 否 | 是否允许手动录入
**示例值**:true +scan | boolean | 否 | 是否允许移动端录入
**示例值**:true +min | number(float) | 否 | 进度、评分等字段的数据范围最小值
**示例值**:0 +max | number(float) | 否 | 进度、评分等字段的数据范围最大值
**示例值**:10 +range_customize | boolean | 否 | 进度等字段是否支持自定义范围
**示例值**:true +currency_code | string | 否 | 货币币种
**示例值**:"CNY" +rating | rating | 否 | 评分字段的相关设置 +symbol | string | 否 | 评分字段的符号展示
**示例值**:"star" +description | app.table.field.description | 否 | 字段的描述 +disable_sync | boolean | 否 | 是否禁止同步,如果为true,表示禁止同步该描述内容到表单的问题描述
**示例值**:true
**默认值**:`true` +text | string | 否 | 字段描述内容,支持换行\n
**示例值**:"请按 name_id 格式填写\n例如:“Alice_20202020”" + +### 请求体示例 +```json +{ + "table": { + "name": "数据表名称", + "default_view_name": "默认的表格视图", + "fields": [ + { + "field_name": "索引字段", + "type": 1 + }, + { + "field_name": "单选", + "type": 3, + "ui_type": "SingleSelect", + "property": { + "options": [ + { + "name": "Enabled", + "color": 0 + }, + { + "name": "Disabled", + "color": 1 + }, + { + "name": "Draft", + "color": 2 + } + ] + } + } + ] + } +} +``` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +table_id | string | 多维表格数据表的 ID +default_view_id | string | 默认表格视图的 ID。该字段仅在请求参数中填写了`default_view_name` 或 `fields` 字段才会返回 +field_id_list | string\[\] | 数据表初始字段的 ID 列表,该字段仅在请求参数中填写了 `fields` 才会返回 + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "table_id": "tblDBTWm6Es84d8c", + "default_view_id": "vewUuKOz2R", + "field_id_list": [ + "fldhr2hBEA" + ] + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +200 | 1254000 | WrongRequestJson | 请求体错误 +200 | 1254001 | WrongRequestBody | 请求体错误。请检查请求体中是否已传入所有必填参数 +200 | 1254002 | Fail | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1254003 | WrongBaseToken | app_token 错误 +200 | 1254004 | WrongTableId | table_id 错误 +200 | 1254007 | EmptyValue | 空值 +200 | 1254008 | EmptyView | 空视图 +200 | 1254009 | WrongFieldId | 字段 id 错误 +200 | 1254010 | ReqConvError | 请求错误 +400 | 1254012 | NotSupportFieldOrView | 不支持的字段或视图。注意数据表的初始索引字段仅支持以下类型:
- 1:多行文本
- 2:数字
- 5:日期
- 13:电话号码
- 15:超链接
- 20:公式
- 22:地理位置 +200 | 1254013 | TableNameDuplicated | 表名重复 +400 | 1254014 | FieldNameDuplicated | 字段名重复 +400 | 1254021 | EmptyViewName | 视图名为空 +400 | 1254022 | InvalidViewName | 视图名无效 +400 | 1254029 | InvalidFieldName | 字段名无效 +200 | 1254030 | TooLargeResponse | 响应体过大 +200 | 1254036 | Base is copying, please try again later. | 复制多维表格为异步操作,该错误码表示当前多维表格仍在复制中,在复制期间无法操作当前多维表格。需要等待复制完成后再操作 +200 | 1254040 | BaseTokenNotFound | app_token 不存在 +200 | 1254041 | TableIdNotFound | table_id 不存在 +200 | 1254044 | FieldIdNotFound | field_id 不存在 +200 | 1254060 | TextFieldConvFail | 多行文本字段错误 +200 | 1254061 | NumberFieldConvFail | 数字字段错误 +200 | 1254062 | SingleSelectFieldConvFail | 单选字段错误 +200 | 1254063 | MultiSelectFieldConvFail | 多选字段错误 +200 | 1254064 | DatetimeFieldConvFail | 日期字段错误 +200 | 1254065 | CheckboxFieldConvFail | 复选框字段错误 +200 | 1254066 | UserFieldConvFail | 人员字段有误。原因可能是:
- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配
- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组
- 跨应用传入了 `open_id`。如果跨应用传入 ID,建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用
- 若想对人员字段传空,可传 null +200 | 1254067 | LinkFieldConvFail | 关联字段错误 +200 | 1254100 | TableExceedLimit | 数据表或仪表盘数量超限。每个多维表格中,数据表加仪表盘的数量最多为 100 个 +200 | 1254101 | ViewExceedLimit | 视图数量超限, 限制200个 +200 | 1254130 | TooLargeCell | 格子内容过大 +200 | 1254290 | TooManyRequest | 请求过快,稍后重试 +200 | 1254291 | Write conflict | 同一个数据表(table) 不支持并发调用写接口,请检查是否存在并发调用写接口。写接口包括:新增、修改、删除记录;新增、修改、删除字段;修改表单;修改视图等。 +200 | 1254301 | OperationTypeError | 多维表格未开启高级权限或不支持开启高级权限 +403 | 1254302 | Permission denied. | 调用身份缺少多维表格的高级权限。你需要为调用身份授予高级权限:
- 对用户授予高级权限,你需要在多维表格页面右上方 **分享** 入口为当前用户添加可管理权限。![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/df3911b4f747d75914f35a46962d667d_dAsfLjv3QC.png?height=546&lazyload=true&maxWidth=550)
- 对应用授予高级权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&maxWidth=550&width=3278)
![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?maxWidth=550)
**注意**:
在 **添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。
- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。 +400 | 1254607 | Data not ready, please try again later | 该报错一般是由于前置操作未执行完成,或本次操作数据太大,服务器计算超时导致。遇到该错误码时,建议等待一段时间后重试。通常有以下几种原因:
- **编辑操作频繁**:开发者对多维表格的编辑操作非常频繁。可能会导致由于等待前置操作处理完成耗时过长而超时的情况。多维表格底层对数据表的处理基于版本维度的串行方式,不支持并发。因此,并发请求时容易出现此类错误,不建议开发者对单个数据表进行并发请求。
- **批量操作负载重**:开发者在多维表格中进行批量新增、删除等操作时,如果数据表的数据量非常大,可能会导致单次请求耗时过长,最终导致请求超时。建议开发者适当降低批量请求的 page_size 以减少请求耗时。
- **资源分配与计算开销**:资源分配是基于单文档维度的,如果读接口涉及公式计算、排序等计算逻辑,会占用较多资源。例如,并发读取一个文档下的多个数据表也可能导致该文档阻塞。 +200 | 1255001 | InternalError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255002 | RpcError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255003 | MarshalError | 序列化错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255004 | UmMarshalError | 反序列化错误 +200 | 1255005 | ConvError | 服务内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) diff --git a/新增多个数据表.md b/新增多个数据表.md new file mode 100644 index 0000000..c6e349e --- /dev/null +++ b/新增多个数据表.md @@ -0,0 +1,131 @@ +# 新增多个数据表 + +新增多个数据表,仅可指定数据表名称。 + +## 前提条件 + +调用此接口前,请确保当前调用身份(tenant_access_token 或 user_access_token)已有多维表格的编辑等文档权限,否则接口将返回 HTTP 403 或 400 状态码。了解更多,参考[如何为应用或用户开通文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。 + +## 使用限制 + +每个多维表格中,数据表与仪表盘的总数量上限为 100。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/batch_create +HTTP Method | POST +接口频率限制 | [10 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 新增数据表(base:table:create)
查看、评论、编辑和管理多维表格(bitable:app) +字段权限要求 | **注意事项**:该接口返回体中存在下列敏感字段,仅当开启对应的权限后才会返回;如果无需获取这些字段,则不建议申请
获取用户 user ID(contact:user.employee_id:readonly) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) +Content-Type | string | 是 | **固定值**:"application/json; charset=utf-8" + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +app_token | string | 多维表格 App 的唯一标识。不同形态的多维表格,其 app_token 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 app_token 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 obj_type 的值为 bitable 时,obj_token 字段的值才是多维表格的 app_token。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
**示例值**:"appbcbWCzen6D8dezhoCH2RpMAh" + +### 查询参数 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +user_id_type | string | 否 | 用户 ID 类型
**示例值**:open_id
**可选值有**:
- open_id:标识一个用户在某个应用中的身份。同一个用户在不同应用中的 Open ID 不同。[了解更多:如何获取 Open ID](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-openid)
- union_id:标识一个用户在某个应用开发商下的身份。同一用户在同一开发商下的应用中的 Union ID 是相同的,在不同开发商下的应用中的 Union ID 是不同的。通过 Union ID,应用开发商可以把同个用户在多个应用中的身份关联起来。[了解更多:如何获取 Union ID?](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-union-id)
- user_id:标识一个用户在某个租户内的身份。同一个用户在租户 A 和租户 B 内的 User ID 是不同的。在同一个租户内,一个用户的 User ID 在所有应用(包括商店应用)中都保持一致。User ID 主要用于在不同的应用间打通用户数据。[了解更多:如何获取 User ID?](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-user-id)
**默认值**:`open_id`
**当值为 `user_id`,字段权限要求**:
获取用户 user ID(contact:user.employee_id:readonly) + +### 请求体 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +tables | req_table\[\] | 否 | tables +name | string | 否 | 数据表名称。该字段必填。
**注意**:
- 名称中的首尾空格将会被默认去除。
- 数据表名称不可以包含 `/ \ ? * : [ ]` 等特殊字符。
**示例值**:"一个新的数据表"
**数据校验规则**:
- 长度范围:`1` ~ `100` 字符 + +### 请求体示例 +```json +{ + "tables": [ + { + "name": "一个新的数据表" + } + ] +} +``` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +table_ids | string\[\] | table ids + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "table_ids": [ + "tblIovTTN2eIW2hn" + ] + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +200 | 1254000 | WrongRequestJson | 请求体错误 +200 | 1254001 | WrongRequestBody | 请求体错误 +200 | 1254002 | Fail | 内部错误,有疑问可咨询客服 +200 | 1254003 | WrongBaseToken | app_token 错误 +200 | 1254004 | WrongTableId | table_id 错误 +200 | 1254005 | WrongViewId | view_id 错误 +200 | 1254006 | WrongRecordId | 检查 record_id +200 | 1254007 | EmptyValue | 空值 +200 | 1254008 | EmptyView | 空视图 +200 | 1254009 | WrongFieldId | 字段 id 错误 +200 | 1254010 | ReqConvError | 请求错误 +400 | 1254013 | TableNameDuplicated | 表名重复 +200 | 1254030 | TooLargeResponse | 响应体过大 +400 | 1254036 | Base is copying, please try again later. | 复制多维表格为异步操作,该错误码表示当前多维表格仍在复制中,在复制期间无法操作当前多维表格。需要等待复制完成后再操作 +200 | 1254040 | BaseTokenNotFound | app_token 不存在 +200 | 1254041 | TableIdNotFound | table_id 不存在 +200 | 1254042 | ViewIdNotFound | view_id 不存在 +200 | 1254043 | RecordIdNotFound | record_id 不存在 +200 | 1254044 | FieldIdNotFound | field_id 不存在 +200 | 1254060 | TextFieldConvFail | 多行文本字段错误 +200 | 1254061 | NumberFieldConvFail | 数字字段错误 +200 | 1254062 | SingleSelectFieldConvFail | 单选字段错误 +200 | 1254063 | MultiSelectFieldConvFail | 多选字段错误 +200 | 1254064 | DatetimeFieldConvFail | 日期字段错误 +200 | 1254065 | CheckboxFieldConvFail | 复选框字段错误 +200 | 1254066 | UserFieldConvFail | 人员字段有误。原因可能是:
- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配
- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组
- 跨应用传入了 `open_id`。如果跨应用传入 ID,建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用 +200 | 1254067 | LinkFieldConvFail | 关联字段错误 +200 | 1254100 | TableExceedLimit | 数据表或仪表盘数量超限。每个多维表格中,数据表加仪表盘的数量最多为 100 个 +200 | 1254101 | ViewExceedLimit | 视图数量超限, 限制200个 +200 | 1254102 | FileExceedLimit | 超限 +200 | 1254103 | RecordExceedLimit | 记录数量超限, 限制20,000条 +200 | 1254104 | RecordAddOnceExceedLimit | 单次添加记录数量超限, 限制500条 +200 | 1254130 | TooLargeCell | 格子内容过大 +200 | 1254290 | TooManyRequest | 请求过快,稍后重试 +200 | 1254291 | Write conflict | 同一个数据表(table) 不支持并发调用写接口,请检查是否存在并发调用写接口。写接口包括:新增、修改、删除记录;新增、修改、删除字段;修改表单;修改视图等。 +200 | 1254301 | OperationTypeError | 多维表格未开启高级权限或不支持开启高级权限 +403 | 1254302 | The role has no permissions. | 调用身份缺少多维表格的高级权限。你需要为调用身份授予高级权限:
- 对用户授予高级权限,你需要在多维表格页面右上方 **分享** 入口为当前用户添加可管理权限。![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/df3911b4f747d75914f35a46962d667d_dAsfLjv3QC.png?height=546&lazyload=true&maxWidth=550)
- 对应用授予高级权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&maxWidth=550&width=3278)
![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?maxWidth=550)
**注意**:
在 **添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。
- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。 +200 | 1255001 | InternalError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255002 | RpcError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255003 | MarshalError | 序列化错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255004 | UmMarshalError | 反序列化错误 +200 | 1255005 | ConvError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +504 | 1255040 | 请求超时 | 进行重试 diff --git a/更新多维表格元数据.md b/更新多维表格元数据.md new file mode 100644 index 0000000..cc86c02 --- /dev/null +++ b/更新多维表格元数据.md @@ -0,0 +1,107 @@ +# 更新多维表格元数据 + +更新多维表格元数据,包括多维表格的名称、是否开启高级权限。 + +## 注意事项 + +- 在线文档和电子表格中嵌入的多维表格、知识库中的多维表格不支持开启高级权限。 +- 此接口非原子操作,先修改多维表格名称,后开关高级权限,可能存在部分成功的情况。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token +HTTP Method | PUT +接口频率限制 | [10 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 更新多维表格(base:app:update)
查看、评论、编辑和管理多维表格(bitable:app) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) +Content-Type | string | 是 | **固定值**:"application/json; charset=utf-8" + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +app_token | string | 目标多维表格的 App token。该接口仅支持存储在云空间文件夹中的多维表格,即 URL 以 **feishu.cn/base** 开头的多维表格形态。该类多维表格的 app_token 为 URL 下图高亮部分:
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_sTn7sVvhOB.png?height=766&lazyload=true&maxWidth=700&width=3004)
**示例值**:"appbcbWCzen6D8dezhoCH2RpMAh" + +### 请求体 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +name | string | 否 | 新的多维表格名称,不传则不更新名称。
**示例值**:"新的多维表格名称" +is_advanced | boolean | 否 | 多维表格是否开启高级权限。不传则不更新设置。可选值:
- true:开启高级权限
- false:关闭高级权限
**示例值**:true + +### 请求体示例 +```json +{ + "name": "新的多维表格名称", + "is_advanced": true +} +``` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +app | display_app_v2 | 多维表格元数据 +app_token | string | 多维表格的唯一标识 app_token +name | string | 多维表格的名称 +is_advanced | boolean | 多维表格是否已开启高级权限 +time_zone | string | 文档时区 + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "app": { + "app_token": "appbcbWCzen6D8dezhoCH2RpMAh", + "name": "新的多维表格名字", + "is_advanced": true + } + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +200 | 1254000 | WrongRequestJson | 请求体错误 +200 | 1254001 | WrongRequestBody | 请求体错误 +200 | 1254002 | Fail | 内部错误,有疑问可咨询客服 +200 | 1254003 | WrongBaseToken | app_token 错误 +200 | 1254010 | ReqConvError | 请求错误 +200 | 1254031 | InvalidAppName | 多维表格名称格式错误,长度不超过 100 个字符,不能包含 ? / \ * : [ ] +400 | 1254036 | Base is copying, please try again later. | 多维表格副本复制中,稍后重试 +200 | 1254040 | BaseTokenNotFound | app_token 不存在 +200 | 1254043 | RecordIdNotFound | record_id 不存在 +200 | 1254200 | internal error | 内部错误 +200 | 1254290 | TooManyRequest | 请求过快,稍后重试 +200 | 1254291 | Write conflict | 同一个数据表(table) 不支持并发调用写接口,请检查是否存在并发调用写接口。写接口包括:新增、修改、删除记录;新增、修改、删除字段;修改表单;修改视图等。 +400 | 1254301 | OperationTypeError | 多维表格未开启高级权限或不支持开启高级权限 +403 | 1254302 | Permission denied. | 无访问权限, 常由表格开启了高级权限造成, 请在高级权限设置中添加一个包含应用的群, 给予这个群读写权限 +403 | 1254304 | The role has no permissions. | 无权限 +200 | 1255001 | InternalError | 内部错误,有疑问可咨询客服 +200 | 1255002 | RpcError | 内部错误,有疑问可咨询客服 +200 | 1255003 | MarshalError | 序列化错误,有疑问可咨询客服 +200 | 1255004 | UmMarshalError | 反序列化错误 +504 | 1255040 | 请求超时 | 进行重试 + +## 补充错误码 + +**错误码** | **原因** | **排查建议** | +| ------- | ------- | ----------------- | +| 1254061 | 字段格式错误。 | 确认对应字段类型参数格式是否正确。 diff --git a/更新数据表.md b/更新数据表.md new file mode 100644 index 0000000..f5b045d --- /dev/null +++ b/更新数据表.md @@ -0,0 +1,78 @@ +# 更新数据表 + +更新数据表的名称。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token/tables/:table_id +HTTP Method | PATCH +接口频率限制 | [10 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 更新数据表(base:table:update)
查看、评论、编辑和管理多维表格(bitable:app) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) +Content-Type | string | 是 | **固定值**:"application/json; charset=utf-8" + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +app_token | string | 多维表格 App 的唯一标识。不同形态的多维表格,其 `app_token` 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值才是多维表格的 `app_token`。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
**示例值**:"XrgTb4y1haKYnasu0xXb1g7lcSg"
**数据校验规则**:
- 最小长度:`1` 字符 +table_id | string | 多维表格数据表的唯一标识。获取方式:
- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`
- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976)
**示例值**:"tbl1TkhyTWDkSoZ3" + +### 请求体 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +name | string | 否 | 数据表的新名称。
**注意**:
- 名称中的首尾空格将会被去除。
- 数据表名称不可以包含 `/ \ ? * : [ ]` 等特殊字符。
- 如果名称为空或和旧名称相同,接口仍然会返回成功,但是名称不会被更改。
**示例值**:"新的数据表名称"
**数据校验规则**:
- 长度范围:`1` ~ `100` 字符
- 正则校验:`^[^\[\]\:\\\/\?\*]+$` + +### 请求体示例 +```json +{ + "name": "新的数据表名称" +} +``` + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +name | string | 新的数据表名称 + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "name": "新的数据表名称" + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +400 | 1254001 | WrongRequestBody | 请求体错误 +400 | 1254002 | Fail | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +400 | 1254003 | WrongBaseToken | app_token 错误 +400 | 1254004 | WrongTableId | table_id 错误 +400 | 1254013 | TableNameDuplicated | 表名重复 +403 | 1254036 | Base is copying, please try again later. | 复制多维表格为异步操作,该错误码表示当前多维表格仍在复制中,在复制期间无法操作当前多维表格。需要等待复制完成后再操作 +404 | 1254040 | BaseTokenNotFound | app_token 不存在 +404 | 1254041 | TableIdNotFound | table_id 不存在 +429 | 1254290 | TooManyRequest | 请求过快,稍后重试 +403 | 1254302 | Permission denied. | 调用身份缺少多维表格的高级权限。你需要为调用身份授予高级权限:
- 对用户授予高级权限,你需要在多维表格页面右上方 **分享** 入口为当前用户添加可管理权限。![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/df3911b4f747d75914f35a46962d667d_dAsfLjv3QC.png?height=546&lazyload=true&maxWidth=550)
- 对应用授予高级权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&maxWidth=550&width=3278)
![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?maxWidth=550)
**注意**:
在 **添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。
- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。 +500 | 1255001 | InternalError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) diff --git a/获取多维表格元数据.md b/获取多维表格元数据.md new file mode 100644 index 0000000..ca456a9 --- /dev/null +++ b/获取多维表格元数据.md @@ -0,0 +1,108 @@ +# 获取多维表格元数据 + +获取指定多维表格的元数据信息,包括多维表格名称、多维表格版本号、多维表格是否开启高级权限等。 + +## 请求 + +基本 |   +---|--- +HTTP URL | https://open.feishu.cn/open-apis/bitable/v1/apps/:app_token +HTTP Method | GET +接口频率限制 | [20 次/秒](https://open.feishu.cn/document/ukTMukTMukTM/uUzN04SN3QjL1cDN) +支持的应用类型 | Custom App、Store App +权限要求
**调用该 API 所需的权限。开启其中任意一项权限即可调用**
开启任一权限即可 | 获取多维表格信息(base:app:read)
查看、评论、编辑和管理多维表格(bitable:app)
查看、评论和导出多维表格(bitable:app:readonly) + +### 请求头 + +名称 | 类型 | 必填 | 描述 +---|---|---|--- +Authorization | string | 是 | `tenant_access_token`

`user_access_token`
**值格式**:"Bearer `access_token`"
**示例值**:"Bearer u-7f1bcd13fc57d46bac21793a18e560"
[了解更多:如何选择与获取 access token](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-choose-which-type-of-token-to-use) + +### 路径参数 + +名称 | 类型 | 描述 +---|---|--- +app_token | string | 多维表格 App 的唯一标识。不同形态的多维表格,其 `app_token` 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值才是多维表格的 `app_token`。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
**示例值**:"appbcbWCzen6D8dezhoCH2RpMAh" + +## 响应 + +### 响应体 + +名称 | 类型 | 描述 +---|---|--- +code | int | 错误码,非 0 表示失败 +msg | string | 错误描述 +data | \- | \- +app | display_app | 多维表格元数据 +app_token | string | 多维表格的唯一标识 app_token +name | string | 多维表格的名称 +revision | int | 多维表格的版本号。对多维表格进行修改时更新,如新增、删除数据表,修改数据表名等,初始为 1,每次更新+1 +is_advanced | boolean | 多维表格是否开启了高级权限。取值包括:
- true:开启了高级权限
- false:关闭了高级权限
了解更多参考飞书帮助中心文档[使用多维表格高级权限](https://www.feishu.cn/hc/zh-CN/articles/588604550568)。 +time_zone | string | 多维表格的时区 +formula_type | int | 多维表格的公式字段类型。可结合[字段相关 API](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/create)使用。
**可选值有**:
- 1:不支持指定公式字段类型
- 2:支持指定公式字段类型 +advance_version | string | 文档高级权限版本。可结合[自定义角色 API](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/create)使用。
**可选值有**:
- v1:v1版本
- v2:v2版本 + +### 响应体示例 +```json +{ + "code": 0, + "msg": "success", + "data": { + "app": { + "app_token": "appbcbWCzen6D8dezhoCH2RpMAh", + "name": "mybase", + "revision": 1, + "is_advanced": false, + "time_zone": "Asia/Beijing", + "formula_type": 1, + "advance_version": "v1" + } + } +} +``` + +### 错误码 + +HTTP状态码 | 错误码 | 描述 | 排查建议 +---|---|---|--- +200 | 1254000 | WrongRequestJson | 请求体错误 +200 | 1254001 | WrongRequestBody | 请求体错误 +200 | 1254002 | Fail | 导致报 1254002 错误码的场景较多,请参考以下建议排查:
- 如果单次操作的内容变更较大,请尝试在单次操作中减少数据量
- 如果你并发调用了接口,请尝试控制请求间隔,稍后重试
- 如果在知识库(wiki)中创建多维表格,请检查你是否使用了知识库[创建知识空间节点](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space-node/create)接口创建多维表格。在此场景下不能使用[创建多维表格](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/create)接口
- 请检查接口参数是否有误。例如,在分页查询多维表格时,传递了无效的 page_token,或传递了错误的数据表的 table_id
- 如果该报错偶尔发生,可能是服务器超时或不稳定,请重试解决 +200 | 1254003 | WrongBaseToken | app_token 错误。app_token 是多维表格 App 的唯一标识。不同形态的多维表格,其 `app_token` 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值才是多维表格的 `app_token`。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。 +200 | 1254004 | WrongTableId | table_id 错误。table_id 是多维表格数据表的唯一标识。获取方式:
- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`
- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976) +200 | 1254005 | WrongViewId | view_id 错误。view_id 是多维表格中视图的唯一标识。获取方式:
- 在多维表格的 URL 地址栏中,`view_id` 是下图中高亮部分:
![view_id.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_DJMgVH9x2S.png?height=748&lazyload=true&width=2998)
- 通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取。暂时无法获取到嵌入到云文档中的多维表格的 `view_id`。
**注意**:
当 `filter` 参数 或 `sort` 参数不为空时,请求视为对数据表中的全部数据做条件过滤,指定的 `view_id` 会被忽略。 +200 | 1254006 | WrongRecordId | record_id 错误。record_id 是数据表中一条记录的唯一标识。通过[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)接口获取 +200 | 1254007 | EmptyValue | 空值 +200 | 1254008 | EmptyView | 空视图 +200 | 1254009 | WrongFieldId | field_id 错误。field_id 是数据表中一个字段的唯一标识。通过[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)接口获取 +200 | 1254010 | ReqConvError | 请求错误 +200 | 1254030 | TooLargeResponse | 响应体过大 +400 | 1254036 | Base is copying, please try again later. | 复制多维表格为异步操作,该错误码表示当前多维表格仍在复制中,在复制期间无法操作当前多维表格。需要等待复制完成后再操作 +200 | 1254040 | BaseTokenNotFound | app_token 不存在。不同形态的多维表格,其 `app_token` 的获取方式不同:
- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:
![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)
- 如果多维表格的 URL 以 ==**feishu.cn/wiki**== 开头,你需调用知识库相关[获取知识空间节点信息](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/wiki-v2/space/get_node)接口获取多维表格的 app_token。当 `obj_type` 的值为 `bitable` 时,`obj_token` 字段的值才是多维表格的 `app_token`。
了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。 +200 | 1254041 | TableIdNotFound | table_id 不存在。获取方式:
- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`
- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`
![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976) +200 | 1254042 | ViewIdNotFound | view_id 不存在。获取方式:
- 在多维表格的 URL 地址栏中,`view_id` 是下图中高亮部分:
![view_id.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_DJMgVH9x2S.png?height=748&lazyload=true&width=2998)
- 通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取。暂时无法获取到嵌入到云文档中的多维表格的 `view_id`。
**注意**:
当 `filter` 参数 或 `sort` 参数不为空时,请求视为对数据表中的全部数据做条件过滤,指定的 `view_id` 会被忽略。 +200 | 1254043 | RecordIdNotFound | record_id 不存在。record_id 是数据表中一条记录的唯一标识。请通过[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)接口获取。 +200 | 1254044 | FieldIdNotFound | field_id 不存在。field_id 是数据表中一个字段的唯一标识。通过[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)接口获取。 +200 | 1254060 | TextFieldConvFail | 文本字段错误 +200 | 1254061 | NumberFieldConvFail | 数字字段错误 +200 | 1254062 | SingleSelectFieldConvFail | 单选字段错误 +200 | 1254063 | MultiSelectFieldConvFail | 多选字段错误 +200 | 1254064 | DatetimeFieldConvFail | 日期字段错误 +200 | 1254065 | CheckboxFieldConvFail | 复选框字段错误 +200 | 1254066 | UserFieldConvFail | 人员字段有误。原因可能是:
- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配
- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组
- 跨应用传入了 `open_id`。如果跨应用传入 ID,建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用
- 若想对人员字段传空,可传 null +200 | 1254067 | LinkFieldConvFail | 关联字段错误 +200 | 1254100 | TableExceedLimit | 数据表或仪表盘数量超限。每个多维表格中,数据表加仪表盘的数量最多为 100 个 +200 | 1254101 | ViewExceedLimit | 视图数量超限, 限制200个 +200 | 1254102 | FileExceedLimit | 文件数量超限 +200 | 1254103 | RecordExceedLimit | 记录数量超限, 限制20,000条 +200 | 1254104 | RecordAddOnceExceedLimit | 单次添加记录数量超限, 单次调用最多更新 1,000 条记录 +200 | 1254130 | TooLargeCell | 格子内容过大 +200 | 1254290 | TooManyRequest | 请求过快,稍后重试 +200 | 1254291 | Write conflict | 在同一个数据表中,并发调用了读写接口或请求过快,出现冲突。请参考以下建议解决:
- 确保没有并发调用多维表格读写相关接口
- 若操作量较大,建议在接口与接口之间增加 0.5 或 1 秒的延迟,也可在报错中增加重试逻辑,确保业务的稳定性
- 对于写接口,可以将接口中的查询参数 `ignore_consistency_check` 设置为 true,表示在读写操作时,暂时忽略一致性检查,以提高性能 +200 | 1254301 | OperationTypeError | 多维表格未开启高级权限或不支持开启高级权限 +200 | 1255001 | InternalError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255002 | RpcError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255003 | MarshalError | 序列化错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255004 | UmMarshalError | 反序列化错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +200 | 1255005 | ConvError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW) +504 | 1255040 | Request timed out, please try again later | 请求超时,进行重试