Hyperf方案 飞书多维表格(Bitable)数据同步=定时将数据库数据同步到飞书多维表格,或反向将多维表格数据写入数据库

张开发
2026/4/11 3:13:09 15 分钟阅读

分享文章

Hyperf方案 飞书多维表格(Bitable)数据同步=定时将数据库数据同步到飞书多维表格,或反向将多维表格数据写入数据库
整体流程 定时任务触发 → 查增量数据 → 分页写入/读取 Bitable → 更新同步状态---1.配置追加 config/autoload/feishu.phpbitable[app_tokenenv(FEISHU_BITABLE_APP_TOKEN,),// 多维表格 app_tokentable_idenv(FEISHU_BITABLE_TABLE_ID,),// 数据表 ID],---2.Bitable API 封装 app/Service/Feishu/BitableService.php?phpnamespaceApp\Service\Feishu;use Hyperf\Guzzle\ClientFactory;use Hyperf\Contract\ConfigInterface;classBitableService{privatestring $appToken;privatestring $tableId;publicfunction__construct(privateClientFactory $clientFactory,privateConfigInterface $config,privateAccessToken $accessToken){$this-appToken$config-get(feishu.bitable.app_token);$this-tableId$config-get(feishu.bitable.table_id);}/** * 新增记录单条 */publicfunctionaddRecord(array $fields):string{$data$this-post(/bitable/v1/apps/{$this-appToken}/tables/{$this-tableId}/records,[fields$fields,]);return$data[data][record][record_id];}/** * 批量新增记录最多 500 条/次 */publicfunctionbatchAddRecords(array $rows):array{$data$this-post(/bitable/v1/apps/{$this-appToken}/tables/{$this-tableId}/records/batch_create,[recordsarray_map(fn($r)[fields$r],$rows)]);returnarray_column($data[data][records],record_id);}/** * 更新记录 */publicfunctionupdateRecord(string $recordId,array $fields):void{$this-put(/bitable/v1/apps/{$this-appToken}/tables/{$this-tableId}/records/{$recordId},[fields$fields]);}/** * 批量更新最多 500 条/次 */publicfunctionbatchUpdateRecords(array $records):void{// $records [[record_id xxx, fields [...]], ...]$this-post(/bitable/v1/apps/{$this-appToken}/tables/{$this-tableId}/records/batch_update,[records$records]);}/** * 删除记录 */publicfunctiondeleteRecord(string $recordId):void{$this-delete(/bitable/v1/apps/{$this-appToken}/tables/{$this-tableId}/records/{$recordId});}/** * 分页拉取全部记录自动翻页 */publicfunctiongetAllRecords(array $filter[]):\Generator{$pageTokennull;do{$queryarray_merge([page_size500],$filter);if($pageToken){$query[page_token]$pageToken;}$client$this-clientFactory-create([timeout15]);$response$client-get($this-config-get(feishu.base_url)./bitable/v1/apps/{$this-appToken}/tables/{$this-tableId}/records,[headers[AuthorizationBearer .$this-accessToken-get()],query$query,]);$datajson_decode($response-getBody()-getContents(),true);if($data[code]!0){thrownew\RuntimeException(拉取 Bitable 记录失败: .$data[msg]);}foreach($data[data][items]??[]as $item){yield $item;}$pageToken$data[data][has_more]?$data[data][page_token]:null;}while($pageToken);}/** * 按条件查询记录支持飞书过滤语法 * filter 示例: AND(CurrentValue.[状态]待处理) */publicfunctionqueryRecords(string $filter,int$pageSize100):\Generator{return$this-getAllRecords([filter$filter,page_size$pageSize]);}// -------- HTTP 基础方法 --------privatefunctionpost(string $path,array $payload):array{$client$this-clientFactory-create([timeout15]);$response$client-post($this-config-get(feishu.base_url).$path,[headers[AuthorizationBearer .$this-accessToken-get(),Content-Typeapplication/json,],json$payload,]);return$this-parse($response);}privatefunctionput(string $path,array $payload):array{$client$this-clientFactory-create([timeout15]);$response$client-put($this-config-get(feishu.base_url).$path,[headers[AuthorizationBearer .$this-accessToken-get(),Content-Typeapplication/json,],json$payload,]);return$this-parse($response);}privatefunctiondelete(string $path):void{$client$this-clientFactory-create([timeout10]);$client-delete($this-config-get(feishu.base_url).$path,[headers[AuthorizationBearer .$this-accessToken-get()]]);}privatefunctionparse(\Psr\Http\Message\ResponseInterface $response):array{$datajson_decode($response-getBody()-getContents(),true);if($data[code]!0){thrownew\RuntimeException(Bitable API 错误 [{$data[code]}]: {$data[msg]});}return$data;}}---3.增量同步逻辑 app/Service/Feishu/SyncService.php?phpnamespaceApp\Service\Feishu;use App\Model\Order;// 示例订单模型use App\Model\FeishuSyncLog;// 同步记录表use Psr\SimpleCache\CacheInterface;classSyncService{publicfunction__construct(privateBitableService $bitable,privateCacheInterface $cache){}// DB → Bitable /** * 增量将数据库数据推送到 Bitable */publicfunctionpushToFeishu():void{$lastSyncAt$this-getLastSyncTime(db_to_feishu);// 只取上次同步后变更的数据$rowsOrder::where(updated_at,,$lastSyncAt)-orderBy(updated_at)-get();if($rows-isEmpty())return;$toAdd[];$toUpdate[];foreach($rows as $row){$fields$this-mapToFeishu($row);$logFeishuSyncLog::where(local_id,$row-id)-first();if($log){$toUpdate[][record_id$log-record_id,fields$fields];}else{$toAdd[][local_id$row-id,fields$fields];}}// 批量新增500 条分片foreach(array_chunk($toAdd,500)as $chunk){$recordIds$this-bitable-batchAddRecords(array_column($chunk,fields));foreach($chunk as $i$item){FeishuSyncLog::create([local_id$item[local_id],record_id$recordIds[$i],]);}}// 批量更新500 条分片foreach(array_chunk($toUpdate,500)as $chunk){$this-bitable-batchUpdateRecords($chunk);}$this-setLastSyncTime(db_to_feishu,$rows-last()-updated_at);}// Bitable → DB /** * 将 Bitable 数据拉取写入数据库 */publicfunctionpullFromFeishu():void{$lastSyncAt$this-getLastSyncTime(feishu_to_db);// 飞书过滤只取最近修改的毫秒时间戳$filterTsstrtotime($lastSyncAt)*1000;$filterCurrentValue.[_last_modified_time]{$filterTs};$latestTs0;foreach($this-bitable-queryRecords($filter)as $item){$fields$item[fields];$data$this-mapToLocal($fields);Order::updateOrCreate([feishu_record_id$item[record_id]],$data);$modifiedTs$item[fields][_last_modified_time]??0;$latestTsmax($latestTs,$modifiedTs);}if($latestTs0){$this-setLastSyncTime(feishu_to_db,date(Y-m-d H:i:s,$latestTs/1000));}}// 字段映射 privatefunctionmapToFeishu(Order $order):array{return[订单号$order-order_no,状态$order-status_label,金额(float)$order-amount,创建时间strtotime($order-created_at)*1000,// 飞书日期字段用毫秒备注$order-remark??,];}privatefunctionmapToLocal(array $fields):array{return[order_no$fields[订单号]??,status$fields[状态]??,amount$fields[金额]??0,remark$fields[备注]??,synced_atdate(Y-m-d H:i:s),];}// 同步时间戳管理 privatefunctiongetLastSyncTime(string $direction):string{return$this-cache-get(feishu:sync:last:{$direction},1970-01-01 00:00:00);}privatefunctionsetLastSyncTime(string $direction,string $time):void{$this-cache-set(feishu:sync:last:{$direction},$time);}}---4.定时任务 app/Crontab/FeishuSyncCrontab.php?phpnamespaceApp\Crontab;use App\Service\Feishu\SyncService;use Hyperf\Crontab\Annotation\Crontab;use Hyperf\Di\Annotation\Inject;#[Crontab(rule:*/5 * * * *,// 每 5 分钟name:FeishuSync,singleton:true,// 防止重叠执行onOneServer:true,// 集群只跑一台memo:飞书多维表格数据同步)]classFeishuSyncCrontab{#[Inject]privateSyncService $syncService;publicfunctionexecute():void{// DB → 飞书$this-syncService-pushToFeishu();// 飞书 → DB按需开启// $this-syncService-pullFromFeishu();}}启用定时任务组件 composer require hyperf/crontab config/autoload/processes.phpreturn[Hyperf\Crontab\Process\CrontabDispatcherProcess::class,];---5.同步日志表MigrationSchema::create(feishu_sync_logs,function(Blueprint $table){$table-id();$table-unsignedBigInteger(local_id)-index();$table-string(record_id)-index();// 飞书 record_id$table-timestamps();});// orders 表追加$table-string(feishu_record_id)-nullable()-index();$table-timestamp(synced_at)-nullable();---关键点汇总 ┌────────────────┬──────────────────────────────────────────────────────────────┐ │ 要点 │ 说明 │ ├────────────────┼──────────────────────────────────────────────────────────────┤ │ 分页上限 │ 单次最多500条用 page_token 翻页Generator 避免内存爆 │ ├────────────────┼──────────────────────────────────────────────────────────────┤ │ 批量写入上限 │ batch_create/batch_update 单次最多500条 │ ├────────────────┼──────────────────────────────────────────────────────────────┤ │ 增量标识 │ DB→飞书用 updated_at飞书→DB 用 _last_modified_time毫秒 │ ├────────────────┼──────────────────────────────────────────────────────────────┤ │ 日期字段 │ 飞书日期类型传毫秒时间戳不是字符串 │ ├────────────────┼──────────────────────────────────────────────────────────────┤ │ singleton │ 定时任务加 singleton:true防止上次未跑完下次又触发 │ ├────────────────┼──────────────────────────────────────────────────────────────┤ │ onOneServer │ 多实例部署必须加否则重复写入 │ ├────────────────┼──────────────────────────────────────────────────────────────┤ │ app_token 获取 │ 打开多维表格URL 中 base/后面那段即为 app_token │ ├────────────────┼──────────────────────────────────────────────────────────────┤ │ table_id 获取 │ 多维表格右上角「...」→「API」中可查 │ └────────────────┴──────────────────────────────────────────────────────────────┘

更多文章