KnowledgeMasteryService.php 24 KB

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