PHP编写的mysql热备份及增量备份

本文基于PHPxtrabackup 具体的安装方式本文不再解释

本文的备份条件 每10分钟执行一次请自己crontab配置。每天凌晨时进行整量备份,其后每10分钟备份一次。可配合钉钉进行提醒

shell版本的mysql热备份

<?php

class CSQLite3 {

    // Variables
    var $m_sDb = "";
    var $m_iDbh = 0;
    var $m_iRs = 0;
    private $mode;

    /**
     * @param string $sDb database filename
     * @param   [type] $mode SQLITE3_BOTH、SQLITE3_ASSOC、SQLITE3_NUM
     *
     * @created 2017/04/18
     */
    public function __construct ($sDb = '', $mode = SQLITE3_ASSOC) {
        $this->m_sDb = defined ('_MYSQL_DB') ? _MYSQL_DB : null;
        $this->mode = $mode;

        if ($sDb) $this->m_sDb = $sDb;

        //check the file exists
        if (!is_file ($this->m_sDb))
            throw new Exception('CSQLite3: File ' . $this->m_sDb . ' wasn\'t found.');

        if (!$this->m_iDbh) {
            $this->vConnect ();
        }

        return $this->m_iDbh;
    }

    public function __destruct () {
        $this->vClose ();
    }


    /**
     * @desc 連線資料庫
     */
    function vConnect () {
        $this->m_iDbh = new SQLite3($this->m_sDb);
    }

    /**
     * @desc 關閉資料庫
     */
    function vClose () {
        $this->m_iDbh->close ();
        $this->m_iDbh = null;
    }

    /**
     * @desc query db
     *
     * @param $sSql SQL語法
     *
     * @return value of variable $m_iRs
     */
    function iQuery ($sSql) {
        $result = $this->m_iDbh->query ($sSql);
        if (!$result) {
            throw new Exception($this->m_iDbh->lastErrorMsg ());
        }
        $this->m_iRs = $result;

        return $result;
    }

    /**
     * @desc    執行不須回傳值的語法
     * @created 2017/04/18
     */
    function vExec ($sSql) {
        $result = $this->m_iDbh->exec ($sSql);
        if (!$result) {
            throw new Exception($this->m_iDbh->lastErrorMsg ());
        }
        $this->m_iRs = $result;
    }

    /**
     * @desc 取得sql結果
     *
     * @param $iRs        resource result
     * @param result_type : MYSQLI_BOTH, MYSQLI_ASSOC, MYSQLI_NUM
     *
     * @return Fetch a result row as an associative array, a numeric array, or both.
     */
    function aFetchArray ($iRs) {
        # Get Columns
        // $i = 0;
        // while($iRs->columnName($i)){
        //     $columns[] = $iRs->columnName($i);
        //     $i++;
        // }

        $resx = $iRs->fetchArray (MYSQLI_BOTH);

        return $resx;
    }


    /**
     * @param $iRs resource result
     *
     * @return Fetch a result row as an associative array, a numeric array, or both.
     * @desc 取得sql結果
     */
    function aFetchAssoc ($iRs = 0) {
        $resx = $iRs->fetchArray (SQLITE3_ASSOC);

        return $resx;
    }


    /**
     * @return Get the ID generated from the previous INSERT operation
     * @desc
     */
    function iGetInsertId () {
        return $this->m_iDbh->lastInsertRowID ();
    }

    /**
     * @desc    資料庫更動序號(取得insert後的自動流水號)
     * @created 2017/04/18
     */
    function iGetChangeRowID () {
        return $this->m_iDbh->changes ();
    }

    /**
     * delete
     *
     * @param string  $table
     * @param string  $where
     * @param integer $limit
     *
     * @return integer Affected Rows
     */
    function vDelete ($sTable, $sWhere) {
        if (!$sWhere) throw new Exception("CSQLite3->vDelete: fail no where. table: $sTable");
        $this->iQuery ("DELETE FROM $sTable WHERE $sWhere");
        if (!$this->m_iRs) {
            throw new Exception("CSQLite3->vDelete: fail to delete data in $sTable");
        }
        $iChangeRows = $this->iGetChangeRowID ();

        return $iChangeRows;
    }


