Ver código fonte

知识图谱 api

yemeishu 5 dias atrás
pai
commit
0be6c15e61

+ 212 - 0
app/Http/Controllers/Api/KnowledgePointTreeController.php

@@ -0,0 +1,212 @@
+<?php
+
+namespace App\Http\Controllers\Api;
+
+use App\Models\KnowledgePoint;
+use Illuminate\Http\JsonResponse;
+use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\Facades\Cache;
+
+class KnowledgePointTreeController
+{
+    /**
+     * 获取知识点树形结构
+     * 从 MySQL 数据库读取数据,构建层级结构
+     *
+     * @param Request $request
+     * @return JsonResponse
+     */
+    public function index(Request $request): JsonResponse
+    {
+        try {
+            // 使用缓存避免重复查询
+            $cacheKey = 'knowledge-point-tree-' . md5($request->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;
+    }
+}

+ 1 - 1
app/Services/MistakeBookService.php

@@ -16,7 +16,7 @@ class MistakeBookService
     protected int $timeout;
 
     // 缓存时间(秒)
-    const CACHE_TTL_LIST = 300; // 5分钟
+    const CACHE_TTL_LIST = 60; // 5分钟
     const CACHE_TTL_SUMMARY = 600; // 10分钟
     const CACHE_TTL_PATTERNS = 1800; // 30分钟
 

+ 5 - 10
routes/api.php

@@ -417,16 +417,11 @@ Route::delete('/questions/{id}', function (int $id, QuestionServiceApi $service)
     }
 });
 
-// 获取知识点选项
-Route::get('/knowledge-points', function (QuestionServiceApi $service) {
-    try {
-        $points = $service->getKnowledgePointOptions();
-        return response()->json($points);
-    } catch (\Exception $e) {
-        \Log::error('Failed to get knowledge points: ' . $e->getMessage());
-        return response()->json([], 500);
-    }
-});
+use App\Http\Controllers\Api\KnowledgePointTreeController;
+
+// 获取知识点树形结构(从 MySQL 数据库)
+Route::get('/knowledge-points', [KnowledgePointTreeController::class, 'index'])
+    ->name('api.knowledge-points.index');
 
 // 智能出卷对外接口:生成试卷并返回PDF/判卷地址
 Route::post('/intelligent-exams', [IntelligentExamController::class, 'store'])