*/ class DataBaseController extends BaseController { /** * 存储路径 * * @var */ public $path; /** * 配置信息 * * @var */ public $config; public function init() { parent::init(); /** ------ 备份配置配置 ------ **/ $this->path = Yii::getAlias('@addons') . '/RfDevTool/console/backup'; // 数据库备份根路径 // 读取备份配置 $this->config = [ 'path' => realpath($this->path) . DIRECTORY_SEPARATOR, 'part' => 20 * 1204 * 1024, // 数据库备份卷大小 'compress' => 1, // 压缩级别 'level' => 9, // 数据库备份文件压缩级别 'lock' => 'backup.lock', // 数据库备份锁 ]; // 判断目测是否存在,不存在则创建 if (!is_dir($this->path)) { mkdir($this->path, 0755, true); } } /** * 备份列表 * * @return string * @throws \yii\db\Exception */ public function actionBackups() { $models = Yii::$app->db->createCommand('SHOW TABLE STATUS')->queryAll(); $models = array_map('array_change_key_case', $models); return $this->render('backups', [ 'models' => $models ]); } /** * 修改表名称 * * @return array|mixed */ public function actionUpdateTableComment() { $table = Yii::$app->request->get('table'); $comment = Yii::$app->request->get('comment'); try { $migration = new Migration(); $migration->addCommentOnTable($table, trim($comment)); ob_clean(); } catch (\Exception $e) { } return ResultHelper::json(200, '修改成功'); } /** * 备份检测 * * @return array */ public function actionExport() { $tables = Yii::$app->request->post('tables'); if (empty($tables)) { return ResultHelper::json(404, '请选择要备份的表'); } // 读取备份配置 $config = $this->config; // 检查备份目录是否可写 if (!is_writeable($config['path'])) { return ResultHelper::json(404, '备份目录不存在或不可写,请检查后重试!'); } // 检查是否有正在执行的任务 $lock = $config['path'] . $config['lock']; if (is_file($lock)) { return ResultHelper::json(404, '检测到有一个备份任务正在执行,请稍后或清理缓存后再试'); } // 创建锁文件 file_put_contents($lock, time()); // 生成备份文件信息 $file = [ 'name' => date('Ymd-His', time()), 'part' => 1, ]; // 创建备份文件 $Database = new Database($file, $config); if (false !== $Database->create()) { // 缓存配置信息 Yii::$app->session->set('backup_config', $config); // 缓存文件信息 Yii::$app->session->set('backup_file', $file); // 缓存要备份的表 Yii::$app->session->set('backup_tables', $tables); $tab = ['id' => 0, 'start' => 0]; return ResultHelper::json(200, '初始化成功!', [ 'tables' => $tables, 'tab' => $tab ]); } return ResultHelper::json(404, '初始化失败,备份文件创建失败!'); } /** * 开始备份 * * @return array * @throws \yii\db\Exception */ public function actionExportStart() { $tables = Yii::$app->session->get('backup_tables'); $file = Yii::$app->session->get('backup_file'); $config = Yii::$app->session->get('backup_config'); $id = Yii::$app->request->post('id'); $start = Yii::$app->request->post('start'); // 备份指定表 $database = new Database($file, $config); $start = $database->backup($tables[$id], $start); if ($start === false) { return ResultHelper::json(404, '备份出错!'); } elseif ($start === 0) { // 下一表 if (isset($tables[++$id])) { $tab = ['id' => $id, 'start' => 0]; return ResultHelper::json(200, '备份完成', [ 'tablename' => $tables[--$id], 'achieveStatus' => 0, 'tab' => $tab, ]); } // 备份完成,清空缓存 unlink($config['path'] . $config['lock']); Yii::$app->session->set('backup_tables', null); Yii::$app->session->set('backup_file', null); Yii::$app->session->set('backup_config', null); return ResultHelper::json(200, '备份完成', [ 'tablename' => $tables[--$id], 'achieveStatus' => 1 ]); } else { $tab = ['id' => $id, 'start' => $start[0]]; $rate = floor(100 * ($start[0] / $start[1])); // 对下一个表进行备份 return ResultHelper::json(200, "正在备份...({$rate}%)", [ 'tablename' => $tables[$id], 'achieveStatus' => 0, 'tab' => $tab, ]); } } /** * 优化表 * * @return array * @param String|array $tables 表名 * @throws \yii\db\Exception */ public function actionOptimize() { $tables = Yii::$app->request->post('tables', ''); if (!$tables) { return ResultHelper::json(404, '请指定要优化的表!'); } // 判断是否是数组 if (is_array($tables)) { $tables = implode('`,`', $tables); if (Yii::$app->db->createCommand("OPTIMIZE TABLE `{$tables}`")->queryAll()) { return ResultHelper::json(200, '数据表优化完成'); } return ResultHelper::json(404, '数据表优化出错请重试!'); } $list = Yii::$app->db->createCommand("REPAIR TABLE `{$tables}`")->queryOne(); // 判断是否成功 if ($list['Msg_text'] == "OK") { return ResultHelper::json(200, "数据表'{$tables}'优化完成!"); } return ResultHelper::json(404, "数据表'{$tables}'优化出错!错误信息:" . $list['Msg_text']); } /** * 修复表 * * @return array * @param String|array $tables 表名 * @throws \yii\db\Exception */ public function actionRepair() { $tables = Yii::$app->request->post('tables', ''); if (!$tables) { return ResultHelper::json(404, '请指定要修复的表!'); } // 判断是否是数组 if (is_array($tables)) { $tables = implode('`,`', $tables); if (Yii::$app->db->createCommand("REPAIR TABLE `{$tables}`")->queryAll()) { return ResultHelper::json(200, '数据表修复化完成'); } return ResultHelper::json(404, '数据表修复出错请重试!'); } $list = Yii::$app->db->createCommand("REPAIR TABLE `{$tables}`")->queryOne(); if ($list['Msg_text'] == "OK") { return ResultHelper::json(200, "数据表'{$tables}'修复完成!"); } return ResultHelper::json(404, "数据表'{$tables}'修复出错!错误信息:" . $list['Msg_text']); } /********************************************************************************/ /************************************还原数据库************************************/ /********************************************************************************/ /** * 还原列表 */ protected function actionRestore() { Yii::$app->language = ""; // 文件夹路径 $path = $this->path; $flag = \FilesystemIterator::KEY_AS_FILENAME; $glob = new \FilesystemIterator($path, $flag); $list = []; foreach ($glob as $name => $file) { // 正则匹配文件名 if (preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql(?:\.gz)?$/', $name)) { $name = sscanf($name, '%4s%2s%2s-%2s%2s%2s-%d'); $date = "{$name[0]}-{$name[1]}-{$name[2]}"; $time = "{$name[3]}:{$name[4]}:{$name[5]}"; $part = $name[6]; if (isset($list["{$date} {$time}"])) { $info = $list["{$date} {$time}"]; $info['part'] = max($info['part'], $part); $info['size'] = $info['size'] + $file->getSize(); } else { $info['part'] = $part; $info['size'] = $file->getSize(); } $extension = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION)); $info['compress'] = ($extension === 'SQL') ? '-' : $extension; $info['time'] = strtotime("{$date} {$time}"); $info['filename'] = $file->getBasename(); $list["{$date} {$time}"] = $info; } } krsort($list); return $this->render('restore', [ 'list' => $list ]); } /** * 初始化还原 */ protected function actionRestoreInit() { $time = Yii::$app->request->post('time'); $config = $this->config; // 获取备份文件信息 $name = date('Ymd-His', $time) . '-*.sql*'; $path = realpath($config['path']) . DIRECTORY_SEPARATOR . $name; $files = glob($path); $list = []; $size = 0; foreach ($files as $name => $file) { $size += filesize($file); $basename = basename($file); $match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d'); $gz = preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename); $list[$match[6]] = array($match[6], $file, $gz); } // 排序数组 ksort($list); // 检测文件正确性 $last = end($list); if (count($list) === $last[0]) { // 缓存备份列表 Yii::$app->session->set('backup_list', $list); return ResultHelper::json(200, '初始化完成', [ 'part' => 1, 'start' => 0, ]); } return ResultHelper::json(200, "备份文件可能已经损坏,请检查!"); } /** * 开始还原到数据库 * * @return array * @throws \yii\db\Exception */ protected function actionRestoreStart() { set_time_limit(0); $config = $this->config; $part = Yii::$app->request->post('part'); $start = Yii::$app->request->post('start'); $list = Yii::$app->session->get('backup_list'); $arr = [ 'path' => realpath($config['path']) . DIRECTORY_SEPARATOR, 'compress' => $list[$part][2] ]; $db = new Database($list[$part], $arr); $start = $db->import($start); if ($start === false) { return ResultHelper::json(200, "备份文件可能已经损坏,请检查!"); } elseif ($start === 0) { // 下一卷 if (isset($list[++$part])) { return ResultHelper::json(200, "正在还原...#{$part}", [ 'part' => $part, 'start1' => $start, 'start' => 0, 'achieveStatus' => 0, ]); } Yii::$app->session->set('backup_list', null); return ResultHelper::json(200, "还原完成"); } else { if ($start[1]) { $rate = floor(100 * ($start[0] / $start[1])); return ResultHelper::json(200, "正在还原...#{$part} ({$rate}%)", [ 'part' => $part, 'start' => $start[0], 'achieveStatus' => 0, ]); } return ResultHelper::json(200, "正在还原...#{$part}", [ 'part' => $part, 'start' => $start[0], 'gz' => 1, 'start1' => $start, 'achieveStatus' => 0, ]); } } /** * 删除文件 */ public function actionDelete($time) { $config = $this->config; $name = date('Ymd-His', $time) . '-*.sql*'; $path = realpath($config['path']) . DIRECTORY_SEPARATOR . $name; array_map("unlink", glob($path)); if (count(glob($path))) { return $this->message('文件删除失败,请检查权限!', $this->redirect(['restore']), 'error'); } return $this->message('文件删除成功', $this->redirect(['restore'])); } /** * 数据字典 * * @return array * @throws \yii\db\Exception */ public function actionDataDictionary() { // 获取全部表结构信息 $tableSchema = Yii::$app->db->schema->getTableSchemas(); $tableSchema = ArrayHelper::toArray($tableSchema); // 获取全部表信息 $tables = Yii::$app->db->createCommand('SHOW TABLE STATUS')->queryAll(); $tables = array_map('array_change_key_case', $tables); $tableSchemas = []; foreach ($tableSchema as $item) { $key = $item['name']; $tableSchemas[$key]['table_name'] = $key;// 表名 $tableSchemas[$key]['item'] = []; foreach ($item['columns'] as $column) { $tmpArr = []; $tmpArr['name'] = $column['name']; // 字段名称 $tmpArr['type'] = $column['dbType']; // 类型 $tmpArr['defaultValue'] = $column['defaultValue']; // 默认值 $tmpArr['comment'] = $column['comment']; // 注释 $tmpArr['isPrimaryKey'] = $column['isPrimaryKey']; // 是否主键 $tmpArr['autoIncrement'] = $column['autoIncrement']; // 是否自动增长 $tmpArr['unsigned'] = $column['unsigned']; // 是否无符号 $tmpArr['allowNull'] = $column['allowNull']; // 是否允许为空 $tableSchemas[$key]['item'][] = $tmpArr; unset($tmpArr); } } /*--------------- 开始生成 --------------*/ $str = ''; $i = 0; foreach ($tableSchemas as $key => $datum) { $table_comment = $tables[$i]['comment']; $str .= "### {$key}" . "
"; $str .= "#### {$table_comment}" . "
"; $str .= "字段 | 类型 | 允许为空 | 默认值 | 字段说明" . "
"; $str .= "---|---|---|---|---" . "
"; foreach ($datum['item'] as $item) { empty($item['comment']) && $item['comment'] = "无"; $item['allowNull'] = !empty($item['allowNull']) ? "是" : '否'; is_array($item['defaultValue']) && $item['defaultValue'] = json_encode($item['defaultValue']); $str .= "{$item['name']} | {$item['type']} | {$item['allowNull']} | {$item['defaultValue']} | {$item['comment']}" . "
"; } $str .= "
"; $i++; } return ResultHelper::json(200, '返回成功', ['str' => $str]); } }