|
|
<?php
|
|
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
|
|
use App\Models\Course;
|
|
|
use App\Models\Calendar;
|
|
|
use Illuminate\Console\Command;
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
class LinkCoursesToCalendar extends Command
|
|
|
{
|
|
|
/**
|
|
|
* The name and signature of the console command.
|
|
|
*
|
|
|
* @var string
|
|
|
*/
|
|
|
protected $signature = 'link:courses-to-calendar';
|
|
|
|
|
|
/**
|
|
|
* The console command description.
|
|
|
*
|
|
|
* @var string
|
|
|
*/
|
|
|
protected $description = '将指定的课程列表关联到calendars日历表';
|
|
|
|
|
|
/**
|
|
|
* 课程列表
|
|
|
*
|
|
|
* @var array
|
|
|
*/
|
|
|
protected $courseList = [
|
|
|
'第三期:张平院士— 6G通信与AI融合',
|
|
|
'高研班|第四期高级科创人才研修班-第七模块',
|
|
|
'高研班|第五期高级科创人才研修班-第五模块',
|
|
|
'校友返校日|AI+产业融合创新论坛',
|
|
|
'高研班|第六期高级科创人才研修班-第三模块',
|
|
|
'第二课堂|走进珂玛科技',
|
|
|
'人才培训|省科技厅高级技术经理人专班-开学模块',
|
|
|
'初创班|首期技术经理人领航班-第一模块',
|
|
|
'专题培训|苏州市科技企业资本运作公开课',
|
|
|
'初创班|首期高校科技成果转化班-第一模块',
|
|
|
'高研班|第五期高级科创人才研修班-毕业模块',
|
|
|
'高研班|第四期高级科创人才研修班-第八模块',
|
|
|
'第二课堂|走进世华科技',
|
|
|
'初创班|首期高校科技成果转化班-第二模块',
|
|
|
'高研班|第六期高级科创人才研修班-第四模块',
|
|
|
'人才培训|省科技厅高级技术经理人专班-实践模块',
|
|
|
'初创班|首期技术经理人领航班-第二模块',
|
|
|
'攀峰班|首期苏州科技企业资本运作研修班-开学模块',
|
|
|
'初创班|首期技术经理人领航班-结业模块',
|
|
|
'高研班|第七期高级科创人才研修班-开学模块',
|
|
|
'产业加速营|具身智能极客营-开学模块',
|
|
|
'产业加速营|苏州市人工智能潜在独角兽训练营-开学模块',
|
|
|
'初创班|首期高校科技成果转化班-结业模块',
|
|
|
'第二课堂|走进姑苏区',
|
|
|
'高研班|第六期高级科创人才研修班-第五模块',
|
|
|
'产业加速营|苏州市人工智能潜在独角兽训练营-第二模块',
|
|
|
'夏令营|2025年度小科学家夏令营',
|
|
|
'攀峰班|首期苏州科技企业资本运作研修班-第二模块',
|
|
|
'产业加速营|苏州市人工智能潜在独角兽训练营-第三模块',
|
|
|
'人才培训|江苏青年科技人才"U35青创学院"培训',
|
|
|
'高研班|第七期高级科创人才研修班-第二模块',
|
|
|
'第二课堂|走进科沃斯',
|
|
|
'高研班|第四期高级科创人才研修班-开学模块',
|
|
|
'高研班|第三期高级科创人才研修班-结业模块',
|
|
|
'第二课堂|走进永鼎',
|
|
|
'第二课堂|走进旭创',
|
|
|
'高研班|第五期高级科创人才研修班-开学模块',
|
|
|
'高研班|第四期高级科创人才研修班-第二模块',
|
|
|
'第二课堂|走进亨通',
|
|
|
'第二课堂|走进企查查',
|
|
|
'科技大讲堂|第一期: 凯文凯利、丁文江院士领衔',
|
|
|
'高研班|第四期高级科创人才研修班-第三模块',
|
|
|
'第二课堂|走进华为苏研所',
|
|
|
'人才培训|2024年姑苏领军人才培育营',
|
|
|
'专题培训|苏州市标杆孵化器培训',
|
|
|
'高研班|第五期高级科创人才研修班-第一模块',
|
|
|
'第二课堂|走进亚盛医药',
|
|
|
'第二课堂|新加坡海外游学',
|
|
|
'第二课堂|走进苏州市市场监督管理局',
|
|
|
'第二课堂|走进信达生物',
|
|
|
'人才培训|江苏省高层次人才专题培训',
|
|
|
'夏令营|2024年度小科学家夏令营',
|
|
|
'高研班|第五期高级科创人才研修班-第二模块',
|
|
|
'高研班|第四期高级科创人才研修班-第四模块',
|
|
|
'专题培训|太仓市国资审计高质量发展专题培训',
|
|
|
'第二课堂|走进天准科技',
|
|
|
'高研班|第五期高级科创人才研修班-第三模块',
|
|
|
'科技金融沙龙|上海交通大学新能源沙龙',
|
|
|
'科技大讲堂|第二期:伊雷娜·克罗宁解析空间计算',
|
|
|
'人才培训|苏州市科技企业孵化器沙龙',
|
|
|
'高研班|第四期高级科创人才研修班-第五模块',
|
|
|
'人才培训|苏州乡镇党委书记专题研修班',
|
|
|
'科技金融沙龙|资本市场新机遇研讨沙龙',
|
|
|
'产业加速营|人工智能产业加速营',
|
|
|
'高研班|第六期高级科创人才研修班-开学模块',
|
|
|
'第二课堂|走进苏州市低空经济发展展示馆',
|
|
|
'高研班|第四期高级科创人才研修班-第六模块',
|
|
|
'人才培训|2024姑苏领军人才创业营',
|
|
|
'产业加速营|集成电路产业专班',
|
|
|
'专题培训|关税与出海:应对2025美国新政',
|
|
|
'高研班|第五期高级科创人才研修班-第四模块',
|
|
|
'高研班|第六期高级科创人才研修班-第二模块',
|
|
|
];
|
|
|
|
|
|
/**
|
|
|
* Create a new command instance.
|
|
|
*
|
|
|
* @return void
|
|
|
*/
|
|
|
public function __construct()
|
|
|
{
|
|
|
parent::__construct();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* Execute the console command.
|
|
|
*
|
|
|
* @return mixed
|
|
|
*/
|
|
|
public function handle()
|
|
|
{
|
|
|
$this->info("开始将课程关联到calendars日历表...");
|
|
|
$this->info("总共需要处理 " . count($this->courseList) . " 个课程");
|
|
|
|
|
|
$linkedCount = 0;
|
|
|
$notFoundCourses = [];
|
|
|
$alreadyLinkedCourses = [];
|
|
|
|
|
|
DB::beginTransaction();
|
|
|
|
|
|
try {
|
|
|
foreach ($this->courseList as $courseName) {
|
|
|
$this->info("正在处理课程: {$courseName}");
|
|
|
|
|
|
// 查找匹配的课程
|
|
|
$course = $this->findCourse($courseName);
|
|
|
|
|
|
if (!$course) {
|
|
|
$this->warn("✗ 未找到匹配的课程: {$courseName}");
|
|
|
$notFoundCourses[] = $courseName;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
$this->info("✓ 找到匹配课程: {$course->name} (ID: {$course->id})");
|
|
|
|
|
|
// 检查是否已经存在日历记录
|
|
|
$existingCalendar = Calendar::where('course_id', $course->id)
|
|
|
->where('type', 1) // 类型1为课程
|
|
|
->first();
|
|
|
|
|
|
if ($existingCalendar) {
|
|
|
$this->warn("⚠ 课程已存在日历记录: {$course->name}");
|
|
|
$alreadyLinkedCourses[] = $course->name;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// 创建日历记录
|
|
|
$calendarData = $this->createCalendarData($course);
|
|
|
$calendar = Calendar::create($calendarData);
|
|
|
|
|
|
$this->info("✓ 成功创建日历记录 (ID: {$calendar->id}) 关联课程: {$course->name}");
|
|
|
$linkedCount++;
|
|
|
}
|
|
|
|
|
|
DB::commit();
|
|
|
|
|
|
$this->info("\n" . str_repeat('=', 60));
|
|
|
$this->info("处理完成!");
|
|
|
$this->info("成功关联课程数量: {$linkedCount}");
|
|
|
$this->info("已存在日历记录: " . count($alreadyLinkedCourses));
|
|
|
$this->info("未找到匹配课程: " . count($notFoundCourses));
|
|
|
|
|
|
// 显示未找到的课程
|
|
|
if (!empty($notFoundCourses)) {
|
|
|
$this->warn("\n未找到匹配的课程列表:");
|
|
|
foreach ($notFoundCourses as $course) {
|
|
|
$this->warn(" - {$course}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 显示已存在日历记录的课程
|
|
|
if (!empty($alreadyLinkedCourses)) {
|
|
|
$this->warn("\n已存在日历记录的课程列表:");
|
|
|
foreach ($alreadyLinkedCourses as $course) {
|
|
|
$this->warn(" - {$course}");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
DB::rollback();
|
|
|
$this->error("处理过程中发生错误: " . $e->getMessage());
|
|
|
$this->error("已回滚所有更改");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
$this->info("\n所有操作已完成!");
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 查找匹配的课程
|
|
|
*/
|
|
|
private function findCourse($courseName)
|
|
|
{
|
|
|
// 1. 精确匹配
|
|
|
$course = Course::where('name', $courseName)
|
|
|
->whereNull('deleted_at')
|
|
|
->first();
|
|
|
|
|
|
if ($course) {
|
|
|
return $course;
|
|
|
}
|
|
|
|
|
|
// 2. 模糊匹配
|
|
|
$course = Course::where('name', 'like', "%{$courseName}%")
|
|
|
->whereNull('deleted_at')
|
|
|
->first();
|
|
|
|
|
|
if ($course) {
|
|
|
$this->info("通过模糊匹配找到课程: '{$course->name}'");
|
|
|
return $course;
|
|
|
}
|
|
|
|
|
|
// 3. 相似度匹配
|
|
|
$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.3) { // 设置最低相似度阈值
|
|
|
$this->info("通过相似度匹配找到课程 (相似度: " . round($highestSimilarity * 100, 2) . "%): '{$bestMatch->name}'");
|
|
|
return $bestMatch;
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 创建日历数据
|
|
|
*/
|
|
|
private function createCalendarData($course)
|
|
|
{
|
|
|
return [
|
|
|
'type' => 1, // 类型1为课程
|
|
|
'course_id' => $course->id,
|
|
|
'date' => $course->start_date ?? now()->format('Y-m-d'),
|
|
|
'title' => $course->name,
|
|
|
'content' => $course->content ?? '',
|
|
|
'start_time' => $course->start_date ? $course->start_date . ' 09:00:00' : null,
|
|
|
'end_time' => $course->end_date ? $course->end_date . ' 17:00:00' : null,
|
|
|
'url' => $course->url ?? '',
|
|
|
'created_at' => now(),
|
|
|
'updated_at' => now(),
|
|
|
];
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 计算字符串相似度
|
|
|
*/
|
|
|
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);
|
|
|
}
|
|
|
}
|