如何使用 PHP 进行数据库备份和恢复?Tinkphp5.0数据库备份、还原和下载完整示例

导读:

参考了很多前辈们的代码,才最终做出来。我的数据库比较小,所以没有弄分卷备份,做开发嘛,都是根据实际需求做的,代码简单实用就好!先上两张效果图给大家看看:

 

 

一、先写一个备份数据库类 Backup.php

小白可能会有疑问,为什么我要单独写成一个类?因为这个方法可能会多次调用,写成一个类,可以减少代码的重用,比如说手动备份要调用,每天定时自动备份也需要调用。好了,直接上代码:

<?php
namespace app\common;
 
use think\Db;
use think\Model;
use think\Cookie;
use think\Exception;
/**
 * 备份数据库类
 */
class Backup {
    /*
     * 导出数据库到sql文件
     * @param $string $dir  备份保存目录(可选,默认为data)
     */
    public static function export($dir='data') {
        //保存路径,默认为站点根目录下的data文件夹里
        $path = ROOT_PATH .$dir.DIRECTORY_SEPARATOR;    
        //获取数据库名
        $dbname=config("database.database");    
        //检查目录是否存在  
        if(!is_dir($path)) { 
            //新建目录  
            mkdir($path, 0777, true);
        }
        //检查目录是否可写  
        if(!is_writable($path)) {
            chmod($path,0777);
        }
        //检查文件是否存在
        $fileName=$dbname.'_'.date("Y-m-d",time()).'.sql';
        //数据库保存相对路径
        $filePath = $dir.DIRECTORY_SEPARATOR.$fileName;
        //硬盘保存绝对路径
        $savepath = $path.$fileName;
        try{
            if(!file_exists($savepath)) {
                //获取mysql版本
                $version = Db::query("select version() as ver");
                $ver = $version[0]['ver'];
                $info = "-- ----------------------------\r\n";
                $info .= "-- 日期:".date("Y-m-d H:i:s",time())."\r\n";
                $info .= "-- MySQL - ".$ver." : Database - ".$dbname."\r\n";
                $info .= "-- ----------------------------\r\n\r\n";
                $info .= "CREATE DATAbase IF NOT EXISTS `".$dbname."` DEFAULT CHARACTER SET utf8 ;\r\n\r\n";
                $info .= "USE `".$dbname."`;\r\n\r\n";
                file_put_contents($savepath,$info,FILE_APPEND);
                //查询所有表  
                $sql="show tables";
                //执行原生SQL语句
                $result=Db::query($sql);            
                foreach ($result as $k=>$v) {
                    //查询表结构  
                    $table = $v['Tables_in_'.$dbname];
                    $sql_table = "show create table ".$table;
                    $res = Db::query($sql_table);             
                    $info_table = "-- ----------------------------\r\n";
                    $info_table .= "-- Table structure for `".$table."`\r\n";
                    $info_table .= "-- ----------------------------\r\n\r\n";
                    $info_table .= "DROP TABLE IF EXISTS `".$table."`;\r\n\r\n";
                    $info_table .= $res[0]['Create Table'].";\r\n\r\n";
                    //查询表数据  
                    $info_table .= "-- ----------------------------\r\n";
                    $info_table .= "-- Data for the table `".$table."`\r\n";
                    $info_table .= "-- ----------------------------\r\n\r\n";
                    file_put_contents($savepath,$info_table,FILE_APPEND);
                    $sql_data = "select * from ".$table;
                    $data = Db::query($sql_data);
                    $count= count($data); 
                    if($count<1) continue;
                    foreach ($data as $key => $value) {
                        $sqlStr = "INSERT INTO `{$table}` VALUES (";
                        foreach($value as $column) {
                            //对单引号和换行符进行一下转义
                            $column = str_replace( array("'","\r\n"), array("\'","\\r\\n"),$column);
                            //当值为空时,使用default默认
                            $sqlStr .=empty($column) ? "default, " : "'".$column."', ";
                        }
                        //去掉最后一个逗号和空格  
                        $sqlStr = substr($sqlStr,0,strlen($sqlStr)-2);
                        $sqlStr .= ");\r\n";
                        //$sqlStr = "INSERT INTO `{$table}` VALUES ('" . str_replace(array("\r", "\n"), array('\\r', '\\n'), implode("', '", $value)) . "');\n";
                        file_put_contents($savepath,$sqlStr,FILE_APPEND);
                    }
                    $info = "\r\n";
                    file_put_contents($savepath,$info,FILE_APPEND);
                }
                //计算文件大小
                $size=filesize($savepath);
                return array('code' => 1, 'msg' => '数据库备份成功','name'=>$fileName,'size'=>$size,'path'=>$filePath);
            }else{
                return array('code' => 0, 'msg' => '备份文件已存在');
            }
        } catch (\Exception $e) {
            return array('code' => 0, 'msg' => '备份数据库失败,Error:'.$e);
        }
    }
    /**
     * 将sql文件导入到数据库
     *
     * @param string $sqlfile sql文件
     */
    public static function import($sqlfile) {
        if(!file_exists($sqlfile)) {
            return array('code' => 0, 'msg' => '备份文件不存在');
        }
        try {
            // 创建保存sqlsql语句的数组
            $sqls = array ();
            $file = fopen ($sqlfile, "rb" );
            // 创建表缓冲变量
            $table_buffer = '';
            while ( ! feof ($file) ) {
                // 读取每一行sql
                $row = fgets ( $file);
                // 如果结尾没有包含';'(即为一个完整的sql语句,这里是插入语句),并且不包含'ENGINE='(即创建表的最后一句)
                if (! preg_match ( '/;/', $row ) || preg_match ( '/ENGINE=/', $row )) {
                    // 将本次sql语句与创建表sql连接存起来
                    $table_buffer .= $row;
                    // 如果包含了创建表的最后一句
                    if (preg_match ( '/ENGINE=/', $table_buffer)) {
                        //执行sql语句创建表
                        Db::execute($table_buffer);
                        // 清空当前,准备下一个表的创建
                        $table_buffer = '';
                    }
                    // 跳过本次
                    continue;
                }
                //执行sql语句
                Db::execute($row);
            }
            fclose ($file);
            return array('code' => 1, 'msg' => '还原数据库成功');    
        }
        catch (\Exception $e) {
            return array('code' => 0, 'msg' => '还原数据库失败,Error:'.$e);
        }
    }
}

