|
|
|
|
@ -2,6 +2,7 @@
|
|
|
|
|
|
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
|
|
|
|
|
|
use App\Models\Calendar;
|
|
|
|
|
use App\Models\Course;
|
|
|
|
|
use Illuminate\Console\Command;
|
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
@ -22,7 +23,7 @@ class UpdateCourseUrls extends Command
|
|
|
|
|
*
|
|
|
|
|
* @var string
|
|
|
|
|
*/
|
|
|
|
|
protected $description = '从Excel文件读取课程信息,匹配phome_ecms_news表的titleurl并更新courses表的url字段';
|
|
|
|
|
protected $description = '从Excel文件读取课程信息,匹配courses表,获取新闻链接,并创建calendar记录';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new command instance.
|
|
|
|
|
@ -58,7 +59,9 @@ class UpdateCourseUrls extends Command
|
|
|
|
|
|
|
|
|
|
$this->info("Excel文件包含 {$sheetCount} 个工作表");
|
|
|
|
|
|
|
|
|
|
$totalUpdated = 0;
|
|
|
|
|
$totalCreated = 0;
|
|
|
|
|
$failedCourses = [];
|
|
|
|
|
$failedNews = [];
|
|
|
|
|
|
|
|
|
|
// 处理每个工作表
|
|
|
|
|
for ($sheetIndex = 0; $sheetIndex < $sheetCount; $sheetIndex++) {
|
|
|
|
|
@ -67,11 +70,29 @@ class UpdateCourseUrls extends Command
|
|
|
|
|
|
|
|
|
|
$this->info("正在处理工作表: {$sheetName}");
|
|
|
|
|
|
|
|
|
|
$updated = $this->processWorksheet($worksheet, $sheetName);
|
|
|
|
|
$totalUpdated += $updated;
|
|
|
|
|
list($created, $sheetFailedCourses, $sheetFailedNews) = $this->processWorksheet($worksheet, $sheetName);
|
|
|
|
|
$totalCreated += $created;
|
|
|
|
|
$failedCourses = array_merge($failedCourses, $sheetFailedCourses);
|
|
|
|
|
$failedNews = array_merge($failedNews, $sheetFailedNews);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->info("处理完成,总共更新了 {$totalUpdated} 条记录");
|
|
|
|
|
$this->info("处理完成,总共创建了 {$totalCreated} 条日历记录");
|
|
|
|
|
|
|
|
|
|
// 显示匹配失败的课程
|
|
|
|
|
if (!empty($failedCourses)) {
|
|
|
|
|
$this->warn("匹配失败的课程:");
|
|
|
|
|
foreach (array_unique($failedCourses) as $failedCourse) {
|
|
|
|
|
$this->warn(" - {$failedCourse}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示匹配失败的新闻
|
|
|
|
|
if (!empty($failedNews)) {
|
|
|
|
|
$this->warn("匹配失败的新闻:");
|
|
|
|
|
foreach (array_unique($failedNews) as $failedNewsItem) {
|
|
|
|
|
$this->warn(" - {$failedNewsItem}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->error("处理Excel文件时发生错误: " . $e->getMessage());
|
|
|
|
|
@ -98,70 +119,150 @@ class UpdateCourseUrls extends Command
|
|
|
|
|
$headers[$col] = trim($cellValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->info("表头: " . implode(', ', $headers));
|
|
|
|
|
|
|
|
|
|
// 找到"课程"和"跳转链接"列的位置
|
|
|
|
|
// 找到"课程"、"开始时间"、"结束时间"、"跳转链接"列的位置
|
|
|
|
|
$courseColumn = null;
|
|
|
|
|
$startTimeColumn = null;
|
|
|
|
|
$endTimeColumn = null;
|
|
|
|
|
$linkColumn = null;
|
|
|
|
|
|
|
|
|
|
foreach ($headers as $colIndex => $header) {
|
|
|
|
|
if (strpos($header, '课程') !== false) {
|
|
|
|
|
$courseColumn = $colIndex;
|
|
|
|
|
}
|
|
|
|
|
if (strpos($header, '开始时间') !== false) {
|
|
|
|
|
$startTimeColumn = $colIndex;
|
|
|
|
|
}
|
|
|
|
|
if (strpos($header, '结束时间') !== false) {
|
|
|
|
|
$endTimeColumn = $colIndex;
|
|
|
|
|
}
|
|
|
|
|
if (strpos($header, '跳转链接') !== false) {
|
|
|
|
|
$linkColumn = $colIndex;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$courseColumn || !$linkColumn) {
|
|
|
|
|
$this->warn("工作表 {$sheetName} 中未找到'课程'或'跳转链接'列");
|
|
|
|
|
return 0;
|
|
|
|
|
if (!$courseColumn || !$startTimeColumn || !$endTimeColumn || !$linkColumn) {
|
|
|
|
|
$this->warn("工作表 {$sheetName} 中未找到必要的列(课程、开始时间、结束时间、跳转链接)");
|
|
|
|
|
return [0, [], []];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->info("找到课程列: {$courseColumn},跳转链接列: {$linkColumn}");
|
|
|
|
|
|
|
|
|
|
$updated = 0;
|
|
|
|
|
$this->info("找到课程列: {$courseColumn},开始时间列: {$startTimeColumn},结束时间列: {$endTimeColumn},跳转链接列: {$linkColumn}");
|
|
|
|
|
$created = 0;
|
|
|
|
|
$failedCourses = [];
|
|
|
|
|
$failedNews = [];
|
|
|
|
|
|
|
|
|
|
// 处理数据行
|
|
|
|
|
for ($row = 2; $row <= $highestRow; $row++) {
|
|
|
|
|
$courseName = trim($worksheet->getCellByColumnAndRow($courseColumn, $row)->getCalculatedValue());
|
|
|
|
|
|
|
|
|
|
// 获取开始时间和结束时间的原始值,避免格式化问题
|
|
|
|
|
$startTimeCell = $worksheet->getCellByColumnAndRow($startTimeColumn, $row);
|
|
|
|
|
$endTimeCell = $worksheet->getCellByColumnAndRow($endTimeColumn, $row);
|
|
|
|
|
|
|
|
|
|
// 优先使用原始值,如果没有则使用计算值
|
|
|
|
|
$startTime = $startTimeCell->getValue();
|
|
|
|
|
if ($startTime === null) {
|
|
|
|
|
$startTime = trim($startTimeCell->getCalculatedValue());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$endTime = $endTimeCell->getValue();
|
|
|
|
|
if ($endTime === null) {
|
|
|
|
|
$endTime = trim($endTimeCell->getCalculatedValue());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$jumpLink = trim($worksheet->getCellByColumnAndRow($linkColumn, $row)->getCalculatedValue());
|
|
|
|
|
|
|
|
|
|
if (empty($courseName) || empty($jumpLink)) {
|
|
|
|
|
if (empty($courseName) || empty($startTime) || empty($endTime)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->info("处理行 {$row}: 课程='{$courseName}', 跳转链接='{$jumpLink}'");
|
|
|
|
|
|
|
|
|
|
// 从phome_ecms_news表获取titleurl
|
|
|
|
|
list($title, $titleUrl) = $this->getTitleUrlFromNews($jumpLink);
|
|
|
|
|
$this->info("处理行 {$row}: 课程='{$courseName}', 开始时间='{$startTime}' (类型: " . gettype($startTime) . "), 结束时间='{$endTime}' (类型: " . gettype($endTime) . "), 跳转链接='{$jumpLink}'");
|
|
|
|
|
|
|
|
|
|
if ($titleUrl) {
|
|
|
|
|
// 更新courses表
|
|
|
|
|
$updateCount = $this->updateCourseUrl($courseName, $titleUrl, $title);
|
|
|
|
|
$updated += $updateCount;
|
|
|
|
|
// 1. 匹配courses表
|
|
|
|
|
$courseId = $this->matchCourse($courseName);
|
|
|
|
|
if (!$courseId) {
|
|
|
|
|
$this->warn("✗ 未找到匹配的课程: '{$courseName}'");
|
|
|
|
|
$failedCourses[] = $courseName;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($updateCount > 0) {
|
|
|
|
|
$this->info("✓ 成功更新课程 '{$courseName}' 的URL为: {$titleUrl}");
|
|
|
|
|
} else {
|
|
|
|
|
$this->warn("✗ 未找到匹配的课程: '{$courseName}'");
|
|
|
|
|
$failedCourses[] = $courseName;
|
|
|
|
|
// 2. 匹配phome_ecms_news表获取url和title
|
|
|
|
|
$url = null;
|
|
|
|
|
$title = null;
|
|
|
|
|
if (!empty($jumpLink)) {
|
|
|
|
|
list($title, $url) = $this->getTitleUrlFromNews($jumpLink);
|
|
|
|
|
if (!$url) {
|
|
|
|
|
$this->warn("✗ 未找到匹配的新闻标题: '{$jumpLink}'");
|
|
|
|
|
$failedNews[] = $jumpLink;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 3. 更新courses表的url字段
|
|
|
|
|
if ($url && $title) {
|
|
|
|
|
$this->updateCourseUrl($courseId, $url, $title);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 4. 创建calendar记录
|
|
|
|
|
$calendarCreated = $this->createCalendarRecord($courseId, $courseName, $startTime, $endTime, $url, $title, $courseName);
|
|
|
|
|
if ($calendarCreated) {
|
|
|
|
|
$created++;
|
|
|
|
|
$this->info("✓ 成功创建日历记录: '{$courseName}'");
|
|
|
|
|
} else {
|
|
|
|
|
$this->warn("✗ 未找到匹配的新闻标题: '{$jumpLink}'");
|
|
|
|
|
$this->warn("✗ 创建日历记录失败: '{$courseName}'");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示匹配失败的课程
|
|
|
|
|
if (!empty($failedCourses)) {
|
|
|
|
|
$this->warn("工作表 {$sheetName} 中匹配失败的课程:");
|
|
|
|
|
foreach ($failedCourses as $failedCourse) {
|
|
|
|
|
$this->warn(" - {$failedCourse}");
|
|
|
|
|
return [$created, $failedCourses, $failedNews];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 匹配courses表
|
|
|
|
|
*/
|
|
|
|
|
private function matchCourse($courseName)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// 直接匹配
|
|
|
|
|
$course = Course::where('name', $courseName)->first();
|
|
|
|
|
|
|
|
|
|
if ($course) {
|
|
|
|
|
$this->info("通过直接匹配找到课程: '{$course->name}' (ID: {$course->id})");
|
|
|
|
|
return $course->id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 模糊匹配
|
|
|
|
|
$course = Course::where('name', 'like', "%{$courseName}%")
|
|
|
|
|
->whereNull('deleted_at')
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if ($course) {
|
|
|
|
|
$this->info("通过模糊匹配找到课程: '{$course->name}' (ID: {$course->id})");
|
|
|
|
|
return $course->id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用相似度匹配
|
|
|
|
|
$courses = Course::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.3) {
|
|
|
|
|
$this->info("通过相似度匹配找到课程 (相似度: " . round($highestSimilarity * 100, 2) . "%): '{$bestMatch->name}' (ID: {$bestMatch->id})");
|
|
|
|
|
return $bestMatch->id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->error("查询courses表时发生错误: " . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $updated;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -208,8 +309,9 @@ class UpdateCourseUrls extends Command
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($bestMatch && $highestSimilarity > 0) {
|
|
|
|
|
$this->info("通过相似度匹配找到 (相似度: " . round($highestSimilarity * 100, 2) . "%): '{$bestMatch->title}' -> '{$bestMatch->titleurl}'");
|
|
|
|
|
// 取相似度最高的作为结果,不设置阈值限制
|
|
|
|
|
if ($bestMatch && $highestSimilarity > 0.3) {
|
|
|
|
|
$this->info("通过相似度匹配找到新闻 (相似度: " . round($highestSimilarity * 100, 2) . "%): '{$bestMatch->title}' -> '{$bestMatch->titleurl}'");
|
|
|
|
|
return [$bestMatch->title, $bestMatch->titleurl];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -223,58 +325,130 @@ class UpdateCourseUrls extends Command
|
|
|
|
|
/**
|
|
|
|
|
* 更新courses表的url字段
|
|
|
|
|
*/
|
|
|
|
|
private function updateCourseUrl($courseName, $titleUrl, $title)
|
|
|
|
|
private function updateCourseUrl($courseId, $titleUrl, $title)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// 直接匹配
|
|
|
|
|
$updateCount = Course::where('name', $courseName)
|
|
|
|
|
->whereNull('deleted_at')
|
|
|
|
|
->update(['url' => $titleUrl, 'url_title' => $title]);
|
|
|
|
|
$course = Course::find($courseId);
|
|
|
|
|
if ($course) {
|
|
|
|
|
$course->url = $titleUrl;
|
|
|
|
|
$course->url_title = $title;
|
|
|
|
|
$course->save();
|
|
|
|
|
$this->info("✓ 成功更新课程URL: '{$course->name}' -> '{$titleUrl}'");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->error("更新courses表时发生错误: " . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建calendar记录
|
|
|
|
|
*/
|
|
|
|
|
private function createCalendarRecord($courseId, $courseName, $startTime, $endTime, $url = null, $title = null, $calendarTitle = null)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// 转换时间格式
|
|
|
|
|
$startDateTime = $this->parseDateTime($startTime);
|
|
|
|
|
$endDateTime = $this->parseDateTime($endTime);
|
|
|
|
|
|
|
|
|
|
if ($updateCount > 0) {
|
|
|
|
|
return $updateCount;
|
|
|
|
|
if (!$startDateTime || !$endDateTime) {
|
|
|
|
|
$this->warn("时间格式解析失败: 开始时间='{$startTime}', 结束时间='{$endTime}'");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 模糊匹配
|
|
|
|
|
$updateCount = Course::where('name', 'like', "%{$courseName}%")
|
|
|
|
|
->whereNull('deleted_at')
|
|
|
|
|
->update(['url' => $titleUrl, 'url_title' => $title]);
|
|
|
|
|
// 检查是否已存在相同的日历记录
|
|
|
|
|
$existingCalendar = Calendar::where('course_id', $courseId)
|
|
|
|
|
->where('start_time', $startDateTime)
|
|
|
|
|
->where('end_time', $endDateTime)
|
|
|
|
|
->first();
|
|
|
|
|
|
|
|
|
|
if ($updateCount > 0) {
|
|
|
|
|
$this->info("通过模糊匹配更新了课程");
|
|
|
|
|
return $updateCount;
|
|
|
|
|
if ($existingCalendar) {
|
|
|
|
|
$this->info("日历记录已存在,跳过创建: '{$courseName}'");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 使用相似度匹配
|
|
|
|
|
$courses = Course::whereNull('deleted_at')
|
|
|
|
|
->whereNotNull('name')
|
|
|
|
|
->where('name', '!=', '')
|
|
|
|
|
->get();
|
|
|
|
|
// 创建新的日历记录
|
|
|
|
|
$calendar = new Calendar();
|
|
|
|
|
$calendar->type = 1; // 课程类型
|
|
|
|
|
$calendar->course_id = $courseId;
|
|
|
|
|
$calendar->title = $calendarTitle ?: $courseName; // 使用Excel中的课程名字作为title
|
|
|
|
|
$calendar->start_time = $startDateTime;
|
|
|
|
|
$calendar->end_time = $endDateTime;
|
|
|
|
|
$calendar->date = $startDateTime->format('Y-m-d');
|
|
|
|
|
$calendar->url = $url;
|
|
|
|
|
$calendar->is_publish = 1; // 默认发布
|
|
|
|
|
$calendar->save();
|
|
|
|
|
|
|
|
|
|
$bestMatch = null;
|
|
|
|
|
$highestSimilarity = 0;
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
foreach ($courses as $course) {
|
|
|
|
|
$similarity = $this->calculateSimilarity($courseName, $course->name);
|
|
|
|
|
if ($similarity > $highestSimilarity) {
|
|
|
|
|
$highestSimilarity = $similarity;
|
|
|
|
|
$bestMatch = $course;
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->error("创建calendar记录时发生错误: " . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 解析日期时间格式
|
|
|
|
|
*/
|
|
|
|
|
private function parseDateTime($dateTimeString)
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
// 处理Excel数字格式的日期时间
|
|
|
|
|
if (is_numeric($dateTimeString)) {
|
|
|
|
|
$excelDate = (float) $dateTimeString;
|
|
|
|
|
|
|
|
|
|
// Excel日期从1900年1月1日开始计算天数
|
|
|
|
|
// 需要减去2是因为Excel错误地认为1900年是闰年
|
|
|
|
|
$unixTimestamp = ($excelDate - 25569) * 86400;
|
|
|
|
|
|
|
|
|
|
$dateTime = new \DateTime();
|
|
|
|
|
$dateTime->setTimestamp($unixTimestamp);
|
|
|
|
|
|
|
|
|
|
$this->info("Excel数字日期转换: {$dateTimeString} -> " . $dateTime->format('Y-m-d H:i:s'));
|
|
|
|
|
return $dateTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 尝试多种日期时间格式
|
|
|
|
|
$formats = [
|
|
|
|
|
'Y-m-d H:i:s',
|
|
|
|
|
'Y-m-d H:i',
|
|
|
|
|
'Y/m/d H:i:s',
|
|
|
|
|
'Y/m/d H:i',
|
|
|
|
|
'Y-m-d',
|
|
|
|
|
'Y/m/d',
|
|
|
|
|
'd/m/Y H:i:s',
|
|
|
|
|
'd/m/Y H:i',
|
|
|
|
|
'd-m-Y H:i:s',
|
|
|
|
|
'd-m-Y H:i'
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
foreach ($formats as $format) {
|
|
|
|
|
$dateTime = \DateTime::createFromFormat($format, $dateTimeString);
|
|
|
|
|
if ($dateTime !== false) {
|
|
|
|
|
// 如果只有日期没有时间,设置默认时间
|
|
|
|
|
if (strpos($format, 'H:i') === false) {
|
|
|
|
|
$dateTime->setTime(9, 0, 0); // 默认上午9点
|
|
|
|
|
}
|
|
|
|
|
return $dateTime;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($bestMatch && $highestSimilarity > 0) {
|
|
|
|
|
$bestMatch->url = $titleUrl;
|
|
|
|
|
$bestMatch->url_title = $title;
|
|
|
|
|
$bestMatch->save();
|
|
|
|
|
$this->info("通过相似度匹配更新了课程 (相似度: " . round($highestSimilarity * 100, 2) . "%): '{$bestMatch->name}'");
|
|
|
|
|
return 1;
|
|
|
|
|
// 尝试使用strtotime
|
|
|
|
|
$timestamp = strtotime($dateTimeString);
|
|
|
|
|
if ($timestamp !== false) {
|
|
|
|
|
$dateTime = new \DateTime();
|
|
|
|
|
$dateTime->setTimestamp($timestamp);
|
|
|
|
|
return $dateTime;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
|
$this->error("更新courses表时发生错误: " . $e->getMessage());
|
|
|
|
|
$this->error("解析日期时间时发生错误: " . $e->getMessage());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|