合并分支 'zhl' 到 'master'
监控脚本 查看合并请求 !1866
正在显示
2 个修改的文件
包含
300 行增加
和
7 行删除
app/Console/Commands/Monitor/Supervisory.php
0 → 100644
| 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 | } |
-
请 注册 或 登录 后发表评论