KnowledgeGraphService.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\Http;
  4. use Illuminate\Support\Facades\Log;
  5. class KnowledgeGraphService
  6. {
  7. protected string $baseUrl;
  8. public function __construct()
  9. {
  10. // 从配置文件读取base_url,如果没有配置则使用容器服务名
  11. $this->baseUrl = config('services.knowledge_api.base_url', env('KNOWLEDGE_API_BASE', env('KNOWLEDGE_API_BASE_URL', 'http://localhost:5011')));
  12. $this->baseUrl = rtrim($this->baseUrl, '/');
  13. }
  14. /**
  15. * 获取知识点列表
  16. */
  17. public function listKnowledgePoints(int $page = 1, int $perPage = 100): array
  18. {
  19. try {
  20. $response = Http::timeout(10)
  21. ->withHeaders(['Accept' => 'application/json'])
  22. ->get($this->baseUrl . '/knowledge-points/', [
  23. 'page' => $page,
  24. 'per_page' => $perPage
  25. ]);
  26. if ($response->successful()) {
  27. $data = $response->json();
  28. $points = $data['data'] ?? $data ?? [];
  29. // 格式化知识点数据
  30. return array_map(function($kp) {
  31. return [
  32. 'id' => (string)($kp['id'] ?? $kp['kp_id'] ?? uniqid()),
  33. 'kp_code' => $kp['kp_code'] ?? $kp['kp_id'] ?? $kp['code'] ?? 'KP_UNKNOWN',
  34. 'cn_name' => $kp['cn_name'] ?? $kp['kp_name'] ?? $kp['name'] ?? $kp['kp_code'] ?? '未知知识点',
  35. 'category' => $kp['category'] ?? '数学',
  36. 'phase' => $kp['phase'] ?? '',
  37. 'grade' => $kp['grade'] ?? null,
  38. 'importance' => $kp['importance'] ?? 0,
  39. ];
  40. }, $points);
  41. }
  42. Log::warning('知识图谱API调用失败', [
  43. 'status' => $response->status(),
  44. 'url' => $this->baseUrl . '/knowledge-points',
  45. 'response' => $response->json()
  46. ]);
  47. } catch (\Exception $e) {
  48. Log::error('获取知识点列表失败', [
  49. 'error' => $e->getMessage()
  50. ]);
  51. }
  52. // 返回备用数据
  53. return $this->getFallbackKnowledgePoints();
  54. }
  55. /**
  56. * 根据知识点代码获取技能列表
  57. */
  58. public function getSkillsByKnowledgePoint(string $kpCode): array
  59. {
  60. try {
  61. $response = Http::timeout(10)
  62. ->withHeaders(['Accept' => 'application/json'])
  63. ->get($this->baseUrl . "/graph/node/{$kpCode}");
  64. if ($response->successful()) {
  65. $data = $response->json();
  66. $skills = $data['skills'] ?? [];
  67. return array_map(function($skill) {
  68. return [
  69. 'code' => $skill['skill_code'] ?? '',
  70. 'name' => $skill['skill_name'] ?? '',
  71. 'weight' => $skill['weight'] ?? 0,
  72. ];
  73. }, $skills);
  74. }
  75. Log::warning('获取技能列表失败', [
  76. 'status' => $response->status(),
  77. 'kp_code' => $kpCode
  78. ]);
  79. } catch (\Exception $e) {
  80. Log::error('获取技能列表异常', [
  81. 'kp_code' => $kpCode,
  82. 'error' => $e->getMessage()
  83. ]);
  84. }
  85. return [];
  86. }
  87. /**
  88. * 获取所有技能列表
  89. */
  90. public function listSkills(int $page = 1, int $perPage = 50): array
  91. {
  92. // ... (existing code)
  93. try {
  94. $response = Http::timeout(10)
  95. ->get($this->baseUrl . '/skills/', [
  96. 'page' => $page,
  97. 'per_page' => $perPage
  98. ]);
  99. if ($response->successful()) {
  100. $data = $response->json();
  101. $skills = $data['data'] ?? $data ?? [];
  102. // 格式化技能数据
  103. return array_map(function($skill) {
  104. return [
  105. 'id' => (string)($skill['id'] ?? $skill['skill_code'] ?? uniqid()),
  106. 'code' => $skill['skill_code'] ?? $skill['code'] ?? 'SK_UNKNOWN',
  107. 'name' => $skill['skill_name'] ?? $skill['name'] ?? $skill['skill_code'] ?? '未知技能',
  108. 'category' => $skill['skill_type'] ?? $skill['category'] ?? '基础技能'
  109. ];
  110. }, $skills);
  111. }
  112. Log::warning('技能API调用失败', [
  113. 'status' => $response->status()
  114. ]);
  115. } catch (\Exception $e) {
  116. Log::error('获取技能列表失败', [
  117. 'error' => $e->getMessage()
  118. ]);
  119. }
  120. // 返回备用数据
  121. return $this->getFallbackSkills();
  122. }
  123. /**
  124. * 获取关联关系列表
  125. */
  126. public function listRelations(int $page = 1, int $perPage = 100): array
  127. {
  128. try {
  129. $response = Http::timeout(10)
  130. ->get($this->baseUrl . '/graph/relations', [
  131. 'page' => $page,
  132. 'per_page' => $perPage
  133. ]);
  134. if ($response->successful()) {
  135. $data = $response->json();
  136. $relations = $data['data'] ?? [];
  137. return array_map(function($rel) {
  138. return [
  139. 'source_kp' => $rel['source'] ?? $rel['source_kp'] ?? '',
  140. 'target_kp' => $rel['target'] ?? $rel['target_kp'] ?? '',
  141. 'relation_type' => $rel['relation_type'] ?? 'PREREQUISITE',
  142. 'relation_direction' => $rel['relation_direction'] ?? 'DOWNSTREAM',
  143. 'weight' => $rel['weight'] ?? 0,
  144. 'description' => $rel['description'] ?? '',
  145. ];
  146. }, $relations);
  147. }
  148. Log::warning('获取关联关系列表失败', [
  149. 'status' => $response->status()
  150. ]);
  151. } catch (\Exception $e) {
  152. Log::error('获取关联关系列表异常', [
  153. 'error' => $e->getMessage()
  154. ]);
  155. }
  156. return [];
  157. }
  158. /**
  159. * 导出完整图谱数据 (用于可视化)
  160. */
  161. public function exportGraph(): array
  162. {
  163. try {
  164. $response = Http::timeout(30)
  165. ->get($this->baseUrl . '/graph/export');
  166. if ($response->successful()) {
  167. return $response->json();
  168. }
  169. Log::warning('导出图谱数据失败', [
  170. 'status' => $response->status()
  171. ]);
  172. } catch (\Exception $e) {
  173. Log::error('导出图谱数据异常', [
  174. 'error' => $e->getMessage()
  175. ]);
  176. }
  177. return ['nodes' => [], 'edges' => []];
  178. }
  179. /**
  180. * 检查服务健康状态
  181. */
  182. public function checkHealth(): bool
  183. {
  184. try {
  185. $response = Http::timeout(5)
  186. ->get($this->baseUrl . '/health');
  187. return $response->successful();
  188. } catch (\Exception $e) {
  189. Log::error('知识图谱服务健康检查失败', [
  190. 'error' => $e->getMessage()
  191. ]);
  192. return false;
  193. }
  194. }
  195. /**
  196. * 获取备用知识点数据
  197. */
  198. private function getFallbackKnowledgePoints(): array
  199. {
  200. return [
  201. ['id' => 'kp_1', 'code' => 'KP1001', 'name' => '因式分解基础', 'subject' => '数学', 'phase' => '初中', 'importance' => 5],
  202. ['id' => 'kp_2', 'code' => 'KP1002', 'name' => '提取公因式', 'subject' => '数学', 'phase' => '初中', 'importance' => 5],
  203. ['id' => 'kp_3', 'code' => 'KP1003', 'name' => '平方差公式', 'subject' => '数学', 'phase' => '初中', 'importance' => 5],
  204. ['id' => 'kp_4', 'code' => 'KP1004', 'name' => '完全平方公式', 'subject' => '数学', 'phase' => '初中', 'importance' => 5],
  205. ['id' => 'kp_5', 'code' => 'KP1005', 'name' => '分组分解法', 'subject' => '数学', 'phase' => '初中', 'importance' => 4],
  206. ['id' => 'kp_6', 'code' => 'KP1006', 'name' => '十字相乘法', 'subject' => '数学', 'phase' => '初中', 'importance' => 5],
  207. ['id' => 'kp_7', 'code' => 'KP1007', 'name' => '有理数运算', 'subject' => '数学', 'phase' => '初中', 'importance' => 5],
  208. ['id' => 'kp_8', 'code' => 'KP1008', 'name' => '一元二次方程', 'subject' => '数学', 'phase' => '初中', 'importance' => 5],
  209. ['id' => 'kp_9', 'code' => 'KP1009', 'name' => '不等式', 'subject' => '数学', 'phase' => '初中', 'importance' => 4],
  210. ['id' => 'kp_10', 'code' => 'KP1010', 'name' => '函数基础', 'subject' => '数学', 'phase' => '初中', 'importance' => 5],
  211. ];
  212. }
  213. /**
  214. * 获取备用技能数据
  215. */
  216. private function getFallbackSkills(): array
  217. {
  218. return [
  219. ['id' => 'sk_1', 'code' => 'SK001', 'name' => '计算能力', 'category' => '基础技能'],
  220. ['id' => 'sk_2', 'code' => 'SK002', 'name' => '逻辑推理', 'category' => '思维技能'],
  221. ['id' => 'sk_3', 'code' => 'SK003', 'name' => '模式识别', 'category' => '认知技能'],
  222. ['id' => 'sk_4', 'code' => 'SK004', 'name' => '代数运算', 'category' => '专业技能'],
  223. ['id' => 'sk_5', 'code' => 'SK005', 'name' => '解题能力', 'category' => '专业技能'],
  224. ['id' => 'sk_6', 'code' => 'SK006', 'name' => '分析能力', 'category' => '思维技能'],
  225. ['id' => 'sk_7', 'code' => 'SK007', 'name' => '抽象思维', 'category' => '高级技能'],
  226. ['id' => 'sk_8', 'code' => 'SK008', 'name' => '创新思维', 'category' => '高级技能'],
  227. ];
  228. }
  229. /**
  230. * 导入知识图谱数据
  231. */
  232. public function importGraph(array $treeData, array $edgesData): bool
  233. {
  234. try {
  235. $response = Http::timeout(60) // 导入可能耗时较长
  236. ->post($this->baseUrl . '/import/graph', [
  237. 'tree_data' => $treeData,
  238. 'edges_data' => $edgesData
  239. ]);
  240. if ($response->successful()) {
  241. Log::info('知识图谱导入成功', [
  242. 'response' => $response->json()
  243. ]);
  244. return true;
  245. }
  246. Log::error('知识图谱导入失败', [
  247. 'status' => $response->status(),
  248. 'body' => $response->body()
  249. ]);
  250. } catch (\Exception $e) {
  251. Log::error('知识图谱导入异常', [
  252. 'error' => $e->getMessage()
  253. ]);
  254. }
  255. return false;
  256. }
  257. /**
  258. * 获取单个知识点详情
  259. */
  260. public function getKnowledgePoint(string $code): ?array
  261. {
  262. try {
  263. $response = Http::timeout(10)
  264. ->get($this->baseUrl . "/knowledge-points/{$code}");
  265. if ($response->successful()) {
  266. return $response->json();
  267. }
  268. } catch (\Exception $e) {
  269. Log::error('获取知识点详情失败', ['error' => $e->getMessage()]);
  270. }
  271. return null;
  272. }
  273. /**
  274. * 创建知识点
  275. */
  276. public function createKnowledgePoint(array $data): bool
  277. {
  278. try {
  279. $response = Http::timeout(10)
  280. ->post($this->baseUrl . '/knowledge-points/', $data);
  281. return $response->successful();
  282. } catch (\Exception $e) {
  283. Log::error('创建知识点失败', ['error' => $e->getMessage()]);
  284. return false;
  285. }
  286. }
  287. /**
  288. * 更新知识点
  289. */
  290. public function updateKnowledgePoint(string $code, array $data): bool
  291. {
  292. try {
  293. $response = Http::timeout(10)
  294. ->patch($this->baseUrl . "/knowledge-points/{$code}", $data);
  295. return $response->successful();
  296. } catch (\Exception $e) {
  297. Log::error('更新知识点失败', ['error' => $e->getMessage()]);
  298. return false;
  299. }
  300. }
  301. /**
  302. * 删除知识点
  303. */
  304. public function deleteKnowledgePoint(string $code): bool
  305. {
  306. try {
  307. $response = Http::timeout(10)
  308. ->delete($this->baseUrl . "/knowledge-points/{$code}");
  309. return $response->successful();
  310. } catch (\Exception $e) {
  311. Log::error('删除知识点失败', ['error' => $e->getMessage()]);
  312. return false;
  313. }
  314. }
  315. }