DatabaseBackup.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. /*
  3. * @Author: juneChen && junechen_0606@163.com
  4. * @Date: 2022-12-07 13:56:26
  5. * @LastEditors: juneChen && junechen_0606@163.com
  6. * @LastEditTime: 2022-12-15 15:49:40
  7. * @Description: 数据库备份控制器
  8. *
  9. * Copyright (c) 2022 by juneChen, All Rights Reserved.
  10. */
  11. namespace app\admin\controller\security;
  12. use app\common\controller\Backend;
  13. use juneChen\dataTableBackup\MysqlBackupOrRestore;
  14. class DatabaseBackup extends Backend
  15. {
  16. public function initialize(): void
  17. {
  18. parent::initialize();
  19. }
  20. /**
  21. * 获取数据表
  22. *
  23. * @return void
  24. * @author juneChen <junechen_0606@163.com>
  25. */
  26. public function index(): void
  27. {
  28. $mysqlBackupOrRestore = new MysqlBackupOrRestore();
  29. $data_list = $mysqlBackupOrRestore->getTableList();
  30. $this->success('', [
  31. 'list' => $data_list,
  32. 'remark' => '',
  33. 'total' => 100
  34. ]);
  35. }
  36. /**
  37. * 备份数据表
  38. *
  39. * @param [type] $tables 数据表
  40. * @return void
  41. * @author juneChen <junechen_0606@163.com>
  42. */
  43. public function backups($tables = null): void
  44. {
  45. if ($this->request->isPost() && !empty($tables) && is_array($tables)) {
  46. // 初始化
  47. $backup = config('databaseBackup.backup');
  48. if (!is_dir($backup['path'])) {
  49. mkdir($backup['path'], 0755, true);
  50. }
  51. // 读取备份配置
  52. $config = array(
  53. 'path' => realpath($backup['path']) . DIRECTORY_SEPARATOR,
  54. 'part' => $backup['part'],
  55. 'compress' => $backup['compress'],
  56. 'level' => $backup['level'],
  57. );
  58. // 检查是否有正在执行的任务
  59. $lock = "{$config['path']}backup.lock";
  60. if (is_file($lock)) {
  61. $this->error(__('A backup task is in progress'));
  62. } else {
  63. // 创建锁文件
  64. file_put_contents($lock, $this->request->time());
  65. }
  66. // 检查备份目录是否可写
  67. is_writeable($config['path']) || $this->error('The backup directory does not exist or cannot be written');
  68. // 生成备份文件信息
  69. $file = array(
  70. 'name' => date('Ymd-His', $this->request->time()),
  71. 'part' => 1,
  72. );
  73. // 创建备份文件
  74. $Database = new MysqlBackupOrRestore($file, $config);
  75. if (false !== $Database->create()) {
  76. $start = 0;
  77. // 备份指定表
  78. foreach ($tables as $table) {
  79. $start = $Database->backup($table, $start);
  80. while (0 !== $start) {
  81. if (false === $start) { // 出错
  82. $this->error(__("Backup error"));
  83. }
  84. $start = $Database->backup($table, $start[0]);
  85. }
  86. }
  87. // 备份完成,删除锁定文件
  88. unlink($lock);
  89. $this->success();
  90. } else {
  91. $this->error(__('Backup file creation failed'));
  92. }
  93. } else {
  94. $this->error(__('Parameter error'));
  95. }
  96. }
  97. /**
  98. * 备份文件列表
  99. *
  100. * @return void
  101. * @author juneChen <junechen_0606@163.com>
  102. */
  103. public function restoreList(): void
  104. {
  105. // 列出备份文件列表
  106. $backup = config('databaseBackup.backup');
  107. if (!is_dir($backup['path'])) {
  108. mkdir($backup['path'], 0755, true);
  109. }
  110. $path = realpath($backup['path']);
  111. $flag = \FilesystemIterator::KEY_AS_FILENAME;
  112. $glob = new \FilesystemIterator($path, $flag);
  113. $data_list = [];
  114. foreach ($glob as $name => $file) {
  115. if (preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql(?:\.gz)?$/', $name)) {
  116. $name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d');
  117. $date = "{$name[0]}-{$name[1]}-{$name[2]}";
  118. $time = "{$name[3]}:{$name[4]}:{$name[5]}";
  119. $part = $name[6];
  120. if (isset($data_list["{$date}{$time}"])) {
  121. $info = $data_list["{$date}{$time}"];
  122. $info['number'] = max($info['number'], $part);
  123. $info['dataSize'] = $info['dataSize'] + $file->getSize();
  124. } else {
  125. $info['number'] = $part;
  126. $info['dataSize'] = $file->getSize();
  127. }
  128. $extension = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
  129. $info['compression'] = ($extension === 'SQL') ? '-' : $extension;
  130. $info['backupTime'] = strtotime("{$date} {$time}");
  131. $info['backupName'] = "{$name[0]}{$name[1]}{$name[2]}-{$name[3]}{$name[4]}{$name[5]}";
  132. $data_list["{$date}{$time}"] = $info;
  133. }
  134. }
  135. if (!empty($data_list)) {
  136. krsort($data_list);
  137. $data_list = array_values($data_list);
  138. }
  139. $this->success('', [
  140. 'list' => $data_list,
  141. 'remark' => '',
  142. 'total' => 1
  143. ]);
  144. }
  145. /**
  146. * 还原数据库
  147. *
  148. * @param integer $time 文件时间戳
  149. * @return void
  150. * @author juneChen <junechen_0606@163.com>
  151. */
  152. public function restore($time = 0): void
  153. {
  154. if ($time === 0) $this->error(__('Parameter error'));
  155. $backup = config('databaseBackup.backup');
  156. // 初始化
  157. $name = date('Ymd-His', $time) . '-*.sql*';
  158. $path = realpath($backup['path']) . DIRECTORY_SEPARATOR . $name;
  159. $files = glob($path);
  160. $list = array();
  161. foreach ($files as $name) {
  162. $basename = basename($name);
  163. $match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d');
  164. $gz = preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename);
  165. $list[$match[6]] = array($match[6], $name, $gz);
  166. }
  167. ksort($list);
  168. // 检测文件正确性
  169. $last = end($list);
  170. if (count($list) === $last[0]) {
  171. foreach ($list as $item) {
  172. $config = [
  173. 'path' => realpath($backup['path']) . DIRECTORY_SEPARATOR,
  174. 'compress' => $item[2]
  175. ];
  176. $file = [
  177. "name" => $item[1]
  178. ];
  179. $Database = new MysqlBackupOrRestore($file, $config);
  180. $start = $Database->restore(0);
  181. // 循环导入数据
  182. while (0 !== $start) {
  183. if (false === $start) { // 出错
  184. $this->error(__('Error restoring data'));
  185. }
  186. $start = $Database->restore($start[0]);
  187. }
  188. }
  189. $this->success();
  190. } else {
  191. $this->error(__("The backup file may be corrupted"));
  192. }
  193. }
  194. }