You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
9.6 KiB

4 months ago
<?php
namespace App\Console\Commands;
use App\Models\Course;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use PhpOffice\PhpSpreadsheet\IOFactory;
class UpdateCourseUrls extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'update:course-urls {file=课程台账.xlsx}';
/**
* The console command description.
*
* @var string
*/
protected $description = '从Excel文件读取课程信息匹配phome_ecms_news表的titleurl并更新courses表的url字段';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$fileName = $this->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);
}
}