Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

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