|
@@ -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;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|