getQueryString() ?? ''); $treeData = Cache::remember($cacheKey, 3600, function () { return $this->buildTreeFromDatabase(); }); return response()->json([ 'success' => true, 'data' => $treeData, ]); } catch (\Exception $e) { Log::error('获取知识点树形结构失败', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString(), ]); return response()->json([ 'success' => false, 'message' => '获取知识点数据失败:' . $e->getMessage(), 'data' => [], ], 500); } } /** * 从数据库构建树形结构 * * @return array */ private function buildTreeFromDatabase(): array { // 从数据库获取所有知识点 $knowledgePoints = KnowledgePoint::query() ->orderBy('kp_code') ->get() ->toArray(); if (empty($knowledgePoints)) { Log::warning('数据库中没有找到知识点数据'); return []; } // 构建节点映射(以 kp_code 为键) $nodesMap = []; foreach ($knowledgePoints as $point) { $kpCode = $point['kp_code']; $nodesMap[$kpCode] = [ 'id' => $kpCode, 'label' => $point['name'] ?? $kpCode, 'children' => [], // 可选字段:如果数据库中有相关数据则添加 'skills' => $this->extractSkills($point), 'direct_score' => $this->extractDirectScore($point), 'related_score' => $this->extractRelatedScore($point), // 保留原始数据用于调试 '_raw' => $point, ]; } // 构建树形结构 $rootNodes = []; foreach ($nodesMap as $kpCode => &$node) { $parentKpCode = $this->getParentKpCode($node['_raw']); // 如果没有父节点,则为根节点 if (empty($parentKpCode) || !isset($nodesMap[$parentKpCode])) { $rootNodes[] = &$node; } else { // 添加到父节点的 children 中 $nodesMap[$parentKpCode]['children'][] = &$node; } } // 清理临时数据 foreach ($nodesMap as &$node) { unset($node['_raw']); } Log::info('成功构建知识点树形结构', [ 'total_nodes' => count($knowledgePoints), 'root_nodes' => count($rootNodes), ]); return $rootNodes; } /** * 提取技能点信息 * * @param array $point * @return array */ private function extractSkills(array $point): array { // 如果数据库中有 skills 字段,直接返回 if (isset($point['skills']) && is_array($point['skills'])) { return $point['skills']; } // 否则返回空数组 return []; } /** * 提取直接得分范围 * * @param array $point * @return array */ private function extractDirectScore(array $point): array { // 如果数据库中有 direct_score 字段,直接返回 if (isset($point['direct_score']) && is_array($point['direct_score'])) { return $point['direct_score']; } // 从 stats 字段提取(如果存在) if (isset($point['stats']) && is_array($point['stats'])) { if (isset($point['stats']['direct_score'])) { return (array) $point['stats']['direct_score']; } } // 默认值 return [1, 3]; } /** * 提取相关得分范围 * * @param array $point * @return array */ private function extractRelatedScore(array $point): array { // 如果数据库中有 related_score 字段,直接返回 if (isset($point['related_score']) && is_array($point['related_score'])) { return $point['related_score']; } // 从 stats 字段提取(如果存在) if (isset($point['stats']) && is_array($point['stats'])) { if (isset($point['stats']['related_score'])) { return (array) $point['stats']['related_score']; } } // 默认值 return [2, 5]; } /** * 获取父知识点代码 * * @param array $point * @return string|null */ private function getParentKpCode(array $point): ?string { // 直接从 parent_kp_code 字段获取 if (!empty($point['parent_kp_code'])) { return $point['parent_kp_code']; } // 如果没有 parent_kp_code,尝试从 kp_code 推断 // 例如:R01 的父节点可能是 S01,M01 的父节点可能是 M00 $kpCode = $point['kp_code'] ?? ''; if (empty($kpCode)) { return null; } // 尝试从编码规则推断父节点 // M01 -> M00, S01 -> M01, R01 -> S01 等 if (preg_match('/^([A-Z]+)\d+$/', $kpCode, $matches)) { $prefix = $matches[1]; $number = (int) substr($kpCode, strlen($prefix)); if ($number > 0) { // 降级:R01 -> S01 (去掉一位数字) $parentNumber = (int) (substr($kpCode, strlen($prefix), -1)); if ($parentNumber > 0) { return $prefix . str_pad($parentNumber, strlen($kpCode) - strlen($prefix), '0', STR_PAD_LEFT); } } } return null; } }