UpgradeController.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. <?php
  2. /**
  3. * @copyright (C)2016-2099 Hnaoyun Inc.
  4. * @author XingMeng
  5. * @email hnxsh@foxmail.com
  6. * @date 2018年8月14日
  7. * 在线更新
  8. */
  9. namespace app\admin\controller\system;
  10. use core\basic\Controller;
  11. use core\basic\Model;
  12. class UpgradeController extends Controller
  13. {
  14. // 服务器地址
  15. private $server = 'https://www.pbootcms.com';
  16. // 更新分支
  17. private $branch;
  18. // 强制同步文件
  19. private $force;
  20. // 修改版本
  21. private $revise;
  22. // 文件列表
  23. public $files = array();
  24. public function __construct()
  25. {
  26. error_reporting(0);
  27. $this->branch = $this->config('upgrade_branch') == '3.X.dev' ? '3.X.dev' : '3.X';
  28. $this->force = $this->config('upgrade_force') ?: 0;
  29. $this->revise = $this->config('revise_version') ?: 0;
  30. }
  31. public function index()
  32. {
  33. switch (get('action')) {
  34. case 'local':
  35. $upfile = $this->local();
  36. break;
  37. default:
  38. $upfile = array();
  39. }
  40. $this->assign('upfile', $upfile);
  41. $this->assign('branch', $this->branch);
  42. $this->assign('force', $this->force);
  43. $this->assign('revise', $this->revise);
  44. $this->assign('snuser', $this->config('sn_user') ?: 0);
  45. $this->assign('site', get_http_url());
  46. $this->display('system/upgrade.html');
  47. }
  48. // 检查更新
  49. public function check()
  50. {
  51. // 清理目录,检查下载目录及备份目录
  52. path_delete(RUN_PATH . '/upgrade', true);
  53. if (! check_dir(RUN_PATH . '/upgrade', true)) {
  54. json(0, '目录写入权限不足,无法正常升级!' . RUN_PATH . '/upgrade');
  55. }
  56. check_dir(DOC_PATH . STATIC_DIR . '/backup/upgrade', true);
  57. $files = $this->getServerList();
  58. $db = get_db_type();
  59. foreach ($files as $key => $value) {
  60. // 过滤掉相对路径
  61. $value->path = preg_replace_r('{\.\.(\/|\\\\)}', '', $value->path);
  62. $file = ROOT_PATH . $value->path;
  63. if (@md5_file($file) != $value->md5) {
  64. // 筛选数据库更新脚本
  65. if (preg_match('/([\w]+)-([\w\.]+)-update\.sql/i', $file, $matches)) {
  66. if ($matches[1] != $db || ! $this->compareVersion($matches[2], APP_VERSION . '.' . RELEASE_TIME . '.' . $this->revise)) {
  67. continue;
  68. }
  69. }
  70. if (file_exists($file)) {
  71. $files[$key]->type = '<span style="color:Red">覆盖</span>';
  72. $files[$key]->ltime = date('Y-m-d H:i:s', filemtime($file));
  73. } else {
  74. $files[$key]->type = '新增';
  75. $files[$key]->ltime = '无';
  76. }
  77. $files[$key]->ctime = date('Y-m-d H:i:s', $files[$key]->ctime);
  78. $upfile[] = $files[$key];
  79. }
  80. }
  81. if (! $upfile) {
  82. json(1, '您的系统无任何文件需要更新!');
  83. } else {
  84. json(1, $upfile);
  85. }
  86. }
  87. // 执行下载
  88. public function down()
  89. {
  90. if (! ! $list = get('list')) {
  91. if (! is_array($list)) { // 单个文件转换为数组
  92. $list = array(
  93. $list
  94. );
  95. }
  96. $len = count($list) ?: 0;
  97. foreach ($list as $value) {
  98. // 过滤掉相对路径
  99. $value = preg_replace_r('{\.\.(\/|\\\\)}', '', $value);
  100. // 本地存储路径
  101. $path = RUN_PATH . '/upgrade' . $value;
  102. // 自动创建目录
  103. if (! check_dir(dirname($path), true)) {
  104. json(0, '目录写入权限不足,无法下载升级文件!' . dirname($path));
  105. }
  106. // 定义执行下载的类型
  107. $types = '.zip|.rar|.doc|.docx|.ppt|.pptx|.xls|.xlsx|.chm|.ttf|.otf|';
  108. $pathinfo = explode(".", basename($path));
  109. $ext = end($pathinfo); // 获取扩展
  110. if (preg_match('/\.' . $ext . '\|/i', $types)) {
  111. $result = $this->getServerDown('/release/' . $this->branch . $value, $path);
  112. } else {
  113. $result = $this->getServerFile($value, $path);
  114. }
  115. }
  116. if ($len == 1) {
  117. json(1, "更新文件 " . basename($value) . " 下载成功!");
  118. } else {
  119. json(1, "更新文件" . basename($value) . "等文件全部下载成功!");
  120. }
  121. } else {
  122. json(0, '请选择要下载的文件!');
  123. }
  124. }
  125. // 执行更新
  126. public function update()
  127. {
  128. if ($_POST) {
  129. if (! ! $list = post('list')) {
  130. $list = explode(',', $list);
  131. $backdir = date('YmdHis');
  132. // 分离文件
  133. foreach ($list as $value) {
  134. // 过滤掉相对路径
  135. $value = preg_replace_r('{\.\.(\/|\\\\)}', '', $value);
  136. if (stripos($value, '/script/') === 0 && preg_match('/\.sql$/i', $value)) {
  137. $sqls[] = $value;
  138. } else {
  139. $path = RUN_PATH . '/upgrade' . $value;
  140. $des_path = ROOT_PATH . $value;
  141. $back_path = DOC_PATH . STATIC_DIR . '/backup/upgrade/' . $backdir . $value;
  142. if (! check_dir(dirname($des_path), true)) {
  143. json(0, '目录写入权限不足,无法正常升级!' . dirname($des_path));
  144. }
  145. if (file_exists($des_path)) { // 文件存在时执行备份
  146. check_dir(dirname($back_path), true);
  147. copy($des_path, $back_path);
  148. }
  149. // 如果后台入口文件修改过名字,则自动适配
  150. if (stripos($path, 'admin.php') !== false && stripos($_SERVER['SCRIPT_FILENAME'], 'admin.php') === false) {
  151. if (file_exists($_SERVER['SCRIPT_FILENAME'])) {
  152. $des_path = $_SERVER['SCRIPT_FILENAME'];
  153. }
  154. }
  155. $files[] = array(
  156. 'sfile' => $path,
  157. 'dfile' => $des_path
  158. );
  159. }
  160. }
  161. // 更新数据库
  162. if (isset($sqls)) {
  163. $db = new DatabaseController();
  164. switch (get_db_type()) {
  165. case 'sqlite':
  166. copy(DOC_PATH . $this->config('database.dbname'), DOC_PATH . STATIC_DIR . '/backup/sql/' . date('YmdHis') . '_' . basename($this->config('database.dbname')));
  167. break;
  168. case 'mysql':
  169. $db->backupDB();
  170. break;
  171. }
  172. sort($sqls); // 排序
  173. foreach ($sqls as $value) {
  174. $path = RUN_PATH . '/upgrade' . $value;
  175. if (file_exists($path)) {
  176. $sql = file_get_contents($path);
  177. if (! $this->upsql($sql)) {
  178. $this->log("数据库 $value 更新失败!");
  179. json(0, "数据库" . basename($value) . " 更新失败!");
  180. }
  181. } else {
  182. json(0, "数据库文件" . basename($value) . "不存在!");
  183. }
  184. }
  185. }
  186. // 替换文件
  187. if (isset($files)) {
  188. foreach ($files as $value) {
  189. if (! copy($value['sfile'], $value['dfile'])) {
  190. $this->log("文件 " . $value['dfile'] . " 更新失败!");
  191. json(0, "文件 " . basename($value['dfile']) . " 更新失败,请重试!");
  192. }
  193. }
  194. }
  195. // 清理缓存
  196. path_delete(RUN_PATH . '/upgrade', true);
  197. path_delete(RUN_PATH . '/cache');
  198. path_delete(RUN_PATH . '/complite');
  199. path_delete(RUN_PATH . '/config');
  200. $this->log("系统更新成功!");
  201. json(1, '系统更新成功!');
  202. } else {
  203. json(0, '请选择要更新的文件!');
  204. }
  205. }
  206. }
  207. // 缓存文件
  208. private function local()
  209. {
  210. $files = $this->getLoaclList(RUN_PATH . '/upgrade');
  211. $files = json_decode(json_encode($files));
  212. foreach ($files as $key => $value) {
  213. $file = ROOT_PATH . $value->path;
  214. if (file_exists($file)) {
  215. $files[$key]->type = '<span style="color:Red">覆盖</span>';
  216. $files[$key]->ltime = date('Y-m-d H:i:s', filemtime($file));
  217. } else {
  218. $files[$key]->type = '新增';
  219. $files[$key]->ltime = '无';
  220. }
  221. $files[$key]->ctime = date('Y-m-d H:i:s', $files[$key]->ctime);
  222. $upfile[] = $files[$key];
  223. }
  224. return $upfile;
  225. }
  226. // 执行更新数据库
  227. private function upsql($sql)
  228. {
  229. $sql = explode(';', $sql);
  230. $model = new Model();
  231. foreach ($sql as $value) {
  232. $value = trim($value);
  233. if ($value) {
  234. $model->amd($value);
  235. }
  236. }
  237. return true;
  238. }
  239. // 获取列表
  240. private function getServerList()
  241. {
  242. $param = array(
  243. 'version' => APP_VERSION . '.' . RELEASE_TIME . '.' . $this->revise,
  244. 'branch' => $this->branch,
  245. 'force' => $this->force,
  246. 'site' => get_http_url(),
  247. 'snuser' => $this->config('sn_user')
  248. );
  249. $url = $this->server . '/index.php?p=/upgrade/getlist&' . http_build_query($param);
  250. if (! ! $rs = json_decode(get_url($url, '', '', true))) {
  251. if ($rs->code) {
  252. if (is_array($rs->data)) {
  253. return $rs->data;
  254. } else {
  255. json(1, $rs->data);
  256. }
  257. } else {
  258. json(0, $rs->data);
  259. }
  260. } else {
  261. $this->log('连接更新服务器发生错误,请稍后再试!');
  262. json(0, '连接更新服务器发生错误,请稍后再试!');
  263. }
  264. }
  265. // 获取文件
  266. private function getServerFile($source, $des)
  267. {
  268. $url = $this->server . '/index.php?p=/upgrade/getFile&branch=' . $this->branch;
  269. $data['path'] = $source;
  270. $file = basename($source);
  271. if (! ! $rs = json_decode(get_url($url, $data, '', true))) {
  272. if ($rs->code) {
  273. if (! file_put_contents($des, base64_decode($rs->data))) {
  274. $this->log("更新文件 " . $file . " 下载失败!");
  275. json(0, "更新文件 " . $file . " 下载失败!");
  276. } else {
  277. return true;
  278. }
  279. } else {
  280. json(0, $rs->data);
  281. }
  282. } else {
  283. $this->log("更新文件 " . $file . " 获取失败!");
  284. json(0, "更新文件 " . $file . " 获取失败!");
  285. }
  286. }
  287. // 获取非文本文件
  288. private function getServerDown($source, $des)
  289. {
  290. $url = $this->server . $source;
  291. $file = basename($source);
  292. if (($sfile = fopen($url, "rb")) && ($dfile = fopen($des, "wb"))) {
  293. while (! feof($sfile)) {
  294. $fwrite = fwrite($dfile, fread($sfile, 1024 * 8), 1024 * 8);
  295. if ($fwrite === false) {
  296. $this->log("更新文件 " . $file . " 下载失败!");
  297. json(0, "更新文件 " . $file . " 下载失败!");
  298. }
  299. }
  300. if ($sfile) {
  301. fclose($sfile);
  302. }
  303. if ($dfile) {
  304. fclose($dfile);
  305. }
  306. return true;
  307. } else {
  308. $this->log("更新文件 " . $file . " 获取失败!");
  309. json(0, "更新文件 " . $file . " 获取失败!");
  310. }
  311. }
  312. // 获取文件列表
  313. private function getLoaclList($path)
  314. {
  315. $files = scandir($path);
  316. foreach ($files as $value) {
  317. if ($value != '.' && $value != '..') {
  318. if (is_dir($path . '/' . $value)) {
  319. $this->getLoaclList($path . '/' . $value);
  320. } else {
  321. $file = $path . '/' . $value;
  322. // 避免中文乱码
  323. if (! mb_check_encoding($file, 'utf-8')) {
  324. $out_path = mb_convert_encoding($file, 'UTF-8', 'GBK');
  325. } else {
  326. $out_path = $file;
  327. }
  328. $out_path = str_replace(RUN_PATH . '/upgrade', '', $out_path);
  329. $this->files[] = array(
  330. 'path' => $out_path,
  331. 'md5' => md5_file($file),
  332. 'ctime' => filemtime($file)
  333. );
  334. }
  335. }
  336. }
  337. return $this->files;
  338. }
  339. // 比较程序本号
  340. private function compareVersion($sv, $cv)
  341. {
  342. if (empty($sv) || $sv == $cv) {
  343. return 0;
  344. }
  345. $sv = explode('.', $sv);
  346. $cv = explode('.', $cv);
  347. $len = count($sv) > count($cv) ? count($sv) : count($cv);
  348. for ($i = 0; $i < $len; $i ++) {
  349. $n1 = $sv[$i] or 0;
  350. $n2 = $cv[$i] or 0;
  351. if ($n1 > $n2) {
  352. return 1;
  353. } elseif ($n1 < $n2) {
  354. return 0;
  355. }
  356. }
  357. return 0;
  358. }
  359. }