Supervisory.php 11.6 KB
<?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\DeployOptimize;
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_deploy_optimize.domain','!=',0)
            ->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));

        // 通知对应优化师
        $tmp = [];
        foreach ($spot_projects as $item) {
            $tmp[$item['domain']] = $item['project_id'];
        }
        $project_ids = array_column($spot_projects, 'project_id');
        $optimist = DeployOptimize::leftJoin('gl_manage', 'gl_project_deploy_optimize.optimist_mid', '=', 'gl_manage.id')
            ->whereIn('project_id', $project_ids)
            ->pluck('mobile', 'project_id')
            ->toArray();

        $notice = [];
        $all_url = array_merge($error_url, $error, $page_404, $tdk_error);
        foreach ($all_url as $url) {
            if (FALSE == empty($optimist[$tmp[parse_url($url, PHP_URL_HOST)]]))
                $notice[] = '86' . $optimist[$tmp[parse_url($url, PHP_URL_HOST)]];
        }
        $notice = array_filter(array_unique($notice));

        $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, // 是否@所有人
            'atMobiles' => $notice
        ];
        $dingService->handle($body, $link);
    }

    public function output($message)
    {
        echo date('Y-m-d H:i:s') . ' ' . $message . PHP_EOL;
    }
}