DataBaseController.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. <?php
  2. namespace addons\RfDevTool\backend\controllers;
  3. use Yii;
  4. use common\helpers\ArrayHelper;
  5. use common\helpers\ResultHelper;
  6. use addons\RfDevTool\common\models\Database;
  7. use yii\db\Migration;
  8. /**
  9. * 数据备份还原
  10. *
  11. * Class DataBaseController
  12. * @package backend\modules\sys\controllers
  13. * @author jianyan74 <751393839@qq.com>
  14. */
  15. class DataBaseController extends BaseController
  16. {
  17. /**
  18. * 存储路径
  19. *
  20. * @var
  21. */
  22. public $path;
  23. /**
  24. * 配置信息
  25. *
  26. * @var
  27. */
  28. public $config;
  29. public function init()
  30. {
  31. parent::init();
  32. /** ------ 备份配置配置 ------ **/
  33. $this->path = Yii::getAlias('@addons') . '/RfDevTool/console/backup'; // 数据库备份根路径
  34. // 读取备份配置
  35. $this->config = [
  36. 'path' => realpath($this->path) . DIRECTORY_SEPARATOR,
  37. 'part' => 20 * 1204 * 1024, // 数据库备份卷大小
  38. 'compress' => 1, // 压缩级别
  39. 'level' => 9, // 数据库备份文件压缩级别
  40. 'lock' => 'backup.lock', // 数据库备份锁
  41. ];
  42. // 判断目测是否存在,不存在则创建
  43. if (!is_dir($this->path)) {
  44. mkdir($this->path, 0755, true);
  45. }
  46. }
  47. /**
  48. * 备份列表
  49. *
  50. * @return string
  51. * @throws \yii\db\Exception
  52. */
  53. public function actionBackups()
  54. {
  55. $models = Yii::$app->db->createCommand('SHOW TABLE STATUS')->queryAll();
  56. $models = array_map('array_change_key_case', $models);
  57. return $this->render('backups', [
  58. 'models' => $models
  59. ]);
  60. }
  61. /**
  62. * 修改表名称
  63. *
  64. * @return array|mixed
  65. */
  66. public function actionUpdateTableComment()
  67. {
  68. $table = Yii::$app->request->get('table');
  69. $comment = Yii::$app->request->get('comment');
  70. try {
  71. $migration = new Migration();
  72. $migration->addCommentOnTable($table, trim($comment));
  73. ob_clean();
  74. } catch (\Exception $e) {
  75. }
  76. return ResultHelper::json(200, '修改成功');
  77. }
  78. /**
  79. * 备份检测
  80. *
  81. * @return array
  82. */
  83. public function actionExport()
  84. {
  85. $tables = Yii::$app->request->post('tables');
  86. if (empty($tables)) {
  87. return ResultHelper::json(404, '请选择要备份的表');
  88. }
  89. // 读取备份配置
  90. $config = $this->config;
  91. // 检查备份目录是否可写
  92. if (!is_writeable($config['path'])) {
  93. return ResultHelper::json(404, '备份目录不存在或不可写,请检查后重试!');
  94. }
  95. // 检查是否有正在执行的任务
  96. $lock = $config['path'] . $config['lock'];
  97. if (is_file($lock)) {
  98. return ResultHelper::json(404, '检测到有一个备份任务正在执行,请稍后或清理缓存后再试');
  99. }
  100. // 创建锁文件
  101. file_put_contents($lock, time());
  102. // 生成备份文件信息
  103. $file = [
  104. 'name' => date('Ymd-His', time()),
  105. 'part' => 1,
  106. ];
  107. // 创建备份文件
  108. $Database = new Database($file, $config);
  109. if (false !== $Database->create()) {
  110. // 缓存配置信息
  111. Yii::$app->session->set('backup_config', $config);
  112. // 缓存文件信息
  113. Yii::$app->session->set('backup_file', $file);
  114. // 缓存要备份的表
  115. Yii::$app->session->set('backup_tables', $tables);
  116. $tab = ['id' => 0, 'start' => 0];
  117. return ResultHelper::json(200, '初始化成功!', [
  118. 'tables' => $tables,
  119. 'tab' => $tab
  120. ]);
  121. }
  122. return ResultHelper::json(404, '初始化失败,备份文件创建失败!');
  123. }
  124. /**
  125. * 开始备份
  126. *
  127. * @return array
  128. * @throws \yii\db\Exception
  129. */
  130. public function actionExportStart()
  131. {
  132. $tables = Yii::$app->session->get('backup_tables');
  133. $file = Yii::$app->session->get('backup_file');
  134. $config = Yii::$app->session->get('backup_config');
  135. $id = Yii::$app->request->post('id');
  136. $start = Yii::$app->request->post('start');
  137. // 备份指定表
  138. $database = new Database($file, $config);
  139. $start = $database->backup($tables[$id], $start);
  140. if ($start === false) {
  141. return ResultHelper::json(404, '备份出错!');
  142. } elseif ($start === 0) {
  143. // 下一表
  144. if (isset($tables[++$id])) {
  145. $tab = ['id' => $id, 'start' => 0];
  146. return ResultHelper::json(200, '备份完成', [
  147. 'tablename' => $tables[--$id],
  148. 'achieveStatus' => 0,
  149. 'tab' => $tab,
  150. ]);
  151. }
  152. // 备份完成,清空缓存
  153. unlink($config['path'] . $config['lock']);
  154. Yii::$app->session->set('backup_tables', null);
  155. Yii::$app->session->set('backup_file', null);
  156. Yii::$app->session->set('backup_config', null);
  157. return ResultHelper::json(200, '备份完成', [
  158. 'tablename' => $tables[--$id],
  159. 'achieveStatus' => 1
  160. ]);
  161. } else {
  162. $tab = ['id' => $id, 'start' => $start[0]];
  163. $rate = floor(100 * ($start[0] / $start[1]));
  164. // 对下一个表进行备份
  165. return ResultHelper::json(200, "正在备份...({$rate}%)", [
  166. 'tablename' => $tables[$id],
  167. 'achieveStatus' => 0,
  168. 'tab' => $tab,
  169. ]);
  170. }
  171. }
  172. /**
  173. * 优化表
  174. *
  175. * @return array
  176. * @param String|array $tables 表名
  177. * @throws \yii\db\Exception
  178. */
  179. public function actionOptimize()
  180. {
  181. $tables = Yii::$app->request->post('tables', '');
  182. if (!$tables) {
  183. return ResultHelper::json(404, '请指定要优化的表!');
  184. }
  185. // 判断是否是数组
  186. if (is_array($tables)) {
  187. $tables = implode('`,`', $tables);
  188. if (Yii::$app->db->createCommand("OPTIMIZE TABLE `{$tables}`")->queryAll()) {
  189. return ResultHelper::json(200, '数据表优化完成');
  190. }
  191. return ResultHelper::json(404, '数据表优化出错请重试!');
  192. }
  193. $list = Yii::$app->db->createCommand("REPAIR TABLE `{$tables}`")->queryOne();
  194. // 判断是否成功
  195. if ($list['Msg_text'] == "OK") {
  196. return ResultHelper::json(200, "数据表'{$tables}'优化完成!");
  197. }
  198. return ResultHelper::json(404, "数据表'{$tables}'优化出错!错误信息:" . $list['Msg_text']);
  199. }
  200. /**
  201. * 修复表
  202. *
  203. * @return array
  204. * @param String|array $tables 表名
  205. * @throws \yii\db\Exception
  206. */
  207. public function actionRepair()
  208. {
  209. $tables = Yii::$app->request->post('tables', '');
  210. if (!$tables) {
  211. return ResultHelper::json(404, '请指定要修复的表!');
  212. }
  213. // 判断是否是数组
  214. if (is_array($tables)) {
  215. $tables = implode('`,`', $tables);
  216. if (Yii::$app->db->createCommand("REPAIR TABLE `{$tables}`")->queryAll()) {
  217. return ResultHelper::json(200, '数据表修复化完成');
  218. }
  219. return ResultHelper::json(404, '数据表修复出错请重试!');
  220. }
  221. $list = Yii::$app->db->createCommand("REPAIR TABLE `{$tables}`")->queryOne();
  222. if ($list['Msg_text'] == "OK") {
  223. return ResultHelper::json(200, "数据表'{$tables}'修复完成!");
  224. }
  225. return ResultHelper::json(404, "数据表'{$tables}'修复出错!错误信息:" . $list['Msg_text']);
  226. }
  227. /********************************************************************************/
  228. /************************************还原数据库************************************/
  229. /********************************************************************************/
  230. /**
  231. * 还原列表
  232. */
  233. protected function actionRestore()
  234. {
  235. Yii::$app->language = "";
  236. // 文件夹路径
  237. $path = $this->path;
  238. $flag = \FilesystemIterator::KEY_AS_FILENAME;
  239. $glob = new \FilesystemIterator($path, $flag);
  240. $list = [];
  241. foreach ($glob as $name => $file) {
  242. // 正则匹配文件名
  243. if (preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql(?:\.gz)?$/', $name)) {
  244. $name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d');
  245. $date = "{$name[0]}-{$name[1]}-{$name[2]}";
  246. $time = "{$name[3]}:{$name[4]}:{$name[5]}";
  247. $part = $name[6];
  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. $extension = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
  257. $info['compress'] = ($extension === 'SQL') ? '-' : $extension;
  258. $info['time'] = strtotime("{$date} {$time}");
  259. $info['filename'] = $file->getBasename();
  260. $list["{$date} {$time}"] = $info;
  261. }
  262. }
  263. krsort($list);
  264. return $this->render('restore', [
  265. 'list' => $list
  266. ]);
  267. }
  268. /**
  269. * 初始化还原
  270. */
  271. protected function actionRestoreInit()
  272. {
  273. $time = Yii::$app->request->post('time');
  274. $config = $this->config;
  275. // 获取备份文件信息
  276. $name = date('Ymd-His', $time) . '-*.sql*';
  277. $path = realpath($config['path']) . DIRECTORY_SEPARATOR . $name;
  278. $files = glob($path);
  279. $list = [];
  280. $size = 0;
  281. foreach ($files as $name => $file) {
  282. $size += filesize($file);
  283. $basename = basename($file);
  284. $match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d');
  285. $gz = preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename);
  286. $list[$match[6]] = array($match[6], $file, $gz);
  287. }
  288. // 排序数组
  289. ksort($list);
  290. // 检测文件正确性
  291. $last = end($list);
  292. if (count($list) === $last[0]) {
  293. // 缓存备份列表
  294. Yii::$app->session->set('backup_list', $list);
  295. return ResultHelper::json(200, '初始化完成', [
  296. 'part' => 1,
  297. 'start' => 0,
  298. ]);
  299. }
  300. return ResultHelper::json(200, "备份文件可能已经损坏,请检查!");
  301. }
  302. /**
  303. * 开始还原到数据库
  304. *
  305. * @return array
  306. * @throws \yii\db\Exception
  307. */
  308. protected function actionRestoreStart()
  309. {
  310. set_time_limit(0);
  311. $config = $this->config;
  312. $part = Yii::$app->request->post('part');
  313. $start = Yii::$app->request->post('start');
  314. $list = Yii::$app->session->get('backup_list');
  315. $arr = [
  316. 'path' => realpath($config['path']) . DIRECTORY_SEPARATOR,
  317. 'compress' => $list[$part][2]
  318. ];
  319. $db = new Database($list[$part], $arr);
  320. $start = $db->import($start);
  321. if ($start === false) {
  322. return ResultHelper::json(200, "备份文件可能已经损坏,请检查!");
  323. } elseif ($start === 0) {
  324. // 下一卷
  325. if (isset($list[++$part])) {
  326. return ResultHelper::json(200, "正在还原...#{$part}", [
  327. 'part' => $part,
  328. 'start1' => $start,
  329. 'start' => 0,
  330. 'achieveStatus' => 0,
  331. ]);
  332. }
  333. Yii::$app->session->set('backup_list', null);
  334. return ResultHelper::json(200, "还原完成");
  335. } else {
  336. if ($start[1]) {
  337. $rate = floor(100 * ($start[0] / $start[1]));
  338. return ResultHelper::json(200, "正在还原...#{$part} ({$rate}%)", [
  339. 'part' => $part,
  340. 'start' => $start[0],
  341. 'achieveStatus' => 0,
  342. ]);
  343. }
  344. return ResultHelper::json(200, "正在还原...#{$part}", [
  345. 'part' => $part,
  346. 'start' => $start[0],
  347. 'gz' => 1,
  348. 'start1' => $start,
  349. 'achieveStatus' => 0,
  350. ]);
  351. }
  352. }
  353. /**
  354. * 删除文件
  355. */
  356. public function actionDelete($time)
  357. {
  358. $config = $this->config;
  359. $name = date('Ymd-His', $time) . '-*.sql*';
  360. $path = realpath($config['path']) . DIRECTORY_SEPARATOR . $name;
  361. array_map("unlink", glob($path));
  362. if (count(glob($path))) {
  363. return $this->message('文件删除失败,请检查权限!', $this->redirect(['restore']), 'error');
  364. }
  365. return $this->message('文件删除成功', $this->redirect(['restore']));
  366. }
  367. /**
  368. * 数据字典
  369. *
  370. * @return array
  371. * @throws \yii\db\Exception
  372. */
  373. public function actionDataDictionary()
  374. {
  375. // 获取全部表结构信息
  376. $tableSchema = Yii::$app->db->schema->getTableSchemas();
  377. $tableSchema = ArrayHelper::toArray($tableSchema);
  378. // 获取全部表信息
  379. $tables = Yii::$app->db->createCommand('SHOW TABLE STATUS')->queryAll();
  380. $tables = array_map('array_change_key_case', $tables);
  381. $tableSchemas = [];
  382. foreach ($tableSchema as $item) {
  383. $key = $item['name'];
  384. $tableSchemas[$key]['table_name'] = $key;// 表名
  385. $tableSchemas[$key]['item'] = [];
  386. foreach ($item['columns'] as $column) {
  387. $tmpArr = [];
  388. $tmpArr['name'] = $column['name']; // 字段名称
  389. $tmpArr['type'] = $column['dbType']; // 类型
  390. $tmpArr['defaultValue'] = $column['defaultValue']; // 默认值
  391. $tmpArr['comment'] = $column['comment']; // 注释
  392. $tmpArr['isPrimaryKey'] = $column['isPrimaryKey']; // 是否主键
  393. $tmpArr['autoIncrement'] = $column['autoIncrement']; // 是否自动增长
  394. $tmpArr['unsigned'] = $column['unsigned']; // 是否无符号
  395. $tmpArr['allowNull'] = $column['allowNull']; // 是否允许为空
  396. $tableSchemas[$key]['item'][] = $tmpArr;
  397. unset($tmpArr);
  398. }
  399. }
  400. /*--------------- 开始生成 --------------*/
  401. $str = '';
  402. $i = 0;
  403. foreach ($tableSchemas as $key => $datum) {
  404. $table_comment = $tables[$i]['comment'];
  405. $str .= "### {$key}" . "<br>";
  406. $str .= "#### {$table_comment}" . "<br>";
  407. $str .= "字段 | 类型 | 允许为空 | 默认值 | 字段说明" . "<br>";
  408. $str .= "---|---|---|---|---" . "<br>";
  409. foreach ($datum['item'] as $item) {
  410. empty($item['comment']) && $item['comment'] = "无";
  411. $item['allowNull'] = !empty($item['allowNull']) ? "是" : '否';
  412. is_array($item['defaultValue']) && $item['defaultValue'] = json_encode($item['defaultValue']);
  413. $str .= "{$item['name']} | {$item['type']} | {$item['allowNull']} | {$item['defaultValue']} | {$item['comment']}" . "<br>";
  414. }
  415. $str .= "<br>";
  416. $i++;
  417. }
  418. return ResultHelper::json(200, '返回成功', ['str' => $str]);
  419. }
  420. }
粤ICP备19079148号