Tools.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520
  1. <?php
  2. /**
  3. * 易优CMS
  4. * ============================================================================
  5. * 版权所有 2016-2028 海南赞赞网络科技有限公司,并保留所有权利。
  6. * 网站地址: http://www.eyoucms.com
  7. * ----------------------------------------------------------------------------
  8. * 如果商业用途务必到官方购买正版授权, 以免引起不必要的法律纠纷.
  9. * ============================================================================
  10. * Author: 小虎哥 <1105415366@qq.com>
  11. * Date: 2018-4-3
  12. */
  13. namespace app\admin\controller;
  14. use think\Db;
  15. use think\Backup;
  16. class Tools extends Base {
  17. public function _initialize() {
  18. parent::_initialize();
  19. $this->language_access(); // 多语言功能操作权限
  20. }
  21. /**
  22. * 数据表列表
  23. */
  24. public function index()
  25. {
  26. $dbtables = Db::query('SHOW TABLE STATUS');
  27. $total = 0;
  28. $list = array();
  29. foreach ($dbtables as $k => $v) {
  30. if (preg_match('/^'.PREFIX.'/i', $v['Name'])) {
  31. $v['size'] = format_bytes($v['Data_length'] + $v['Index_length']);
  32. $list[$k] = $v;
  33. $total += $v['Data_length'] + $v['Index_length'];
  34. }
  35. }
  36. $path = tpCache('global.web_sqldatapath');
  37. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  38. @unlink(realpath(trim($path, '/')) . DS . 'backup.lock');
  39. // if (session('?backup_config.path')) {
  40. //备份完成,清空缓存
  41. session('backup_tables', null);
  42. session('backup_file', null);
  43. session('backup_config', null);
  44. // }
  45. $this->assign('list', $list);
  46. $this->assign('total', format_bytes($total));
  47. $this->assign('tableNum', count($list));
  48. return $this->fetch();
  49. }
  50. /**
  51. * 数据备份
  52. */
  53. public function export($tables = null, $id = null, $start = null,$optstep = 0)
  54. {
  55. //防止备份数据过程超时
  56. function_exists('set_time_limit') && set_time_limit(0);
  57. @ini_set('memory_limit','-1');
  58. /*升级完自动备份所有数据表*/
  59. if ('all' == $tables) {
  60. $dbtables = Db::query('SHOW TABLE STATUS');
  61. $list = array();
  62. foreach ($dbtables as $k => $v) {
  63. if (preg_match('/^'.PREFIX.'/i', $v['Name'])) {
  64. $list[] = $v['Name'];
  65. }
  66. }
  67. $tables = $list;
  68. unlink(session('backup_config.path') . 'backup.lock');
  69. }
  70. /*--end*/
  71. if(IS_POST && !empty($tables) && is_array($tables) && empty($optstep)){ //初始化
  72. $path = tpCache('global.web_sqldatapath');
  73. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  74. $path = trim($path, '/');
  75. if(!empty($path) && !is_dir($path)){
  76. mkdir($path, 0755, true);
  77. }
  78. //读取备份配置
  79. $config = array(
  80. 'path' => realpath($path) . DS,
  81. 'part' => config('DATA_BACKUP_PART_SIZE'),
  82. 'compress' => config('DATA_BACKUP_COMPRESS'),
  83. 'level' => config('DATA_BACKUP_COMPRESS_LEVEL'),
  84. );
  85. //检查是否有正在执行的任务
  86. $lock = "{$config['path']}backup.lock";
  87. if(is_file($lock)){
  88. return json(array('info'=>'检测到有一个备份任务正在执行,请稍后再试!', 'status'=>0, 'url'=>''));
  89. } else {
  90. //创建锁文件
  91. file_put_contents($lock, $_SERVER['REQUEST_TIME']);
  92. }
  93. //检查备份目录是否可写
  94. if(!is_writeable($config['path'])){
  95. return json(array('info'=>'备份目录不存在或不可写,请检查后重试!', 'status'=>0, 'url'=>''));
  96. }
  97. session('backup_config', $config);
  98. //生成备份文件信息
  99. $file = array(
  100. 'name' => date('Ymd-His', $_SERVER['REQUEST_TIME']),
  101. 'part' => 1,
  102. 'version' => getCmsVersion(),
  103. );
  104. session('backup_file', $file);
  105. //缓存要备份的表
  106. session('backup_tables', $tables);
  107. //创建备份文件
  108. $Database = new Backup($file, $config);
  109. if(false !== $Database->create()){
  110. $speed = (floor((1/count($tables))*10000)/10000*100);
  111. $speed = sprintf("%.2f", $speed);
  112. $tab = array('id' => 0, 'start' => 0, 'speed'=>$speed, 'table'=>$tables[0], 'optstep'=>1);
  113. return json(array('tables' => $tables, 'tab' => $tab, 'info'=>'初始化成功!', 'status'=>1, 'url'=>''));
  114. } else {
  115. return json(array('info'=>'初始化失败,备份文件创建失败!', 'status'=>0, 'url'=>''));
  116. }
  117. } elseif (IS_POST && is_numeric($id) && is_numeric($start) && 1 == intval($optstep)) { //备份数据
  118. $tables = session('backup_tables');
  119. //备份指定表
  120. $Database = new Backup(session('backup_file'), session('backup_config'));
  121. $start = $Database->backup($tables[$id], $start);
  122. if(false === $start){ //出错
  123. return json(array('info'=>'备份出错!', 'status'=>0, 'url'=>''));
  124. } elseif (0 === $start) { //下一表
  125. if(isset($tables[++$id])){
  126. $speed = (floor((($id+1)/count($tables))*10000)/10000*100);
  127. $speed = sprintf("%.2f", $speed);
  128. $tab = array('id' => $id, 'start' => 0, 'speed' => $speed, 'table'=>$tables[$id], 'optstep'=>1);
  129. return json(array('tab' => $tab, 'info'=>'备份完成!', 'status'=>1, 'url'=>''));
  130. } else { //备份完成,清空缓存
  131. /*自动覆盖安装目录下的eyoucms.sql*/
  132. $install_time = DEFAULT_INSTALL_DATE;
  133. $constsant_path = APP_PATH.MODULE_NAME.'/conf/constant.php';
  134. if (file_exists($constsant_path)) {
  135. require_once($constsant_path);
  136. defined('INSTALL_DATE') && $install_time = INSTALL_DATE;
  137. }
  138. $install_path = ROOT_PATH.'install';
  139. if (!is_dir($install_path) || !file_exists($install_path)) {
  140. $install_path = ROOT_PATH.'install_'.$install_time;
  141. }
  142. if (is_dir($install_path) && file_exists($install_path)) {
  143. $srcfile = session('backup_config.path').session('backup_file.name').'-'.session('backup_file.part').'-'.session('backup_file.version').'.sql';
  144. $dstfile = $install_path.'/eyoucms.sql';
  145. if(@copy($srcfile, $dstfile)){
  146. /*替换所有表的前缀为官方默认ey_,并重写安装数据包里*/
  147. $eyouDbStr = file_get_contents($dstfile);
  148. $dbtables = Db::query('SHOW TABLE STATUS');
  149. $tableName = $eyTableName = [];
  150. foreach ($dbtables as $k => $v) {
  151. if (preg_match('/^'.PREFIX.'/i', $v['Name'])) {
  152. $tableName[] = "`{$v['Name']}`";
  153. $eyTableName[] = preg_replace('/^`'.PREFIX.'/i', '`ey_', "`{$v['Name']}`");
  154. }
  155. }
  156. $eyouDbStr = str_replace($tableName, $eyTableName, $eyouDbStr);
  157. @file_put_contents($dstfile, $eyouDbStr);
  158. unset($eyouDbStr);
  159. /*--end*/
  160. } else {
  161. @unlink($dstfile); // 复制失败就删掉,避免安装错误的数据包
  162. }
  163. }
  164. /*--end*/
  165. unlink(session('backup_config.path') . 'backup.lock');
  166. session('backup_tables', null);
  167. session('backup_file', null);
  168. session('backup_config', null);
  169. return json(array('info'=>'备份完成!', 'status'=>1, 'url'=>''));
  170. }
  171. } else {
  172. $rate = floor(100 * ($start[0] / $start[1]));
  173. $speed = floor((($id+1)/count($tables))*10000)/10000*100 + ($rate/100);
  174. $speed = sprintf("%.2f", $speed);
  175. $tab = array('id' => $id, 'start' => $start[0], 'speed' => $speed, 'table'=>$tables[$id], 'optstep'=>1);
  176. return json(array('tab' => $tab, 'info'=>"正在备份...({$rate}%)", 'status'=>1, 'url'=>''));
  177. }
  178. } else {//出错
  179. return json(array('info'=>'参数有误', 'tab'=>['speed'=>-1], 'status'=>0, 'url'=>''));
  180. }
  181. }
  182. /**
  183. * 优化
  184. */
  185. public function optimize()
  186. {
  187. $batchFlag = input('get.batchFlag', 0, 'intval');
  188. //批量删除
  189. if ($batchFlag) {
  190. $table = input('key', array());
  191. }else {
  192. $table[] = input('tablename' , '');
  193. }
  194. if (empty($table)) {
  195. $this->error('请选择数据表');
  196. }
  197. $strTable = implode(',', $table);
  198. if (!DB::query("OPTIMIZE TABLE {$strTable} ")) {
  199. $strTable = '';
  200. }
  201. $this->success("操作成功" . $strTable, url('Tools/index'));
  202. }
  203. /**
  204. * 修复
  205. */
  206. public function repair()
  207. {
  208. $batchFlag = input('get.batchFlag', 0, 'intval');
  209. //批量删除
  210. if ($batchFlag) {
  211. $table = input('key', array());
  212. }else {
  213. $table[] = input('tablename' , '');
  214. }
  215. if (empty($table)) {
  216. $this->error('请选择数据表');
  217. }
  218. $strTable = implode(',', $table);
  219. if (!DB::query("REPAIR TABLE {$strTable} ")) {
  220. $strTable = '';
  221. }
  222. $this->success("操作成功" . $strTable, url('Tools/index'));
  223. }
  224. /**
  225. * 数据还原
  226. */
  227. public function restore()
  228. {
  229. $path = tpCache('global.web_sqldatapath');
  230. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  231. $path = trim($path, '/');
  232. if(!empty($path) && !is_dir($path)){
  233. mkdir($path, 0755, true);
  234. }
  235. $path = realpath($path);
  236. $flag = \FilesystemIterator::KEY_AS_FILENAME;
  237. $glob = new \FilesystemIterator($path, $flag);
  238. $list = array();
  239. $filenum = $total = 0;
  240. foreach ($glob as $name => $file) {
  241. if(preg_match('/^\d{8,8}-\d{6,6}-\d+-v\d+\.\d+\.\d+(.*)\.sql(?:\.gz)?$/', $name)){
  242. $name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d-%s');
  243. $date = "{$name[0]}-{$name[1]}-{$name[2]}";
  244. $time = "{$name[3]}:{$name[4]}:{$name[5]}";
  245. $part = $name[6];
  246. $version = preg_replace('#\.sql(.*)#i', '', $name[7]);
  247. $info = pathinfo($file);
  248. if(isset($list["{$date} {$time}"])){
  249. $info = $list["{$date} {$time}"];
  250. $info['part'] = max($info['part'], $part);
  251. $info['size'] = $info['size'] + $file->getSize();
  252. } else {
  253. $info['part'] = $part;
  254. $info['size'] = $file->getSize();
  255. }
  256. $info['compress'] = ($info['extension'] === 'sql') ? '-' : $info['extension'];
  257. $info['time'] = strtotime("{$date} {$time}");
  258. $info['version'] = $version;
  259. $filenum++;
  260. $total += $info['size'];
  261. $list["{$date} {$time}"] = $info;
  262. }
  263. }
  264. array_multisort($list, SORT_DESC);
  265. $this->assign('list', $list);
  266. $this->assign('filenum',$filenum);
  267. $this->assign('total',$total);
  268. return $this->fetch();
  269. }
  270. /**
  271. * 上传sql文件
  272. */
  273. public function restoreUpload()
  274. {
  275. $this->error('该功能仅限技术人员使用!');
  276. $file = request()->file('sqlfile');
  277. if(empty($file)){
  278. $this->error('请上传sql文件');
  279. }
  280. // 移动到框架应用根目录/data/sqldata/ 目录下
  281. $path = tpCache('global.web_sqldatapath');
  282. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  283. $path = trim($path, '/');
  284. $image_upload_limit_size = intval(tpCache('basic.file_size') * 1024 * 1024);
  285. $info = $file->validate(['size'=>$image_upload_limit_size,'ext'=>'sql,gz'])->move($path, $_FILES['sqlfile']['name']);
  286. if ($info) {
  287. //上传成功 获取上传文件信息
  288. $file_path_full = $info->getPathName();
  289. if (file_exists($file_path_full)) {
  290. $sqls = Backup::parseSql($file_path_full);
  291. if(Backup::install($sqls)){
  292. /*清除缓存*/
  293. delFile(RUNTIME_PATH);
  294. /*--end*/
  295. $this->success("执行sql成功", url('Tools/restore'));
  296. }else{
  297. $this->error('执行sql失败');
  298. }
  299. } else {
  300. $this->error('sql文件上传失败');
  301. }
  302. } else {
  303. //上传错误提示错误信息
  304. $this->error($file->getError());
  305. }
  306. }
  307. /**
  308. * 执行还原数据库操作
  309. * @param int $time
  310. * @param null $part
  311. * @param null $start
  312. */
  313. public function import($time = 0, $part = null, $start = null)
  314. {
  315. function_exists('set_time_limit') && set_time_limit(0);
  316. if(is_numeric($time) && is_null($part) && is_null($start)){ //初始化
  317. //获取备份文件信息
  318. $name = date('Ymd-His', $time) . '-*.sql*';
  319. $path = tpCache('global.web_sqldatapath');
  320. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  321. $path = trim($path, '/');
  322. $path = realpath($path) . DS . $name;
  323. $files = glob($path);
  324. $list = array();
  325. foreach($files as $name){
  326. $basename = basename($name);
  327. $match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d');
  328. $gz = preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename);
  329. $list[$match[6]] = array($match[6], $name, $gz);
  330. }
  331. ksort($list);
  332. //检测文件正确性
  333. $last = end($list);
  334. if(count($list) === $last[0]){
  335. session('backup_list', $list); //缓存备份列表
  336. $part = 1;
  337. $start = 0;
  338. $data = array('part' => $part, 'start' => $start);
  339. // $this->success('初始化完成!', null, array('part' => $part, 'start' => $start));
  340. respose(array('code'=>1, 'msg'=>"初始化完成!准备还原#{$part}...", 'rate'=>'', 'data'=>$data));
  341. } else {
  342. // $this->error('备份文件可能已经损坏,请检查!');
  343. respose(array('code'=>0, 'msg'=>"备份文件可能已经损坏,请检查!"));
  344. }
  345. } elseif(is_numeric($part) && is_numeric($start)) {
  346. $list = session('backup_list');
  347. $path = tpCache('global.web_sqldatapath');
  348. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  349. $path = trim($path, '/');
  350. $db = new Backup($list[$part], array(
  351. 'path' => realpath($path) . DS,
  352. 'compress' => $list[$part][2]));
  353. $start = $db->import($start);
  354. if(false === $start){
  355. // $this->error('还原数据出错!');
  356. respose(array('code'=>0, 'msg'=>"还原数据出错!", 'rate'=>'0%'));
  357. } elseif(0 === $start) { //下一卷
  358. if(isset($list[++$part])){
  359. $data = array('part' => $part, 'start' => 0);
  360. // $this->success("正在还g原...#{$part}", null, $data);
  361. $rate = (floor((($start+1)/count($list))*10000)/10000*100).'%';
  362. respose(array('code'=>1, 'msg'=>"正在还原#{$part}...", 'rate'=>$rate, 'data'=>$data));
  363. } else {
  364. session('backup_list', null);
  365. delFile(RUNTIME_PATH);
  366. respose(array('code'=>1, 'msg'=>"还原完成...", 'rate'=>'100%'));
  367. // $this->success('还原完成!');
  368. }
  369. } else {
  370. $data = array('part' => $part, 'start' => $start[0]);
  371. if($start[1]){
  372. $rate = floor(100 * ($start[0] / $start[1])).'%';
  373. respose(array('code'=>1, 'msg'=>"正在还原#{$part}...", 'rate'=>$rate, 'data'=>$data));
  374. // $this->success("正在还d原...#{$part} ({$rate}%)", null, $data);
  375. } else {
  376. $data['gz'] = 1;
  377. respose(array('code'=>1, 'msg'=>"正在还原#{$part}...", 'data'=>$data, 'start'=>$start));
  378. // $this->success("正在还s原...#{$part}", null, $data);
  379. }
  380. }
  381. } else {
  382. // $this->error('参数错误!');
  383. respose(array('code'=>0, 'msg'=>"参数有误", 'rate'=>'0%'));
  384. }
  385. }
  386. /**
  387. * (新)执行还原数据库操作
  388. * @param int $time
  389. */
  390. public function new_import($time = 0)
  391. {
  392. function_exists('set_time_limit') && set_time_limit(0);
  393. @ini_set('memory_limit','-1');
  394. if(is_numeric($time) && intval($time) > 0){
  395. //获取备份文件信息
  396. $name = date('Ymd-His', $time) . '-*.sql*';
  397. $path = tpCache('global.web_sqldatapath');
  398. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  399. $path = trim($path, '/');
  400. $path = realpath($path) . DS . $name;
  401. $files = glob($path);
  402. $list = array();
  403. foreach($files as $name){
  404. $basename = basename($name);
  405. $match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d-%s');
  406. $gz = preg_match('/^\d{8,8}-\d{6,6}-\d+-v\d+\.\d+\.\d+(.*)\.sql.gz$/', $basename);
  407. $list[$match[6]] = array($match[6], $name, $gz);
  408. }
  409. ksort($list);
  410. //检测文件正确性
  411. $last = end($list);
  412. $file_path_full = !empty($last[1]) ? $last[1] : '';
  413. if (file_exists($file_path_full)) {
  414. /*校验sql文件是否属于当前CMS版本*/
  415. preg_match('/(\d{8,8})-(\d{6,6})-(\d+)-(v\d+\.\d+\.\d+(.*))\.sql/i', $file_path_full, $matches);
  416. $version = getCmsVersion();
  417. if ($matches[4] != $version) {
  418. $this->error('sql不兼容当前版本:'.$version, url('Tools/restore'));
  419. }
  420. /*--end*/
  421. $sqls = Backup::parseSql($file_path_full);
  422. if(Backup::install($sqls)){
  423. /*清除缓存*/
  424. delFile(RUNTIME_PATH);
  425. /*--end*/
  426. $this->success('操作成功', request()->baseFile(), '', 1, [], '_parent');
  427. }else{
  428. $this->error('操作失败!', url('Tools/restore'));
  429. }
  430. }
  431. }
  432. else
  433. {
  434. $this->error("参数有误", url('Tools/restore'));
  435. }
  436. exit;
  437. }
  438. /**
  439. * 下载
  440. * @param int $time
  441. */
  442. public function downFile($time = 0)
  443. {
  444. $name = date('Ymd-His', $time) . '-*.sql*';
  445. $path = tpCache('global.web_sqldatapath');
  446. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  447. $path = trim($path, '/');
  448. $path = realpath($path) . DS . $name;
  449. $files = glob($path);
  450. if(is_array($files)){
  451. foreach ($files as $filePath){
  452. if (!file_exists($filePath)) {
  453. $this->error("该文件不存在,可能是被删除");
  454. }else{
  455. $filename = basename($filePath);
  456. header("Content-type: application/octet-stream");
  457. header('Content-Disposition: attachment; filename="' . $filename . '"');
  458. header("Content-Length: " . filesize($filePath));
  459. readfile($filePath);
  460. }
  461. }
  462. }
  463. }
  464. /**
  465. * 删除备份文件
  466. * @param Integer $time 备份时间
  467. */
  468. public function del()
  469. {
  470. $time_arr = input('del_id/a');
  471. $time_arr = eyIntval($time_arr);
  472. if(is_array($time_arr) && !empty($time_arr)){
  473. foreach ($time_arr as $key => $val) {
  474. $name = date('Ymd-His', $val) . '-*.sql*';
  475. $path = tpCache('global.web_sqldatapath');
  476. $path = !empty($path) ? $path : config('DATA_BACKUP_PATH');
  477. $path = trim($path, '/');
  478. $path = realpath($path) . DS . $name;
  479. array_map("unlink", glob($path));
  480. if(count(glob($path))){
  481. $this->error('备份文件删除失败,请检查目录权限!');
  482. }
  483. }
  484. $this->success('删除成功!');
  485. } else {
  486. $this->error('参数有误');
  487. }
  488. }
  489. }