first commit

This commit is contained in:
0Xiao0
2026-03-23 10:45:02 +08:00
commit e6e4cd8119
40 changed files with 5364 additions and 0 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
.dockerignore
.gitignore
.git
README.md

24
.env.example Normal file
View File

@ -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

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
.env

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.13

15
Dockerfile Normal file
View File

@ -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

70
README.md Normal file
View File

@ -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)**

237
api.py Normal file
View File

@ -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

130
append_bitables.py Normal file
View File

@ -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)

58
batch_get_records.py Normal file
View File

@ -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()

108
bitable_calendar.py Normal file
View File

@ -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()

54
config.py Normal file
View File

@ -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", "")

2
exec.ps1 Normal file
View File

@ -0,0 +1,2 @@
docker build -t bitable-calendar .
docker run --env-file .env -it bitable-calendar

2
exec.sh Normal file
View File

@ -0,0 +1,2 @@
docker build -t bitable-calendar .
docker run --env-file .env -it bitable-calendar

971
export_web_view.py Normal file
View File

@ -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"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bitable Records - Interactive View</title>
<style>
:root {
--bg-color: #f8fafc;
--text-color: #334155;
--card-bg: #ffffff;
--border-color: #e2e8f0;
--accent-color: #3b82f6;
--hover-bg: #f1f5f9;
--tag-bg: #e0e7ff;
--tag-text: #3730a3;
--text-muted: #64748b;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: var(--bg-color);
color: var(--text-color);
margin: 0;
padding: 40px 20px;
line-height: 1.5;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: var(--card-bg);
border-radius: 12px;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 30px;
border: 1px solid var(--border-color);
}
h1 {
text-align: center;
font-weight: 700;
margin-bottom: 30px;
font-size: 28px;
color: #0f172a;
}
.toolbar {
display: flex;
gap: 12px;
margin-bottom: 24px;
padding: 16px;
background: #f8fafc;
border: 1px solid var(--border-color);
border-radius: 8px;
flex-wrap: wrap;
align-items: center;
}
.toolbar input, .toolbar select, .toolbar button {
padding: 8px 12px;
border: 1px solid #cbd5e1;
border-radius: 6px;
font-size: 14px;
outline: none;
background: white;
}
.toolbar input {
flex-grow: 1;
min-width: 200px;
}
.toolbar input:focus, .toolbar select:focus {
border-color: var(--accent-color);
box-shadow: 0 0 0 2px #dbeafe;
}
.toolbar button {
cursor: pointer;
font-weight: 500;
transition: background 0.2s;
}
.toolbar button:hover {
background: var(--hover-bg);
}
.tabs {
display: flex;
border-bottom: 2px solid var(--border-color);
margin-bottom: 20px;
gap: 8px;
overflow-x: auto;
}
.tab {
padding: 10px 20px;
cursor: pointer;
background: transparent;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -2px;
font-weight: 500;
color: var(--text-muted);
transition: all 0.2s;
white-space: nowrap;
}
.tab:hover {
color: var(--text-color);
background: var(--hover-bg);
border-radius: 6px 6px 0 0;
}
.tab.active {
color: var(--accent-color);
border-bottom-color: var(--accent-color);
}
ul.tree {
list-style-type: none;
padding-left: 0;
margin: 0;
}
ul.tree ul {
list-style-type: none;
padding-left: 28px;
border-left: 2px solid #cbd5e1;
margin-left: 14px;
margin-top: 4px;
}
li { margin: 6px 0; }
.node-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background: #ffffff;
border: 1px solid var(--border-color);
border-radius: 6px;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.02);
}
.node-content:hover {
background: var(--hover-bg);
border-color: #cbd5e1;
}
.dimmed .node-content {
opacity: 0.5;
background: #f8fafc;
}
details { margin-bottom: 6px; }
summary {
list-style: none;
cursor: pointer;
display: block;
user-select: none;
outline: none;
}
summary::-webkit-details-marker { display: none; }
.toggle-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
margin-right: 8px;
color: #94a3b8;
transition: transform 0.2s ease;
font-family: monospace;
font-size: 14px;
}
details[open] > summary .toggle-icon {
transform: rotate(90deg);
color: var(--accent-color);
}
.task-left {
display: flex;
align-items: center;
flex-grow: 1;
min-width: 0;
}
.task-title {
font-weight: 600;
font-size: 15px;
color: #1e293b;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-right: 16px;
}
.task-meta {
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
font-size: 13px;
color: var(--text-muted);
}
.meta-item {
display: flex;
align-items: center;
gap: 6px;
}
.avatar {
width: 20px;
height: 20px;
border-radius: 50%;
background: #e2e8f0;
display: inline-block;
vertical-align: middle;
object-fit: cover;
}
.tags {
display: flex;
gap: 6px;
align-items: center;
}
.tag {
background: var(--tag-bg);
color: var(--tag-text);
padding: 2px 8px;
border-radius: 12px;
font-weight: 600;
font-size: 12px;
white-space: nowrap;
}
.empty-leaf {
padding-left: 32px;
}
.unnamed-task {
color: #94a3b8;
font-style: italic;
}
.stats {
font-size: 14px;
color: var(--text-muted);
margin-bottom: 12px;
text-align: right;
}
</style>
</head>
<body>
<div class="container">
<h1>ALL IN AI</h1>
<div id="globalAiSummaryResult" style="display:none;margin-bottom:20px;padding:16px;background:#ffffff;border:1px solid #c4b5fd;border-left:4px solid #8b5cf6;border-radius:8px;font-size:14px;color:#4c1d95;white-space:pre-wrap;line-height:1.6;max-height:500px;overflow-y:auto;box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);"></div>
<div class="tabs" id="tabContainer"></div>
<div class="toolbar" id="toolbarFilters">
<input type="text" id="searchInput" placeholder="Search tasks...">
<div style="display:flex; align-items:center; gap:6px;">
<span style="font-size:14px;color:#64748b;">Date:</span>
<input type="date" id="dateFrom" title="Start Date">
<span style="color:#cbd5e1;">-</span>
<input type="date" id="dateTo" title="End Date">
</div>
<select id="statusFilter">
<option value="">All Statuses</option>
</select>
<select id="leaderFilter">
<option value="">All Leaders</option>
</select>
<select id="sortOrder">
<option value="default">Sort: Default</option>
<option value="startDate">Sort: Start Date</option>
<option value="priority">Sort: Priority</option>
</select>
<button id="toggleExpand">Toggle Expand All</button>
</div>
<div class="stats" id="statsDisplay"></div>
<div id="summaryDisplay"></div>
<div id="aiSummaryResult" style="display:none;margin-bottom:16px;padding:16px;background:#ffffff;border:1px solid #bbf7d0;border-radius:8px;font-size:14px;color:#334155;white-space:pre-wrap;line-height:1.6;max-height:500px;overflow-y:auto;box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);"></div>
<div id="tree-container"></div>
</div>
<script>
const tabData = {RECORDS_JSON}; // Array of {name, records}
let rawRecords = [];
let records = [];
function extractText(val) {
if (Array.isArray(val) && val.length > 0) return val[0].text || '';
return val ? String(val) : '';
}
function formatDate(ts) {
if (!ts) return '';
const d = new Date(Number(ts));
const mo = String(d.getMonth()+1).padStart(2,'0');
const da = String(d.getDate()).padStart(2,'0');
return `${mo}-${da}`;
}
function parseRecords() {
records = rawRecords.map(r => {
const fields = r.fields || {};
const taskNameRaw = fields['Task'] || fields['Task Description'];
const taskName = extractText(taskNameRaw) || 'Untitled Task';
const status = extractText(fields['Status']).trim();
const priority = extractText(fields['Priority']).trim();
const overdue = extractText(fields['Overdue']).trim();
const manday = fields['Manday count'] || '';
const progressNotes = extractText(fields['Progress notes']).trim();
let tcRaw = fields['Target Customers'];
let targetCustomers = '';
if (Array.isArray(tcRaw)) {
targetCustomers = tcRaw.map(x => typeof x === 'object' ? (x.text || '') : String(x)).join(', ').trim();
} else if (tcRaw) {
targetCustomers = String(tcRaw).trim();
}
if (targetCustomers) {
console.log("Parsed Target Customers for [" + taskName + "]:", targetCustomers);
}
let parentRaw = fields['父记录'];
let parentId = null;
let parentName = null;
if (Array.isArray(parentRaw) && parentRaw.length > 0) {
if (parentRaw[0].record_ids && parentRaw[0].record_ids.length > 0) {
parentId = parentRaw[0].record_ids[0];
} else {
parentName = parentRaw[0].text;
}
} else if (typeof parentRaw === 'string') {
parentName = parentRaw.trim();
}
const startDateRaw = fields['Start date'];
const endDateRaw = fields['End Date'];
const startDate = startDateRaw ? Number(startDateRaw) : Infinity;
const endDate = endDateRaw ? Number(endDateRaw) : Infinity;
const leadersRaw = Array.isArray(fields['Task leader']) ? fields['Task leader'] : [];
const leaders = leadersRaw.filter(x => x && typeof x === 'object').map(x => ({name: x.name, avatar: x.avatar_url}));
return {
id: r.record_id,
taskName,
status,
priority,
overdue,
manday,
progressNotes,
targetCustomers,
parentId,
parentName,
startDate,
endDate,
startDateStr: formatDate(startDateRaw),
endDateStr: formatDate(endDateRaw),
leaders
};
});
// Build hierarchy mapping
const nameMap = {};
records.forEach(r => nameMap[r.taskName] = r.id);
records.forEach(r => {
if (!r.parentId && r.parentName && nameMap[r.parentName]) {
r.parentId = nameMap[r.parentName];
}
if (r.parentId === r.id) r.parentId = null;
});
// Populate dropdowns
const statuses = new Set();
const leaderNames = new Set();
records.forEach(r => {
if(r.status) statuses.add(r.status);
r.leaders.forEach(l => leaderNames.add(l.name));
});
const statusSelect = document.getElementById('statusFilter');
statusSelect.innerHTML = '<option value="">All Statuses</option>';
Array.from(statuses).sort().forEach(s => {
const opt = document.createElement('option');
opt.value = opt.textContent = s;
statusSelect.appendChild(opt);
});
const leaderSelect = document.getElementById('leaderFilter');
leaderSelect.innerHTML = '<option value="">All Leaders</option>';
Array.from(leaderNames).sort().forEach(l => {
const opt = document.createElement('option');
opt.value = opt.textContent = l;
leaderSelect.appendChild(opt);
});
}
function switchTab(index) {
if (!tabData[index]) return;
rawRecords = tabData[index].records;
document.querySelectorAll('.tab').forEach((t, i) => {
if (i === index) t.classList.add('active');
else t.classList.remove('active');
});
// Reset filters visual
document.getElementById('searchInput').value = '';
document.getElementById('sortOrder').value = 'default';
parseRecords();
render();
}
function initTabs() {
const container = document.getElementById('tabContainer');
tabData.forEach((tab, index) => {
const btn = document.createElement('button');
btn.className = 'tab';
btn.textContent = tab.name;
btn.onclick = () => switchTab(index);
container.appendChild(btn);
});
const summaryBtn = document.createElement('button');
summaryBtn.className = 'tab';
summaryBtn.style.marginLeft = 'auto'; // Push to the right
summaryBtn.style.color = '#8b5cf6';
summaryBtn.style.fontWeight = 'bold';
summaryBtn.innerHTML = '🌟 全局大总结';
summaryBtn.onclick = window.requestGlobalSummary;
container.appendChild(summaryBtn);
if (tabData.length > 0) {
switchTab(0);
}
}
// UI State
let isExpandAll = false;
document.getElementById('searchInput').addEventListener('input', render);
document.getElementById('statusFilter').addEventListener('change', render);
document.getElementById('leaderFilter').addEventListener('change', render);
document.getElementById('sortOrder').addEventListener('change', render);
document.getElementById('dateFrom').addEventListener('change', render);
document.getElementById('dateTo').addEventListener('change', render);
document.getElementById('toggleExpand').addEventListener('click', () => {
isExpandAll = !isExpandAll;
render();
});
function sortNodes(nodes, sortKey) {
if (sortKey === 'default') return nodes;
nodes.sort((a, b) => {
if (sortKey === 'startDate') return a.startDate - b.startDate;
if (sortKey === 'priority') {
const getP = p => p.includes('High') || p.includes('Important') ? 3 : p.includes('Normal') ? 2 : 1;
return getP(b.priority) - getP(a.priority);
}
return 0;
});
nodes.forEach(n => sortNodes(n.children, sortKey));
return nodes;
}
function filterNodes(nodes, search, statusFilter, leaderFilter, dateFrom, dateTo) {
let result = [];
nodes.forEach(n => {
let matchSearch = n.taskName.toLowerCase().includes(search.toLowerCase()) ||
(n.targetCustomers && n.targetCustomers.toLowerCase().includes(search.toLowerCase()));
let matchStatus = statusFilter === '' || n.status === statusFilter;
let matchLeader = leaderFilter === '' || n.leaders.some(l => l.name === leaderFilter);
let matchDate = true;
if (dateFrom !== -Infinity || dateTo !== Infinity) {
let s = n.startDate !== Infinity ? n.startDate : (n.endDate !== Infinity ? n.endDate : Infinity);
let e = n.endDate !== Infinity ? n.endDate : (n.startDate !== Infinity ? n.startDate : Infinity);
if (s !== Infinity && e !== Infinity) {
matchDate = (s <= dateTo) && (e >= dateFrom);
} else {
matchDate = false;
}
}
let filteredChildren = filterNodes(n.children, search, statusFilter, leaderFilter, dateFrom, dateTo);
let selfMatch = matchSearch && matchStatus && matchLeader && matchDate;
if (selfMatch || filteredChildren.length > 0) {
let cloned = {...n, children: filteredChildren};
cloned.isMatch = selfMatch;
result.push(cloned);
}
});
return result;
}
function genTags(n) {
let html = '<div class="tags">';
if (n.status) {
let bg = '#f1f5f9', fg = '#475569';
if (n.status.includes('Ongoing') || n.status.includes('进行中')) { bg='#dbeafe'; fg='#1e40af'; }
else if (n.status.includes('Completed') || n.status.includes('完成')) { bg='#d1fae5'; fg='#065f46'; }
html += `<span class="tag" style="background:${bg};color:${fg}">${n.status}</span>`;
}
if (n.priority) {
if (n.priority.includes('High') || n.priority.includes('Important')) {
html += `<span class="tag" style="background:#ffedd5;color:#c2410c">${n.priority}</span>`;
} else if (!n.priority.includes('Normal')) {
html += `<span class="tag" style="background:#f1f5f9;color:#475569">${n.priority}</span>`;
}
}
if (n.overdue && !n.overdue.includes('')) {
html += `<span class="tag" style="background:#fee2e2;color:#991b1b">${n.overdue}</span>`;
}
if (n.targetCustomers) {
html += `<span class="tag" style="background:#fce7f3;color:#9d174d">🎯 ${n.targetCustomers}</span>`;
}
html += '</div>';
return html;
}
function genMeta(n) {
let html = '<div class="task-meta">';
if (n.leaders.length > 0) {
html += '<div class="meta-item">';
n.leaders.forEach(l => {
if (l.avatar) html += `<img src="${l.avatar}" class="avatar" title="${l.name}">`;
else html += `<span>${l.name}</span>`;
});
html += '</div>';
}
if (n.startDateStr || n.endDateStr) {
html += `<div class="meta-item">🗓️ ${n.startDateStr} ~ ${n.endDateStr}</div>`;
}
if (n.manday) {
html += `<div class="meta-item">⏱️ ${n.manday}d</div>`;
}
html += '</div>';
return html;
}
window.toggleNotes = function(event, id) {
event.stopPropagation();
event.preventDefault();
const el = document.getElementById('notes-' + id);
if (!el) return;
if (el.style.display === 'none') {
el.style.display = 'block';
} else {
el.style.display = 'none';
}
};
function renderHTML(nodes, isRoot) {
if (nodes.length === 0) return '';
let html = isRoot ? '<ul class="tree">' : '<ul>';
nodes.forEach(n => {
let dimClass = n.isMatch === false ? 'dimmed' : '';
let tags = genTags(n);
let meta = genMeta(n);
// Hide details by default, show toggle button if notes exist
let toggleNotesBtn = '';
let notesHtml = '';
if (n.progressNotes) {
toggleNotesBtn = `<span title="Toggle Progress Notes" style="cursor:pointer; font-size:14px; margin-right:6px;" onclick="window.toggleNotes(event, '${n.id}')">📝</span>`;
notesHtml = `<div id="notes-${n.id}" style="display:none; font-size:13px; color:#64748b; margin-top:8px; padding:8px; background:#f8fafc; border-radius:6px; border:1px dashed #cbd5e1; font-family: monospace; white-space: pre-wrap;">${n.progressNotes}</div>`;
}
html += `<li class="${dimClass}">`;
if (n.children.length > 0) {
let openAttr = isExpandAll ? 'open' : '';
html += `<details ${openAttr}>
<summary>
<div class="node-content" style="flex-wrap: wrap;">
<div style="width:100%; display:flex; align-items:center; justify-content:space-between;">
<div class="task-left">
<span class="toggle-icon">▶</span>
${toggleNotesBtn}
<span class="task-title">${n.taskName}</span>
${tags}
</div>
${meta}
</div>
${notesHtml ? '<div style="width:100%; padding-left:24px;">' + notesHtml + '</div>' : ''}
</div>
</summary>
${renderHTML(n.children, false)}
</details>`;
} else {
let unnamedClass = n.taskName === 'Untitled Task' ? 'unnamed-task' : '';
html += `<div class="node-content empty-leaf" style="flex-wrap: wrap;">
<div style="width:100%; display:flex; align-items:center; justify-content:space-between;">
<div class="task-left">
${toggleNotesBtn}
<span class="task-title ${unnamedClass}">${n.taskName}</span>
${tags}
</div>
${meta}
</div>
${notesHtml ? '<div style="width:100%; padding-left:8px;">' + notesHtml + '</div>' : ''}
</div>`;
}
html += '</li>';
});
html += '</ul>';
return html;
}
function countNodes(nodes) {
return nodes.reduce((sum, n) => sum + 1 + countNodes(n.children), 0);
}
function collectMatchedNodes(nodes) {
let acc = [];
nodes.forEach(n => {
if (n.isMatch) acc.push(n);
acc = acc.concat(collectMatchedNodes(n.children));
});
return acc;
}
function generateSummary(nodes, status, leader) {
let matched = collectMatchedNodes(nodes);
if (matched.length === 0) return '';
let totalMandays = 0;
let completed = 0;
let ongoing = 0;
let taskNames = [];
matched.forEach(r => {
if (r.manday) {
let num = parseFloat(r.manday);
if (!isNaN(num)) totalMandays += num;
}
if (r.status && (r.status.includes('Completed') || r.status.includes('完成'))) completed++;
else if (r.status && (r.status.includes('Ongoing') || r.status.includes('进行中'))) ongoing++;
if (r.taskName && r.taskName !== 'Untitled Task') {
taskNames.push(r.taskName);
}
});
let html = '<div style="margin-bottom:16px; padding:12px 16px; background:#f0fdf4; border: 1px solid #bbf7d0; border-left:4px solid #22c55e; border-radius:6px; font-size:14px; color:#166534; box-shadow: 0 1px 2px rgba(0,0,0,0.05);">';
html += '<div style="font-weight:600; margin-bottom:6px; display:flex; align-items:center; gap:6px;"><span>💡</span>智能总结 (Smart Summary)</div>';
let parts = [];
if (!status && !leader) {
let title = document.getElementById('searchInput') && document.getElementById('searchInput').value ? '搜索结果' : '当前全局';
parts.push(`${title}共有 <strong>${matched.length}</strong> 项任务 (已完成 ${completed} 项,进行中 ${ongoing} 项)。`);
if (totalMandays > 0) parts.push(`预计总人力:<strong>${Math.round(totalMandays*10)/10}</strong> 人日。`);
parts.push(`核心事项概览:`);
} else if (leader && !status) {
parts.push(`<strong>${leader}</strong> 共有 <strong>${matched.length}</strong> 项任务 (已完成 ${completed} 项,进行中 ${ongoing} 项)。`);
if (totalMandays > 0) parts.push(`预计总人力:<strong>${Math.round(totalMandays*10)/10}</strong> 人日。`);
parts.push(`Ta 负责的主要事项有:`);
} else if (status && !leader) {
let isDone = status.includes('Completed') || status.includes('完成');
if (isDone) {
parts.push(`✅ 已经干完了 <strong>${matched.length}</strong> 项任务!`);
if (totalMandays > 0) parts.push(`共计消耗人力:<strong>${Math.round(totalMandays*10)/10}</strong> 人日。`);
parts.push(`具体完成了这些东西:`);
} else {
parts.push(`有 <strong>${matched.length}</strong> 项任务处于 "<strong>${status}</strong>" 状态。`);
if (totalMandays > 0) parts.push(`关联的人力评估:<strong>${Math.round(totalMandays*10)/10}</strong> 人日。`);
parts.push(`具体包含:`);
}
} else if (leader && status) {
parts.push(`<strong>${leader}</strong> 有 <strong>${matched.length}</strong> 项状态为 "<strong>${status}</strong>" 的任务。`);
if (totalMandays > 0) parts.push(`相关人力评估:<strong>${Math.round(totalMandays*10)/10}</strong> 人日。`);
parts.push(`相关任务:`);
}
if (taskNames.length > 0) {
let displayNames = taskNames.slice(0, 10).map(n => `「${n}」`).join('');
if (taskNames.length > 10) displayNames += ` 等等 (共${taskNames.length}个)`;
parts.push(`${displayNames}。`);
}
html += '<div style="line-height: 1.6;">' + parts.join(' ') + '</div>';
window._lastMatchedNodes = matched;
html += `<div style="margin-top:12px;display:flex;gap:8px;align-items:center;">
<button onclick="window.requestAISummary()" style="background:#22c55e;color:white;border:none;padding:6px 12px;border-radius:6px;cursor:pointer;font-weight:500;font-size:13px;transition:background 0.2s;" onmouseover="this.style.background='#16a34a'" onmouseout="this.style.background='#22c55e'">🤖 请求 AI 深度总结</button>
<button onclick="window.configAI()" style="background:transparent;border:1px solid #bbf7d0;color:#166534;padding:6px 12px;border-radius:6px;cursor:pointer;font-size:13px;transition:background 0.2s;" onmouseover="this.style.background='#dcfce7'" onmouseout="this.style.background='transparent'">⚙️ 配置 API</button>
</div>`;
html += '</div>';
return html;
}
window.configAI = function() {
let currentKey = localStorage.getItem('ai_api_key') || '{DEFAULT_AI_KEY}';
let currentUrl = localStorage.getItem('ai_base_url') || '{DEFAULT_AI_URL}';
let currentModel = localStorage.getItem('ai_model') || '{DEFAULT_AI_MODEL}';
let key = prompt('请输入您的 API Key (无需则留空或取消):', currentKey);
if (key === null) return;
let url = prompt('请输入 API Base URL (例如 https://api.openai.com/v1 或兼容地址):', currentUrl);
if (url === null) return;
let model = prompt('请输入模型名称 (例如 gpt-4o, deepseek-chat):', currentModel);
if (model === null) return;
localStorage.setItem('ai_api_key', key);
localStorage.setItem('ai_base_url', url);
localStorage.setItem('ai_model', model);
alert('AI 配置已成功保存在浏览器本地!');
};
async function fetchAndStreamAI(promptText, box) {
let key = localStorage.getItem('ai_api_key') || '{DEFAULT_AI_KEY}';
let url = localStorage.getItem('ai_base_url') || '{DEFAULT_AI_URL}';
let model = localStorage.getItem('ai_model') || '{DEFAULT_AI_MODEL}';
if (!key && url.includes('openai.com')) {
alert('系统检测到您未配置 API Key请先进行配置');
window.configAI();
key = localStorage.getItem('ai_api_key');
if (!key) return;
}
box.style.display = 'block';
box.textContent = '🚀 正在请求 AI这可能需要一点时间请稍等...\n如果长时间不响应请检查浏览器控制台报错或网络代理。';
try {
let fetchUrl = url;
if (fetchUrl.endsWith('/')) fetchUrl = fetchUrl.slice(0, -1);
if (!fetchUrl.endsWith('/chat/completions')) fetchUrl += '/chat/completions';
const response = await fetch(fetchUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + key
},
body: JSON.stringify({
model: model,
messages: [
{ role: 'system', content: '你是一位资深项目负责人,擅长做简洁、高价值的工作汇报,突出结果与业务价值,而非技术细节。' },
{ role: 'user', content: promptText }
],
stream: true,
temperature: 0.7
})
});
if (!response.ok) {
const errBox = await response.text();
box.textContent = `网络请求失败 (状态码 ${response.status}): \n${errBox}\n\n请确认提供的 API Base URL(${url}) 和 API Key 是否正确。`;
return;
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
let content = '';
box.textContent = '思考中...';
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (line.trim().startsWith('data:') && line.trim() !== 'data: [DONE]') {
try {
let jsonStr = line.replace(/^data:\s*/, '');
const parsed = JSON.parse(jsonStr);
if (parsed.choices && parsed.choices[0].delta && parsed.choices[0].delta.content) {
content += parsed.choices[0].delta.content;
let displayContent = content.replace(/<think>[\s\S]*?(?:<\/think>\n*|$)/gi, '');
if (content.toLowerCase().includes('<think>') && !content.toLowerCase().includes('</think>')) {
displayContent = "🤔 正在深度思考中...\n\n" + displayContent;
}
box.textContent = displayContent;
box.scrollTop = box.scrollHeight;
}
} catch(e) {}
}
}
}
} catch(e) {
box.textContent = `发生异常: ${e.message}\n请检查网络连通性或 API 跨域(CORS)限制问题。`;
}
}
window.requestAISummary = function() {
const matched = window._lastMatchedNodes || [];
if (matched.length === 0) {
alert("当前视图没有任何任务记录,无需总结。");
return;
}
const tasksForAI = matched.map(m => ({
TaskName: m.taskName,
Status: m.status,
Priority: m.priority,
Leaders: m.leaders.map(l => l.name).join(','),
ProgressNotes: m.progressNotes || "",
Manday: m.manday || "0",
TargetCustomers: m.targetCustomers || ""
}));
const promptText = `请将以下多个任务整理汇报摘要:
要求:
1. 每个任务用3-4行总结须明确提及该任务相关的 TargetCustomers 目标客户信息)
2. 最后增加【整体情况总结】:
- 完成情况(完成/进行中)
- 当前重点方向(包含核心目标客户的总体进展)
输出结构:
(多个任务总结)
【整体情况总结】
- 数据如下:
${JSON.stringify(tasksForAI, null, 2)}`;
const box = document.getElementById('aiSummaryResult');
fetchAndStreamAI(promptText, box);
};
window.requestGlobalSummary = function() {
let allTasks = [];
tabData.forEach(tab => {
tab.records.forEach(r => {
const fields = r.fields || {};
const taskNameRaw = fields['Task'] || fields['Task Description'];
let taskName = '';
if (Array.isArray(taskNameRaw) && taskNameRaw.length > 0) taskName = taskNameRaw[0].text || '';
else taskName = taskNameRaw ? String(taskNameRaw) : 'Untitled Task';
let statusRaw = fields['Status'];
let status = '';
if (Array.isArray(statusRaw) && statusRaw.length > 0) status = statusRaw[0].text || '';
else status = statusRaw ? String(statusRaw) : '';
let targetCustomersRaw = fields['Target Customers'];
let targetCustomers = '';
if (Array.isArray(targetCustomersRaw)) {
targetCustomers = targetCustomersRaw.map(x => typeof x === 'object' ? (x.text || '') : String(x)).join(', ').trim();
} else if (targetCustomersRaw) {
targetCustomers = String(targetCustomersRaw).trim();
}
allTasks.push({
TabName: tab.name,
TaskName: taskName,
Status: status.trim(),
TargetCustomers: targetCustomers.trim()
});
});
});
if (allTasks.length === 0) {
alert("所有视图均没有任务记录,无需总结。");
return;
}
const promptText = `请将以下来自多个部门/维度的表格任务进行全局大总结:
要求:
1. 按照不同的 Tab (即数据来源) 独立提取核心进展与问题。
2. 特别留意并提炼出 TargetCustomers (目标客户) 的主要推行情况。
3. 最后给出一个【全局统筹结论】,指出哪些方向正常,哪些可能存在延期或需要重点关注。
4. 尽量言简意赅,不要只是把每一条数据罗列一遍,要有跨表分析和高层次的提炼。
- 数据如下:
${JSON.stringify(allTasks, null, 2)}`;
const box = document.getElementById('globalAiSummaryResult');
fetchAndStreamAI(promptText, box);
};
function render() {
const search = document.getElementById('searchInput').value;
const status = document.getElementById('statusFilter').value;
const leader = document.getElementById('leaderFilter').value;
const sortKey = document.getElementById('sortOrder').value;
let dfRaw = document.getElementById('dateFrom').value;
let dtRaw = document.getElementById('dateTo').value;
const dateFrom = dfRaw ? new Date(dfRaw).getTime() : -Infinity;
const dateTo = dtRaw ? new Date(dtRaw).getTime() + 86400000 - 1 : Infinity;
let nodeMap = {};
records.forEach(r => { nodeMap[r.id] = {...r, children: []}; });
let roots = [];
records.forEach(r => {
let n = nodeMap[r.id];
if (r.parentId && nodeMap[r.parentId]) nodeMap[r.parentId].children.push(n);
else roots.push(n);
});
// Sort logic inside roots recursively helps visual grouping
roots.sort((a,b) => b.children.length - a.children.length); // roots with children on top by default
let filtered = filterNodes(roots, search, status, leader, dateFrom, dateTo);
let sorted = sortNodes(filtered, sortKey);
let renderedHtml = renderHTML(sorted, true);
document.getElementById('tree-container').innerHTML = renderedHtml || '<p style="text-align:center;color:#64748b;margin-top:40px;">No matching records found.</p>';
let visibleCount = countNodes(sorted);
let isFilterActive = search !== '' || status !== '' || leader !== '';
let msg = `Showing ${visibleCount} / ${records.length} tasks`;
if (isFilterActive) {
msg += ' (Filtered)';
}
document.getElementById('statsDisplay').textContent = msg;
document.getElementById('summaryDisplay').innerHTML = generateSummary(filtered, status, leader);
// Clear AI Summary on any filter change so it doesn't show stale info
let aiBox = document.getElementById('aiSummaryResult');
if (aiBox) {
aiBox.style.display = 'none';
aiBox.textContent = '';
}
}
// Initialize tabs and first render
initTabs();
</script>
</body>
</html>
"""
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()

59
get_records.py Normal file
View File

@ -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.")

158
import_bitables.py Normal file
View File

@ -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)

39
list_test.py Normal file
View File

@ -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()

6
main.py Normal file
View File

@ -0,0 +1,6 @@
def main():
print("Hello from python!")
if __name__ == "__main__":
main()

221
merge_bitables.py Normal file
View File

@ -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"
)

40
mock.py Executable file
View File

@ -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',
}
]

915
outline_view.html Normal file

File diff suppressed because one or more lines are too long

10
pyproject.toml Normal file
View File

@ -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",
]

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
python-dotenv
requests

76
server.py Normal file
View File

@ -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"<h1>Internal Server Error: Could not fetch data</h1><p>{e}</p>".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}")

44
test_batch_get.py Normal file
View File

@ -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()

34
utils.py Normal file
View File

@ -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

110
uv.lock generated Normal file
View File

@ -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" },
]

117
列出数据表.md Normal file
View File

@ -0,0 +1,117 @@
# 列出数据表
列出多维表格中的所有数据表,包括其 ID、版本号和名称。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 获取数据表信息(base:table:read)<br>查看、评论、编辑和管理多维表格(bitable:app)<br>查看、评论和导出多维表格(bitable:app:readonly)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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` 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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`<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。<br>**示例值**"appbcbWCzen6D8dezhoCH2RpMAh"
### 查询参数
名称 | 类型 | 必填 | 描述
---|---|---|---
page_token | string | 否 | 分页标记,第一次请求不填,表示从头开始遍历;分页查询结果还有更多项时会同时返回新的 page_token下次遍历可采用该 page_token 获取查询结果<br>**示例值**tblsRc9GRRXKqhvW
page_size | int | 否 | 分页大小<br>**示例值**10<br>**默认值**`20`<br>**数据校验规则**<br>- 最大值:`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 | 人员字段有误。原因可能是:<br>- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配<br>- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组<br>- 跨应用传入了 `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. | 请求超时,请进行重试

124
创建多维表格.md Normal file
View File

@ -0,0 +1,124 @@
# 创建多维表格
在指定文件夹中创建一个多维表格,包含一个空白的数据表。
**注意事项**:要基于模板创建多维表格,可先获取模板多维表格 `app_token` 作为文件 token再调用[复制文件](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/drive-v1/file/copy)接口创建多维表格。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 创建多维表格(base:app:create)<br>查看、评论、编辑和管理多维表格(bitable:app)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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 个字符。<br>**示例值**"一篇新的多维表格"
folder_token | string | 否 | 多维表格 App 归属文件夹。默认为空,表示多维表格将被创建在云空间根目录。了解如何获取文件夹 Token参考[如何获取云文档资源相关 Token](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#08bb5df6)。<br>**注意**<br>请确保调用身份拥有在该文件夹中的编辑权限。若应用使用的是 `tenant_access_token` 权限,此处仅可指定应用创建的文件夹。详情参考[如何为应用开通云文档相关资源的权限](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-add-permissions-to-app)。<br>**示例值**"fldcnqquW1svRIYVT2Np6Iabcef"
time_zone | string | 否 | 文档时区,详情参考[文档时区介绍](https://feishu.feishu.cn/docx/YKRndTM7VoyDqpxqqeEcd67MnEf)。<br>**示例值**"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 | 人员字段有误。原因可能是:<br>- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配<br>- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组<br>- 跨应用传入了 `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 | 请求超时 | 进行重试

108
复制多维表格.md Normal file
View File

@ -0,0 +1,108 @@
# 复制多维表格
复制一个多维表格,可以指定复制到某个有权限的文件夹下。
**注意事项**:当多维表格记录数超 50,000 条可复制上限时,仅可复制多维表格结构。
## 前提条件
调用此接口前请确保当前调用身份tenant_access_token 或 user_access_token已有多维表格和目标文件夹的阅读、编辑等文档权限否则接口将返回 HTTP 403 或 400 状态码。了解更多,参考[如何为应用或用户开通云文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 复制多维表格(base:app:copy)<br>查看、评论、编辑和管理多维表格(bitable:app)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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)获取。<br>**示例值**"AW3Qbtr2cakCnesXzXVbbsrIcVT "
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
name | string | 否 | 多维表格 App 的名称<br>**示例值**"一篇新的多维表格"
folder_token | string | 否 | 了解如何获取文件夹 Token参考[如何获取云文档资源相关 Token](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#08bb5df6)。<br>**注意**<br>请确保调用身份拥有在该文件夹中的编辑权限。若应用使用的是 `tenant_access_token` 权限,此处仅可指定应用创建的文件夹。详情参考[如何为应用开通云文档相关资源的权限](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-add-permissions-to-app)。<br>**示例值**"fldcnqquW1svRIYVT2Np6Iabcef"
without_content | boolean | 否 | 是否复制多维表格中的内容,默认 false即复制多维表格中的内容。可取值<br>* true不复制<br>* false复制<br>**示例值**false
time_zone | string | 否 | 文档时区,详情参考[文档时区介绍](https://feishu.feishu.cn/docx/YKRndTM7VoyDqpxqqeEcd67MnEf)。<br>**示例值**"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 | 请求超时 | 进行重试

194
多维表格常见问题.md Normal file
View File

@ -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` 字段,即为记录总数。

272
多维表格概述.md Normal file
View File

@ -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)<br>`POST` /open-apis/bitable/v1/apps | 创建多维表格(base:app:create)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[复制多维表格](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/copy)<br>`POST` /open-apis/bitable/v1/apps/:app_token/copy | 复制多维表格(base:app:copy)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[获取多维表格元数据](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/get)<br>`GET` /open-apis/bitable/v1/apps/:app_token | 查看、评论和导出多维表格(bitable:app:readonly)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[更新多维表格元数据](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app/update)<br>`PUT` /open-apis/bitable/v1/apps/:app_token | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`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)<br>`GET` /open-apis/bitable/v1/apps/:app_token/tables | 查看、评论和导出多维表格(bitable:app:readonly)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[新增数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[新增多个数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/batch_create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables/batch_create | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[删除数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/delete)<br>`DELETE` /open-apis/bitable/v1/apps/:app_token/tables/:table_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[删除多个数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/batch_delete)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables/batch_delete | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`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)<br>`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views | 查看、评论和导出多维表格(bitable:app:readonly)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[新增视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[更新视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/patch)<br>`PATCH` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views/:view_id | 查看、评论和导出多维表格(bitable:app:readonly)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[检索视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/get)<br>`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views/:view_id | 检索视图(base:view:read)<br>查看、评论、编辑和管理多维表格(bitable:app)<br>查看、评论和导出多维表格(bitable:app:readonly) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[删除视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/delete)<br>`DELETE` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/views/:view_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`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)<br>`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records | 查看、评论和导出多维表格(bitable:app:readonly)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[检索记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/get)<br>`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/:record_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[新增记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[新增多条记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/batch_create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_create | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[更新记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/update)<br>`PUT` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/:record_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[更新多条记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/batch_update)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_update | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[删除记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/delete)<br>`DELETE` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/:record_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[删除多条记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/batch_delete)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/records/batch_delete | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`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)<br>`GET` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/fields | 查看、评论和导出多维表格(bitable:app:readonly)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[新增字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/fields | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[更新字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/update)<br>`PUT` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/fields/:field_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[删除字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/delete)<br>`DELETE` /open-apis/bitable/v1/apps/:app_token/tables/:table_id/fields/:field_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`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)<br>`POST` /open-apis/bitable/v1/apps/:app_token/dashboards/:block_id/copy | 复制仪表盘(base:dashboard:copy)<br>查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[列出仪表盘](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-dashboard/list)<br>`GET` /open-apis/bitable/v1/apps/:app_token/dashboards | 获取仪表盘信息(base:dashboard:read)<br>查看、评论、编辑和管理多维表格(bitable:app)<br>查看、评论和导出多维表格(bitable:app:readonly) | `tenant_access_token`<br>`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)<br>`GET` /open-apis/bitable/v1/apps/:app_token/roles | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[新增自定义权限](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/roles/:role_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[更新自定义权限](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/update)<br>`PUT` /open-apis/bitable/v1/apps/:app_token/roles/:role_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[删除自定义权限](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/delete)<br>`DELETE` /open-apis/bitable/v1/apps/:app_token/roles/:role_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`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)<br>`GET` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[新增协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[删除协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/delete)<br>`DELETE` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/:member_id | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[批量新增协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/batch_create)<br>`POST` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/batch_create | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**
[批量删除协作者](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role-member/batch_delete)<br>`DELETE` /open-apis/bitable/v1/apps/:app_token/roles/:role_id/members/batch_delete | 查看、评论、编辑和管理多维表格(bitable:app) | `tenant_access_token`<br>`user_access_token` | **✓** | **✓**

296
批量获取记录.md Normal file
View File

@ -0,0 +1,296 @@
# 批量获取记录
通过多个记录 ID 查询记录信息。该接口最多支持查询 100 条记录。
## 注意事项
若多维表格开启了高级权限,你需确保调用身份拥有多维表格的可管理权限,否则可能出现调用成功但返回数据为空的情况。了解具体步骤,参考[如何为应用或用户开通文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 检索特定记录(base:record:read)<br>查看、评论、编辑和管理多维表格(bitable:app)<br>查看、评论和导出多维表格(bitable:app:readonly)
字段权限要求 | **注意事项**:该接口返回体中存在下列敏感字段,仅当开启对应的权限后才会返回;如果无需获取这些字段,则不建议申请<br>获取用户基本信息(contact:user.base:readonly)<br>以应用身份访问通讯录(contact:contact:access_as_app)<br>读取通讯录(contact:contact:readonly)<br>以应用身份读取通讯录(contact:contact:readonly_as_app)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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` 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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`<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。<br>**示例值**"NQRxbRkBMa6OnZsjtERcxhabcef"<br>**数据校验规则**<br>- 长度范围:`0` `100` 字符
table_id | string | 多维表格数据表的唯一标识。获取方式:<br>- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`<br>- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976)<br>**示例值**"tbl0xe5g8PPabcef"<br>**数据校验规则**<br>- 长度范围:`0` `50` 字符
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
record_ids | string\[\] | 是 | 记录 ID 列表。调用[查询记录](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-record/search)获取。<br>**示例值**["recyO3299N"]<br>**数据校验规则**<br>- 长度范围:`1` `100`
user_id_type | string | 否 | 此次调用中使用的用户 id 的类型<br>**示例值**"open_id"<br>**可选值有**<br>- user_id以user_id来识别用户<br>- union_id以union_id来识别用户<br>- open_id以open_id来识别用户
with_shared_url | boolean | 否 | 是否返回记录的分享链接。可选值:<br>- true返回分享链接<br>- false不返回分享链接<br>**默认值**false<br>**示例值**true
automatic_fields | boolean | 否 | 是否返回自动计算的字段。可选值:<br>- true返回自动计算的字段<br>- false不返回自动计算的字段<br>**默认值**false<br>**示例值**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&lt;string, union&gt; | 记录字段
record_id | string | 记录 ID
created_by | person | 创建人信息
id | string | 创建人 ID。ID 类型与请求体中的 `user_id_type` 指定的类型一致。
name | string | 创建人中文姓名
en_name | string | 创建人英文姓名
email | string | 创建人邮箱
avatar_url | string | 创建人头像链接<br>**字段权限要求(满足任一)**<br>获取用户基本信息(contact:user.base:readonly)<br>以应用身份访问通讯录(contact:contact:access_as_app)<br>读取通讯录(contact:contact:readonly)<br>以应用身份读取通讯录(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 | 修改人头像链接<br>**字段权限要求(满足任一)**<br>获取用户基本信息(contact:user.base:readonly)<br>以应用身份访问通讯录(contact:contact:access_as_app)<br>读取通讯录(contact:contact:readonly)<br>以应用身份读取通讯录(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&amp;cut_type=default-face&amp;quality=&amp;format=jpeg&amp;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&amp;cut_type=default-face&amp;quality=&amp;format=jpeg&amp;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&amp;cut_type=default-face&amp;quality=&amp;format=jpeg&amp;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&amp;cut_type=default-face&amp;quality=&amp;format=jpeg&amp;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&amp;cut_type=default-face&amp;quality=&amp;format=jpeg&amp;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&amp;cut_type=&amp;quality=&amp;format=jpeg&amp;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&amp;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&amp;cut_type=default-face&amp;quality=&amp;format=jpeg&amp;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 错误码的场景较多,请参考以下建议排查:<br>- 如果单次操作的内容变更较大,请尝试在单次操作中减少数据量<br>- 如果你并发调用了接口,请尝试控制请求间隔,稍后重试<br>- 如果在知识库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)接口<br>- 请检查接口参数是否有误。例如,在分页查询多维表格时,传递了无效的 page_token或传递了错误的数据表的 table_id<br>- 如果该报错偶尔发生,可能是服务器超时或不稳定,请重试解决
400 | 1254003 | WrongBaseToken | app_token 错误。app_token 是多维表格 App 的唯一标识。不同形态的多维表格,其 `app_token` 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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`<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
400 | 1254004 | WrongTableId | table_id 错误。table_id 是多维表格数据表的唯一标识。获取方式:<br>- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`<br>- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`<br>![](//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 是多维表格中视图的唯一标识。获取方式:<br>- 在多维表格的 URL 地址栏中,`view_id` 是下图中高亮部分:<br>![view_id.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_DJMgVH9x2S.png?height=748&lazyload=true&width=2998)<br>- 通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取。暂时无法获取到嵌入到云文档中的多维表格的 `view_id`<br>**注意**<br>`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` 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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`<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
404 | 1254041 | TableIdNotFound | table_id 不存在。获取方式:<br>- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`<br>- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976)
404 | 1254042 | ViewIdNotFound | view_id 不存在。获取方式:<br>- 在多维表格的 URL 地址栏中,`view_id` 是下图中高亮部分:<br>![view_id.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_DJMgVH9x2S.png?height=748&lazyload=true&width=2998)<br>- 通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取。暂时无法获取到嵌入到云文档中的多维表格的 `view_id`<br>**注意**<br>`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 | 字段名称不存在。请检查:<br>- 接口中字段名称和多维表格中的字段名称是否完全匹配。如果难以排查,建议你调用[列出字段](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/list)接口获取字段名称,因为根据表格页面的 UI 名称可能会忽略空格、换行或特殊符号等差异。<br>- 表格是否开启了高级权限但调用身份缺少对应字段的权限。你需要为调用身份授予高级权限:<br>- 对用户授予高级权限,你需要在多维表格页面右上方 **分享** 入口为当前用户添加可管理权限。![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/df3911b4f747d75914f35a46962d667d_dAsfLjv3QC.png?height=546&lazyload=true&maxWidth=550)<br>- 对应用授予高级权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&maxWidth=550&width=3278)<br>![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?maxWidth=550)<br>**注意**<br>**添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。 <br>- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。
500 | 1254060 | TextFieldConvFail | 多行文本字段错误
500 | 1254061 | NumberFieldConvFail | 数字字段错误
500 | 1254062 | SingleSelectFieldConvFail | 单选字段错误
500 | 1254063 | MultiSelectFieldConvFail | 多选字段错误
500 | 1254064 | DatetimeFieldConvFail | 日期字段错误
500 | 1254065 | CheckboxFieldConvFail | 复选框字段错误
500 | 1254066 | UserFieldConvFail | 人员字段有误。原因可能是:<br>- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配<br>- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组<br>- 跨应用传入了 `open_id`。如果跨应用传入 ID建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用<br>- 若想对人员字段传空,可传 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 | 在同一个数据表中,并发调用了读写接口或请求过快,出现冲突。请参考以下建议解决:<br>- 确保没有并发调用多维表格读写相关接口<br>- 若操作量较大,建议在接口与接口之间增加 0.5 或 1 秒的延迟,也可在报错中增加重试逻辑,确保业务的稳定性<br>- 对于写接口,可以将接口中的查询参数 `ignore_consistency_check` 设置为 true表示在读写操作时暂时忽略一致性检查以提高性能
400 | 1254301 | OperationTypeError | 多维表格未开启高级权限或不支持开启高级权限
400 | 1254302 | RolePermNotAllow | 无访问权限,常由表格开启了高级权限造成。请确保当前调用身份具有高级权限或多维表格的管理权限:<br>- 对于应用身份,你需要通过云文档网页页面右上方 「**...**」->「**...更多**」-> 「**添加文档应用**」入口添加并授予应用可管理权限,或在高级权限设置中添加一个包含应用的群组,给予这个群读写权限<br>- 对于用户身份,你需要通过云文档网页的「**分享**」入口授予用户管理权限<br>了解更多,参考[如何为应用或用户开通云文档权限](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 | 该报错一般是由于前置操作未执行完成,或本次操作数据太大,服务器计算超时导致。遇到该错误码时,建议等待一段时间后重试。通常有以下几种原因:<br>- **编辑操作频繁**:开发者对多维表格的编辑操作非常频繁。可能会导致由于等待前置操作处理完成耗时过长而超时的情况。多维表格底层对数据表的处理基于版本维度的串行方式,不支持并发。因此,并发请求时容易出现此类错误,不建议开发者对单个数据表进行并发请求。<br>- **批量操作负载重**:开发者在多维表格中进行批量新增、删除等操作时,如果数据表的数据量非常大,可能会导致单次请求耗时过长,最终导致请求超时。建议开发者适当降低批量请求的 page_size 以减少请求耗时。<br>- **资源分配与计算开销**:资源分配是基于单文档维度的,如果读接口涉及公式计算、排序等计算逻辑,会占用较多资源。例如,并发读取一个文档下的多个数据表也可能导致该文档阻塞。

159
批量获取评论.md Normal file
View File

@ -0,0 +1,159 @@
# 批量获取评论
该接口用于根据评论 ID 列表批量获取云文档评论信息,包括评论和回复 ID、回复的内容、评论人和回复人的用户 ID 等。支持返回全局评论以及局部评论(可通过 is_whole 字段区分)。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 获取云文档中的评论(docs:document.comment:read)<br>查看、评论、编辑和管理云空间中所有文件(drive:drive)<br>查看、评论和下载云空间中所有文件(drive:drive:readonly)
字段权限要求 | **注意事项**:该接口返回体中存在下列敏感字段,仅当开启对应的权限后才会返回;如果无需获取这些字段,则不建议申请<br>获取用户 user ID(contact:user.employee_id:readonly)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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<br>**示例值**"doxbcdl03Vsxhm7Qmnj110abcef"
### 查询参数
名称 | 类型 | 必填 | 描述
---|---|---|---
file_type | string | 是 | 云文档类型<br>**示例值**docx<br>**可选值有**<br>- doc旧版文档类型已不推荐使用<br>- docx新版文档类型<br>- sheet电子表格类型<br>- file文件类型<br>- slides幻灯片
user_id_type | string | 否 | 用户 ID 类型<br>**示例值**open_id<br>**可选值有**<br>- open_id标识一个用户在某个应用中的身份。同一个用户在不同应用中的 Open ID 不同。[了解更多:如何获取 Open ID](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-openid)<br>- union_id标识一个用户在某个应用开发商下的身份。同一用户在同一开发商下的应用中的 Union ID 是相同的,在不同开发商下的应用中的 Union ID 是不同的。通过 Union ID应用开发商可以把同个用户在多个应用中的身份关联起来。[了解更多:如何获取 Union ID](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-union-id)<br>- 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)<br>**默认值**`open_id`<br>**当值为 `user_id`,字段权限要求**<br>获取用户 user ID(contact:user.employee_id:readonly)
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
comment_ids | string\[\] | 是 | 需要获取数据的评论 ID ,可通过调用获取云文档所有评论接口获取 comment_id<br>**示例值**["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 | 回复的内容元素<br>**可选值有**<br>- text_run普通文本<br>- docs_linkat 云文档链接<br>- personat 联系人
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 | 数据迁移中,暂时无法上传。

80
数据结构概述.md Normal file
View File

@ -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)。<br>```json<br>[<br>{<br>"text": "网站更新任务正在进行中",<br>"type": "text"<br>}<br>]<br>```
## 字段
字段即多维表格数据表中的“列”,是一个`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区分<br>- 1文本默认值、条码需声明 <code>"ui_type": "Barcode"</code>)、邮箱(需声明<code>"ui_type": "Email"</code>)<br>- 2数字默认值、进度需声明 <code>"ui_type": "Progress"</code>)、货币(需声明 <code>"ui_type": "Currency"</code>)、评分(需声明 <code>"ui_type": "Rating"</code>)<br>- 3单选<br>- 4多选<br>- 5日期<br>- 7复选框<br>- 11人员<br>- 13电话号码<br>- 15超链接<br>- 17附件<br>- 18单向关联<br>- 19查找引用<br>- 20公式<br>- 21双向关联<br>- 22地理位置<br>- 23群组<br>- 24流程不支持通过写接口新增或编辑仅支持读接口 <br>- 1001创建时间<br>- 1002最后更新时间<br>- 1003创建人<br>- 1004修改人<br>- 1005自动编号<br>- 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 类型:<br>- "Text":文本<br>- "Email":邮箱<br>- "Barcode":条码<br>- "Number":数字<br>- "Progress":进度<br>- "Currency":货币<br>- "Rating":评分<br>- "SingleSelect":单选<br>- "MultiSelect":多选<br>- "DateTime":日期<br>- "Checkbox":复选框<br>- "User":人员<br>- "GroupChat":群组<br>- "Phone":电话号码<br>- "Url":超链接<br>- "Attachment":附件<br>- "SingleLink":单向关联<br>- "Formula":公式<br>- "Lookup": 查找引用<br>- "DuplexLink":双向关联<br>- "Location":地理位置<br>- "CreatedTime":创建时间<br>- "ModifiedTime":最后更新时间<br>- "CreatedUser":创建人<br>- "ModifiedUser":修改人<br>- "AutoNumber":自动编号<br>- "Button":按钮
is_hidden | true/false | 字段是否是隐藏字段。
## 视图
视图是一个 object 结构类型。
参数 | 类型 | 描述
---|---|---
view_id | string | 视图 ID。`view_id` 在一个多维表格中唯一,在全局不一定唯一。获取方式:<br>- 你可通过多维表格 URL 获取 `view_id`,下图高亮部分即为当前视图的唯一标识。<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_c76RMwZAgW.png?height=748&lazyload=true&maxWidth=700&width=2998)<br>- 你也可通过[列出视图](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 类型。<br>- grid表格视图<br>- kanban看板视图<br>- gallery画册视图<br>- gantt甘特视图<br>- form表单视图
## 自定义数据结构
### delete_record
| 参数 | 数据类型 | 描述 |
| --------- | --------------- | ----------- |
|`deleted` | `boolean` | 是否删除成功 |
|`record_id` | `string` | 单条记录的 ID |

189
新增一个数据表.md Normal file
View File

@ -0,0 +1,189 @@
# 新增一个数据表
新增一个数据表,支持传入数据表名称、视图名称和字段。
## 前提条件
调用此接口前请确保当前调用身份tenant_access_token 或 user_access_token已有多维表格的编辑等文档权限否则接口将返回 HTTP 403 或 400 状态码。了解更多,参考[如何为应用或用户开通文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。
## 使用限制
每个多维表格中,数据表与仪表盘的总数量上限为 100。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 新增数据表(base:table:create)<br>查看、评论、编辑和管理多维表格(bitable:app)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 app_token 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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。<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。<br>**示例值**"appbcbWCzen6D8dezhoCH2RpMAh"<br>**数据校验规则**<br>- 最小长度:`1` 字符
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
table | req_table | 否 | 数据表
name | string | 否 | 数据表名称。该字段必填。<br>**注意**<br>- 名称中的首尾空格将会被默认去除<br>- 数据表名称不可以包含 `/ \ ? * : [ ]` 等特殊字符<br>**示例值**"一个新的数据表"<br>**数据校验规则**<br>- 长度范围:`1` `100` 字符
default_view_name | string | 否 | 默认表格视图的名称。<br>注意:<br>- 名称中的首尾空格将会被去除<br>- 名称中不允许包含 [ ] 两个字符<br>**示例值**"表格视图"
fields | app.table.create_header\[\] | 否 | 数据表的初始字段。了解如何填写字段,参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。<br>**注意**<br>- 如果传入了 `default_view_name` 字段,则必须传入 `fields` 字段<br>- 如果不传 `default_view_name` 字段,则 `fields` 字段为可选字段<br>- 若 `default_view_name` 字段和 `fields` 字段都不传,将会创建一个仅包含索引字段的空数据表。<br>- 数据表的第一个字段为索引字段。索引字段仅支持以下类型:<br>- 1多行文本<br>- 2数字<br>- 5日期<br>- 13电话号码<br>- 15超链接<br>- 20公式<br>- 22地理位置 <br>**数据校验规则**<br>- 长度范围:`1` `300`
field_name | string | 是 | 字段名称<br>**示例值**"问题描述"
type | int | 是 | 字段类型。不支持新增 19 查找引用字段类型。<br>**示例值**1<br>**可选值有**<br>- 1文本<br>- 2数字<br>- 3单选<br>- 4多选<br>- 5日期<br>- 7复选框<br>- 11人员<br>- 13电话号码<br>- 15超链接<br>- 17附件<br>- 18单向关联<br>- 20公式<br>- 21双向关联<br>- 22地理位置<br>- 23群组<br>- 1001创建时间<br>- 1002最后更新时间<br>- 1003创建人<br>- 1004修改人<br>- 1005自动编号
ui_type | string | 否 | 字段在界面上的展示类型,例如 Progress 进度字段是数字的一种展示形态<br>**示例值**"Progress"<br>**可选值有**<br>- Text文本<br>- Barcode条码<br>- Number数字<br>- Progress进度<br>- Currency货币<br>- Rating评分<br>- SingleSelect单选<br>- MultiSelect多选<br>- DateTime日期<br>- Checkbox复选框<br>- User人员<br>- GroupChat群组<br>- Phone电话号码<br>- Url超链接<br>- Attachment附件<br>- SingleLink单向关联<br>- Formula公式<br>- DuplexLink双向关联<br>- Location地理位置<br>- CreatedTime创建时间<br>- ModifiedTime最后更新时间<br>- CreatedUser创建人<br>- ModifiedUser修改人<br>- AutoNumber自动编号
property | app.table.field.property | 否 | 字段属性
options | app.table.field.property.option\[\] | 否 | 单选、多选字段的选项信息
name | string | 否 | 选项名<br>**示例值**"红色"
id | string | 否 | 选项 ID创建时不可指定 ID<br>**示例值**"optKl35lnG"
color | int | 否 | 选项颜色,详情参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。<br>**示例值**0<br>**数据校验规则**<br>- 取值范围:`0` `54`
formatter | string | 否 | 数字、公式字段的显示格式。详情参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。<br>**示例值**"0"
date_formatter | string | 否 | 日期、创建时间、最后更新时间字段的显示格式。默认为 "yyyy/MM/dd",详情参考[字段编辑指南](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-field/guide)。<br>**示例值**"2021/01/30"
auto_fill | boolean | 否 | 日期字段中新纪录自动填写创建时间。默认为 false<br>**示例值**false
multiple | boolean | 否 | 人员字段中允许添加多个成员,单向关联、双向关联中允许添加多个记录<br>**示例值**false
table_id | string | 否 | 单向关联、双向关联字段中关联的数据表的id<br>**示例值**"tblsRc9GRRXKqhvW"
table_name | string | 否 | 单向关联、双向关联字段中关联的数据表的名字<br>**示例值**"table2"
back_field_name | string | 否 | 双向关联字段中关联的数据表中对应的双向关联字段的名字<br>**示例值**"table1-双向关联"
auto_serial | app.field.property.auto_serial | 否 | 自动编号类型
type | string | 是 | 自动编号类型<br>**示例值**"auto_increment_number"<br>**可选值有**<br>- custom自定义编号<br>- auto_increment_number自增数字
options | app.field.property.auto_serial.options\[\] | 否 | 自动编号规则列表
type | string | 是 | 自动编号的可选规则项类型<br>**示例值**"created_time"<br>**可选值有**<br>- system_number自增数字位,value范围1-9<br>- fixed_text固定字符最大长度20<br>- created_time创建时间支持格式 "yyyyMMdd"、"yyyyMM"、"yyyy"、"MMdd"、"MM"、"dd"
value | string | 是 | 与自动编号的可选规则项类型相对应的取值<br>**示例值**"yyyyMMdd"
location | app.field.property.location | 否 | 地理位置输入方式
input_type | string | 是 | 地理位置输入限制<br>**示例值**"not_limit"<br>**可选值有**<br>- only_mobile只允许移动端上传<br>- not_limit无限制
formula_expression | string | 否 | 公式字段的表达式<br>**示例值**"bitable::$table[tblNj92WQBAasdEf].$field[fldMV60rYs]*2"
allowed_edit_modes | allowed_edit_modes | 否 | 字段支持的编辑模式
manual | boolean | 否 | 是否允许手动录入<br>**示例值**true
scan | boolean | 否 | 是否允许移动端录入<br>**示例值**true
min | number(float) | 否 | 进度、评分等字段的数据范围最小值<br>**示例值**0
max | number(float) | 否 | 进度、评分等字段的数据范围最大值<br>**示例值**10
range_customize | boolean | 否 | 进度等字段是否支持自定义范围<br>**示例值**true
currency_code | string | 否 | 货币币种<br>**示例值**"CNY"
rating | rating | 否 | 评分字段的相关设置
symbol | string | 否 | 评分字段的符号展示<br>**示例值**"star"
description | app.table.field.description | 否 | 字段的描述
disable_sync | boolean | 否 | 是否禁止同步如果为true表示禁止同步该描述内容到表单的问题描述<br>**示例值**true<br>**默认值**`true`
text | string | 否 | 字段描述内容,支持换行\n<br>**示例值**"请按 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 | 不支持的字段或视图。注意数据表的初始索引字段仅支持以下类型:<br>- 1多行文本<br>- 2数字<br>- 5日期<br>- 13电话号码<br>- 15超链接<br>- 20公式<br>- 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 | 人员字段有误。原因可能是:<br>- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配<br>- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组<br>- 跨应用传入了 `open_id`。如果跨应用传入 ID建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用<br>- 若想对人员字段传空,可传 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. | 调用身份缺少多维表格的高级权限。你需要为调用身份授予高级权限:<br>- 对用户授予高级权限,你需要在多维表格页面右上方 **分享** 入口为当前用户添加可管理权限。![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/df3911b4f747d75914f35a46962d667d_dAsfLjv3QC.png?height=546&lazyload=true&maxWidth=550)<br>- 对应用授予高级权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&maxWidth=550&width=3278)<br>![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?maxWidth=550)<br>**注意**<br>**添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。 <br>- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。
400 | 1254607 | Data not ready, please try again later | 该报错一般是由于前置操作未执行完成,或本次操作数据太大,服务器计算超时导致。遇到该错误码时,建议等待一段时间后重试。通常有以下几种原因:<br>- **编辑操作频繁**:开发者对多维表格的编辑操作非常频繁。可能会导致由于等待前置操作处理完成耗时过长而超时的情况。多维表格底层对数据表的处理基于版本维度的串行方式,不支持并发。因此,并发请求时容易出现此类错误,不建议开发者对单个数据表进行并发请求。<br>- **批量操作负载重**:开发者在多维表格中进行批量新增、删除等操作时,如果数据表的数据量非常大,可能会导致单次请求耗时过长,最终导致请求超时。建议开发者适当降低批量请求的 page_size 以减少请求耗时。<br>- **资源分配与计算开销**:资源分配是基于单文档维度的,如果读接口涉及公式计算、排序等计算逻辑,会占用较多资源。例如,并发读取一个文档下的多个数据表也可能导致该文档阻塞。
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)

131
新增多个数据表.md Normal file
View File

@ -0,0 +1,131 @@
# 新增多个数据表
新增多个数据表,仅可指定数据表名称。
## 前提条件
调用此接口前请确保当前调用身份tenant_access_token 或 user_access_token已有多维表格的编辑等文档权限否则接口将返回 HTTP 403 或 400 状态码。了解更多,参考[如何为应用或用户开通文档权限](https://open.feishu.cn/document/ukTMukTMukTM/uczNzUjL3czM14yN3MTN#16c6475a)。
## 使用限制
每个多维表格中,数据表与仪表盘的总数量上限为 100。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 新增数据表(base:table:create)<br>查看、评论、编辑和管理多维表格(bitable:app)
字段权限要求 | **注意事项**:该接口返回体中存在下列敏感字段,仅当开启对应的权限后才会返回;如果无需获取这些字段,则不建议申请<br>获取用户 user ID(contact:user.employee_id:readonly)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 app_token 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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。<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。<br>**示例值**"appbcbWCzen6D8dezhoCH2RpMAh"
### 查询参数
名称 | 类型 | 必填 | 描述
---|---|---|---
user_id_type | string | 否 | 用户 ID 类型<br>**示例值**open_id<br>**可选值有**<br>- open_id标识一个用户在某个应用中的身份。同一个用户在不同应用中的 Open ID 不同。[了解更多:如何获取 Open ID](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-openid)<br>- union_id标识一个用户在某个应用开发商下的身份。同一用户在同一开发商下的应用中的 Union ID 是相同的,在不同开发商下的应用中的 Union ID 是不同的。通过 Union ID应用开发商可以把同个用户在多个应用中的身份关联起来。[了解更多:如何获取 Union ID](https://open.feishu.cn/document/uAjLw4CM/ugTN1YjL4UTN24CO1UjN/trouble-shooting/how-to-obtain-union-id)<br>- 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)<br>**默认值**`open_id`<br>**当值为 `user_id`,字段权限要求**<br>获取用户 user ID(contact:user.employee_id:readonly)
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
tables | req_table\[\] | 否 | tables
name | string | 否 | 数据表名称。该字段必填。<br>**注意**<br>- 名称中的首尾空格将会被默认去除。<br>- 数据表名称不可以包含 `/ \ ? * : [ ]` 等特殊字符。<br>**示例值**"一个新的数据表"<br>**数据校验规则**<br>- 长度范围:`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 | 人员字段有误。原因可能是:<br>- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配<br>- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组<br>- 跨应用传入了 `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. | 调用身份缺少多维表格的高级权限。你需要为调用身份授予高级权限:<br>- 对用户授予高级权限,你需要在多维表格页面右上方 **分享** 入口为当前用户添加可管理权限。![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/df3911b4f747d75914f35a46962d667d_dAsfLjv3QC.png?height=546&lazyload=true&maxWidth=550)<br>- 对应用授予高级权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&maxWidth=550&width=3278)<br>![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?maxWidth=550)<br>**注意**<br>**添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。 <br>- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。
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 | 请求超时 | 进行重试

View File

@ -0,0 +1,107 @@
# 更新多维表格元数据
更新多维表格元数据,包括多维表格的名称、是否开启高级权限。
## 注意事项
- 在线文档和电子表格中嵌入的多维表格、知识库中的多维表格不支持开启高级权限。
- 此接口非原子操作,先修改多维表格名称,后开关高级权限,可能存在部分成功的情况。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 更新多维表格(base:app:update)<br>查看、评论、编辑和管理多维表格(bitable:app)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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 下图高亮部分:<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_sTn7sVvhOB.png?height=766&lazyload=true&maxWidth=700&width=3004)<br>**示例值**"appbcbWCzen6D8dezhoCH2RpMAh"
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
name | string | 否 | 新的多维表格名称,不传则不更新名称。<br>**示例值**"新的多维表格名称"
is_advanced | boolean | 否 | 多维表格是否开启高级权限。不传则不更新设置。可选值:<br>- true开启高级权限<br>- false关闭高级权限<br>**示例值**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 | 字段格式错误。 | 确认对应字段类型参数格式是否正确。

78
更新数据表.md Normal file
View File

@ -0,0 +1,78 @@
# 更新数据表
更新数据表的名称。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 更新数据表(base:table:update)<br>查看、评论、编辑和管理多维表格(bitable:app)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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` 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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`<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。<br>**示例值**"XrgTb4y1haKYnasu0xXb1g7lcSg"<br>**数据校验规则**<br>- 最小长度:`1` 字符
table_id | string | 多维表格数据表的唯一标识。获取方式:<br>- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`<br>- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976)<br>**示例值**"tbl1TkhyTWDkSoZ3"
### 请求体
名称 | 类型 | 必填 | 描述
---|---|---|---
name | string | 否 | 数据表的新名称。<br>**注意**<br>- 名称中的首尾空格将会被去除。<br>- 数据表名称不可以包含 `/ \ ? * : [ ]` 等特殊字符。<br>- 如果名称为空或和旧名称相同,接口仍然会返回成功,但是名称不会被更改。<br>**示例值**"新的数据表名称"<br>**数据校验规则**<br>- 长度范围:`1` `100` 字符<br>- 正则校验:`^[^\[\]\:\\\/\?\*]+$`
### 请求体示例
```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. | 调用身份缺少多维表格的高级权限。你需要为调用身份授予高级权限:<br>- 对用户授予高级权限,你需要在多维表格页面右上方 **分享** 入口为当前用户添加可管理权限。![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/df3911b4f747d75914f35a46962d667d_dAsfLjv3QC.png?height=546&lazyload=true&maxWidth=550)<br>- 对应用授予高级权限,你需通过多维表格页面右上方 **「...」** -> **「...更多」** ->**「添加文档应用」** 入口为应用添加可管理权限。<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/22c027f63c540592d3ca8f41d48bb107_CSas7OYJBR.png?height=1994&maxWidth=550&width=3278)<br>![image.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/9f3353931fafeea16a39f0eb887db175_0tjzC9P3zU.png?maxWidth=550)<br>**注意**<br>**添加文档应用** 前,你需确保目标应用至少开通了一个多维表格的 [API 权限](https://open.feishu.cn/document/ukTMukTMukTM/uYTM5UjL2ETO14iNxkTN/scope-list)。否则你将无法在文档应用窗口搜索到目标应用。 <br>- 你也可以在 **多维表格高级权限设置** 中添加用户或一个包含应用的群组, 给予这个群自定义的读写等权限。
500 | 1255001 | InternalError | 内部错误,请联系[技术支持](https://applink.feishu.cn/TLJpeNdW)

View File

@ -0,0 +1,108 @@
# 获取多维表格元数据
获取指定多维表格的元数据信息,包括多维表格名称、多维表格版本号、多维表格是否开启高级权限等。
## 请求
基本 | &nbsp;
---|---
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
权限要求<br>**调用该 API 所需的权限。开启其中任意一项权限即可调用**<br>开启任一权限即可 | 获取多维表格信息(base:app:read)<br>查看、评论、编辑和管理多维表格(bitable:app)<br>查看、评论和导出多维表格(bitable:app:readonly)
### 请求头
名称 | 类型 | 必填 | 描述
---|---|---|---
Authorization | string | 是 | `tenant_access_token`<br><br>`user_access_token`<br>**值格式**"Bearer `access_token`"<br>**示例值**"Bearer u-7f1bcd13fc57d46bac21793a18e560"<br>[了解更多:如何选择与获取 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` 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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`<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。<br>**示例值**"appbcbWCzen6D8dezhoCH2RpMAh"
## 响应
### 响应体
名称 | 类型 | 描述
---|---|---
code | int | 错误码,非 0 表示失败
msg | string | 错误描述
data | \- | \-
app | display_app | 多维表格元数据
app_token | string | 多维表格的唯一标识 app_token
name | string | 多维表格的名称
revision | int | 多维表格的版本号。对多维表格进行修改时更新,如新增、删除数据表,修改数据表名等,初始为 1每次更新+1
is_advanced | boolean | 多维表格是否开启了高级权限。取值包括:<br>- true开启了高级权限<br>- false关闭了高级权限<br>了解更多参考飞书帮助中心文档[使用多维表格高级权限](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)使用。<br>**可选值有**<br>- 1不支持指定公式字段类型<br>- 2支持指定公式字段类型
advance_version | string | 文档高级权限版本。可结合[自定义角色 API](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-role/create)使用。<br>**可选值有**<br>- v1v1版本<br>- v2v2版本
### 响应体示例
```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 错误码的场景较多,请参考以下建议排查:<br>- 如果单次操作的内容变更较大,请尝试在单次操作中减少数据量<br>- 如果你并发调用了接口,请尝试控制请求间隔,稍后重试<br>- 如果在知识库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)接口<br>- 请检查接口参数是否有误。例如,在分页查询多维表格时,传递了无效的 page_token或传递了错误的数据表的 table_id<br>- 如果该报错偶尔发生,可能是服务器超时或不稳定,请重试解决
200 | 1254003 | WrongBaseToken | app_token 错误。app_token 是多维表格 App 的唯一标识。不同形态的多维表格,其 `app_token` 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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`<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
200 | 1254004 | WrongTableId | table_id 错误。table_id 是多维表格数据表的唯一标识。获取方式:<br>- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`<br>- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`<br>![](//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 是多维表格中视图的唯一标识。获取方式:<br>- 在多维表格的 URL 地址栏中,`view_id` 是下图中高亮部分:<br>![view_id.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_DJMgVH9x2S.png?height=748&lazyload=true&width=2998)<br>- 通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取。暂时无法获取到嵌入到云文档中的多维表格的 `view_id`<br>**注意**<br>`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` 的获取方式不同:<br>- 如果多维表格的 URL 以 ==**feishu.cn/base**== 开头,该多维表格的 `app_token` 是下图高亮部分:<br>![app_token.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/6916f8cfac4045ba6585b90e3afdfb0a_GxbfkJHZBa.png?height=766&lazyload=true&width=3004)<br>- 如果多维表格的 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`<br>了解更多,参考[多维表格 app_token 获取方式](https://open.feishu.cn/document/ukTMukTMukTM/uUDN04SN0QjL1QDN/bitable-overview#-752212c)。
200 | 1254041 | TableIdNotFound | table_id 不存在。获取方式:<br>- 你可通过多维表格 URL 获取 `table_id`,下图高亮部分即为当前数据表的 `table_id`<br>- 也可通过[列出数据表](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table/list)接口获取 `table_id`<br>![](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/18741fe2a0d3cafafaf9949b263bb57d_yD1wkOrSju.png?height=746&lazyload=true&maxWidth=700&width=2976)
200 | 1254042 | ViewIdNotFound | view_id 不存在。获取方式:<br>- 在多维表格的 URL 地址栏中,`view_id` 是下图中高亮部分:<br>![view_id.png](//sf3-cn.feishucdn.com/obj/open-platform-opendoc/140668632c97e0095832219001d17c54_DJMgVH9x2S.png?height=748&lazyload=true&width=2998)<br>- 通过[列出视图](https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/bitable-v1/app-table-view/list)接口获取。暂时无法获取到嵌入到云文档中的多维表格的 `view_id`<br>**注意**<br>`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 | 人员字段有误。原因可能是:<br>- `user_id_type` 参数指定的 ID 类型与传入的 ID 类型不匹配<br>- 传入了不识别的类型或结构,目前只支持填写 `id` 参数,且需要传入数组<br>- 跨应用传入了 `open_id`。如果跨应用传入 ID建议使用 `user_id`。不同应用获取的 `open_id` 不能交叉使用<br>- 若想对人员字段传空,可传 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 | 在同一个数据表中,并发调用了读写接口或请求过快,出现冲突。请参考以下建议解决:<br>- 确保没有并发调用多维表格读写相关接口<br>- 若操作量较大,建议在接口与接口之间增加 0.5 或 1 秒的延迟,也可在报错中增加重试逻辑,确保业务的稳定性<br>- 对于写接口,可以将接口中的查询参数 `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 | 请求超时,进行重试