作者 周海龙

合并分支 'zhl' 到 'master'

监控脚本



查看合并请求 !1866
  1 +<?php
  2 +/**
  3 + * Created by PhpStorm.
  4 + * User: zhl
  5 + * Date: 2025/4/15
  6 + * Time: 10:25
  7 + */
  8 +namespace App\Console\Commands\Monitor;
  9 +
  10 +use App\Models\Domain\DomainInfo;
  11 +use App\Models\Product\Keyword;
  12 +use App\Models\Project\OnlineCheck;
  13 +use App\Models\Project\Project;
  14 +use App\Repositories\ToolRepository;
  15 +use App\Services\DingService;
  16 +use App\Services\ProjectServer;
  17 +use Illuminate\Console\Command;
  18 +use Illuminate\Support\Facades\DB;
  19 +
  20 +/**
  21 + * Class Supervisory
  22 + * @package App\Console\Commands\Monitor
  23 + */
  24 +class Supervisory extends Command
  25 +{
  26 + /**
  27 + * The name and signature of the console command.
  28 + *
  29 + * @var string
  30 + */
  31 + protected $signature = 'monitor_supervisory';
  32 +
  33 + /**
  34 + * The console command description.
  35 + *
  36 + * @var string
  37 + */
  38 + protected $description = '监控脚本';
  39 +
  40 + /**
  41 + * Supervisory constructor.
  42 + */
  43 + public function __construct()
  44 + {
  45 + parent::__construct();
  46 + }
  47 +
  48 + /**
  49 + * @return bool
  50 + */
  51 + public function handle()
  52 + {
  53 + list($robots_ids, $close_ids) = $this->getRobotsProject();
  54 + $spot_projects = $this->getSpotCheck();
  55 + #TODO robots、 TDK、 top-search、 top-blog
  56 + list($error_num, $error, $error_url, $page_404, $tdk_error) = $this->spotCheckPage($spot_projects);
  57 + $this->sendMessage($robots_ids, $close_ids, $error_num, $error, $error_url, $page_404, $tdk_error, $spot_projects);
  58 + return true;
  59 + }
  60 +
  61 + /**
  62 + * 抽查数据
  63 + * @param $projects
  64 + * @return array
  65 + */
  66 + public function spotCheckPage($projects)
  67 + {
  68 + $error_num = 0;
  69 + $error = [];
  70 + $error_url = [];
  71 + $page_404 = [];
  72 + $tdk_error = [];
  73 + $tdk = [];
  74 + foreach ($projects as $project) {
  75 + $this->output('抽查项目:' . $project['project_id'] . ', 域名:' . $project['domain']);
  76 +
  77 + $host = 'https://' . $project['domain'] . '/';
  78 +
  79 + // AI blog页面
  80 + $blog_url = $host . 'top-blog/';
  81 + list($blog_code, $blog_html) = app(ToolRepository::class)->curlRequest($blog_url, [], $method = 'GET', [], 10);
  82 + if ($blog_code != 200) {
  83 + $error_num++;
  84 + array_push($error_url, $blog_url);
  85 + } else {
  86 + $tdk = $this->analysisHtml($blog_html);
  87 + if (FALSE == is_array($tdk)) {
  88 + $error_num++;
  89 + array_push($error, $blog_url);
  90 + } else if (empty($tdk['title'])) {
  91 + array_push($tdk_error, $blog_url);
  92 + } else if (FALSE !== strpos('404', $tdk['title'])){
  93 + array_push($page_404, $blog_url);
  94 + }
  95 + }
  96 +
  97 + // top search页面
  98 + $search_url = $host . 'top-search/';
  99 + list($search_code, $search_html) = app(ToolRepository::class)->curlRequest($search_url, [], $method = 'GET', [], 10);
  100 + if ($search_code != 200) {
  101 + $error_num++;
  102 + array_push($error_url, $search_url);
  103 + } else {
  104 + $tdk = $this->analysisHtml($search_html);
  105 + if (FALSE == is_array($tdk)) {
  106 + $error_num++;
  107 + array_push($error, $search_url);
  108 + } else if (empty($tdk['title'])) {
  109 + array_push($tdk_error, $search_url);
  110 + } else if (FALSE !== strpos('404', $tdk['title'])){
  111 + array_push($page_404, $search_url);
  112 + }
  113 + }
  114 +
  115 + // 关键词聚合页
  116 + foreach ($project['keyword'] as $item) {
  117 + $keyword_url = $host . $item['route'] . '/';
  118 + $this->output('抽查url:' . $keyword_url);
  119 + list($keyword_code, $keyword_html) = app(ToolRepository::class)->curlRequest($keyword_url, [], $method = 'GET', [], 10);
  120 + if ($keyword_code != 200) {
  121 + // 请求失败
  122 + $error_num++;
  123 + array_push($error_url, $keyword_url);
  124 + } else {
  125 + $tdk = $this->analysisHtml($keyword_html);
  126 + if (FALSE == is_array($tdk)) {
  127 + // 解析HTML失败
  128 + $error_num++;
  129 + array_push($error, $keyword_url);
  130 + } else if (empty($tdk['title'])) {
  131 + array_push($tdk_error, $keyword_url);
  132 + } else {
  133 + if (FALSE !== strpos('404', $tdk['title'])) {
  134 + // 404页面
  135 + array_push($page_404, $keyword_url);
  136 + } else {
  137 + // TDK验证
  138 + $tdk = array_filter(array_unique($tdk));
  139 + if (count($tdk) < 3)
  140 + array_push($tdk_error, $keyword_url);
  141 + }
  142 + }
  143 + }
  144 + }
  145 +
  146 + }
  147 + return [$error_num, $error, $error_url, $page_404, $tdk_error];
  148 + }
  149 +
  150 + /**
  151 + * 获取robots信息
  152 + * @return array
  153 + */
  154 + public function getRobotsProject()
  155 + {
  156 + $this->output('统计robots start');
  157 +
  158 + $ids = Project::where(['robots' => 1])->pluck('id')->toArray();
  159 + file_put_contents(storage_path('data/robots/' . date('Ymd'). '.json'), json_encode($ids, 256));
  160 + if (FALSE == is_file(storage_path('data/robots/' . date('Ymd', strtotime('-1 day')). '.json')))
  161 + return [$ids, []];
  162 +
  163 + $string = file_get_contents(storage_path('data/robots/' . date('Ymd', strtotime('-1 day')). '.json'));
  164 + $yesterday_robots_ids = json_decode($string, true) ?: [];
  165 + $close_ids = [];
  166 + foreach ($yesterday_robots_ids as $id) {
  167 + if (FALSE == in_array($id, $ids)) {
  168 + array_push($close_ids, $id);
  169 + }
  170 + }
  171 + return [$ids, $close_ids];
  172 + }
  173 +
  174 + /**
  175 + * 随机获取抽查项目
  176 + * @return array|int|string
  177 + */
  178 + public function getRandProject()
  179 + {
  180 + $this->output('随机获取项目抽查');
  181 +
  182 + $ids = Project::leftJoin('gl_project_deploy_optimize as b', 'gl_project.id', '=', 'b.project_id')
  183 + ->leftJoin('gl_project_online_check as c', 'gl_project.id', '=', 'c.project_id')
  184 + ->leftJoin('gl_domain_info as d', 'gl_project.id', '=', 'd.project_id')
  185 + ->where('gl_project.type', Project::TYPE_TWO)
  186 + ->where('gl_project.extend_type', 0) // 是否续费是由extend_type字段控制
  187 + ->where('gl_project.delete_status', Project::IS_DEL_FALSE)
  188 + ->where(function ($subQuery) {
  189 + $subQuery->orwhere('c.qa_status', OnlineCheck::STATUS_ONLINE_TRUE)->orwhere('gl_project.is_upgrade', Project::IS_UPGRADE_TRUE);
  190 + })
  191 + ->pluck('gl_project.id')
  192 + ->toArray();
  193 + $project_ids = array_rand($ids, 10);
  194 + return $project_ids;
  195 + }
  196 +
  197 + /**
  198 + * 获取抽查项目数据
  199 + * @return mixed
  200 + */
  201 + public function getSpotCheck()
  202 + {
  203 + $project_ids = $this->getRandProject();
  204 + $projects = DomainInfo::whereIn('project_id', $project_ids)->get(['project_id', 'domain'])->toArray();
  205 + foreach ($projects as &$project) {
  206 + ProjectServer::useProject($project['project_id']);
  207 + $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']);
  208 + DB::disconnect('custom_mysql');
  209 + if ($keyword->isEmpty()) {
  210 + $keyword = [];
  211 + } else {
  212 + $keyword = $keyword->toArray();
  213 + }
  214 + $project['keyword'] = $keyword;
  215 + }
  216 + return $projects;
  217 + }
  218 +
  219 + /**
  220 + * 获取页面TDK 分析请求数据
  221 + * @param $html
  222 + * @return array|string
  223 + */
  224 + public function analysisHtml($html)
  225 + {
  226 + $result = [];
  227 + if (empty($html))
  228 + return $result;
  229 + try {
  230 + $dom = new \DOMDocument();
  231 + @$dom->loadHTML($html);
  232 +
  233 + $title = $dom->getElementsByTagName('title');
  234 + $metas = $dom->getElementsByTagName('meta');
  235 + $result['title'] = $title->length > 0 ? trim($title->item(0)->nodeValue) : '';
  236 + foreach ($metas as $meta) {
  237 + $name = strtolower($meta->getAttribute('name'));
  238 + $content = $meta->getAttribute('content');
  239 +
  240 + if ($name === 'description') {
  241 + $result['description'] = trim($content);
  242 + } elseif ($name === 'keywords') {
  243 + $result['keywords'] = trim($content);
  244 + }
  245 + }
  246 + // 解析页面, 使用完成, 手动释放内存变量
  247 + unset($title);
  248 + unset($metas);
  249 + unset($dom);
  250 + return $result;
  251 + } catch (\Exception $e) {
  252 + return '解析HTML失败:' . $e->getMessage();
  253 + }
  254 + }
  255 +
  256 + public function sendMessage($robots_ids, $close_ids, $error_num, $error, $error_url, $page_404, $tdk_error, $spot_projects)
  257 + {
  258 + $tmp = compact('robots_ids', 'close_ids', 'error_num', 'error', 'error_url', 'page_404', 'tdk_error', 'spot_projects');
  259 + file_put_contents(storage_path('data/robots/' . date('Ymd'). 'log.json'), json_encode($tmp, 256));
  260 + unset($tmp);
  261 +
  262 + $domain = array_column($spot_projects, 'domain');
  263 + $domain = array_unique(array_filter($domain));
  264 +
  265 + $message[] = '开启robots项目数:' . count($robots_ids);
  266 + $message[] = '关闭robots项目:' . ($close_ids ? implode(',', $close_ids) : '无');
  267 + $message[] = '抽查项目数量: ' . count($domain);
  268 + $message[] = 'top-blog: ' . count($domain);
  269 + $message[] = 'top-search: ' . count($domain);
  270 + $message[] = '抽查错误次数:' . $error_num;
  271 + $message[] = '抽查项目域名: ' . implode(' 、 ', $domain);
  272 + $message[] = '请求失败链接: ' . implode(' 、 ', $error_url);
  273 + $message[] = '页面失败链接: ' . implode(' 、 ', $error);
  274 + $message[] = '404页面链接: ' . implode(' 、 ', $page_404);
  275 + $message[] = 'TDK错误链接: ' . implode(' 、 ', $tdk_error);
  276 +
  277 + $msg = implode(PHP_EOL, $message);
  278 +
  279 + $link = 'https://oapi.dingtalk.com/robot/send?access_token=3927b42d072972fcf572e7b01728bf3e1390e08094d6f77c5f28bfd85b19f09f';
  280 + $dingService = new DingService();
  281 + $body = [
  282 + 'keyword' => '项目数据推送',
  283 + 'msg' => $msg,
  284 + 'isAtAll' => false, // 是否@所有人
  285 + ];
  286 + $dingService->handle($body, $link);
  287 + }
  288 +
  289 + public function output($message)
  290 + {
  291 + echo date('Y-m-d H:i:s') . ' ' . $message . PHP_EOL;
  292 + }
  293 +}
