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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?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);
}
}