    /**
     * @param $sTable db table $aField field array $aValue value array
     *
     * @return if return sql is ok  "" is failure
     * @desc insert into table
     */
    function sInsert ($sTable, $aField, $aValue) {
        if (!is_array ($aField)) return 0;
        if (!is_array ($aValue)) return 0;

        count ($aField) == count ($aValue) or die(count ($aField) . ":" . count ($aValue));

        $sSql = "INSERT INTO $sTable ( ";
        for ($i = 1; $i <= count ($aField); $i++) {
            $sSql .= "`" . $aField[$i - 1] . "`";
            if ($i != count ($aField)) $sSql .= ",";
        }

        $sSql .= ") values(";

        for ($i = 1; $i <= count ($aValue); $i++) {
            $sSql .= "'" . $this->escapeString ($aValue[$i - 1]) . "'";
            if ($i != count ($aValue)) $sSql .= ",";
        }
        $sSql .= ")";

        $this->iQuery ($sSql);

        //if(!$this->m_iRs) return NULL;
        if (!$this->m_iRs) throw new Exception("CSQLite3->sInsert: fail to insert data into $sTable");
        else return $sSql;
    }

    /**
     * @param $sTable db table $aField field array $aValue value array $sWhere trem
     *
     * @return if return sql is ok  "" is failure
     * @desc update  table
     */
    function sUpdate ($sTable, $aField, $aValue, $sWhere) {
        if (!is_array ($aField)) return 0;
        if (!is_array ($aValue)) return 0;

        if (count ($aField) != count ($aValue)) return 0;

        $sSql = "update $sTable set ";
        for ($i = 0; $i < count ($aField); $i++) {
            $sSql .= "`" . $aField[$i] . "`='" . $this->escapeString ($aValue[$i]) . "'";
            if (($i + 1) != count ($aField)) $sSql .= ",";
        }

        $sSql .= " where " . $sWhere;
        $this->sSql = $sSql;
        $this->iQuery ($sSql);
        if (!$this->m_iRs) throw new Exception("CSQLite3->sUpdate: fail to update data in $sTable");
        else return $sSql;
    }

    /**
     * @param string $sTable The table name, array $aAdd The add data array
     *
     * @return boolean
     * @desc insert into table
     */
    function bInsert ($sTable, $aAdd) {
        $sSql = "INSERT INTO $sTable (";
        foreach ($aAdd AS $key => $value) {
            $sSql .= "`" . $key . "`,";
        }
        $sSql = substr ($sSql, 0, -1);
        $sSql .= ") values (";
        foreach ($aAdd AS $key => $value) {
            $sSql .= "'" . $value . "',";
        }
        $sSql = substr ($sSql, 0, -1);
        $sSql .= ")";

        $this->sSql = $sSql;
        $this->vExec ($sSql);

        if (!$this->m_iRs) throw new Exception("CSQLite3->bInsert: fail to insert data in $sTable");

        return $this->iGetInsertId ();
    }

    /**
     * @param string $sTable The table name, array $aSrc The source data array, array $aTar The target data array
     *
     * @return boolean
     * @desc update table
     */
    function bUpdate ($sTable, $aSrc, $aTar) {
        $aWhere = array ();
        foreach ($aSrc AS $key => $value) {
            $aWhere[] = "$key = '" . $this->escapeString ($value) . "'";
        }
        $aSrc = array ();
        foreach ($aTar AS $key => $value) {
            $aSet[] = "$key = '" . $this->escapeString ($value) . "'";
        }
        $sSQL = "UPDATE $sTable SET " . implode (",", $aSet) . " WHERE " . (count ($aWhere) > 0 ? implode (" AND ", $aWhere) : "1");

        $this->sSql = $sSQL;
        $this->vExec ($sSQL);
        if (!$this->m_iRs) throw new Exception("CSQLite3->bUpdate: fail to update data in $sTable");
        $iChangeRows = $this->iGetChangeRowID ();

        return $iChangeRows;
    }

