KnowledgeMasteryService.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\Http;
  4. use Illuminate\Support\Facades\Log;
  5. use Illuminate\Support\Facades\Cache;
  6. /**
  7. * 知识点掌握情况服务
  8. *
  9. * 提供:
  10. * 1. 获取学生知识点掌握情况统计
  11. * 2. 获取知识点图谱数据(考试快照)
  12. * 3. 获取知识点图谱快照列表
  13. */
  14. class KnowledgeMasteryService
  15. {
  16. protected string $learningAnalyticsBase;
  17. protected string $knowledgeServiceBase;
  18. protected int $timeout;
  19. public function __construct(?string $learningAnalyticsBase = null, ?string $knowledgeServiceBase = null, ?int $timeout = null)
  20. {
  21. // 已迁移到本地,使用MasteryCalculator
  22. $this->learningAnalyticsBase = '';
  23. $this->knowledgeServiceBase = rtrim(
  24. $knowledgeServiceBase
  25. ?: config('services.knowledge_service.url', env('KNOWLEDGE_SERVICE_API_BASE', 'http://localhost:5011')),
  26. '/'
  27. );
  28. $this->timeout = 20;
  29. }
  30. /**
  31. * 获取学生知识点掌握情况统计
  32. *
  33. * @param string $studentId 学生ID
  34. * @return array
  35. */
  36. public function getStats(string $studentId): array
  37. {
  38. try {
  39. // 使用本地的MasteryCalculator
  40. $masteryCalculator = app(MasteryCalculator::class);
  41. $overview = $masteryCalculator->getStudentMasteryOverview($studentId);
  42. // 转换为兼容格式
  43. $body = [
  44. 'student_id' => $studentId,
  45. 'total_knowledge_points' => $overview['total_knowledge_points'],
  46. 'average_mastery' => $overview['average_mastery_level'],
  47. 'mastered_count' => $overview['mastered_knowledge_points'],
  48. 'good_count' => $overview['good_knowledge_points'],
  49. 'weak_count' => $overview['weak_knowledge_points'],
  50. 'details' => $overview['details'],
  51. ];
  52. // 丰富知识点名称(如果有knowledge_points表)
  53. $body = $this->enrichWithKnowledgePointNames($body);
  54. // 添加知识图谱总数统计
  55. $graphStats = $this->getKnowledgeGraphStats();
  56. $body['graph_total_knowledge_points'] = $graphStats['total'] ?? 0;
  57. Log::info('KnowledgeMasteryService::getStats (Local)', ['student_id' => $studentId]);
  58. return [
  59. 'success' => true,
  60. 'data' => $body,
  61. ];
  62. } catch (\Throwable $e) {
  63. Log::error('Knowledge mastery stats exception', [
  64. 'student_id' => $studentId,
  65. 'error' => $e->getMessage(),
  66. ]);
  67. return [
  68. 'success' => false,
  69. 'error' => '获取知识点掌握情况异常: ' . $e->getMessage(),
  70. ];
  71. }
  72. }
  73. /**
  74. * 丰富知识点名称
  75. */
  76. private function enrichWithKnowledgePointNames(array $data): array
  77. {
  78. if (empty($data['details'])) {
  79. return $data;
  80. }
  81. // 确保 details 是数组格式(处理 stdClass 对象)
  82. $details = $data['details'];
  83. if (!is_array($details)) {
  84. $details = [];
  85. } elseif (isset($details[0]) && is_object($details[0])) {
  86. // 将 stdClass 对象转换为关联数组
  87. $details = array_map(function ($item) {
  88. return (array) $item;
  89. }, $details);
  90. }
  91. // 收集所有kp_code
  92. $kpCodes = array_column($details, 'kp_code');
  93. if (empty($kpCodes)) {
  94. return $data;
  95. }
  96. // 批量获取知识点名称
  97. $kpNames = $this->getKnowledgePointNames($kpCodes);
  98. // 丰富details数据
  99. foreach ($details as &$detail) {
  100. $kpCode = $detail['kp_code'] ?? null;
  101. if ($kpCode && isset($kpNames[$kpCode])) {
  102. $detail['kp_name'] = $kpNames[$kpCode];
  103. } else {
  104. $detail['kp_name'] = $kpCode; // fallback to code
  105. }
  106. }
  107. // 更新原始数据
  108. $data['details'] = $details;
  109. return $data;
  110. }
  111. /**
  112. * 批量获取知识点名称
  113. */
  114. private function getKnowledgePointNames(array $kpCodes): array
  115. {
  116. $result = [];
  117. foreach ($kpCodes as $kpCode) {
  118. $name = $this->getKnowledgePointName($kpCode);
  119. if ($name) {
  120. $result[$kpCode] = $name;
  121. }
  122. }
  123. return $result;
  124. }
  125. /**
  126. * 批量获取知识点名称映射(返回关联数组)
  127. */
  128. private function getKnowledgePointNamesMap(array $kpCodes): array
  129. {
  130. $result = [];
  131. foreach ($kpCodes as $kpCode) {
  132. $name = $this->getKnowledgePointName($kpCode);
  133. // 如果获取不到名称,使用kpCode作为默认值
  134. $result[$kpCode] = $name ?: $kpCode;
  135. }
  136. return $result;
  137. }
  138. /**
  139. * 获取单个知识点名称(带缓存)
  140. */
  141. private function getKnowledgePointName(string $kpCode): ?string
  142. {
  143. $cacheKey = "kp_name_{$kpCode}";
  144. return Cache::remember($cacheKey, 3600, function () use ($kpCode) {
  145. try {
  146. // 首先尝试从知识图谱服务获取
  147. $response = Http::timeout(5)
  148. ->get($this->knowledgeServiceBase . '/knowledge-points/' . $kpCode);
  149. if ($response->successful()) {
  150. $data = $response->json();
  151. $name = $data['cn_name'] ?? $data['en_name'] ?? null;
  152. if ($name) {
  153. return $name;
  154. }
  155. }
  156. } catch (\Throwable $e) {
  157. Log::debug('Failed to get knowledge point name from service, will use local mapping', [
  158. 'kp_code' => $kpCode,
  159. 'error' => $e->getMessage(),
  160. ]);
  161. }
  162. // 如果知识图谱服务不可用,使用本地映射
  163. return $this->getLocalKnowledgePointName($kpCode);
  164. });
  165. }
  166. /**
  167. * 本地知识点名称映射(作为备选方案)
  168. */
  169. private function getLocalKnowledgePointName(string $kpCode): ?string
  170. {
  171. // 常见的数学知识点中文名称映射
  172. $nameMap = [
  173. 'A09' => '因式分解',
  174. 'E01' => '一元二次方程',
  175. 'E03' => '一元二次方程的解法',
  176. 'E04' => '一元二次方程的应用',
  177. 'E06' => '判别式',
  178. 'F03' => '函数的概念',
  179. 'F05' => '函数的图像',
  180. 'F06' => '函数的性质',
  181. 'G02' => '几何图形',
  182. 'G03' => '三角形',
  183. 'R06' => '实数',
  184. 'S01' => '三角函数',
  185. 'S02' => '正弦函数',
  186. 'S03' => '余弦函数',
  187. 'S05' => '正切函数',
  188. 'E01B' => '因式分解法',
  189. 'E03B' => '公式法',
  190. 'E04B' => '配方法',
  191. 'G02B' => '平行四边形',
  192. 'G03E' => '等腰三角形',
  193. 'G03G' => '等边三角形',
  194. 'M01C' => '有理数',
  195. 'M04E' => '不等式',
  196. 'PY02' => '概率初步',
  197. 'ST04' => '统计与概率',
  198. 'M04E1' => '一元一次不等式',
  199. 'PY02D' => '随机事件',
  200. 'SIM02' => '相似三角形',
  201. 'ST04D' => '数据的收集',
  202. 'APP_E4' => '应用题',
  203. 'SIM02A' => '相似比',
  204. ];
  205. return $nameMap[$kpCode] ?? null;
  206. }
  207. /**
  208. * 判断知识点是否为另一个知识点的子知识点
  209. */
  210. private function isChildOf(string $childCode, string $parentCode): bool
  211. {
  212. // 常见的父子关系映射
  213. $parentChildMap = [
  214. 'E01' => ['E01B'], // 一元二次方程 -> 因式分解法
  215. 'E03' => ['E03B'], // 一元二次方程的解法 -> 公式法
  216. 'E04' => ['E04B'], // 一元二次方程的应用 -> 配方法
  217. 'G02' => ['G02B'], // 几何图形 -> 平行四边形
  218. 'G03' => ['G03E', 'G03G'], // 三角形 -> 等腰三角形, 等边三角形
  219. 'M04E' => ['M04E1'], // 不等式 -> 一元一次不等式
  220. 'PY02' => ['PY02D'], // 概率初步 -> 随机事件
  221. 'SIM02' => ['SIM02A'], // 相似三角形 -> 相似比
  222. 'ST04' => ['ST04D'], // 统计与概率 -> 数据的收集
  223. 'G03' => ['G03E', 'G03G'], // 三角形 -> 等腰三角形, 等边三角形
  224. ];
  225. // 直接通过代码前缀判断
  226. if (strpos($childCode, $parentCode) === 0 && $childCode !== $parentCode) {
  227. return true;
  228. }
  229. // 通过映射表判断
  230. if (isset($parentChildMap[$parentCode]) && in_array($childCode, $parentChildMap[$parentCode])) {
  231. return true;
  232. }
  233. return false;
  234. }
  235. /**
  236. * 获取知识图谱统计信息(带缓存)
  237. */
  238. public function getKnowledgeGraphStats(): array
  239. {
  240. return Cache::remember('knowledge_graph_stats', 3600, function () {
  241. try {
  242. $response = Http::timeout(10)
  243. ->get($this->knowledgeServiceBase . '/knowledge-points/');
  244. if ($response->successful()) {
  245. $data = $response->json();
  246. $items = $data['data'] ?? $data ?? [];
  247. return [
  248. 'total' => count($items),
  249. 'updated_at' => now()->toISOString(),
  250. ];
  251. }
  252. } catch (\Throwable $e) {
  253. Log::error('Failed to get knowledge graph stats', [
  254. 'error' => $e->getMessage(),
  255. ]);
  256. }
  257. return ['total' => 0];
  258. });
  259. }
  260. /**
  261. * 获取学生知识点图谱数据
  262. *
  263. * @param string $studentId 学生ID
  264. * @param string|null $examId 考试ID(可选,不指定则返回最新快照)
  265. * @return array
  266. */
  267. public function getGraph(string $studentId, ?string $examId = null): array
  268. {
  269. try {
  270. // 使用本地的MasteryCalculator
  271. $masteryCalculator = app(MasteryCalculator::class);
  272. $overview = $masteryCalculator->getStudentMasteryOverview($studentId);
  273. // 转换为图谱格式
  274. $nodes = [];
  275. $edges = [];
  276. foreach ($overview['details'] as $detail) {
  277. $masteryLevel = floatval($detail->mastery_level ?? 0);
  278. $kpCode = $detail->kp_code;
  279. // 节点
  280. $nodes[] = [
  281. 'id' => $kpCode,
  282. 'label' => $kpCode,
  283. 'mastery' => $masteryLevel,
  284. 'mastery_level' => $this->getMasteryLevelLabel($masteryLevel),
  285. 'color' => $this->getMasteryColor($masteryLevel),
  286. 'size' => 20 + ($masteryLevel * 20),
  287. ];
  288. // 这里可以添加知识点之间的依赖关系边
  289. // 暂时为空,后续从knowledge_points表获取
  290. }
  291. $graphData = [
  292. 'nodes' => $nodes,
  293. 'edges' => $edges,
  294. 'statistics' => [
  295. 'total_nodes' => count($nodes),
  296. 'total_edges' => count($edges),
  297. 'average_mastery' => $overview['average_mastery_level'],
  298. ],
  299. ];
  300. Log::info('KnowledgeMasteryService::getGraph (Local)', ['student_id' => $studentId, 'exam_id' => $examId]);
  301. return [
  302. 'success' => true,
  303. 'data' => $graphData,
  304. ];
  305. } catch (\Throwable $e) {
  306. Log::error('Knowledge graph exception', [
  307. 'student_id' => $studentId,
  308. 'exam_id' => $examId,
  309. 'error' => $e->getMessage(),
  310. ]);
  311. return [
  312. 'success' => false,
  313. 'error' => '获取知识点图谱异常: ' . $e->getMessage(),
  314. ];
  315. }
  316. }
  317. /**
  318. * 获取掌握度等级标签
  319. */
  320. private function getMasteryLevelLabel(float $mastery): string
  321. {
  322. if ($mastery >= 0.85) return 'mastered';
  323. if ($mastery >= 0.70) return 'good';
  324. if ($mastery >= 0.50) return 'fair';
  325. return 'weak';
  326. }
  327. /**
  328. * 获取掌握度颜色
  329. */
  330. private function getMasteryColor(float $mastery): string
  331. {
  332. if ($mastery >= 0.85) return '#52c41a'; // 绿色 - 掌握
  333. if ($mastery >= 0.70) return '#1890ff'; // 蓝色 - 良好
  334. if ($mastery >= 0.50) return '#faad14'; // 橙色 - 一般
  335. return '#ff4d4f'; // 红色 - 薄弱
  336. }
  337. /**
  338. * 获取学生知识点图谱快照列表
  339. *
  340. * @param string $studentId 学生ID
  341. * @param int $limit 返回数量限制
  342. * @return array
  343. */
  344. public function getGraphSnapshots(string $studentId, int $limit = 10): array
  345. {
  346. try {
  347. // 从knowledge_point_mastery_snapshots表获取快照
  348. $snapshots = \DB::table('knowledge_point_mastery_snapshots')
  349. ->where('student_id', $studentId)
  350. ->orderBy('created_at', 'desc')
  351. ->limit($limit)
  352. ->get();
  353. $snapshotList = $snapshots->map(function ($snapshot) {
  354. // 解析掌握度数据
  355. $masteryData = json_decode($snapshot->mastery_data, true) ?: [];
  356. // 提取知识点信息 - 修复字段名
  357. $knowledgePoints = [];
  358. $parentKnowledgePoints = []; // 存储父知识点信息
  359. $kpCodes = array_keys($masteryData); // 收集所有kp_code用于批量获取名称
  360. // 批量获取知识点名称
  361. $kpNamesMap = $this->getKnowledgePointNamesMap($kpCodes);
  362. // 第一步:分类父知识点和子知识点
  363. foreach ($masteryData as $kpCode => $kpData) {
  364. $isParent = (bool) ($kpData['is_parent'] ?? false);
  365. $masteryLevel = floatval($kpData['current_mastery'] ?? 0);
  366. $change = floatval($kpData['change'] ?? 0);
  367. $weight = intval($kpData['weight'] ?? 1);
  368. if ($isParent) {
  369. // 父知识点 - 稍后重新计算
  370. $parentKnowledgePoints[$kpCode] = [
  371. 'kp_code' => $kpCode,
  372. 'kp_name' => $kpNamesMap[$kpCode] ?? $kpCode,
  373. 'original_mastery' => $masteryLevel,
  374. 'change' => $change,
  375. 'weight' => $weight,
  376. 'children' => [] // 存储子知识点
  377. ];
  378. } else {
  379. // 子知识点 - 直接添加
  380. $knowledgePoints[] = [
  381. 'kp_code' => $kpCode,
  382. 'kp_name' => $kpNamesMap[$kpCode] ?? $kpCode,
  383. 'mastery_level' => round($masteryLevel, 2),
  384. 'change' => round($change, 2),
  385. 'is_child' => true,
  386. 'parent_code' => null, // 稍后填入父知识点
  387. ];
  388. }
  389. }
  390. // 第二步:建立父子关系并计算父知识点掌握度
  391. foreach ($masteryData as $kpCode => $kpData) {
  392. $isParent = (bool) ($kpData['is_parent'] ?? false);
  393. if (!$isParent) {
  394. // 查找父知识点(通过代码前缀匹配或其他逻辑)
  395. foreach ($parentKnowledgePoints as $parentCode => $parentData) {
  396. // 简单的父子关系判断:如果子知识点代码包含父知识点代码前缀
  397. if (strpos($kpCode, $parentCode) === 0 || $this->isChildOf($kpCode, $parentCode)) {
  398. // 添加到父知识点的子知识点列表
  399. $parentKnowledgePoints[$parentCode]['children'][] = $kpCode;
  400. // 更新子知识点的父知识点信息
  401. foreach ($knowledgePoints as &$child) {
  402. if ($child['kp_code'] === $kpCode) {
  403. $child['parent_code'] = $parentCode;
  404. break;
  405. }
  406. }
  407. break;
  408. }
  409. }
  410. }
  411. }
  412. // 第三步:计算父知识点的掌握度(加权平均)
  413. foreach ($parentKnowledgePoints as $parentCode => &$parentData) {
  414. if (empty($parentData['children'])) {
  415. // 如果没有子知识点,使用原始值
  416. $parentData['mastery_level'] = round($parentData['original_mastery'], 2);
  417. } else {
  418. // 计算子知识点的加权平均
  419. $totalWeightedMastery = 0;
  420. $totalWeight = 0;
  421. foreach ($parentData['children'] as $childCode) {
  422. if (isset($masteryData[$childCode])) {
  423. $childMastery = floatval($masteryData[$childCode]['current_mastery'] ?? 0);
  424. $childWeight = intval($masteryData[$childCode]['weight'] ?? 1);
  425. $totalWeightedMastery += $childMastery * $childWeight;
  426. $totalWeight += $childWeight;
  427. }
  428. }
  429. if ($totalWeight > 0) {
  430. $parentData['mastery_level'] = round($totalWeightedMastery / $totalWeight, 2);
  431. // 重新计算变化值
  432. $parentData['change'] = round($parentData['mastery_level'] - $parentData['original_mastery'], 2);
  433. } else {
  434. $parentData['mastery_level'] = round($parentData['original_mastery'], 2);
  435. }
  436. }
  437. $parentData['is_parent'] = true;
  438. unset($parentData['original_mastery']); // 移除临时字段
  439. // 添加到知识点列表
  440. $knowledgePoints[] = $parentData;
  441. }
  442. unset($parentData); // 避免引用问题
  443. // 第四步:按知识点代码排序
  444. usort($knowledgePoints, function($a, $b) {
  445. return strcmp($a['kp_code'], $b['kp_code']);
  446. });
  447. return [
  448. 'snapshot_id' => $snapshot->snapshot_id,
  449. 'student_id' => $snapshot->student_id,
  450. 'paper_id' => $snapshot->paper_id,
  451. 'overall_mastery' => floatval($snapshot->overall_mastery),
  452. 'weak_knowledge_points_count' => intval($snapshot->weak_knowledge_points_count),
  453. 'strong_knowledge_points_count' => intval($snapshot->strong_knowledge_points_count),
  454. 'knowledge_points' => $knowledgePoints,
  455. 'knowledge_points_count' => count($knowledgePoints),
  456. 'created_at' => $snapshot->created_at,
  457. 'snapshot_time' => $snapshot->snapshot_time,
  458. ];
  459. })->toArray();
  460. Log::info('KnowledgeMasteryService::getGraphSnapshots (Local)', [
  461. 'student_id' => $studentId,
  462. 'limit' => $limit,
  463. 'snapshot_count' => count($snapshotList)
  464. ]);
  465. return [
  466. 'success' => true,
  467. 'data' => [
  468. 'snapshots' => $snapshotList,
  469. 'total' => count($snapshotList),
  470. ],
  471. ];
  472. } catch (\Throwable $e) {
  473. Log::error('Knowledge graph snapshots exception', [
  474. 'student_id' => $studentId,
  475. 'limit' => $limit,
  476. 'error' => $e->getMessage(),
  477. ]);
  478. return [
  479. 'success' => false,
  480. 'error' => '获取知识点图谱快照列表异常: ' . $e->getMessage(),
  481. ];
  482. }
  483. }
  484. /**
  485. * 获取学生知识点掌握摘要(简化版)
  486. *
  487. * @param string $studentId 学生ID
  488. * @return array
  489. */
  490. public function getSummary(string $studentId): array
  491. {
  492. $stats = $this->getStats($studentId);
  493. if (!$stats['success']) {
  494. return $stats;
  495. }
  496. $data = $stats['data'];
  497. return [
  498. 'success' => true,
  499. 'data' => [
  500. 'student_id' => $data['student_id'] ?? $studentId,
  501. 'total' => $data['total_knowledge_points'] ?? 0,
  502. 'mastered' => $data['mastered_knowledge_points'] ?? 0,
  503. 'unmastered' => $data['unmastered_knowledge_points'] ?? 0,
  504. 'mastery_rate' => $data['mastery_rate'] ?? 0.0,
  505. 'mastery_percentage' => round(($data['mastery_rate'] ?? 0) * 100, 1) . '%',
  506. 'graph_total' => $data['graph_total_knowledge_points'] ?? 0,
  507. ],
  508. ];
  509. }
  510. /**
  511. * 创建知识点掌握度快照
  512. *
  513. * @param string $studentId 学生ID
  514. * @param string $snapshotType 快照类型 (exam/report/manual/scheduled)
  515. * @param string|null $sourceId 来源ID
  516. * @param string|null $sourceName 来源名称
  517. * @param string|null $notes 备注
  518. * @return array
  519. */
  520. public function createSnapshot(
  521. string $studentId,
  522. string $snapshotType = 'report',
  523. ?string $sourceId = null,
  524. ?string $sourceName = null,
  525. ?string $notes = null
  526. ): array {
  527. try {
  528. // 使用LocalAIAnalysisService创建快照
  529. $localAI = app(LocalAIAnalysisService::class);
  530. // 获取当前掌握度数据
  531. $masteryCalculator = app(MasteryCalculator::class);
  532. $overview = $masteryCalculator->getStudentMasteryOverview($studentId);
  533. $snapshots = [];
  534. foreach ($overview['details'] as $detail) {
  535. $snapshotId = \DB::table('knowledge_point_mastery_snapshots')->insertGetId([
  536. 'student_id' => $studentId,
  537. 'kp_code' => $detail->kp_code,
  538. 'mastery_level' => $detail->mastery_level,
  539. 'confidence_level' => $detail->confidence_level,
  540. 'snapshot_type' => $snapshotType,
  541. 'source_id' => $sourceId,
  542. 'source_name' => $sourceName,
  543. 'notes' => $notes,
  544. 'created_at' => now(),
  545. 'updated_at' => now(),
  546. ]);
  547. $snapshots[] = [
  548. 'snapshot_id' => $snapshotId,
  549. 'kp_code' => $detail->kp_code,
  550. 'mastery_level' => $detail->mastery_level,
  551. ];
  552. }
  553. Log::info('KnowledgeMasteryService::createSnapshot (Local)', [
  554. 'student_id' => $studentId,
  555. 'snapshot_type' => $snapshotType,
  556. 'snapshot_count' => count($snapshots),
  557. ]);
  558. return [
  559. 'success' => true,
  560. 'data' => [
  561. 'snapshots' => $snapshots,
  562. 'total_snapshots' => count($snapshots),
  563. ],
  564. ];
  565. } catch (\Throwable $e) {
  566. Log::error('Create knowledge mastery snapshot exception', [
  567. 'student_id' => $studentId,
  568. 'error' => $e->getMessage(),
  569. ]);
  570. return [
  571. 'success' => false,
  572. 'error' => '创建知识点掌握度快照异常: ' . $e->getMessage(),
  573. ];
  574. }
  575. }
  576. }