Update.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. <?php
  2. namespace app\admin\controller;
  3. use app\admin\controller\Base;
  4. use think\Request;
  5. use think\facade\Session;
  6. use think\Db;
  7. use think\facade\Env;
  8. use think\File;//文件操作类
  9. use think\facade\Config;//获取配置类
  10. use my\Backup;
  11. class Update extends Base
  12. {
  13. //删除更新版本session
  14. public function delSession()
  15. {
  16. //删除更新标志位
  17. Session::delete('update');
  18. }
  19. //系统更新版本检测
  20. public function get_version()
  21. {
  22. //当前系统的版本 这个在实际应用中应该是虫数据库获取得到的
  23. $sys_version_num = Db::name('system')->where('id',1)->field('version')->find();
  24. if(empty($sys_version_num['version']) || $sys_version_num['version'] == NULL){
  25. $sys_version_num['version'] = Config::get('app.version');
  26. }
  27. //更新日志内容接口
  28. $update_log = 'http://api.muyucms.com/version/update.log';
  29. //获取更新日志内容
  30. $content = $this->get_url_content($update_log);
  31. //字符串转数组
  32. $arr = explode(',',$content);
  33. //截取版本号
  34. $version = array_shift($arr);
  35. //判断是否获取版本号
  36. if(!$version){
  37. return ["status"=>0,"msg"=>"获取版本号失败!"];
  38. }
  39. //$arr截取掉版本号,剩余的为本次更新日志
  40. $log = $arr;
  41. //版本对比
  42. if((float)$version > $sys_version_num['version'])
  43. {
  44. //更新标志位
  45. Session::set('update',$version);
  46. //返回版本号及更新日志
  47. return ['status'=>1,'version'=>$version,'update_log' => $log];
  48. }else{
  49. //删除更新标志位
  50. Session::delete('update');
  51. return ["status"=>0,"msg"=>"当前已是最新版本!"];
  52. }
  53. }
  54. //更新入口
  55. public function entrance()
  56. {
  57. //当前系统的版本
  58. $sys_version = Db::name('system')->where('id',1)->field('version')->find();
  59. if(empty($sys_version['version']) || $sys_version['version'] == NULL){
  60. $sys_version['version'] = Config::get('app.version');
  61. }
  62. //写入日志文件
  63. $this->write_log("读取系统版本信息!");
  64. //更新日志内容接口
  65. $update_log = 'http://api.muyucms.com/version/update.log';
  66. //获取更新日志内容
  67. $content = $this->get_url_content($update_log);
  68. //字符串转数组
  69. $arr = explode(',',$content);
  70. //截取版本号
  71. $version = array_shift($arr);
  72. //$version==$sys_version['version'] 返回 0 | $version<$sys_version['version'] 返回 -1 | $version>$sys_version['version'] 返回 1
  73. if(bccomp((float)$version - $sys_version['version'],0.1,2) == 1){
  74. //累计更新
  75. for($i = $sys_version['version'] + 0.1 ; $i < (float)$version + 0.1; )
  76. {
  77. //更新日志内容接口
  78. $update_log = "http://api.muyucms.com/version/$i/$i.log";
  79. //获取更新日志内容
  80. $content = $this->get_url_content($update_log);
  81. //字符串转数组
  82. $arr = explode(',',$content);
  83. //截取更新包状态,true表示是要更新,false为否
  84. $zip_status =array_shift($arr);
  85. $zip_status =explode(':',$zip_status);
  86. $zip_status = $zip_status[1];
  87. //截取数据库状态,true表示是要更新,false为否
  88. $sql_status =explode(':',$arr[0]);
  89. $sql_status = $sql_status[1];
  90. //写入日志文件
  91. $this->write_log("进行第".$i."版本更新!");
  92. //执行更新
  93. $res = $this->execute_update($i,(bool)$zip_status,(bool)$sql_status);
  94. if(!$res){
  95. //写入日志文件
  96. $this->write_log("第".$i."版本更新失败!");
  97. $this->error("第".$i."版本更新失败!");
  98. }
  99. $i = $i + 0.1;
  100. sleep(5);
  101. }
  102. //写入日志文件
  103. $this->write_log("累计更新成功!");
  104. //删除更新标志位
  105. Session::delete('update');
  106. $this->success("更新成功!");
  107. }else{
  108. //更新日志内容接口
  109. $update_log = "http://api.muyucms.com/version/$version/$version.log";
  110. //获取更新日志内容
  111. $content = $this->get_url_content($update_log);
  112. //字符串转数组
  113. $arr = explode(',',$content);
  114. //截取更新包状态,true表示是要更新,false为否
  115. $zip_status =array_shift($arr);
  116. $zip_status =explode(':',$zip_status);
  117. $zip_status = $zip_status[1];
  118. //截取数据库状态,true表示是要更新,false为否
  119. $sql_status =explode(':',$arr[0]);
  120. $sql_status = $sql_status[1];
  121. //写入日志文件
  122. $this->write_log("进行平滑更新!");
  123. //执行更新
  124. $res = $this->execute_update($version,(bool)$zip_status,(bool)$sql_status);
  125. if($res){
  126. //写入日志文件
  127. $this->write_log("平滑更新成功!");
  128. //删除更新标志位
  129. Session::delete('update');
  130. $this->success("更新成功!");
  131. }else{
  132. //写入日志文件
  133. $this->write_log("平滑更新失败!");
  134. $this->error("更新失败!");
  135. }
  136. }
  137. }
  138. /* 执行更新操作
  139. * @$version 需要更新的版本
  140. * @$zip_status 更新代码标志位
  141. * @$sql_status 数据库更新标志位
  142. */
  143. public function execute_update($version,$zip_status,$sql_status)
  144. {
  145. //对php.ini参数进行修改
  146. ini_set("max_execution_time", "300");
  147. ini_set('memory_limit', '300M');
  148. // PclZip类库不支持命名空间
  149. include_once Env::get('root_path') . 'extend/pclzip/PclZip.php';
  150. //引入数据库操作类
  151. include_once Env::get('root_path') . 'extend/baksql/Baksql.php';
  152. include_once Env::get('root_path') . 'extend/my/MuyuZip.php';
  153. $zipmuyu = new \MuyuZip;
  154. //获取时间戳
  155. $time = time();
  156. //当前系统的版本
  157. $sys_version = Db::name('system')->where('id',1)->field('version')->find();
  158. if(empty($sys_version['version']) || $sys_version['version'] == NULL){
  159. $sys_version['version'] = Config::get('app.version');
  160. }
  161. $this->rmdirr($_SERVER['DOCUMENT_ROOT'].'/mdata/update/backup_dir');
  162. mkdir($_SERVER['DOCUMENT_ROOT'].'/mdata/update/backup_dir');
  163. //定义备份路径
  164. $folder = $_SERVER['DOCUMENT_ROOT'].'/mdata/update/backup_dir/'.$time;
  165. //创建文件
  166. mkdir($folder);
  167. //远程获取升级包url
  168. $zip_url = "http://api.muyucms.com/version/$version/$version.zip";
  169. //远程获取数据库更新文件url
  170. $sql_url = "http://api.muyucms.com/version/$version/$version.sql";
  171. //定义升级包和数据库文件下载保存地址
  172. $this->rmdirr(Env::get('root_path').'mdata/update/upload_dir');
  173. mkdir(Env::get('root_path').'mdata/update/upload_dir');
  174. $save_dir = Env::get('root_path').'mdata/update/upload_dir/'.$time;
  175. //创建文件
  176. mkdir($save_dir);
  177. //判断是否需要更新网站代码
  178. if($zip_status == true){
  179. //写入日志文件
  180. $this->write_log("备份网站代码!");
  181. //备份网站代码
  182. $backup_code = $this->backup_code($folder,$sys_version['version'],$zipmuyu);
  183. if($backup_code === false)
  184. {
  185. //写入日志文件
  186. $this->write_log("代码备份失败!");
  187. //删除备份文件夹
  188. $this->rmdirr($folder);
  189. //写入日志文件
  190. $this->write_log("删除备份文件夹!");
  191. //代码备份失败
  192. return false;
  193. }
  194. //写入日志文件
  195. $this->write_log("网站代码备份成功!");
  196. $this->write_log("下载升级包!");
  197. //调用文件下载函数下载升级包
  198. $zip_data=$this->getFile($zip_url, $save_dir);
  199. //对更新包解压
  200. $zip = new \ZipArchive;
  201. if ($zip->open($zip_data['save_path']) === true) {
  202. $zip->close();
  203. $pclzip = new \PclZip($zip_data['save_path']);
  204. if($pclzip->extract(PCLZIP_OPT_PATH,$save_dir) != 0){
  205. //写入日志文件
  206. $this->write_log("更新包解压成功!");
  207. }else{
  208. $this->write_log("更新包解压失败!");
  209. //删除放置更新文件夹
  210. $this->rmdirr($save_dir);
  211. //写入日志文件
  212. $this->write_log("删除更新文件夹!");
  213. //删除备份文件夹
  214. $this->rmdirr($folder);
  215. //写入日志文件
  216. $this->write_log("删除备份文件夹!");
  217. return false;
  218. }
  219. } else {
  220. //写入日志文件
  221. $this->write_log("更新包解压失败!");
  222. //删除放置更新文件夹
  223. $this->rmdirr($save_dir);
  224. //写入日志文件
  225. $this->write_log("删除更新文件夹!");
  226. //删除备份文件夹
  227. $this->rmdirr($folder);
  228. //写入日志文件
  229. $this->write_log("删除备份文件夹!");
  230. //更新包解压失败
  231. return false;
  232. }
  233. //写入日志文件
  234. $this->write_log("将解压的文件复制到根目录覆盖!");
  235. //将解压的文件复制到根目录覆盖
  236. $copy_file = $this->copy_to_file($save_dir . '/temp_folder', Env::get('root_path'));
  237. if($copy_file == false){
  238. //写入日志文件
  239. $this->write_log("解压的文件复制到根目录覆盖失败!");
  240. //还原路径
  241. $reduction_path = dirname(Env::get('root_path'));
  242. //删除网站所有文件
  243. $this->rmdirr($_SERVER['DOCUMENT_ROOT']);
  244. //写入日志文件
  245. $this->write_log("对网站代码进行回滚!");
  246. //代码回滚,备份代码解压到根目录的上一层目录
  247. $zips = $zipmuyu->extractTos($folder.'/'.$sys_version['version'].'.zip',$folder);
  248. if($zips == '-1'){
  249. $this->write_log("网站代码进行回滚失败!");
  250. //代码还原失败
  251. return false;
  252. }else{
  253. if(is_dir($folder . '/'.$sys_version['version'])){
  254. $this->copy_to_file($folder . '/'.$sys_version['version'], Env::get('root_path'));
  255. }
  256. $this->copy_to_file($folder . '/muyu', Env::get('root_path'));
  257. }
  258. //写入日志文件
  259. $this->write_log("网站代码进行回滚成功!");
  260. //删除放置更新文件夹
  261. $this->rmdirr($save_dir);
  262. //写入日志文件
  263. $this->write_log("删除更新文件夹!");
  264. //删除备份文件夹
  265. $this->rmdirr($folder);
  266. //写入日志文件
  267. $this->write_log("删除备份文件夹!");
  268. //复制更新文件覆盖失败
  269. return false;
  270. }
  271. //写入日志文件
  272. $this->write_log("解压的文件复制到根目录覆盖成功,代码更新完成!");
  273. }
  274. //判断是否需要更新数据库
  275. if($sql_status == true){
  276. //写入日志文件
  277. $this->write_log("备份数据库!");
  278. //备份数据库
  279. $backup_sql = $this->backup_sql($folder,$sys_version['version']);
  280. if($backup_sql == false)
  281. {
  282. //写入日志文件
  283. $this->write_log("数据库备份失败!");
  284. //删除备份文件夹
  285. $this->rmdirr($folder);
  286. //写入日志文件
  287. $this->write_log("删除备份文件夹!");
  288. //数据库备份失败
  289. return false;
  290. }
  291. //调用文件下载函数下载数据库更新文件
  292. $sql_data=$this->getFile($sql_url, $save_dir);
  293. //数据库更新操作
  294. $zsql = new \org\Baksql(Config::get("database."),$folder,$sys_version['version']);
  295. if(is_file($sql_data['save_path'])){
  296. if (file_exists($sql_data['save_path'])){
  297. //写入日志文件
  298. $this->write_log("数据库更新操作!");
  299. //动态获取数据库配置信息
  300. $db = Db::connect();
  301. //写入日志文件
  302. $this->write_log("读取数据库更新文件内容!");
  303. $sql = file_get_contents($sql_data['save_path']);
  304. $sqlArr = explode(";", trim($sql));
  305. //遍历数组删除为空的元素
  306. foreach($sqlArr as $k=>$v){
  307. if($v == ""){
  308. //执行删除
  309. unset($sqlArr[$k]);
  310. }
  311. }
  312. //循环删除数组中的空元素
  313. foreach ($sqlArr as $query) {
  314. $query = str_replace('#__', Config::get("database.prefix"), $query);
  315. $query = $db->query(trim($query));
  316. if($query === false){
  317. //写入日志文件
  318. $this->write_log("数据库更新失败,数据库数据回滚!");
  319. //数据库回滚(还原)
  320. $info = $zsql->restore($folder .'/' .$sys_version['version'].'.sql');
  321. if($info == false){
  322. //写入日志文件
  323. $this->write_log("数据库回滚失败!");
  324. //数据库还原失败
  325. return false;
  326. }
  327. //还原路径
  328. $reduction_path = dirname($_SERVER['DOCUMENT_ROOT']);
  329. //删除网站所有文件
  330. $this->rmdirr($_SERVER['DOCUMENT_ROOT']);
  331. //写入日志文件
  332. $this->write_log("网站代码数据回滚!");
  333. //代码回滚,备份代码解压到根目录的上一层目录
  334. $zips = $zip->extract(PCLZIP_OPT_PATH, $reduction_path);
  335. if($zips == 0){
  336. //写入日志文件
  337. $this->write_log("网站代码数据回滚失败!");
  338. //代码还原失败
  339. return false;
  340. }
  341. //数据库更新失败
  342. return false;
  343. }
  344. }
  345. //写入日志文件
  346. $this->write_log("数据库更新成功!");
  347. }
  348. }
  349. }
  350. //写入日志文件
  351. $this->write_log("更新系统版本号!");
  352. //更新版本号
  353. $ver = Db::name('system')->where('id',1)->update(['version' => (float)$version]);
  354. //删除放置更新文件夹
  355. $this->rmdirr($save_dir);
  356. //写入日志文件
  357. $this->write_log("删除更新文件夹!");
  358. sleep(1);
  359. //删除备份文件夹
  360. $this->rmdirr($folder);
  361. //写入日志文件
  362. $this->write_log("删除备份文件夹!");
  363. return $ver;
  364. }
  365. /*
  366. * 获得目录下的所有文件路径并复制到指定的目录下面
  367. * $old_dir:目标文件目录
  368. * $new_dir:需要复制到的文件目录
  369. * $quanxian:设置权限
  370. */
  371. public function copy_to_file($old_dir,$new_dir){
  372. //判断有没有目录,没有则创建
  373. if(!is_dir($new_dir)){
  374. @mkdir($new_dir,true);
  375. }
  376. $res = '';
  377. $temp = scandir($old_dir);
  378. if(is_array($temp) && count($temp)>2){
  379. unset($temp[0],$temp[1]);
  380. foreach($temp as $key=>$val){
  381. $file_url=$old_dir.DIRECTORY_SEPARATOR.$val;
  382. //组件新的目录
  383. $xin_dir = str_replace("//","/",$new_dir.DIRECTORY_SEPARATOR.$val);
  384. chmod($new_dir,0777);
  385. //是否是目录
  386. if(is_dir($file_url)){
  387. $res =$this->copy_to_file($file_url,$xin_dir);
  388. }elseif(is_file($file_url)){
  389. $res = copy($file_url,$xin_dir);
  390. }
  391. }
  392. }
  393. chmod($new_dir,0755);
  394. return $res;
  395. }
  396. /* 删除目录文件
  397. * @$dirname 目录路径
  398. */
  399. public function rmdirr($dirname)
  400. {
  401. // Sanity check
  402. if (!file_exists($dirname)) {
  403. return false;
  404. }
  405. // Simple delete for a file
  406. if (is_file($dirname) || is_link($dirname)) {
  407. return unlink($dirname);
  408. }
  409. // Loop through the folder
  410. $dir = dir($dirname);
  411. while (false !== $entry = $dir->read()) {
  412. // Skip pointers
  413. if ($entry == '.' || $entry == '..') {
  414. continue;
  415. }
  416. if($entry == 'mdata'){
  417. continue;
  418. }
  419. // Recurse
  420. $this->rmdirr($dirname . DIRECTORY_SEPARATOR . $entry);
  421. }
  422. // Clean up
  423. $dir->close();
  424. return rmdir($dirname);
  425. }
  426. /* 备份代码
  427. * @$folder 更新目录
  428. * @$zipname 更新包名称
  429. */
  430. public function backup_code($folder,$zipname,&$zip)
  431. {
  432. //定义压缩包所在路径
  433. $zipnames = $folder.'/'.$zipname.'.zip';
  434. //备份
  435. $compzip = $zip->compresszip($zipnames,$_SERVER['DOCUMENT_ROOT']);
  436. if($compzip){
  437. return true;
  438. }else{
  439. return false;
  440. }
  441. }
  442. /* 备份数据库
  443. * @$folder 更新目录
  444. * @$zipname 更新包名称
  445. */
  446. public function backup_sql($folder,$version)
  447. {
  448. $sql = new \org\Baksql(Config::get("database."),$folder,$version);
  449. //备份处理
  450. $info = $sql->backup();
  451. if($info){
  452. return true;
  453. }else{
  454. return false;
  455. }
  456. }
  457. /* 远程下载文件到指定目录
  458. * @$url 文件下载地址
  459. * @$save_dir 文件保存路径
  460. */
  461. public function getFile($url, $save_dir) {
  462. if (trim($url) == '') {
  463. return false;
  464. }
  465. if (trim($save_dir) == '') {
  466. return false;
  467. }
  468. if (0 !== strrpos($save_dir, '/')) {
  469. $save_dir.= '/';
  470. }
  471. $filename = basename($url);
  472. //创建保存目录
  473. if (!file_exists($save_dir)) {
  474. return false;
  475. }
  476. //开始下载
  477. $ch = curl_init();
  478. $timeout = 5;
  479. curl_setopt($ch, CURLOPT_URL, $url);
  480. curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  481. curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
  482. $content = curl_exec($ch);
  483. $status = curl_getinfo($ch);
  484. curl_close($ch);
  485. // 判断执行结果
  486. if ($status['http_code'] ==200) {
  487. $size = strlen($content);
  488. //文件大小
  489. $fp2 = @fopen($save_dir . $filename , 'a');
  490. fwrite($fp2, $content);
  491. fclose($fp2);
  492. unset($content, $url);
  493. $res = [
  494. 'status' =>$status['http_code'] ,
  495. 'file_name' => $filename,
  496. 'save_path' => $save_dir . $filename
  497. ];
  498. } else {
  499. $res = false;
  500. }
  501. return $res;
  502. }
  503. /**
  504. * [write_log 写入日志]
  505. * @param [type] $data [写入的数据]
  506. * @return [type] [description]
  507. */
  508. public function write_log($data){
  509. $time = date('Y-m-d');
  510. //设置路径目录信息
  511. $url = $_SERVER['DOCUMENT_ROOT'].'/mdata/update/log/'.$time.'/'.'update_log.log';
  512. $dir_name=dirname($url);
  513. //目录不存在就创建
  514. if(!file_exists($dir_name))
  515. {
  516. //iconv防止中文名乱码
  517. $res = mkdir(iconv("UTF-8", "GBK", $dir_name),0777,true);
  518. }
  519. $fp = fopen($url,"a");//打开文件资源通道 不存在则自动创建
  520. fwrite($fp,date("Y-m-d H:i:s").var_export($data,true)."\r\n");//写入文件
  521. fclose($fp);//关闭资源通道
  522. }
  523. }