You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

387 lines
14 KiB

4 years ago
  1. <?php
  2. namespace App\Http\Controllers\Admin;
  3. use Illuminate\Support\Facades\DB;
  4. use App\Common\ReturnData;
  5. use Illuminate\Http\Request;
  6. use App\Common\Helper;
  7. use App\Common\Utils\Database as DatabaseUtil;
  8. class DatabaseController extends BaseController
  9. {
  10. public function __construct()
  11. {
  12. parent::__construct();
  13. }
  14. public function index()
  15. {
  16. $data['list'] = $this->get_all_table();
  17. return view('admin.database.index', $data);
  18. }
  19. public function get_all_table()
  20. {
  21. $list = DB::select('SHOW TABLE STATUS');
  22. $list = object_to_array($list);
  23. $list = array_map('array_change_key_case', $list);
  24. return $list;
  25. }
  26. public function recovery()
  27. {
  28. $path = base_path() . DIRECTORY_SEPARATOR . 'database_backup';
  29. //判断目录是否存在
  30. is_writeable($path) || mkdir('./' . C("DB_PATH_NAME") . '', 0777, true);
  31. //列出备份文件列表
  32. $flag = \FilesystemIterator::KEY_AS_FILENAME;
  33. $glob = new \FilesystemIterator($path, $flag);
  34. $list = array();
  35. foreach ($glob as $name => $file) {
  36. if (preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql(?:\.gz)?$/', $name)) {
  37. $name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d');
  38. $date = "{$name[0]}-{$name[1]}-{$name[2]}";
  39. $time = "{$name[3]}:{$name[4]}:{$name[5]}";
  40. $part = $name[6];
  41. if (isset($list["{$date} {$time}"])) {
  42. $info = $list["{$date} {$time}"];
  43. $info['part'] = max($info['part'], $part);
  44. $info['size'] = $info['size'] + $file->getSize();
  45. } else {
  46. $info['part'] = $part;
  47. $info['size'] = $file->getSize();
  48. }
  49. $extension = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
  50. $info['compress'] = ($extension === 'SQL') ? '-' : $extension;
  51. $info['time'] = strtotime("{$date} {$time}");
  52. $list["{$date} {$time}"] = $info;
  53. }
  54. }
  55. $this->assign('list', $list);
  56. $this->display();
  57. }
  58. /**
  59. * 优化表
  60. * @param String $tables 表名
  61. */
  62. public function optimize($tables = null)
  63. {
  64. if (!$tables) {
  65. $tables = request('tables');
  66. }
  67. if (!$tables) {
  68. error_jump("请指定要优化的表");
  69. }
  70. if (is_array($tables)) {
  71. if (count($tables) > 5) {
  72. $all_table = $this->get_all_table();
  73. foreach ($all_table as $k => $v) {
  74. DB::statement("OPTIMIZE TABLE `" . $v['name'] . "`");
  75. }
  76. success_jump("数据库优化完成");
  77. }
  78. $tables = implode('`,`', $tables);
  79. $list = DB::statement("OPTIMIZE TABLE `{$tables}`");
  80. if (!$list) {
  81. error_jump("数据表优化出错请重试");
  82. }
  83. success_jump("数据表优化完成");
  84. }
  85. if (substr_count($tables, ',') > 5) {
  86. $all_table = $this->get_all_table();
  87. foreach ($all_table as $k => $v) {
  88. DB::statement("OPTIMIZE TABLE `" . $v['name'] . "`");
  89. }
  90. success_jump("数据库优化完成");
  91. }
  92. $list = DB::statement("OPTIMIZE TABLE `{$tables}`");
  93. if (!$list) {
  94. error_jump("数据表'{$tables}'优化出错请重试");
  95. }
  96. success_jump("数据表'{$tables}'优化完成");
  97. }
  98. /**
  99. * 修复表
  100. * @param String $tables 表名
  101. */
  102. public function repair($tables = null)
  103. {
  104. if (!$tables) {
  105. $tables = request('tables');
  106. }
  107. if (!$tables) {
  108. error_jump("请指定要修复的表");
  109. }
  110. if (is_array($tables)) {
  111. if (count($tables) > 5) {
  112. $all_table = $this->get_all_table();
  113. foreach ($all_table as $k => $v) {
  114. DB::statement("REPAIR TABLE `" . $v['name'] . "`");
  115. }
  116. success_jump("数据库修复完成");
  117. }
  118. $tables = implode('`,`', $tables);
  119. $list = DB::statement("REPAIR TABLE `{$tables}`");
  120. if (!$list) {
  121. error_jump("数据表修复出错请重试");
  122. }
  123. success_jump("数据表修复完成");
  124. }
  125. if (substr_count($tables, ',') > 5) {
  126. $all_table = $this->get_all_table();
  127. foreach ($all_table as $k => $v) {
  128. DB::statement("REPAIR TABLE `" . $v['name'] . "`");
  129. }
  130. success_jump("数据库修复完成");
  131. }
  132. $list = DB::statement("REPAIR TABLE `{$tables}`");
  133. if (!$list) {
  134. error_jump("数据表'{$tables}'修复出错请重试");
  135. }
  136. success_jump("数据表'{$tables}'修复完成");
  137. }
  138. /**
  139. * 删除备份文件
  140. * @param Integer $time 备份时间
  141. */
  142. public function del($time = 0)
  143. {
  144. if (!$time) {
  145. error_jump('参数错误');
  146. }
  147. $name = date('Ymd-His', $time) . '-*.sql*';
  148. $path = base_path() . DIRECTORY_SEPARATOR . 'database_backup' . DIRECTORY_SEPARATOR . $name;
  149. array_map("unlink", glob($path));
  150. if (count(glob($path))) {
  151. error_jump('备份文件删除失败,请检查权限');
  152. }
  153. success_jump('备份文件删除成功');
  154. }
  155. /**
  156. * 备份数据库
  157. * @param String $tables 表名
  158. * @param Integer $id 表ID
  159. * @param Integer $start 起始行数
  160. */
  161. public function tables_backup()
  162. {
  163. $tables = request('tables');
  164. if (!$tables) {
  165. error_jump('参数错误');
  166. }
  167. $tables = explode(',', $tables);
  168. //初始化
  169. if (!(!empty($tables) && is_array($tables))) {
  170. error_jump('参数错误');
  171. }
  172. if (count($tables) > 5) {
  173. $all_table = $this->get_all_table();
  174. $tables = array_column($all_table, 'name');
  175. }
  176. $backup_path = base_path() . DIRECTORY_SEPARATOR . 'database_backup' . DIRECTORY_SEPARATOR;
  177. foreach ($tables as $table) {
  178. $filename = "{$backup_path}{$table}.sql";
  179. //判断文件是否已经存在
  180. if (file_exists($filename)) {
  181. unlink($filename);
  182. }
  183. $fp = @fopen($filename, 'a');
  184. //将每个表的表结构导出到文件
  185. $table_structure = DB::select("SHOW CREATE TABLE `{$table}`");
  186. $table_structure = object_to_array($table_structure);
  187. $sql = "\n";
  188. $sql .= "-- -----------------------------\n";
  189. $sql .= "-- Table structure for `{$table}`\n";
  190. $sql .= "-- -----------------------------\n";
  191. $sql .= "\n";
  192. $sql .= "DROP TABLE IF EXISTS `{$table}`;\n";
  193. $sql .= "\n";
  194. //$sql .= trim($table_structure[0]['create table']) . ";\n";
  195. $sql .= trim($table_structure[0]['Create Table']) . ";\n";
  196. $sql .= "\n";
  197. //将每个表的数据导出到文件
  198. $table_data = DB::select("select * from " . $table);
  199. if ($table_data) {
  200. $table_data = object_to_array($table_data);
  201. //数据量5万条以下导出
  202. if (count($table_data) < 50000) {
  203. $sqlStr = "INSERT INTO `" . $table . "` VALUES (";
  204. foreach ($table_data as $k => $v) {
  205. $keys = array_keys($v);
  206. foreach ($keys as $row) {
  207. $sqlStr .= "'" . $v[$row] . "', ";
  208. }
  209. //去掉最后一个逗号和空格
  210. $sqlStr = substr($sqlStr, 0, strlen($sqlStr) - 2);
  211. $sqlStr = $sqlStr . '), (';
  212. }
  213. //去掉最后一个逗号和空格
  214. $sqlStr = substr($sqlStr, 0, strlen($sqlStr) - 3);
  215. $sqlStr .= ";\n";
  216. $sql .= $sqlStr;
  217. }
  218. }
  219. if (false === @fwrite($fp, $sql)) {
  220. error_jump($table . '备份失败');
  221. }
  222. }
  223. success_jump('备份完成');
  224. }
  225. /**
  226. * 备份数据库
  227. * @param String $tables 表名
  228. * @param Integer $id 表ID
  229. * @param Integer $start 起始行数
  230. */
  231. public function backup()
  232. {
  233. if (Helper::isPostRequest()) {
  234. $tables = request('tables');
  235. if (!$tables) {
  236. error_jump('参数错误');
  237. }
  238. $tables = explode(',', $tables);
  239. //初始化
  240. if (!(!empty($tables) && is_array($tables))) {
  241. error_jump('参数错误');
  242. }
  243. //读取备份配置
  244. $config = array(
  245. 'path' => base_path() . DIRECTORY_SEPARATOR . 'database_backup' . DIRECTORY_SEPARATOR, //路径
  246. 'part' => 20971520, // 该值用于限制压缩后的分卷最大长度。单位:B;建议设置20M
  247. 'compress' => 1, // 压缩备份文件需要PHP环境支持gzopen,gzwrite函数 0:不压缩 1:启用压缩
  248. 'level' => 9, // 压缩级别, 1:普通 4:一般 9:最高
  249. );
  250. //检查是否有正在执行的任务
  251. $lock = "{$config['path']}backup_database.lock";
  252. if (is_file($lock)) {
  253. error_jump('检测到有一个备份任务正在执行,请稍后再试');
  254. }
  255. //创建锁文件
  256. file_put_contents($lock, time());
  257. //检查备份目录是否可写 创建备份目录
  258. is_writeable($config['path']) || mkdir($config['path'], 0777, true);
  259. session('backup_config', $config);
  260. //生成备份文件信息
  261. $file = array(
  262. 'name' => date('Ymd-His'),
  263. 'part' => 1,
  264. );
  265. session('backup_file', $file);
  266. //缓存要备份的表
  267. session('backup_tables', $tables);
  268. //创建备份文件
  269. $database = new DatabaseUtil($file, $config);
  270. if (false !== $database->create()) {
  271. $tab = array('id' => 0, 'start' => 0);
  272. success_jump('初始化成功', '', array('tables' => $tables, 'tab' => $tab));
  273. }
  274. error_jump('初始化失败,备份文件创建失败');
  275. }
  276. $id = request('id');
  277. $start = request('start');
  278. //备份数据
  279. if (is_numeric($id) && is_numeric($start)) {
  280. error_jump('参数错误');
  281. }
  282. $tables = session('backup_tables');
  283. //备份指定表
  284. $database = new DatabaseUtil(session('backup_file'), session('backup_config'));
  285. $start = $database->backup($tables[$id], $start);
  286. if (false === $start) { //出错
  287. error_jump('备份出错');
  288. } elseif (0 === $start) { //下一表
  289. if (isset($tables[++$id])) {
  290. $tab = array('id' => $id, 'start' => 0);
  291. success_jump('备份完成', '', array('tab' => $tab));
  292. } else { //备份完成,清空缓存
  293. unlink(session('backup_config.path') . 'backup_database.lock');
  294. session('backup_tables', null);
  295. session('backup_file', null);
  296. session('backup_config', null);
  297. success_jump('备份完成');
  298. }
  299. } else {
  300. $tab = array('id' => $id, 'start' => $start[0]);
  301. $rate = floor(100 * ($start[0] / $start[1]));
  302. success_jump("正在备份...({$rate}%)", '', array('tab' => $tab));
  303. }
  304. }
  305. /**
  306. * 还原数据库
  307. */
  308. public function import($time = 0, $part = null, $start = null)
  309. {
  310. if (is_numeric($time) && is_null($part) && is_null($start)) { //初始化
  311. //获取备份文件信息
  312. $name = date('Ymd-His', $time) . '-*.sql*';
  313. $path = realpath(C('DB_PATH')) . DIRECTORY_SEPARATOR . $name;
  314. $files = glob($path);
  315. $list = array();
  316. foreach ($files as $name) {
  317. $basename = basename($name);
  318. $match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d');
  319. $gz = preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename);
  320. $list[$match[6]] = array($match[6], $name, $gz);
  321. }
  322. ksort($list);
  323. //检测文件正确性
  324. $last = end($list);
  325. if (count($list) === $last[0]) {
  326. session('backup_list', $list); //缓存备份列表
  327. success_jump('初始化完成', '', array('part' => 1, 'start' => 0));
  328. } else {
  329. error_jump('备份文件可能已经损坏,请检查');
  330. }
  331. } elseif (is_numeric($part) && is_numeric($start)) {
  332. $list = session('backup_list');
  333. $db = new DatabaseUtil($list[$part], array(
  334. 'path' => realpath(C('DB_PATH')) . DIRECTORY_SEPARATOR,
  335. 'compress' => $list[$part][2]
  336. ));
  337. $start = $db->import($start);
  338. if (false === $start) {
  339. error_jump('还原数据出错');
  340. } elseif (0 === $start) { //下一卷
  341. if (isset($list[++$part])) {
  342. $data = array('part' => $part, 'start' => 0);
  343. success_jump("正在还原...#{$part}", '', $data);
  344. } else {
  345. session('backup_list', null);
  346. success_jump('还原完成');
  347. }
  348. } else {
  349. $data = array('part' => $part, 'start' => $start[0]);
  350. if ($start[1]) {
  351. $rate = floor(100 * ($start[0] / $start[1]));
  352. success_jump("正在还原...#{$part} ({$rate}%)", '', $data);
  353. } else {
  354. $data['gz'] = 1;
  355. success_jump("正在还原...#{$part}", '', $data);
  356. }
  357. }
  358. } else {
  359. error_jump('参数错误');
  360. }
  361. }
  362. }