    function escapeString ($value) {
        $result = $this->m_iDbh->escapeString ($value);

        return $result;
    }

}

class Command {
    /**
     * @var bool whether to escape any argument passed through `addArg()`. Default is `true`.
     */
    public $escapeArgs = true;

    /**
     * @var bool whether to escape the command passed to `setCommand()` or the constructor.
     * This is only useful if `$escapeArgs` is `false`. Default is `false`.
     */
    public $escapeCommand = false;

    /**
     * @var bool whether to use `exec()` instead of `proc_open()`. This can be used on Windows system
     * to workaround some quirks there. Note, that any errors from your command will be output directly
     * to the PHP output stream. `getStdErr()` will also not work anymore and thus you also won't get
     * the error output from `getError()` in this case. You also can't pass any environment
     * variables to the command if this is enabled. Default is `false`.
     */
    public $useExec = true;

    /**
     * @var bool whether to capture stderr (2>&1) when `useExec` is true. This will try to redirect the
     * stderr to stdout and provide the complete output of both in `getStdErr()` and `getError()`.
     * Default is `true`.
     */
    public $captureStdErr = true;

    /**
     * @var string|null the initial working dir for `proc_open()`. Default is `null` for current PHP working dir.
     */
    public $procCwd;

    /**
     * @var array|null an array with environment variables to pass to `proc_open()`. Default is `null` for none.
     */
    public $procEnv;

    /**
     * @var array|null an array of other_options for `proc_open()`. Default is `null` for none.
     */
    public $procOptions;

    /**
     * @var null|string the locale to temporarily set before calling `escapeshellargs()`. Default is `null` for none.
     */
    public $locale;

    /**
     * @var string to pipe to standard input
     */
    protected $_stdIn;

    /**
     * @var string the command to execute
     */
    protected $_command;

    /**
     * @var array the list of command arguments
     */
    protected $_args = array ();

    /**
     * @var string the full command string to execute
     */
    protected $_execCommand;

    /**
     * @var string the stdout output
     */
    protected $_stdOut = '';

    /**
     * @var string the stderr output
     */
    protected $_stdErr = '';

    /**
     * @var int the exit code
     */
    protected $_exitCode;

    /**
     * @var string the error message
     */
    protected $_error = '';

    /**
     * @var bool whether the command was successfully executed
     */
    protected $_executed = false;

    /**
     * @param string|array $options either a command string or an options array (see setOptions())
     */
    public function __construct ($options = null) {
        if (is_array ($options)) {
            $this->setOptions ($options);
        } elseif (is_string ($options)) {
            $this->setCommand ($options);
        }
    }

    /**
     * @param array $options array of name => value options that should be applied to the object
     *                       You can also pass options that use a setter, e.g. you can pass a `fileName` option which
     *                       will be passed to `setFileName()`.
     *
     * @throws \Exception
     * @return static for method chaining
     */
    public function setOptions ($options) {
        foreach ($options as $key => $value) {
            if (property_exists ($this, $key)) {
                $this->$key = $value;
            } else {
                $method = 'set' . ucfirst ($key);
                if (method_exists ($this, $method)) {
                    call_user_func (array ($this, $method), $value);
                } else {
                    throw new \Exception("Unknown configuration option '$key'");
                }
            }
        }

        return $this;
    }

