baseUrl = rtrim($this->baseUrl ?: config('knowledge.base_url'), '/'); $this->timeout = $this->timeout ?: (int) config('knowledge.timeout', 10); $this->cacheTtl = $this->cacheTtl ?: (int) config('knowledge.cache_ttl', 300); } /** * Fetch and cache all knowledge points by iterating through paginated API responses. * * @return Collection> */ public function listKnowledgePoints(int $perPage = 200, array $filters = []): Collection { $cacheKey = sprintf( 'knowledge-points-all-%d-%s', $perPage, md5(json_encode($filters)) ); return Cache::remember( $cacheKey, now()->addSeconds($this->cacheTtl), function () use ($perPage, $filters): Collection { $page = 1; $results = collect(); while (true) { $response = $this->paginateKnowledgePoints($page, $perPage, $filters); $chunk = collect($response['data'] ?? []); if ($chunk->isEmpty()) { break; } $results = $results->concat($chunk); $meta = $response['meta'] ?? []; $hasNext = $meta['has_next'] ?? ($page < ($meta['total_pages'] ?? $page)); if (! $hasNext) { break; } $page++; } return $results->values(); }, ); } /** * @return array{data: array>, meta: array} */ public function paginateKnowledgePoints(int $page = 1, int $perPage = 50, array $filters = []): array { $query = array_filter([ 'page' => $page, 'per_page' => $perPage, 'phase' => $filters['phase'] ?? null, 'category' => $filters['category'] ?? null, 'grade' => $filters['grade'] ?? null, ], fn ($value) => filled($value)); $response = $this->request('GET', '/knowledge-points', $query); if (is_array($response) && array_key_exists('data', $response)) { return [ 'data' => $response['data'] ?? [], 'meta' => $response['meta'] ?? [], ]; } return [ 'data' => is_array($response) ? $response : [], 'meta' => [ 'page' => $page, 'per_page' => $perPage, 'total' => is_array($response) ? count($response) : 0, 'total_pages' => 1, 'has_next' => false, 'has_prev' => $page > 1, ], ]; } /** * @return array */ public function getKnowledgePointDetail(string $kpCode): array { return Cache::remember( key: "knowledge-point-detail-{$kpCode}", ttl: now()->addSeconds($this->cacheTtl), callback: fn () => $this->request('GET', "/graph/node/{$kpCode}"), ); } /** * @return Collection> */ public function listSkills(?string $kpCode = null, int $limit = 200): Collection { return Cache::remember( key: "knowledge-skills-{$kpCode}-{$limit}", ttl: now()->addSeconds($this->cacheTtl), callback: fn () => collect($this->request('GET', '/skills', array_filter([ 'kp_code' => $kpCode, 'limit' => $limit, ]))), ); } /** * @throws RequestException * @return mixed */ protected function request(string $method, string $path, array $params = []): mixed { $response = $this->http()->send($method, ltrim($path, '/'), [ 'query' => $params, ]); return $response->throw()->json(); } protected function http(): PendingRequest { return Http::baseUrl($this->baseUrl) ->acceptJson() ->timeout($this->timeout) ->retry(2, 200); } }