false, 'message' => '缺少 tableId 或 token', 'fields' => [], 'records' => []]; } try { $fieldResp = Http::withToken($token) ->timeout(60) ->get("$base/api/table/$tableId/field"); if (! $fieldResp->successful()) { return ['success' => false, 'message' => "字段接口 HTTP {$fieldResp->status()}", 'fields' => [], 'records' => []]; } $fields = $fieldResp->json() ?? []; $records = []; $skip = 0; $take = 1000; while (true) { $recResp = Http::withToken($token) ->timeout(60) ->get("$base/api/table/$tableId/record", [ 'fieldKeyType' => 'name', 'take' => $take, 'skip' => $skip, ]); if (! $recResp->successful()) { return ['success' => false, 'message' => "记录接口 HTTP {$recResp->status()}", 'fields' => $fields, 'records' => []]; } $body = $recResp->json() ?? []; $page = $body['records'] ?? []; $records = array_merge($records, $page); if (count($page) < $take) { break; } $skip += $take; } return ['success' => true, 'fields' => $fields, 'records' => $records]; } catch (\Throwable $e) { return ['success' => false, 'message' => $e->getMessage(), 'fields' => [], 'records' => []]; } } /** * 从用户粘贴的整段 API 文档 / curl 命令中解析出连接信息。 * * 容错策略:先清洗零宽字符/markdown 符号,再按 URL → Token → 表名顺序多模式兜底, * 解析不完整时返回 missing 清单与已找到的部分,供前端手动补齐。 * * @return array{success:bool,baseUrl:string,tableId:string,token:string,name:string,missing:array,message?:string} */ public function parseConfig(string $text): array { // 粘贴文本可能带零宽字符(U+200B 等)或 markdown 装饰,先做无害化清洗 $clean = (string) preg_replace('/[\x{200B}-\x{200D}\x{2060}\x{FEFF}\x{00AD}]/u', '', (string) $text); $clean = str_replace(["\r\n", "\r"], "\n", $clean); $clean = str_replace(['`', '**'], ['', ''], $clean); $base = ''; $tableId = ''; $token = ''; $name = ''; // 1) 表名:# Table: xxx(兼容中英文冒号) if (preg_match('/^#+\s*Table\s*[::]\s*(.+)$/mi', $clean, $m)) { $name = trim($m[1]); } // 2) 收集所有 URL,优先取含 /api/table/ 或 /table/ 的完整地址 $urls = []; if (preg_match_all('#https?://[^\s"\'<>()\[\]]+#', $clean, $mm)) { $urls = $mm[0]; } foreach ($urls as $u) { if ($tableId === '' && preg_match('#/(?:api/)?table/([A-Za-z0-9_-]+)#', $u, $m)) { $tableId = $m[1]; if (preg_match('#^(https?://[^/\s]+)#', $u, $bm)) { $base = $bm[1]; } break; } } // 3) Token:Bearer 前缀 → Token:/API Key: 行 → 任意位置 teable_ 开头串 $tokPatterns = [ '#(?:Bearer|bearer)\s+([A-Za-z0-9_.+/=~%-]+)#', '#(?:Token|token|API\s*Key|api\s*key)\s*[::=]\s*([A-Za-z0-9_.+/=~%-]+)#', '#(teable_[A-Za-z0-9_.+/=~%-]+)#', ]; foreach ($tokPatterns as $p) { if (preg_match($p, $clean, $m)) { $token = $m[1]; break; } } // 4) 表 ID 兜底:任意 tbl 开头长串 if ($tableId === '' && preg_match('#\btbl[A-Za-z0-9_-]{8,}#i', $clean, $m)) { $tableId = $m[0]; } // 5) Base URL 兜底:Base URL 行(值可在下一行)→ 任意 URL 的 host if ($base === '') { if (preg_match('#(?:Base\s*URL|baseUrl|基础\s*URL)\s*[::]\s*(https?://[^\s"\'<>()\[\]]+)#i', $clean, $m) || preg_match('#(?:Base\s*URL|baseUrl|基础\s*URL)\s*[::]?\s*\n\s*(https?://[^\s"\'<>()\[\]]+)#i', $clean, $m)) { $base = rtrim($m[1], '/'); } elseif (! empty($urls)) { if (preg_match('#^(https?://[^/\s]+)#', $urls[0], $bm)) { $base = $bm[1]; } } } $missing = []; if ($base === '') { $missing[] = 'Base URL'; } if ($tableId === '') { $missing[] = 'Table ID'; } if ($token === '') { $missing[] = 'Token'; } if (! empty($missing)) { return [ 'success' => false, 'message' => '缺少:'.implode('、', $missing).'。请确认粘贴内容包含表地址(https://…/api/table/tbl…)和 Token(teable_…),或直接在下方输入框手动补齐', 'baseUrl' => $base, 'tableId' => $tableId, 'token' => $token, 'name' => $name, 'missing' => $missing, ]; } return [ 'success' => true, 'message' => '解析成功', 'baseUrl' => $base, 'tableId' => $tableId, 'token' => $token, 'name' => $name, 'missing' => [], ]; } }