    /**
     * @param string $command the command or full command string to execute, like 'gzip' or 'gzip -d'.
     *                        You can still call addArg() to add more arguments to the command. If $escapeCommand was set to true,
     *                        the command gets escaped through escapeshellcmd().
     *
     * @return static for method chaining
     */
    public function setCommand ($command) {
        if ($this->escapeCommand) {
            $command = escapeshellcmd ($command);
        }
        if ($this->getIsWindows ()) {
            // Make sure to switch to correct drive like "E:" first if we have a full path in command
            if (isset($command[1]) && $command[1] === ':') {
                $position = 1;
                // Could be a quoted absolute path because of spaces. i.e. "C:\Program Files (x86)\file.exe"
            } elseif (isset($command[2]) && $command[2] === ':') {
                $position = 2;
            } else {
                $position = false;
            }

            // Absolute path. If it's a relative path, let it slide.
            if ($position) {
                $command = sprintf ($command[$position - 1] . ': && cd %s && %s', escapeshellarg (dirname ($command)), basename ($command));
            }
        }
        $this->_command = $command;

        return $this;
    }

    /**
     * @param string $stdIn If set, the string will be piped to the command via standard input.
     *                      This enables the same functionality as piping on the command line.
     *
     * @return static for method chaining
     */
    public function setStdIn ($stdIn) {
        $this->_stdIn = $stdIn;

        return $this;
    }

    /**
     * @return string|null the command that was set through setCommand() or passed to the constructor. Null if none.
     */
    public function getCommand () {
        return $this->_command;
    }

    /**
     * @return string|bool the full command string to execute. If no command was set with setCommand()
     * or passed to the constructor it will return false.
     */
    public function getExecCommand () {
        if ($this->_execCommand === null) {
            $command = $this->getCommand ();
            if (!$command) {
                $this->_error = 'Could not locate any executable command';

                return false;
            }
            $args = $this->getArgs ();
            $this->_execCommand = $args ? $command . ' ' . $args : $command;
        }

        return $this->_execCommand;
    }

    /**
     * @param string $args the command arguments as string. Note that these will not get escaped!
     *
     * @return static for method chaining
     */
    public function setArgs ($args) {
        $this->_args = array ($args);

        return $this;
    }

    /**
     * @return string the command args that where set through setArgs() or added with addArg() separated by spaces
     */
    public function getArgs () {
        return implode (' ', $this->_args);
    }

    /**
     * @param string            $key    the argument key to add e.g. `--feature` or `--name=`. If the key does not end with
     *                                  and `=`, the $value will be separated by a space, if any. Keys are not escaped unless $value is null
     *                                  and $escape is `true`.
     * @param string|array|null $value  the optional argument value which will get escaped if $escapeArgs is true.
     *                                  An array can be passed to add more than one value for a key, e.g. `addArg('--exclude', array('val1','val2'))`
     *                                  which will create the option `--exclude 'val1' 'val2'`.
     * @param bool|null         $escape if set, this overrides the $escapeArgs setting and enforces escaping/no escaping
     *
     * @return static for method chaining
     */
    public function addArg ($key, $value = null, $escape = null) {
        $doEscape = $escape !== null ? $escape : $this->escapeArgs;
        $useLocale = $doEscape && $this->locale !== null;

        if ($useLocale) {
            $locale = setlocale (LC_CTYPE, 0);   // Returns current locale setting
            setlocale (LC_CTYPE, $this->locale);
        }
        if ($value === null) {
            // Only escape single arguments if explicitely requested
            $this->_args[] = $escape ? escapeshellarg ($key) : $key;
        } else {
            $separator = substr ($key, -1) === '=' ? '' : ' ';
            if (is_array ($value)) {
                $params = array ();
                foreach ($value as $v) {
                    $params[] = $doEscape ? escapeshellarg ($v) : $v;
                }
                $this->_args[] = $key . $separator . implode (' ', $params);
            } else {
                $this->_args[] = $key . $separator . ($doEscape ? escapeshellarg ($value) : $value);
            }
        }
        if ($useLocale) {
            setlocale (LC_CTYPE, $locale);
        }

        return $this;
    }

