一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第18天,点击查看活动详情。
博客需要做一个数据库备份。
每天用phpmyadmin导出sql这个方法太麻烦(亏我想的出来)
嗯……写个用php备份数据库的方法
我这里用的是thinkphp5.0框架,去php中文网找了个对应的model,经过一番调试。
终于是好用了,下边是我使用的方法。
类文件存放位置
先放上类文件的代码:Baksql.php
<?php
namespace org;
class Baksql {
private $config=[];
private $handler;
private $tables = array();//需要备份的表
private $begin; //开始时间
private $error;//错误信息
public function __construct($config) {
$config['path']="databak/"; //默认目录
$config["sqlbakname"]=date("YmdHis",time()).".sql";//默认保存文件
$this->config = $config;
$this->begin = microtime(true);
header("Content-type: text/html;charset=utf-8");
$this->connect();
}
//首次进行pdo连接
private function connect() {
try{
$this->handler =new PDO("{$this->config['type']}:host={$this->config['hostname']};port={$this->config['hostport']};dbname={$this->config['database']};",
$this->config['username'],
$this->config['password'],
array(
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES {$this->config['charset']};",
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
));
}catch (PDOException $e) {
die ("Error!: " . $e->getMessage() . "<br/>");
}
}
/**
* 查询
* @param string $sql
* @return mixed
*/
private function query($sql = '')
{
$stmt = $this->handler->query($sql);
$stmt->setFetchMode(PDO::FETCH_NUM);
$list = $stmt->fetchAll();
return $list;
}
/**
* 获取全部表
* @param string $dbName
* @return array
*/
private function get_dbname($dbName = '*') {
$sql = 'SHOW TABLES';
$list = $this->query($sql);
$tables = array();
foreach ($list as $value)
{
$tables[] = $value[0];
}
return $tables;
}
/**
* 获取表定义语句
* @param string $table
* @return mixed
*/
private function get_dbhead($table = '')
{
$sql = "SHOW CREATE TABLE `{$table}`";
$ddl = $this->query($sql)[0][1] . ';';
return $ddl;
}
/**
* 获取表数据
* @param string $table
* @return mixed
*/
private function get_dbdata($table = '')
{
$sql = "SHOW COLUMNS FROM `{$table}`";
$list = $this->query($sql);
//字段
$columns = '';
//需要返回的SQL
$query = '';
foreach ($list as $value)
{
$columns .= "`{$value[0]}`,";
}
$columns = substr($columns, 0, -1);
$data = $this->query("SELECT * FROM `{$table}`");
foreach ($data as $value)
{
$dataSql = '';
foreach ($value as $v)
{
$dataSql .= "'{$v}',";
}
$dataSql = substr($dataSql, 0, -1);
$query .= "INSERT INTO `{$table}` ({$columns}) VALUES ({$dataSql});
";
}
return $query;
}
/**
* 写入文件
* @param array $tables
* @param array $ddl
* @param array $data
*/
private function writeToFile($tables = array(), $ddl = array(), $data = array())
{
$str = "/*
MySQL Database Backup Tools
";
$str .= "Server:{$this->config['hostname']}:{$this->config['hostport']}
";
$str .= "Database:{$this->config['database']}
";
$str .= "Data:" . date('Y-m-d H:i:s', time()) . "
*/
";
$str .= "SET FOREIGN_KEY_CHECKS=0;
";
$i = 0;
foreach ($tables as $table)
{
$str .= "-- ----------------------------
";
$str .= "-- Table structure for {$table}
";
$str .= "-- ----------------------------
";
$str .= "DROP TABLE IF EXISTS `{$table}`;
";
$str .= $ddl[$i] . "
";
$str .= "-- ----------------------------
";
$str .= "-- Records of {$table}
";
$str .= "-- ----------------------------
";
$str .= $data[$i] . "
";
$i++;
}
if(!file_exists($this->config['path'])){mkdir($this->config['path']);}
return file_put_contents($this->config['path'].$this->config['sqlbakname'], $str) ? true : false;
// return file_put_contents($this->config['path'].$this->config['sqlbakname'], $str) ? '备份成功!花费时间' . round(microtime(true) - $this->begin,2) . 'ms' : '备份失败!';
}
/**
* 设置要备份的表
* @param array $tables
*/
private function setTables($tables = array())
{
if (!empty($tables) && is_array($tables))
{
//备份指定表
$this->tables = $tables;
}
else
{
//备份全部表
$this->tables = $this->get_dbname();
}
}
/**
* 备份
* @param array $tables
* @return bool
*/
public function backup($tables = array())
{
//存储表定义语句的数组
$ddl = array();
//存储数据的数组
$data = array();
$this->setTables($tables);
if (!empty($this->tables))
{
foreach ($this->tables as $table)
{
$ddl[] = $this->get_dbhead($table);
$data[] = $this->get_dbdata($table);
}
//开始写入
return $this->writeToFile($this->tables, $ddl, $data);
}
else
{
$this->error = '数据库中没有表!';
return false;
}
}
/**
* 错误信息
* @return mixed
*/
public function getError()
{
return $this->error;
}
/**
* 还原方法
*/
public function restore($filename = '')
{
$path=$this->config['path'].$filename;
if (!file_exists($path))
{
// $this->error('SQL文件不存在!');
// return false;
$result['code'] = -1;
$result['msg'] = 'SQL文件不存在!';
return $result;
}
else
{
$sql = $this->parseSQL($path);
//dump($sql);die;
try
{
$this->handler->exec($sql);
// echo '还原成功!花费时间', round(microtime(true) - $this->begin,2) . 'ms';
$result['code'] = 1;
$result['msg'] = '还原成功';
return $result;
}
catch (PDOException $e)
{
// $this->error = $e->getMessage();
// return false;
$result['code'] = -2;
$result['msg'] = $e->getMessage();
return $result;
}
}
}
/**
* 解析SQL文件为SQL语句数组
* @param string $path
* @return array|mixed|string
*/
private function parseSQL($path = '')
{
$sql = file_get_contents($path);
$sql = explode("
", $sql);
//先消除--注释
$sql = array_filter($sql, function ($data)
{
if (empty($data) || preg_match('/^--.*/', $data))
{
return false;
}
else
{
return true;
}
});
$sql = implode('', $sql);
//删除/**/注释
$sql = preg_replace('//*.**//', '', $sql);
return $sql;
}
/**
* 下载备份
* @param string $fileName
* @return array|mixed|string
*/
public function downloadFile($fileName) {
$fileName=$this->config['path'].$fileName;
if (file_exists($fileName)){
ob_end_clean();
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Length: ' . filesize($fileName));
header('Content-Disposition: attachment; filename=' . basename($fileName));
readfile($fileName);
}else{
$this->error="文件有错误!";
}
}
/**
* 获取文件是时间
* @param string $file
* @return string
*/
private function getfiletime($file){
$path=$this->config['path'].$file;
$a = filemtime($path);
$time = date("Y-m-d H:i:s", $a);
return $time;
}
/**
* 获取文件是大小
* @param string $file
* @return string
*/
private function getfilesize($file){
$perms=stat($this->config['path'].$file);
$size = $perms['size'];
$a = ['B', 'KB', 'MB', 'GB', 'TB'];
$pos = 0;
while ($size >= 1024) {
$size /= 1024;
$pos++;
}
return round($size, 2). $a[$pos];
}
/**
* 获取文件列表
* @param string $Order 级别
* @return array
*/
public function get_filelist($Order = 0) {
$FilePath=$this->config['path'];
$FilePath = opendir($FilePath);
$FileAndFolderAyy=array();
$i=1;
while (false !== ($filename = readdir($FilePath))) {
if ($filename!="." && $filename!=".."){
$i++;
$FileAndFolderAyy[$i]['name'] = $filename;
$FileAndFolderAyy[$i]['time'] = $this->getfiletime($filename);
$FileAndFolderAyy[$i]['size'] = $this->getfilesize($filename);
}
}
$Order == 0 ? sort($FileAndFolderAyy) : rsort($FileAndFolderAyy);
return $FileAndFolderAyy;
}
public function delfilename($filename){
$path=$this->config['path'].$filename;
if (@unlink($path))
{
$result['code'] = 1;
$result['msg'] = "删除成功";
return $result;
// return '删除成功';
}
}
}
?>
这是控制器中的代码
<?php
namespace appadmincontroller;
use thinkController;
use thinkRequest;//使用request对象
use thinkDb;//引入数据库操作类
use thinkLoader;//使用自动加载类
use thinkSession;//使用session驱动
class Dbbak extends Base
{
/**
* 数据库列表请求
* date:20180530
* another:camellia
*/
public function list(){
$sql = new orgBaksql( hinkConfig::get("database"));
$dblist = $sql->get_filelist();//获取文件列表
// $dbfilenum = $this->ShuLiang("databak/");//获取文件数量
$result['dblist'] = $dblist;
// $result['dbfilenum'] = $dbfilenum;
echo json_encode($result);die;
}
/**
* another:camellia
* date:20180531
* 给我一个文件夹,返回该文件夹下所有的文件数量
*/
public function ShuLiang($url)//造一个方法,给一个参数
{
$sl = 0;//造一个变量,让他默认值为0;
$arr = glob($url);//把该路径下所有的文件存到一个数组里面;
foreach ($arr as $v)//循环便利一下,吧数组$arr赋给$v;
{
if(is_file($v))//先用个if判断一下这个文件夹下的文件是不是文件,有可能是文件夹;
{
$sl++;//如果是文件,数量加一;
}
else
{
$sl += ShuLiang($v."/*");//如果是文件夹,那么再调用函数本身获取此文件夹下文件的数量,这种方法称为递归;
}
}
return $sl;//当这个方法走完后,返回一个值$sl,这个值就是该路径下所有的文件数量;
}
/**
* 数据库备份,删除,还原
* date:20180525
* another:camellia
* 正常a标签请求下载是好用的 但是 ajax请求不好用~为啥我也不知道
* @param tp 就是要操作的类型
* @param name 还原数据库的时候传的参数(要还原的文件名)
*/
public function abcde(){
$type = input("tp");//要操作的类型(还原,删除,备份)
$name = input("name");//文件名
$sql = new orgBaksql( hinkConfig::get("database"));
// 备份
if($type == "aaa"){
$res = $sql->backup();
if($res){
$result['code'] = 1;
// $result['msg'] = "数据库备份成功";
$this->baklist();
}else{
$result['code'] = -1;
$result['msg'] = "数据库备份失败";
}
// 下载
}else if($type == "bbb"){
$sql->downloadFile($name);
break;
// 还原
}else if($type == "ccc"){
$res = $sql->restore($name);
if($res['code'] > 0){
$result['code'] = 1;
$this->baklist();
}else{
$result['code'] = -1;
$result['msg'] = $res['msg'];
}
// 删除
}else if($type == "ddd"){
$res = $sql->delfilename($name);
// var_dump($res);die;
if($res['code']){
$result['code'] = 1;
$this->baklist();
}else{
$result['code'] = -1;
$result['msg'] = "备份删除失败";
}
}
echo json_encode($result);die;
}
}
这个方法一直在用,但是吧,总是觉得有点麻烦。
还需要引一个类文件,如果大数据量会慢的(我是在为我的懒找借口)
这个时候我发现了一个神奇的东西……linux命令
下边是我改造重写之后的方法(我懒,本着能改一个地方绝不改两个地方(改php,就不改前端)的原则……请不要在意那些细节)
<?php
namespace appadmincontroller;
use thinkController;
use thinkRequest;//使用request对象
use thinkDb;//引入数据库操作类
use thinkLoader;//使用自动加载类
use thinkSession;//使用session驱动
class Dbbak extends Base
{
/**
* 数据库列表请求
* date:20181022
* another:camellia
*/
public function list(){
$dblist = Db::query('select * from dbback;');
$result['dblist'] = $dblist;
echo json_encode($result);die;
}
/**
* 数据库备份,删除,还原
* date:20181022
* another:camellia
* 正常a标签请求下载是好用的 但是 ajax请求不好用~为啥我也不知道
*/
public function abcde()
{
$type = input("tp");//要操作的类型(还原,删除,备份)
$id = input("name");//要还原的id
if($type == "aaa"){//备份
$result = $this->backup();
}else if($type == "bbb"){// 下载(未做)
// $sql->downloadFile($name);
// break;
}else if($type == "ccc"){// 还原
$result = $this->restore($id);
}else if($type == "ddd"){// 删除
$result = $this->del($id);
}
echo json_encode($result);die;
}
/**
* 数据库删除
*/
public function ddd($id)
{
$dbback = db('dbback');
$dbinfo = $dbback->where('id',$id)->find();
$filepath = $dbinfo['path'].$dbinfo['filename'];//根据数据库中的数据拼装文件名和路径
if($dbinfo['filename'] == ''){
$result['code'] = -1;
$result['msg'] = '文件名缺失,删除失败';
return $result;
}
// 删除文件命令
/*$shell = "rm -rf ".$filepath;
exec($shell);//*/
$res = Db::execute("delete FROM dbback WHERE id = '{$id}'");
if($res){
$result['code'] = 1;
$result['msg'] = '操作成功';
}else{
$result['code'] = -1;
$result['msg'] = '操作失败';
}
return $result;
}
/**
* 数据库还原
*/
public function ccc($id){
$dbback = db('dbback');
$dbinfo = $dbback->where('id',$id)->find();
$dbname = DB_NAME;//数据库名称
$root_pwd = DB_PASS;//数据库密码
$filepath = $dbinfo['path'].$dbinfo['filename'];
$shell = "mysql -uroot -p$root_pwd $dbname < ".$filepath;
exec($shell);
$result['code'] = 1;
$result['msg'] = '操作成功';
return $result;
}
/**
* 数据库备份
*/
public function aaa(){
// 文件存放名称
$back_name = DB_NAME.'_'.time().'_'.rand(10000, 99999).'.sql';//备份文件名称
$root_pwd = DB_PASS;//数据库密码
$dbname = DB_NAME;//数据库名称
$path = BACK_PATH;//备份文件存放路径
// 数据库备份语句
$shell = "mysqldump -u root -p$root_pwd --databases $dbname > ".$path.$back_name;
exec($shell);
// 现将备份记录写入数据库
$res = Db::execute("insert into dbback (filename,path,backtime) values ('{$back_name}','{$path}',NOW())");
$result['code'] = 1;
$result['msg'] = '操作成功';
return $result;
}
}
下边是linux命令备份方法中使用到的数据表
CREATE TABLE `dbback` (
`id` int(11) NOT NULL,
`filename` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '文件名',
`path` varchar(50) COLLATE utf8mb4_bin NOT NULL COMMENT '存放路径',
`backtime` datetime NOT NULL COMMENT '备份时间',
PRIMARY KEY (`id`);
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
多说一句 上边的删除方法里边是吧删除文件的命令注释了,也就是
rm -rf 命令
如果没有特别准的把握,最好不要用……命令写错可能会递归删除
到最后总结一下吧,使用mysql备份类来实现数据库的备份以及还原操作这个本身是没有问题的,用着也没有问题,但是后期的扩展性可能不是很好。我这里用的是上面我写的第二种方法,第二种方法我们可以自定义数据库备份的命令,这个学习成本要低很多,至于扩展性,只要命令能写的出来,其他的都不是什么大问题。对于只需要数据库备份还原的同学,第一个mysql备份类就可以了,对于愿意折腾,不怕麻烦,比较看重后期扩展性的同学,推荐使用第二种方法。
有好的建议,请在下方输入评论
欢迎访问个人博客 guanchao.site