-
-
Notifications
You must be signed in to change notification settings - Fork 613
/
UpgradeService.php
163 lines (144 loc) · 5.08 KB
/
UpgradeService.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
<?php
namespace App\Services;
use App\Enums\ConfigKey;
use App\Models\Config;
use App\Utils;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use League\Flysystem\Filesystem;
use League\Flysystem\FilesystemException;
use League\Flysystem\Local\LocalFilesystemAdapter;
class UpgradeService
{
const ApiUrl = 'https://api.lsky.pro/v1';
/** @var array|array[] 所有版本 */
protected array $versions = [];
/** @var string 临时目录 */
protected string $temp = 'storage/temp';
/** @var string 升级锁文件 */
protected string $lock = 'upgrading.lock';
protected Filesystem $filesystem;
protected PendingRequest $http;
public function __construct(protected string $version)
{
$this->http = Http::baseUrl(self::ApiUrl)->withOptions(['timeout' => 30])->timeout(30);
$this->filesystem = new Filesystem(new LocalFilesystemAdapter(base_path()));
}
/**
* 是否需要更新
*
* @return bool
* @throws \Exception
*/
public function check(): bool
{
return version_compare($this->version, $this->getVersions()->first()['name']) === -1;
}
/**
* @throws \Exception
*/
public function getVersions(): Collection
{
if (! $this->versions) {
$response = $this->http->timeout(30)->get('/versions');
if (! $response->successful()) {
throw new \Exception('无法请求升级服务器');
}
$this->versions = $response->json() ?: [];
foreach ($this->versions as &$version) {
$version['changelog'] = (new \Parsedown())->text($version['changelog']);
}
}
return collect($this->versions);
}
public function upgrade(): bool
{
try {
if (file_exists(base_path($this->lock))) {
return false;
}
if (! $this->check()) {
return false;
}
file_put_contents(base_path($this->lock), '');
$this->setProgress('准备升级...');
@ini_set('memory_limit', '1G');
// 获取差异信息
$response = $this->http->timeout(30)->get('/diff/'.urlencode(Utils::config(ConfigKey::AppVersion)));
if (! $response->successful()) {
throw new \Exception('无法请求升级服务器');
}
$files = $response->json();
$this->setProgress('下载补丁包...');
foreach ($files as $file) {
if ($file['action'] === 'deleted') continue;
$this->filesystem->write($this->temp.'/'.$file['pathname'], base64_decode($file['content']));
// 校验文件
if ($file['md5'] !== md5_file(base_path($this->temp).'/'.$file['pathname'])) {
throw new \Exception("补丁文件 {$file['pathname']} 校验失败。");
}
}
$this->setProgress('执行升级...');
foreach ($files as $file) {
match ($file['action']) {
'added', 'copied' => $this->filesystem->copy($this->temp.'/'.$file['pathname'], $file['pathname']),
'deleted' => $this->filesystem->delete($file['pathname']),
};
}
$this->setProgress('清除缓存...');
// 更新版本号
$version = $this->getVersions()->first()['name'];
Config::query()->where('name', ConfigKey::AppVersion)->update(['value' => $version]);
// 执行数据库迁移
Artisan::call('migrate');
// 清除配置缓存
Cache::forget('configs');
// 清除缓存
Artisan::call('route:clear');
Artisan::call('cache:clear');
Artisan::call('view:clear');
Artisan::call('package:discover');
} catch (\Throwable $e) {
Utils::e($e, '升级失败');
$this->setProgress($e->getMessage(), 'fail');
@unlink(base_path($this->lock));
return false;
}
$this->setProgress('升级成功,请刷新页面', 'success', 6);
$this->gc();
return true;
}
/**
* 设置升级进度
*
* @param string $message
* @param string $status in installing、success、fail
* @param int $ttl
* @return void
*/
protected function setProgress(string $message, string $status = 'installing', int $ttl = 1800)
{
Cache::put('upgrade_progress', compact('status', 'message'), $ttl);
}
/**
* 获取升级进度
*
* @return void
*/
public function getProgress()
{
Cache::get('upgrade_progress');
}
protected function gc()
{
try {
$this->filesystem->delete($this->lock);
$this->filesystem->deleteDirectory($this->temp);
} catch (FilesystemException $e) {
}
}
}