    /**
     * @param bool $trim whether to `trim()` the return value. The default is `true`.
     *
     * @return string the command output (stdout). Empty if none.
     */
    public function getOutput ($trim = true) {
        return $trim ? trim ($this->_stdOut) : $this->_stdOut;
    }

    /**
     * @param bool $trim whether to `trim()` the return value. The default is `true`.
     *
     * @return string the error message, either stderr or internal message. Empty if none.
     */
    public function getError ($trim = true) {
        return $trim ? trim ($this->_error) : $this->_error;
    }

    /**
     * @param bool $trim whether to `trim()` the return value. The default is `true`.
     *
     * @return string the stderr output. Empty if none.
     */
    public function getStdErr ($trim = true) {
        return $trim ? trim ($this->_stdErr) : $this->_stdErr;
    }

    /**
     * @return int|null the exit code or null if command was not executed yet
     */
    public function getExitCode () {
        return $this->_exitCode;
    }

    /**
     * @return string whether the command was successfully executed
     */
    public function getExecuted () {
        return $this->_executed;
    }

    /**
     * Execute the command
     *
     * @return bool whether execution was successful. If false, error details can be obtained through
     * getError(), getStdErr() and getExitCode().
     */
    public function execute () {
        $command = $this->getExecCommand ();

        if (!$command) {
            return false;
        }

        if ($this->useExec) {
            $execCommand = $this->captureStdErr ? "$command 2>&1" : $command;
            exec ($execCommand, $output, $this->_exitCode);
            $this->_stdOut = implode ("\n", $output);
            if ($this->_exitCode !== 0) {
                $this->_stdErr = $this->_stdOut;
                $this->_error = empty($this->_stdErr) ? 'Command failed' : $this->_stdErr;

                return false;
            }
        } else {
            $descriptors = array (
                1 => array ('pipe', 'w'),
                2 => array ('pipe', $this->getIsWindows () ? 'a' : 'w'),
            );
            if ($this->_stdIn !== null) {
                $descriptors[0] = array ('pipe', 'r');
            }

            $process = proc_open ($command, $descriptors, $pipes, $this->procCwd, $this->procEnv, $this->procOptions);

            if (is_resource ($process)) {

                if ($this->_stdIn !== null) {
                    fwrite ($pipes[0], $this->_stdIn);
                    fclose ($pipes[0]);
                }
                $this->_stdOut = stream_get_contents ($pipes[1]);
                $this->_stdErr = stream_get_contents ($pipes[2]);
                fclose ($pipes[1]);
                fclose ($pipes[2]);

                $this->_exitCode = proc_close ($process);

                if ($this->_exitCode !== 0) {
                    $this->_error = $this->_stdErr ? $this->_stdErr : "Failed without error message: $command";

                    return false;
                }
            } else {
                $this->_error = "Could not run command $command";

                return false;
            }
        }

        $this->_executed = true;

        return true;
    }

    /**
     * @return bool whether we are on a Windows OS
     */
    public function getIsWindows () {
        return strncasecmp (PHP_OS, 'WIN', 3) === 0;
    }

    /**
     * @return string the current command string to execute
     */
    public function __toString () {
        return (string)$this->getExecCommand ();
    }
}

class Dingding {

    public $url = 'https://oapi.dingtalk.com/robot/send';


    function __construct ($access_token) {
        $this->url = $this->url . '?' . http_build_query (array ('access_token' => $access_token), '', '&');
    }

    /**
     * 功能:发送文字消息
     * @author xiaole
     * @time   17/3/1 下午3:30
     */
    public function send_text ($content, $atMobiles = array (), $isAtAll = 0) {

        $array = array (
            'msgtype' => 'text',
            'text'    => array (
                'content' => $content
            ),
            'at'      => array (
                'atMobiles' => $atMobiles
            ),
            'isAtAll' => $isAtAll
        );

        //发送
        return $this->post (json_encode ($array));

    }