二、创建一张数据库表,用于保存备份内容

-- ----------------------------
-- Table structure for `database`
-- ----------------------------
DROP TABLE IF EXISTS `database`;
CREATE TABLE `database` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) DEFAULT NULL,
  `size` varchar(20) DEFAULT '0',
  `path` varchar(255) DEFAULT NULL,
  `create_time` int(11) DEFAULT NULL,
  `update_time` int(11) DEFAULT NULL,
  `delete_time` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

三、备份数据库

thinkphp后端控制器代码

<?php
namespace app\admin\controller;
use think\Controller;
use think\Db;
use app\common\Backup;
    /**
    ** @name='备份'
    */
    public function backup()
    {
        if(request()->isPost()){
            $res=Backup::export();
            if($res['code']==1){
                //写入数据库
                DatabaseModel::create([
                    'name' => $res['name'],
                    'size' => $res['size'],
                    'path' => $res['path']
                ]);
                $this->success($res['msg']);    
            }else{
                $this->error($res['msg']);
            }                    
        }
    } 

四、下载备份文件

1、下载数据库备份文件,需要先将.sql文件压缩成zip,因此建议前端配合使用jszip插件,jszip教程参数这篇文章:https://blog.csdn.net/qq15577969/article/details/115549729

Ps:有的朋友可能会说,直接使用php自带的zip压缩功能不就可以了吗,何必那么麻烦。这个当然也可以,但前提是你使用的php版本安装了php_zip.dll扩展,然后才能使用ZipArchive类实现zip压缩功能。博主使用的php5.6版本是没有这个扩展的,而且博主开发的系统是要授权给用户使用的,用户不一定都懂安装php扩展,所以为了保险起见,直接选择了配合前端jszip插件来实现压缩的方案。

2、前端js代码:

<script type="text/javascript" src="./jszip.min.js">
</script>
<script type="text/javascript">
    $.post('后端下载的地址', {
        'token': '{$token}',
        'id': data.id
    },
    function(res) {
        if (res.code == 1) {
            var zip = new JSZip();
            //res.name是文件名称,res.content是文件内容
            zip.file(res.name, res.content);
            // 生成zip文件并下载
            zip.generateAsync({
                type: 'blob',
                // 压缩类型
                compression: "DEFLATE",
                // STORE:默认不压缩 DEFLATE:需要压缩
                compressionOptions: {
                    level: 9 // 压缩等级1~9 1压缩速度最快,9最优压缩方式
                }
            }).then(function(content) {
                // 下载的文件名
                var filename = res.name + '.zip';
                // 创建隐藏的可下载链接
                var eleLink = document.createElement('a');
                eleLink.download = filename;
                eleLink.style.display = 'none';
                // 下载内容转变成blob地址
                eleLink.href = URL.createObjectURL(content);
                // 触发点击
                document.body.appendChild(eleLink);
                eleLink.click();
                // 然后移除
                document.body.removeChild(eleLink);
            });
        } else {
            mui.toast(res.msg);
        }
    });
</script>

3、thinkphp后端控制器代码:

<?php
   /**
    ** @name='下载'
    */
    public function down()
    {
        if(request()->isPost()){
            $data=input('post.');
            !isset($data['id']) && $this->error('参数非法');
            !is_numeric(trim($data['id'])) && $this->error('参数非法');    
            $file = DatabaseModel::field('name,path')->where('id',intval($data['id']))->find();
            empty($file) && $this->error('备份数据不存在');
            $path=ROOT_PATH.$file['path'];
            //读取文件
            $content=file_get_contents($path);
            if (!empty($content)) {
                $arr=[
                    'code'=>1,
                    'name'=>$file['name'],
                    'content'=>$content
                ];
                header('Content-Type:application/json; charset=utf-8');
                echo json_encode($arr, JSON_UNESCAPED_UNICODE);
                exit;
            }else{
                $this->error('备份文件被损坏');
            }
        }
    }

五、还原备份文件

1、还原数据库需要修改database.php里的数据库配置,如下:

// 数据库连接参数
'params'          => [
    'MYSQL_ATTR_USE_BUFFERED_QUERY' => true,
],

2、thinkphp后端控制器代码:

/**
    ** @name='还原'
    */
    public function recover()
    {
        if(request()->isPost()){
            $data=input('post.');
            !isset($data['id']) && $this->error('参数非法');
            !is_numeric(trim($data['id'])) && $this->error('参数非法');    
            $file = DatabaseModel::field('id,name,path')->where('id',intval($data['id']))->find();
            empty($file) && $this->error('备份数据不存在');
            //.sql文件的全路径
            $path=ROOT_PATH.$file['path'];
            $res=Backup::import($path);
            if($res['code']==1){
                $result=DatabaseModel::where('id',intval($file['id']))->find();
                if(!$result){
                    //删除备份文件
                    unlink($path);
                }
                $this->success($res['msg']);
            }else{
                $this->error($res['msg']);
            }
        }
    }

3、还原的时候,一定要先给用户警告提示,否则会导致数据丢失,如下:

 

六、删除备份文件

thinkphp后端控制器代码:

   /**
    ** @name='删除'
    */
    public function del()
    {
        if(request()->isPost()){
            $data=input('post.');
            !isset($data['id']) && $this->error('参数非法');
            !is_numeric(trim($data['id'])) && $this->error('参数非法');
            Db::startTrans();
            try{
                $file = DatabaseModel::field('path')->where('id',intval($data['id']))->find();
                $path=ROOT_PATH.$file['path'];
                if(file_exists($path)){
                    //thinkphp使用unlink函数来删除文件,参数是文件的地址
                    unlink($path);                    
                }    
                DatabaseModel::where('id',intval($data['id']))->delete();                
                Db::commit();
            } catch (\Exception $e) {
                Db::rollback();
                $this->error('删除备份失败');                
            }
            $this->success('删除备份成功');    
        }
    }

 

posted @ 2024-08-14 17:35  mingruqi  阅读(129)  评论(0编辑  收藏  举报