Upgrade2.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. <?php
  2. ini_set('max_execution_time', 0);
  3. ini_set('memory_limit', '500M');
  4. class Upgrade2
  5. {
  6. protected string $archiveFile = ""; //升级文件地址
  7. protected string $extractPath = ""; //解压目录地址
  8. protected string $root_path = ""; //程序根目录
  9. public string $update_download_url = ""; //升级zip文件下载地址
  10. public string $update_sql_url = ""; //升级sql脚本文件地址
  11. public string $update_script = ""; //升级后执行的脚本地址
  12. protected bool $isLog = false;
  13. protected array $noMove = [];
  14. //构造方法初始化一些数据
  15. function __construct($update_download_url = null, $update_sql_url = null, $update_script = null)
  16. {
  17. $this->archiveFile = runtime_path() . 'mtab.zip';
  18. $this->extractPath = runtime_path();
  19. $this->root_path = root_path();
  20. if ($update_download_url !== null) {
  21. $this->update_download_url = $update_download_url;
  22. }
  23. if ($update_sql_url !== null) {
  24. $this->update_sql_url = $update_sql_url;
  25. }
  26. if ($update_script !== null) {
  27. $this->update_script = $update_script;
  28. }
  29. }
  30. //运行入口
  31. function run($cli = false): bool
  32. {
  33. if ($cli) {
  34. $this->isLog = true;
  35. }
  36. return $this->startUpgrade();
  37. }
  38. public function log($msg)
  39. {
  40. if ($this->isLog) {
  41. print_r($msg . "\n");
  42. }
  43. }
  44. //新的进程启动升级
  45. private function startUpgrade(): bool
  46. {
  47. //如果有程序代码的更新资源则更新程序代码
  48. if (strlen($this->update_download_url) > 1) {
  49. //如果有遗留的解压资源则删除
  50. $this->log("正在检查是否有旧版本的安装包,并删除。");
  51. $this->deleteDirectory("{$this->extractPath}mtab");
  52. //如果存在旧的升级包则删除
  53. $this->delZip();
  54. //下载远程更新包
  55. $this->log("正在下载升级包...");
  56. if (!$this->fileDownload()) {
  57. $this->log('资源下载失败');
  58. abort(0, '资源下载失败');
  59. }
  60. //解压升级包
  61. $this->log("正在解压升级包...");
  62. if (!$this->unzip($this->archiveFile, $this->extractPath)) {
  63. $this->delZip();
  64. abort(0, '升级资源包解压失败');
  65. }
  66. $this->log("正在更新程序...");
  67. $this->deleteDirectory(public_path() . 'dist/'); //删除旧的网站文件
  68. $exclude = root_path() . 'exclude.txt';
  69. if (file_exists($exclude)) {
  70. try {
  71. $noMove = array_map('trim', file($exclude, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES));
  72. //逐行读取,并且去除前后多余空格
  73. foreach ($noMove as $item) {
  74. $this->noMove[] = joinPath(root_path(), $item);
  75. }
  76. } catch (\Throwable $th) {
  77. $this->log($th->getMessage());
  78. }
  79. }
  80. //拷贝覆盖
  81. $this->log("正在覆盖源文件");
  82. $this->copy();
  83. //删除下载的更新包
  84. $this->log("正在删除升级包...");
  85. $this->delZip();
  86. //更新完后的一些操作
  87. }
  88. //如果有数据库的更新资源则更新程序代码
  89. if (strlen($this->update_sql_url) > 1) {
  90. $this->log("正在更新数据库...");
  91. $this->updateSql();
  92. }
  93. if (file_exists("{$this->root_path}install.sql")) {
  94. $this->log("正在更新数据库...");
  95. $this->updateSql("{$this->root_path}install.sql");
  96. }
  97. if (!file_exists(root_path() . "exclude.txt")) {
  98. file_put_contents(root_path() . "exclude.txt", '#从程序根目录开始填写您的更新忽略的文件地址,每行一个,例如/exclude.txt');
  99. }
  100. return true;
  101. }
  102. private function fileDownload(): bool
  103. {
  104. $length = 0;
  105. try {
  106. $f = fopen($this->update_download_url, 'r');
  107. $w = fopen($this->archiveFile, 'wb+');
  108. $fileSize = $this->getFileSize($this->update_download_url);
  109. do {
  110. $a = fread($f, 1024 * 64);
  111. $length += strlen($a);
  112. fwrite($w, $a);
  113. // 计算下载进度
  114. $progress = ($fileSize > 0) ? round($length / $fileSize * 100, 2) : 0;
  115. // 打印进度条,在一行内更新
  116. if ($this->isLog) {
  117. if ($progress <= 100) {
  118. $this->printProgress((int)$progress);
  119. }
  120. }
  121. } while ($a);
  122. fclose($w);
  123. fclose($f);
  124. $this->log("\n下载完成");
  125. } catch (ErrorException $e) {
  126. return false;
  127. }
  128. return true;
  129. }
  130. private function printProgress(float $progress)
  131. {
  132. try {
  133. $barLength = 50; // 进度条的总长度
  134. $completed = round($progress / 100 * $barLength); // 完成的部分
  135. $bar = str_repeat('=', $completed) . str_repeat(' ', max($barLength - $completed, 0)); // 拼接进度条
  136. echo "\r[" . $bar . "] " . $progress . "%";
  137. } catch (Exception $e) {
  138. }
  139. }
  140. private function getFileSize(string $url): int
  141. {
  142. // 使用 HEAD 请求获取文件大小
  143. $headers = get_headers($url, 1);
  144. return isset($headers['Content-Length']) ? (int)$headers['Content-Length'] : 0;
  145. }
  146. //删除升级包
  147. function delZip()
  148. {
  149. if (file_exists($this->archiveFile)) {
  150. unlink($this->archiveFile);
  151. }
  152. }
  153. //解压
  154. private function unzip($archiveFile, $extractPath): bool
  155. {
  156. // 如果没有ZipArchive类则尝试使用shell_exec,调用自带的解压程序解压
  157. if (class_exists("ZipArchive")) {
  158. $zip = new ZipArchive();
  159. if ($zip->open($archiveFile) === TRUE) {
  160. $zip->extractTo($extractPath, null);
  161. $zip->close();
  162. } else {
  163. return false;
  164. }
  165. return true;
  166. }
  167. $cmd = root_path('extend') . "unzip -f {$archiveFile} -d {$extractPath}";
  168. if (function_exists("shell_exec")) {
  169. $status = shell_exec($cmd);
  170. if ($status === '解压成功!') {
  171. return true;
  172. }
  173. } else {
  174. $this->log("shell_exec函数被禁用,无法执行解压命令,即将尝试exec");
  175. }
  176. if (function_exists('exec')) {
  177. $status = exec($cmd);
  178. if ($status === '解压成功!') {
  179. return true;
  180. }
  181. } else {
  182. $this->log('exec函数被禁用,无法执行解压命令,请手动解压覆盖更新');
  183. }
  184. return false;
  185. }
  186. //升级的数据库
  187. function updateSql($path = null)
  188. {
  189. if ($path) {
  190. $f = fopen($path, 'r');
  191. } else {
  192. $f = fopen($this->update_sql_url, 'r');
  193. }
  194. $sql = "";
  195. do {
  196. $sqlTmp = fread($f, 1024);
  197. $sql = $sql . $sqlTmp;
  198. } while ($sqlTmp);
  199. fclose($f);
  200. // 解析SQL文件内容并执行
  201. $sql_statements = explode(';', trim($sql));
  202. foreach ($sql_statements as $sql_statement) {
  203. if (!empty($sql_statement)) {
  204. try {
  205. \think\facade\Db::execute($sql_statement);
  206. } catch (Exception $e) {
  207. }
  208. }
  209. }
  210. }
  211. //递归删除目录
  212. function deleteDirectory($dir)
  213. {
  214. if (!is_dir($dir)) {
  215. return;
  216. }
  217. $files = scandir($dir);
  218. foreach ($files as $file) {
  219. if ($file != '.' && $file != '..') {
  220. if (is_dir("$dir/$file")) {
  221. $this->deleteDirectory("$dir/$file");
  222. } else {
  223. unlink("$dir/$file");
  224. }
  225. }
  226. }
  227. rmdir($dir);
  228. }
  229. // 递归复制目录及其内容
  230. function copyDir($source, $dest)
  231. {
  232. if (!is_dir($dest)) {
  233. mkdir($dest, 0777, true);
  234. }
  235. $files = scandir($source);
  236. foreach ($files as $file) {
  237. if ($file !== '.' && $file !== '..') {
  238. $src = joinPath($source, '/' . $file);
  239. $dst = joinPath($dest, '/' . $file);
  240. // 检查目标路径是否在 noMove 中,并且是文件
  241. if (is_dir($src)) {
  242. $this->copyDir($src, $dst);
  243. } else {
  244. if (in_array($dst, $this->noMove)) {
  245. $this->log('跳过=>' . $dst);
  246. continue; // 跳过复制
  247. }
  248. copy($src, $dst);
  249. }
  250. }
  251. }
  252. }
  253. //覆盖原来的程序
  254. private function copy()
  255. {
  256. //移动覆盖
  257. $this->copyDir("{$this->extractPath}mtab/", "{$this->root_path}");
  258. //删除解压目录
  259. $this->deleteDirectory("{$this->extractPath}mtab");
  260. }
  261. }