    /**
     * 功能:钉钉post方法
     * @author xiaole
     * @time   17/3/1 下午3:22
     */
    private function post ($data) {
        $defaults = array (
            CURLOPT_POST           => 1,
            CURLOPT_HEADER         => 0,
            CURLOPT_URL            => $this->url,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_TIMEOUT        => 15,
            CURLOPT_CONNECTTIMEOUT => 15,
            CURLOPT_SSL_VERIFYHOST => false,
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_HTTPHEADER     => array ('Content-Type: application/json'),
            CURLOPT_POSTFIELDS     => is_array ($data) ? http_build_query ($data, '', '&') : $data,
        );

        $ch = curl_init ();
        curl_setopt_array ($ch, $defaults);
        $result = curl_exec ($ch);
        if (curl_error ($ch)) {
            return curl_error ($ch);
        }
        curl_close ($ch);

        $result_array = json_decode ($result, true);
        if ($result_array['errmsg'] == 'ok') {
            return true;
        } else {
            return $result_array['errcode'];
        }
    }

}

class Back {

    //主要说明
    //此备份文件基于每10分钟执行一次做的判断

    // mysql用户
    public $user = 'root';

    // mysql 密码
    public $password = '123456';

    //sqlite3的记录文件
    //生产环境请写绝对路径
    public $db = 'mysql_back.db';

    // 备份路径
    public $backup_dir = '/tmp/mysql_back_file';

    //innobackupex命令路径
    public $innobackupex = '/usr/bin/innobackupex';

    // 全量备信息名称 前缀
    public $full_backup_prefix = 'full';

    // 增量备信息名称 前缀
    public $increment_prefix = 'incr';

    // mysql配置文件
    public $mysql_conf_file = '/etc/my.cnf';

    /**
     * 功能:全量备份
     * @return string
     * @author xiaole
     * @time   17/12/19 下午4:46
     */
    function full_backup () {
        $name = $this->full_backup_prefix . '_' . date ('Y_m_d_H_i');
        $path = $this->backup_dir . '/' . $name;

        if (!is_dir ($path)) {
            mkdir ($path, 0755, true);
        }

        return array (
            'name'    => $name,
            'path'    => $path,
            'command' => $this->innobackupex . ' --defaults-file=' . $this->mysql_conf_file . ' --host=127.0.0.1 --user=' . $this->user . ' --password=' . $this->password . ' --no-timestamp ' . $path
        );

    }

    /**
     * 功能:增量备份
     * @var string $base_folder 增量备份的基础
     * @return string
     * @author xiaole
     * @time   17/12/19 下午4:46
     */
    function increment_backup ($base_folder) {
        $name = $this->increment_prefix . '_' . date ('Y_m_d_H_i');
        $path = $this->backup_dir . '/' . $name;

        if (!is_dir ($path)) {
            mkdir ($path, 0755, true);
        }

        return array (
            'name'    => $name,
            'path'    => $path,
            'command' => $this->innobackupex . ' --defaults-file=' . $this->mysql_conf_file . ' --host=127.0.0.1 --user=' . $this->user . ' --password=' . $this->password . ' --no-timestamp --incremental ' . $path . ' --incremental-basedir=' . $base_folder
        );
    }

