setName('zm') ->setDescription('zm command'); } protected function execute(Input $input, Output $output) { // 输出结果 // $output->writeln("Hello, {$name}!"); // $output->writeln("Option: {$option}"); $this->aria2 = new Aria2('http://127.0.0.1:16800/jsonrpc','11223344'); //实例化aria2对象 $this->aria2->setOption('enable-async-dns', true); //设置异步dns解析 $this->aria2->setOption('enable-http-pipelining', true); //设置http管道化 $this->aria2->setOption('enable-http-keep-alive', true); //设置http保持连接 $this->completed = false; while($this->completed === false){ try { $this->start(); } catch (\Exception $e) { halt($e); sleep(3); } } dump('下载完成!'); } protected function start() { $savepath = 'E:/zm'; $lable = $this->getLable(); if($lable['code'] == 0){ $leftLabel = $lable['data']['leftLabel']; foreach ($leftLabel[0]['children'] as $key => $course) { $lable_title = $course['title']; $lable_id = $course['id']; $next = true; $curr_page = 1; $curr_total = 0; while($next){ $curr = $this->getCurr($lable_id,$curr_page); $currs = $curr['data']['curriculum']; foreach ($currs as $key => $value) { $curr_total++; $curr_item = $value; $curr_item_title = $curr_item['title']; $c = $savepath . '/' . $lable_title . '/' . $curr_item_title; // dump('存储路径',$c); is_dir($c) || mkdir($c,0755,true); $author = $savepath . '/' . $lable_title . '/' . $curr_item_title . '/' . '作者.txt'; $this->savetxt($author,$curr_item['author']); $introduce = $savepath . '/' . $lable_title . '/' . $curr_item_title . '/' . '介绍.txt'; $this->savetxt($introduce,$curr_item['introduce']); $cover = $curr_item['cover']; $this->download($cover,$c,basename($cover)); $cover_name = basename($cover); $cover_path = "{$c}/{$cover_name}-下载地址.txt"; $this->savetxt($cover_path,$cover); $curr_details = $this->getCurrDetails($curr_item['id']); foreach($curr_details['data']['curriculum']['allClassSectionTrue'] as $class){ $class_path = $c . '/' . $class['title']; is_dir($class_path) || mkdir($class_path,0755,true); $class_details = $this->getClassDetails($class['id']); if($class_details == false){ dump('课程未授权,跳过下载',$class['title']); break; } dump($class_path,$class_details); $class_id = $class_details['data']['classSection']['id']; if(!empty($class_details['data']['classSection']['video'])){ $file_name = $this->getFileNameFromUrl($class_details['data']['classSection']['video']); $downloadurl = $class_path . '/' . $file_name . '-下载地址.txt'; $this->savetxt($downloadurl,$class_details['data']['classSection']['video']); $this->download($class_details['data']['classSection']['video'],$class_path,$file_name); } if(!empty($class_details['data']['classSection']['courseware'])){ $file_name = $this->getFileNameFromUrl($class_details['data']['classSection']['courseware']); $downloadurl = $class_path . '/' . $file_name . '-下载地址.txt'; $this->savetxt($downloadurl,$class_details['data']['classSection']['courseware']); $this->download($class_details['data']['classSection']['courseware'],$class_path,$file_name); } if(!empty($class_details['data']['classSection']['construction_draw'])){ $file_name = $this->getFileNameFromUrl($class_details['data']['classSection']['construction_draw']); $downloadurl = $class_path . '/' . $file_name . '-下载地址.txt'; $this->savetxt($downloadurl,$class_details['data']['classSection']['construction_draw']); $this->download($class_details['data']['classSection']['construction_draw'],$class_path,$file_name); } if(!empty($class_details['data']['classSection']['construction_draw_3d'])){ $file_name = $this->getFileNameFromUrl($class_details['data']['classSection']['construction_draw_3d']); $downloadurl = $class_path . '/' . $file_name . '-下载地址.txt'; $this->savetxt($downloadurl,urldecode($class_details['data']['classSection']['construction_draw_3d'])); $this->download($class_details['data']['classSection']['construction_draw_3d'],$class_path,$file_name); } foreach($class_details['data']['classSection']['allEnclosure'] as $enclosure){ $parts = explode("{$class_id}/", $enclosure['url']); $path = parse_url($enclosure['url'], PHP_URL_PATH); $parts = explode('/', $path); $storage = $class_path . '/' . urldecode($parts[count($parts)-2]); is_dir($storage) || mkdir($storage,0755,true); $downloadurl = $storage . '/' . urldecode(end($parts)) . '-下载地址.txt'; $this->savetxt($downloadurl,urldecode($enclosure['url'])); $this->download($enclosure['url'],$storage,basename($enclosure['url'])); } } } $curr_page++; if($curr_total >= $curr['data']['total']){ $next = false; break; } } // exit(); } dump($leftLabel); }else{ dump($lable['msg']); exit(); } exit(); } private function checkfile($url,$file) { dump($url,$file); if(is_file($file)){ $size = filesize($file); if($size > 0){ return true; }else{ dump('文件大小为0,重新下载'.$file); unlink($file); return 2; } } $key = 'zm_check_header_' . md5($url); try { if(($head = Cache::get($key,null)) === NULL ){ $head = []; $client = new Client(['timeout' => 5.0,]); try { // 发送 HEAD 请求,并添加自定义请求头 $response = $client->head($url, [ 'headers' => [ 'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400', 'referer'=>'https://edu.zmrobo.com/courses', ], ]); // 读取响应状态码 $statusCode = $response->getStatusCode(); if($statusCode != 200){ dump('文件head请求失败,状态码:'.$statusCode); return 0; } // 获取全部响应头(返回关联数组) $headers = $response->getHeaders(); foreach ($headers as $name => $values) { $head[$name] = implode(', ', $values); } } catch (\GuzzleHttp\Exception\RequestException $e) { dump("文件head请求失败: " . $e->getMessage()); return 0; } Cache::set($key,$head,86400); } $kk = 'zm_file_' . pathinfo($file,PATHINFO_BASENAME); if(isset($head['Content-Length']) && ($size = intval($head['Content-Length']))){ $kk .= $size; if(is_file($file) && ($size !== filesize($file))){ dump('文件大小不符,重新下载'.$file); unlink($file); return 2; } } if(isset($head['ETag'])){ $t = $kk . md5($head['ETag']); if(Cache::has($t)){ return Cache::get($t); } } if(isset($head['Content-MD5'])){ $t = $kk . md5($head['Content-MD5']); if(Cache::has($t)){ return Cache::get($t); } } } catch (Exception $e) { dump('检查文件失败:',$url,$file,$e); exit; } return 3; } protected function download($downloadurl,$dir,$filename) { return; $result = $this->checkfile($downloadurl,$dir . '/' . $filename); if($result === true){ dump('文件已存在,跳过下载:' . $filename); return; } $num = 1; while($num > 0){ $activeDownloads = $this->aria2->getGlobalStat(); $num = intval($activeDownloads['result']['numWaiting']); if($num > 50){ dump('当前任务过多,等待任务数:',$num); sleep(1); }else{ break; } } $result = $this->aria2->addUri( [$downloadurl], ['dir'=>$dir, 'header'=>[ 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400', 'referer: https://edu.zmrobo.com/courses', ], 'out'=>$filename] ); } public function getLable($sign = 'null') { $client = new Client(); $result = $client->get("https://edu.zmrobo.com/api/label",[ 'headers'=>[ 'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400', 'referer'=>'https://edu.zmrobo.com/courses', 'E-Date'=>time(), 'Sign'=>$sign, ] ]); $result = json_decode($result->getBody()->getContents(),true); if($result['code'] == -3){ return $this->getLable($result['sign']); }else{ return $result; } } public function getCurr($label_id,$page,$sign = 'null') { $client = new Client(); $options = [ 'headers'=>[ 'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400', 'referer'=>'https://edu.zmrobo.com/courses', 'E-Date'=>time(), 'Content-Type'=>'application/json; charset=utf-8', 'Origin'=>'https://edu.zmrobo.com', 'Sign'=>$sign, ], 'body'=>json_encode([ 'label'=>[[$label_id]], 'page'=>$page, ]) ]; $result = $client->post("https://edu.zmrobo.com/api/curriculum/list",$options); $result = json_decode($result->getBody()->getContents(),true); if($result['code'] == -3){ return $this->getCurr($label_id,$page,$result['sign']); }else if($result['code'] == 0){ return $result; }else{ dump($result['msg']); exit(); } } public function getCurrDetails($curr_id,$sign = 'null') { $client = new Client(); $options = [ 'headers'=>[ 'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400', 'referer'=>'https://edu.zmrobo.com/courses', 'E-Date'=>time(), 'Content-Type'=>'application/json; charset=utf-8', 'Origin'=>'https://edu.zmrobo.com', 'Sign'=>$sign, ], 'body'=>json_encode([ 'id'=>$curr_id, 'user_id'=>$this->user_id, ]) ]; $result = $client->post("https://edu.zmrobo.com/api/curriculum/details",$options); $result = json_decode($result->getBody()->getContents(),true); if($result['code'] == -3){ return $this->getCurrDetails($curr_id,$result['sign']); }else if($result['code'] == 0){ return $result; }else{ dump($result['msg']); exit(); } } public function getClassDetails($curr_id,$sign = 'null') { $client = new Client(); $options = [ 'headers'=>[ 'User-Agent'=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.5845.97 Safari/537.36 Core/1.116.489.400 QQBrowser/13.7.6351.400', 'referer'=>'https://edu.zmrobo.com/courses', 'E-Date'=>time(), 'Content-Type'=>'application/json', 'Origin'=>'https://edu.zmrobo.com', 'Sign'=>$sign, ], 'body'=>json_encode([ 'id'=>$curr_id, 'user_id'=>$this->user_id, 'token'=>"7aa22a83fece93ed3e5a3119b085ced9", ]) ]; $result = $client->post("https://edu.zmrobo.com/api/class/details",$options); $result = json_decode($result->getBody()->getContents(),true); if($result['code'] == -3){ return $this->getClassDetails($curr_id,$result['sign']); }else if($result['code'] == 0){ return $result; }else if($result['code'] == -5){ return false; }else{ dump($result['msg']); exit(); } } private function savetxt(string $file,string $content) { if(is_file($file)){ unlink($file); } $myfile = fopen($file, "w"); fwrite($myfile, $content); fclose($myfile); // dump('已保存文本内容:'.$file); } /** * 从URL中提取并解码文件名 * @param string $url 包含文件名的URL * @param bool $throwExceptions 是否抛出异常 (默认true) * @return string|null 成功返回文件名,失败返回null(仅当$throwExceptions=false时) * @throws \InvalidArgumentException */ function getFileNameFromUrl(string $url, bool $throwExceptions = true): ?string { // 解析URL结构 $parsed = parse_url($url); // 错误检测逻辑 if ($parsed === false || !isset($parsed['path'])) { $errorMsg = "无效的URL格式: " . json_encode($url); if ($throwExceptions) { throw new \InvalidArgumentException($errorMsg); } trigger_error($errorMsg, E_USER_WARNING); return null; } // 提取并解码文件名 return urldecode(basename($parsed['path'])); } }