Browse Source

feat: 新增SystemStatusJob

runphp 1 month ago
parent
commit
22d87e464a
2 changed files with 181 additions and 0 deletions
  1. 8 0
      src/Extension.php
  2. 173 0
      src/Job/SystemStatusJob.php

+ 8 - 0
src/Extension.php

@@ -12,6 +12,7 @@ use SixShop\System\Hook\DebugHook;
 use SixShop\System\Hook\ExtensionStatusHook;
 use SixShop\System\Hook\GatheringCrontabEventHook;
 use SixShop\System\Hook\QueueJobHook;
+use SixShop\System\Job\SystemStatusJob;
 use think\db\exception\PDOException;
 
 use function SixShop\Core\extension_name_list;
@@ -74,6 +75,13 @@ class Extension extends ExtensionAbstract
         return true;
     }
 
+    public function getCronJobs(): array
+    {
+        return [
+            SystemStatusJob::class,
+        ];
+    }
+
     protected function getBaseDir(): string
     {
         return dirname(__DIR__);

+ 173 - 0
src/Job/SystemStatusJob.php

@@ -0,0 +1,173 @@
+<?php
+
+declare(strict_types=1);
+
+namespace SixShop\System\Job;
+
+use Composer\InstalledVersions;
+use SixShop\Core\Attribute\Cron;
+use SixShop\Core\Job\BaseJob;
+use SixShop\Core\Service\CoreService;
+use SixShop\System\Entity\CronJobLogEntity;
+use SixShop\System\Entity\QueueJobLogEntity;
+use SixShop\System\ExtensionManager;
+use Symfony\Component\HttpClient\HttpClient;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+use think\Cache;
+use think\Collection;
+use think\Env;
+use think\Event;
+use think\facade\App;
+use think\facade\Log;
+
+class SystemStatusJob extends BaseJob
+{
+    public const string INDEX_JOB_KEY = 'index_job';
+
+    public const string PACKAGIST_URL = 'https://packagist.jd29.com';
+
+    private HttpClientInterface $httpClient;
+
+    private array $packages = [];
+
+    public function __construct(
+        private readonly Cache $cache,
+        private readonly ExtensionManager $extensionManager,
+        private readonly Event $event,
+        private readonly Env   $env,
+        private readonly CronJobLogEntity $cronJobLogEntity,
+        private readonly QueueJobLogEntity $queueJobLogEntity,
+    ) {
+        $this->httpClient = HttpClient::create([
+            'base_uri' => self::PACKAGIST_URL,
+            'timeout' => 5,
+            'max_connect_duration' => 5,
+            'max_duration' => 15,
+        ]);
+    }
+
+    public function getData(): array
+    {
+        $this->dispatch();
+        return $this->cache->get(self::INDEX_JOB_KEY, []);
+    }
+
+    #[Cron('* * * * *', 'system.status_job')]
+    public function execute(): void
+    {
+        try {
+            $this->fetchPackages();
+        } catch (\Throwable $e) {
+            Log::warning($e->getMessage());
+
+            return;
+        }
+        $availableVersions = $this->getSixShopExtensionAvailableVersions('six-shop/core');
+        if ($availableVersions === null) {
+            Log::error('无法获取 six-shop/core 的最新版本信息');
+
+            return;
+        }
+        $currentVersion = InstalledVersions::getPrettyVersion('six-shop/core');
+        $data = [
+            'app_name' => $this->env->get('APP_NAME'),
+            'php_version' => PHP_VERSION,
+            'thinkphp_version' => App::version(),
+            'php_extension' => get_loaded_extensions(),
+            'six_shop_extension' => [],
+            'version' => $currentVersion,
+            'available_versions' => $availableVersions,
+            'update_available' => $this->hasUpdateAvailable($availableVersions, $currentVersion),
+        ];
+        foreach (CoreService::getExtensionComposerMap() as $extensionID => $extensionInfo) {
+            $availableVersions = $this->getSixShopExtensionAvailableVersions($extensionInfo['name']);
+            if ($availableVersions === null) {
+                continue;
+            }
+            $currentVersion = InstalledVersions::getPrettyVersion($extensionInfo['name']);
+            $available = isset($extensionInfo['extra']['sixshop']['id']) ? $this->extensionManager->getExtension($extensionID)->available() : true;
+            $data['six_shop_extension'][] = [
+                'id' => $extensionID,
+                'name' => $extensionInfo['name'],
+                'version' => $currentVersion,
+                'available' => $available,
+                'available_versions' => $availableVersions,
+                'update_available' => $this->hasUpdateAvailable($availableVersions, $currentVersion),
+            ];
+        }
+        $data['crontab_list'] = $this->cronJobLogEntity->getLatestPerJob();
+        $data['queue_job_list'] = $this->queueJobLogEntity->getLatestPerJob();
+        $this->runtimeLog('deploy', $data);
+        $this->runtimeLog('admin_deploy', $data);
+        // 扩展状态数据
+        $collection = Collection::make();
+        $this->event->trigger('extension_runtime_status', $collection);
+        $collection->each(function ($item) {
+            $item[] = ['label' => '最后上报时间', 'name' => 'last_report_time', 'value' => date('Y-m-d H:i:s')];
+
+            return $item;
+        });
+        $data['extension_runtime_status'] = $collection->toArray();
+        // 应用状态数据收集完毕
+        $this->event->trigger('post_app_status', $data);
+        $this->cache->set(self::INDEX_JOB_KEY, $data);
+    }
+
+    private function runtimeLog(string $key, array &$data): void
+    {
+        if (file_exists(root_path('runtime') . $key . '.log')) {
+            $data[$key] = trim(file_get_contents(root_path('runtime') . $key . '.log'));
+        }
+    }
+
+    private function fetchPackages(): void
+    {
+        $packages = $this->fetchJson('/packages.json');
+        $this->packages = $this->cache->remember('extension_packages:' . md5(serialize($packages)), function () use ($packages) {
+            foreach ($packages['available-packages'] as $package) {
+                if (!str_starts_with($package, 'six-shop/') && !str_starts_with($package, 'sixdec/')) {
+                    continue;
+                }
+                $packageData = $this->fetchJson(sprintf('/p2/%s.json', $package))['packages'][$package];
+                $versions = array_map(fn ($v) => [
+                    'version' => $v['version'],
+                    'source' => $v['source'],
+                ], $packageData);
+                usort($versions, fn ($a, $b) => version_compare($b['version'], $a['version']));
+                $this->packages[$package] = $versions;
+            }
+
+            return $this->packages;
+        }, 3600);
+    }
+
+    private function fetchJson(string $url): array
+    {
+        $response = $this->httpClient->request('GET', $url);
+        if ($response->getStatusCode() !== 200) {
+            throw new \RuntimeException('Failed to fetch JSON: ' . $response->getStatusCode() . ' ' . $response->getContent());
+        }
+
+        return $response->toArray();
+    }
+
+    private function getSixShopExtensionAvailableVersions(string $name): ?array
+    {
+        if (!isset($this->packages[$name])) {
+            return null;
+        }
+
+        return $this->packages[$name];
+    }
+
+    private function hasUpdateAvailable(array $availableVersions, string $currentVersion): bool
+    {
+        foreach ($availableVersions as $version) {
+            if (version_compare($version['version'], $currentVersion, '>')) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+}