    /**
     * 功能:获取要执行的命令
     * @author xiaole
     * @time   17/12/19 下午5:24
     */
    function getcommand () {
        //获取时间
        //如果当天0点0分,执行全备
        if (date ('H') == '00' and date ('i') == '00') {

            $command_array = $this->full_backup ();

            return array (
                'code'    => 'ok',
                'type'    => $this->full_backup_prefix,
                'name'    => $command_array['name'],
                'path'    => $command_array['path'],
                'command' => $command_array['command'],
            );
        } else {
            //否则开始执行增量备份(第一次0:10分 以全备为基础)
            if (date ('H') == '00' and date ('i') == '10') {
                $base_name = $this->full_backup_prefix . '_' . date ('Y_m_d_H_i', time () - 600);
            } else {
                $base_name = $this->increment_prefix . '_' . date ('Y_m_d_H_i', time () - 600);
            }

            //查找数据库中是否有上次备份记录
            $sqlite = new CSQLite3($this->db);
            $sql = $sqlite->iQuery ("select * from history order by id desc limit 1");
            $result = $sqlite->aFetchArray ($sql);

            if (empty($result)) {
//                return array (
//                    'code' => 'error',
//                    'info' => '找不到上次备份记录'
//                );

                $command_array = $this->full_backup ();

                return array (
                    'code'    => 'ok',
                    'type'    => $this->full_backup_prefix,
                    'name'    => $command_array['name'],
                    'path'    => $command_array['path'],
                    'command' => $command_array['command'],
                );

            } else {
                // 判断该目录是否存在
                if (is_dir ($this->backup_dir . '/' . $base_name)) {
                    if ($base_name != $result['name']) {
                        return array (
                            'code' => 'error',
                            'info' => '上次备份未结束,此次备份跳过'
                        );
                    } else {
                        $command_array = $this->increment_backup ($this->backup_dir . '/' . $base_name);

                        return array (
                            'code'    => 'ok',
                            'type'    => $this->increment_prefix,
                            'name'    => $command_array['name'],
                            'path'    => $command_array['path'],
                            'command' => $command_array['command'],
                        );
                    }

                } else {
                    $command_array = $this->increment_backup ($this->backup_dir . '/' . $result['name']);

                    return array (
                        'code'    => 'ok',
                        'type'    => $this->increment_prefix,
                        'name'    => $command_array['name'],
                        'path'    => $command_array['path'],
                        'command' => $command_array['command'],
                    );
                }

            }

        }
    }
}

////程序执行开始时间
$start = microtime (true);
//
$back = new Back();
$info = $back->getcommand ();

//往数据库插入的数据
$data = array ();

//设置备份文件大小
$size = 0;

//设置结果
$result_array = array (
    'status' => 0,
    'info'   => ''
);


switch ($info['code']) {
    case 'ok':

        $command = new Command($info['command']);
        if ($command->execute ()) {

            //检查备份文件大小
            $du_command = new Command("du -sh " . $back->backup_dir . '/' . $info['name']);
            if ($du_command->execute ()) {

                $arr = explode (' ', preg_replace ('/[\n\r\t]/', ' ', $du_command->getOutput ()));

                if (count ($arr) == 2) {
                    $size = $arr[0];
                }

            }

            $data = array (
                'name'       => $info['name'],
                'command'    => $info['command'],
                'stdout'     => str_replace ("'", '"', $command->getOutput ()),
                'type'       => $info['type'],
                'createtime' => date ('Y-m-d H:i:s'),
            );

            $result_array['status'] = 1;
            $result_array['info'] = '自动备份成功!' . "\r\n备份类型:" . $info['type'] . "\r\n备份路径: " . $back->backup_dir . '/' . $info['name'] . "\r\n文件大小:" . $size;

        } else {
            $result_array['info'] = $command->getError () . "\r\n" . "错误代码" . $command->getExitCode ();
        }

        break;
    case 'error':
        $result_array['info'] = $info['info'];
        break;
}


//判断结果

$exe_time = (microtime (true) - $start);

if (!empty($data)) {
    $data['exe_time'] = $exe_time;
    $sqlite = new CSQLite3($back->db);
    $sqlite->bInsert ('history', $data);
}

if (date ('H') >= 8 and date ('H') <= 18) {
    if(date('i') == 0 ){
        //回调钉钉提示
        $dingding = new Dingding('token');
        $dingding->send_text ($result_array['info'] . "\r\n备份用时:".round($exe_time,2)."\r\n注:本机器人只在早8晚6提示,且1次/小时");
    }


}

下载地址在这