|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* Created by PhpStorm.
|
|
|
|
* User: zhl
|
|
|
|
* Date: 2025/4/15
|
|
|
|
* Time: 10:25
|
|
|
|
*/
|
|
|
|
namespace App\Console\Commands\Monitor;
|
|
|
|
|
|
|
|
use App\Models\Domain\DomainInfo;
|
|
|
|
use App\Models\Product\Keyword;
|
|
|
|
use App\Models\Project\OnlineCheck;
|
|
|
|
use App\Models\Project\Project;
|
|
|
|
use App\Repositories\ToolRepository;
|
|
|
|
use App\Services\DingService;
|
|
|
|
use App\Services\ProjectServer;
|
|
|
|
use Illuminate\Console\Command;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class Supervisory
|
|
|
|
* @package App\Console\Commands\Monitor
|
|
|
|
*/
|
|
|
|
class Supervisory extends Command
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* The name and signature of the console command.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $signature = 'monitor_supervisory';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The console command description.
|
|
|
|
*
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $description = '监控脚本';
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Supervisory constructor.
|
|
|
|
*/
|
|
|
|
public function __construct()
|
|
|
|
{
|
|
|
|
parent::__construct();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public function handle()
|
|
|
|
{
|
|
|
|
list($robots_ids, $close_ids) = $this->getRobotsProject();
|
|
|
|
$spot_projects = $this->getSpotCheck();
|
|
|
|
#TODO robots、 TDK、 top-search、 top-blog
|
|
|
|
list($error_num, $error, $error_url, $page_404, $tdk_error) = $this->spotCheckPage($spot_projects);
|
|
|
|
$this->sendMessage($robots_ids, $close_ids, $error_num, $error, $error_url, $page_404, $tdk_error, $spot_projects);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 抽查数据
|
|
|
|
* @param $projects
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function spotCheckPage($projects)
|
|
|
|
{
|
|
|
|
$error_num = 0;
|
|
|
|
$error = [];
|
|
|
|
$error_url = [];
|
|
|
|
$page_404 = [];
|
|
|
|
$tdk_error = [];
|
|
|
|
$tdk = [];
|
|
|
|
foreach ($projects as $project) {
|
|
|
|
$this->output('抽查项目:' . $project['project_id'] . ', 域名:' . $project['domain']);
|
|
|
|
|
|
|
|
$host = 'https://' . $project['domain'] . '/';
|
|
|
|
|
|
|
|
// AI blog页面
|
|
|
|
$blog_url = $host . 'top-blog/';
|
|
|
|
list($blog_code, $blog_html) = app(ToolRepository::class)->curlRequest($blog_url, [], $method = 'GET', [], 10);
|
|
|
|
if ($blog_code != 200) {
|
|
|
|
$error_num++;
|
|
|
|
array_push($error_url, $blog_url);
|
|
|
|
} else {
|
|
|
|
$tdk = $this->analysisHtml($blog_html);
|
|
|
|
if (FALSE == is_array($tdk)) {
|
|
|
|
$error_num++;
|
|
|
|
array_push($error, $blog_url);
|
|
|
|
} else if (empty($tdk['title'])) {
|
|
|
|
array_push($tdk_error, $blog_url);
|
|
|
|
} else if (FALSE !== strpos('404', $tdk['title'])){
|
|
|
|
array_push($page_404, $blog_url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// top search页面
|
|
|
|
$search_url = $host . 'top-search/';
|
|
|
|
list($search_code, $search_html) = app(ToolRepository::class)->curlRequest($search_url, [], $method = 'GET', [], 10);
|
|
|
|
if ($search_code != 200) {
|
|
|
|
$error_num++;
|
|
|
|
array_push($error_url, $search_url);
|
|
|
|
} else {
|
|
|
|
$tdk = $this->analysisHtml($search_html);
|
|
|
|
if (FALSE == is_array($tdk)) {
|
|
|
|
$error_num++;
|
|
|
|
array_push($error, $search_url);
|
|
|
|
} else if (empty($tdk['title'])) {
|
|
|
|
array_push($tdk_error, $search_url);
|
|
|
|
} else if (FALSE !== strpos('404', $tdk['title'])){
|
|
|
|
array_push($page_404, $search_url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 关键词聚合页
|
|
|
|
foreach ($project['keyword'] as $item) {
|
|
|
|
$keyword_url = $host . $item['route'] . '/';
|
|
|
|
$this->output('抽查url:' . $keyword_url);
|
|
|
|
list($keyword_code, $keyword_html) = app(ToolRepository::class)->curlRequest($keyword_url, [], $method = 'GET', [], 10);
|
|
|
|
if ($keyword_code != 200) {
|
|
|
|
// 请求失败
|
|
|
|
$error_num++;
|
|
|
|
array_push($error_url, $keyword_url);
|
|
|
|
} else {
|
|
|
|
$tdk = $this->analysisHtml($keyword_html);
|
|
|
|
if (FALSE == is_array($tdk)) {
|
|
|
|
// 解析HTML失败
|
|
|
|
$error_num++;
|
|
|
|
array_push($error, $keyword_url);
|
|
|
|
} else if (empty($tdk['title'])) {
|
|
|
|
array_push($tdk_error, $keyword_url);
|
|
|
|
} else {
|
|
|
|
if (FALSE !== strpos('404', $tdk['title'])) {
|
|
|
|
// 404页面
|
|
|
|
array_push($page_404, $keyword_url);
|
|
|
|
} else {
|
|
|
|
// TDK验证
|
|
|
|
$tdk = array_filter(array_unique($tdk));
|
|
|
|
if (count($tdk) < 3)
|
|
|
|
array_push($tdk_error, $keyword_url);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return [$error_num, $error, $error_url, $page_404, $tdk_error];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 获取robots信息
|
|
|
|
* @return array
|
|
|
|
*/
|
|
|
|
public function getRobotsProject()
|
|
|
|
{
|
|
|
|
$this->output('统计robots start');
|
|
|
|
|
|
|
|
$ids = Project::where(['robots' => 1])->pluck('id')->toArray();
|
|
|
|
file_put_contents(storage_path('data/robots/' . date('Ymd'). '.json'), json_encode($ids, 256));
|
|
|
|
if (FALSE == is_file(storage_path('data/robots/' . date('Ymd', strtotime('-1 day')). '.json')))
|
|
|
|
return [$ids, []];
|
|
|
|
|
|
|
|
$string = file_get_contents(storage_path('data/robots/' . date('Ymd', strtotime('-1 day')). '.json'));
|
|
|
|
$yesterday_robots_ids = json_decode($string, true) ?: [];
|
|
|
|
$close_ids = [];
|
|
|
|
foreach ($yesterday_robots_ids as $id) {
|
|
|
|
if (FALSE == in_array($id, $ids)) {
|
|
|
|
array_push($close_ids, $id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return [$ids, $close_ids];
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 随机获取抽查项目
|
|
|
|
* @return array|int|string
|
|
|
|
*/
|
|
|
|
public function getRandProject()
|
|
|
|
{
|
|
|
|
$this->output('随机获取项目抽查');
|
|
|
|
|
|
|
|
$ids = Project::leftJoin('gl_project_deploy_optimize as b', 'gl_project.id', '=', 'b.project_id')
|
|
|
|
->leftJoin('gl_project_online_check as c', 'gl_project.id', '=', 'c.project_id')
|
|
|
|
->leftJoin('gl_domain_info as d', 'gl_project.id', '=', 'd.project_id')
|
|
|
|
->where('gl_project.type', Project::TYPE_TWO)
|
|
|
|
->where('gl_project.extend_type', 0) // 是否续费是由extend_type字段控制
|
|
|
|
->where('gl_project.delete_status', Project::IS_DEL_FALSE)
|
|
|
|
->where(function ($subQuery) {
|
|
|
|
$subQuery->orwhere('c.qa_status', OnlineCheck::STATUS_ONLINE_TRUE)->orwhere('gl_project.is_upgrade', Project::IS_UPGRADE_TRUE);
|
|
|
|
})
|
|
|
|
->pluck('gl_project.id')
|
|
|
|
->toArray();
|
|
|
|
$project_ids = array_rand($ids, 10);
|
|
|
|
return $project_ids;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 获取抽查项目数据
|
|
|
|
* @return mixed
|
|
|
|
*/
|
|
|
|
public function getSpotCheck()
|
|
|
|
{
|
|
|
|
$project_ids = $this->getRandProject();
|
|
|
|
$projects = DomainInfo::whereIn('project_id', $project_ids)->get(['project_id', 'domain'])->toArray();
|
|
|
|
foreach ($projects as &$project) {
|
|
|
|
ProjectServer::useProject($project['project_id']);
|
|
|
|
$keyword = Keyword::where(['project_id' => $project['project_id'], 'status' => Keyword::STATUS_ACTIVE])->inRandomOrder()->take(10)->get(['id', 'title', 'seo_title', 'seo_keywords', 'seo_description', 'route']);
|
|
|
|
DB::disconnect('custom_mysql');
|
|
|
|
if ($keyword->isEmpty()) {
|
|
|
|
$keyword = [];
|
|
|
|
} else {
|
|
|
|
$keyword = $keyword->toArray();
|
|
|
|
}
|
|
|
|
$project['keyword'] = $keyword;
|
|
|
|
}
|
|
|
|
return $projects;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 获取页面TDK 分析请求数据
|
|
|
|
* @param $html
|
|
|
|
* @return array|string
|
|
|
|
*/
|
|
|
|
public function analysisHtml($html)
|
|
|
|
{
|
|
|
|
$result = [];
|
|
|
|
if (empty($html))
|
|
|
|
return $result;
|
|
|
|
try {
|
|
|
|
$dom = new \DOMDocument();
|
|
|
|
@$dom->loadHTML($html);
|
|
|
|
|
|
|
|
$title = $dom->getElementsByTagName('title');
|
|
|
|
$metas = $dom->getElementsByTagName('meta');
|
|
|
|
$result['title'] = $title->length > 0 ? trim($title->item(0)->nodeValue) : '';
|
|
|
|
foreach ($metas as $meta) {
|
|
|
|
$name = strtolower($meta->getAttribute('name'));
|
|
|
|
$content = $meta->getAttribute('content');
|
|
|
|
|
|
|
|
if ($name === 'description') {
|
|
|
|
$result['description'] = trim($content);
|
|
|
|
} elseif ($name === 'keywords') {
|
|
|
|
$result['keywords'] = trim($content);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 解析页面, 使用完成, 手动释放内存变量
|
|
|
|
unset($title);
|
|
|
|
unset($metas);
|
|
|
|
unset($dom);
|
|
|
|
return $result;
|
|
|
|
} catch (\Exception $e) {
|
|
|
|
return '解析HTML失败:' . $e->getMessage();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function sendMessage($robots_ids, $close_ids, $error_num, $error, $error_url, $page_404, $tdk_error, $spot_projects)
|
|
|
|
{
|
|
|
|
$tmp = compact('robots_ids', 'close_ids', 'error_num', 'error', 'error_url', 'page_404', 'tdk_error', 'spot_projects');
|
|
|
|
file_put_contents(storage_path('data/robots/' . date('Ymd'). 'log.json'), json_encode($tmp, 256));
|
|
|
|
unset($tmp);
|
|
|
|
|
|
|
|
$domain = array_column($spot_projects, 'domain');
|
|
|
|
$domain = array_unique(array_filter($domain));
|
|
|
|
|
|
|
|
$message[] = '开启robots项目数:' . count($robots_ids);
|
|
|
|
$message[] = '关闭robots项目:' . ($close_ids ? implode(',', $close_ids) : '无');
|
|
|
|
$message[] = '抽查项目数量: ' . count($domain);
|
|
|
|
$message[] = 'top-blog: ' . count($domain);
|
|
|
|
$message[] = 'top-search: ' . count($domain);
|
|
|
|
$message[] = '抽查错误次数:' . $error_num;
|
|
|
|
$message[] = '抽查项目域名: ' . implode(' 、 ', $domain);
|
|
|
|
$message[] = '请求失败链接: ' . implode(' 、 ', $error_url);
|
|
|
|
$message[] = '页面失败链接: ' . implode(' 、 ', $error);
|
|
|
|
$message[] = '404页面链接: ' . implode(' 、 ', $page_404);
|
|
|
|
$message[] = 'TDK错误链接: ' . implode(' 、 ', $tdk_error);
|
|
|
|
|
|
|
|
$msg = implode(PHP_EOL, $message);
|
|
|
|
|
|
|
|
$link = 'https://oapi.dingtalk.com/robot/send?access_token=3927b42d072972fcf572e7b01728bf3e1390e08094d6f77c5f28bfd85b19f09f';
|
|
|
|
$dingService = new DingService();
|
|
|
|
$body = [
|
|
|
|
'keyword' => '项目数据推送',
|
|
|
|
'msg' => $msg,
|
|
|
|
'isAtAll' => false, // 是否@所有人
|
|
|
|
];
|
|
|
|
$dingService->handle($body, $link);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function output($message)
|
|
|
|
{
|
|
|
|
echo date('Y-m-d H:i:s') . ' ' . $message . PHP_EOL;
|
|
|
|
}
|
|
|
|
} |
|
|
\ No newline at end of file |
...
|
...
|
|