@@ -23,14 +23,14 @@ class DingService @@ -23,14 +23,14 @@ class DingService
23 23
24 24
25 /** 25 /**
26 - * @remark :钉钉发送错误信息  
27 - * @name :handle  
28 - * @author :lyh  
29 - * @method :post  
30 - * @time :2025/3/19 18:03 26 + * 钉钉发送错误信息
  27 + * @param array $body
  28 + * @param string $link
  29 + * @return int
31 */ 30 */
32 - public function handle(array $body) 31 + public function handle(array $body, $link = '')
33 { 32 {
  33 + $link = $link ?: self::LINK;
34 $msgKey = mb_substr($body['msg'], 50); 34 $msgKey = mb_substr($body['msg'], 50);
35 if (!$this->getData(RedisKey::DING_MSG . $msgKey)) { 35 if (!$this->getData(RedisKey::DING_MSG . $msgKey)) {
36 $arr = [ 36 $arr = [
@@ -44,7 +44,7 @@ class DingService @@ -44,7 +44,7 @@ class DingService
44 'isAtAll' => $body['isAtAll'], 44 'isAtAll' => $body['isAtAll'],
45 ] 45 ]
46 ]; 46 ];
47 - $re = json_decode(HttpUtils::post(self::LINK, $arr), true); 47 + $re = json_decode(HttpUtils::post($link, $arr), true);
48 $this->setData(RedisKey::DING_MSG . $msgKey, true, 60); 48 $this->setData(RedisKey::DING_MSG . $msgKey, true, 60);
49 return $re['errcode'] ?? 0; 49 return $re['errcode'] ?? 0;
50 } 50 }