argument('file'); $filePath = base_path($fileName); if (!file_exists($filePath)) { $this->error("文件不存在: {$filePath}"); return; } $this->info("开始处理文件: {$fileName}"); try { // 读取Excel文件 $spreadsheet = IOFactory::load($filePath); $sheetCount = $spreadsheet->getSheetCount(); $this->info("Excel文件包含 {$sheetCount} 个工作表"); $totalUpdated = 0; // 处理每个工作表 for ($sheetIndex = 0; $sheetIndex < $sheetCount; $sheetIndex++) { $worksheet = $spreadsheet->getSheet($sheetIndex); $sheetName = $worksheet->getTitle(); $this->info("正在处理工作表: {$sheetName}"); $updated = $this->processWorksheet($worksheet, $sheetName); $totalUpdated += $updated; } $this->info("处理完成,总共更新了 {$totalUpdated} 条记录"); } catch (\Exception $e) { $this->error("处理Excel文件时发生错误: " . $e->getMessage()); return; } } /** * 处理单个工作表 */ private function processWorksheet($worksheet, $sheetName) { $highestRow = $worksheet->getHighestRow(); $highestColumn = $worksheet->getHighestColumn(); $this->info("工作表 {$sheetName} 有 {$highestRow} 行,最高列为 {$highestColumn}"); // 读取第一行作为表头 $headers = []; $highestColumnIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($highestColumn); for ($col = 1; $col <= $highestColumnIndex; $col++) { $cellValue = $worksheet->getCellByColumnAndRow($col, 1)->getCalculatedValue(); $headers[$col] = trim($cellValue); } $this->info("表头: " . implode(', ', $headers)); // 找到"课程"和"跳转链接"列的位置 $courseColumn = null; $linkColumn = null; foreach ($headers as $colIndex => $header) { if (strpos($header, '课程') !== false) { $courseColumn = $colIndex; } if (strpos($header, '跳转链接') !== false) { $linkColumn = $colIndex; } } if (!$courseColumn || !$linkColumn) { $this->warn("工作表 {$sheetName} 中未找到'课程'或'跳转链接'列"); return 0; } $this->info("找到课程列: {$courseColumn},跳转链接列: {$linkColumn}"); $updated = 0; $failedCourses = []; // 处理数据行 for ($row = 2; $row <= $highestRow; $row++) { $courseName = trim($worksheet->getCellByColumnAndRow($courseColumn, $row)->getCalculatedValue()); $jumpLink = trim($worksheet->getCellByColumnAndRow($linkColumn, $row)->getCalculatedValue()); if (empty($courseName) || empty($jumpLink)) { continue; } $this->info("处理行 {$row}: 课程='{$courseName}', 跳转链接='{$jumpLink}'"); // 从phome_ecms_news表获取titleurl $titleUrl = $this->getTitleUrlFromNews($jumpLink); if ($titleUrl) { // 更新courses表 $updateCount = $this->updateCourseUrl($courseName, $titleUrl); $updated += $updateCount; if ($updateCount > 0) { $this->info("✓ 成功更新课程 '{$courseName}' 的URL为: {$titleUrl}"); } else { $this->warn("✗ 未找到匹配的课程: '{$courseName}'"); $failedCourses[] = $courseName; } } else { $this->warn("✗ 未找到匹配的新闻标题: '{$jumpLink}'"); } } // 显示匹配失败的课程 if (!empty($failedCourses)) { $this->warn("工作表 {$sheetName} 中匹配失败的课程:"); foreach ($failedCourses as $failedCourse) { $this->warn(" - {$failedCourse}"); } } return $updated; } /** * 从phome_ecms_news表获取titleurl */ private function getTitleUrlFromNews($title) { try { // 直接匹配 $news = DB::table('phome_ecms_news') ->where('title', $title) ->first(); if ($news && !empty($news->titleurl)) { return $news->titleurl; } // 模糊匹配 $news = DB::table('phome_ecms_news') ->where('title', 'like', "%{$title}%") ->first(); if ($news && !empty($news->titleurl)) { $this->info("通过模糊匹配找到: '{$news->title}' -> '{$news->titleurl}'"); return $news->titleurl; } // 使用相似度匹配 $allNews = DB::table('phome_ecms_news') ->whereNotNull('title') ->whereNotNull('titleurl') ->where('title', '!=', '') ->where('titleurl', '!=', '') ->get(); $bestMatch = null; $highestSimilarity = 0; foreach ($allNews as $news) { $similarity = $this->calculateSimilarity($title, $news->title); if ($similarity > $highestSimilarity) { $highestSimilarity = $similarity; $bestMatch = $news; } } if ($bestMatch && $highestSimilarity > 0) { $this->info("通过相似度匹配找到 (相似度: " . round($highestSimilarity * 100, 2) . "%): '{$bestMatch->title}' -> '{$bestMatch->titleurl}'"); return $bestMatch->titleurl; } } catch (\Exception $e) { $this->error("查询phome_ecms_news表时发生错误: " . $e->getMessage()); } return null; } /** * 更新courses表的url字段 */ private function updateCourseUrl($courseName, $titleUrl) { try { // 直接匹配 $updateCount = Course::where('name', $courseName) ->whereNull('deleted_at') ->update(['url' => $titleUrl]); if ($updateCount > 0) { return $updateCount; } // 模糊匹配 $updateCount = Course::where('name', 'like', "%{$courseName}%") ->whereNull('deleted_at') ->update(['url' => $titleUrl]); if ($updateCount > 0) { $this->info("通过模糊匹配更新了课程"); return $updateCount; } // 使用相似度匹配 $courses = Course::whereNull('deleted_at') ->whereNotNull('name') ->where('name', '!=', '') ->get(); $bestMatch = null; $highestSimilarity = 0; foreach ($courses as $course) { $similarity = $this->calculateSimilarity($courseName, $course->name); if ($similarity > $highestSimilarity) { $highestSimilarity = $similarity; $bestMatch = $course; } } if ($bestMatch && $highestSimilarity > 0) { $bestMatch->url = $titleUrl; $bestMatch->save(); $this->info("通过相似度匹配更新了课程 (相似度: " . round($highestSimilarity * 100, 2) . "%): '{$bestMatch->name}'"); return 1; } } catch (\Exception $e) { $this->error("更新courses表时发生错误: " . $e->getMessage()); } return 0; } /** * 计算字符串相似度 */ private function calculateSimilarity($str1, $str2) { // 移除空格并转换为小写 $str1 = strtolower(preg_replace('/\s+/', '', $str1)); $str2 = strtolower(preg_replace('/\s+/', '', $str2)); if ($str1 === $str2) { return 1.0; } if (empty($str1) || empty($str2)) { return 0.0; } // 使用Levenshtein距离计算相似度 $maxLen = max(strlen($str1), strlen($str2)); if ($maxLen == 0) { return 1.0; } $distance = levenshtein($str1, $str2); $similarity = 1 - ($distance / $maxLen); // 如果其中一个字符串包含另一个,提高相似度 if (strpos($str1, $str2) !== false || strpos($str2, $str1) !== false) { $containsSimilarity = min(strlen($str1), strlen($str2)) / $maxLen; $similarity = max($similarity, $containsSimilarity); } return max(0, $similarity); } }