重构数据备份和还原功能模块

This commit is contained in:
hdm 2022-05-05 14:16:38 +08:00
parent ca9e679729
commit fb24158e80
5 changed files with 536 additions and 375 deletions

View File

@ -11,171 +11,211 @@ namespace app\admin\controller;
use app\admin\BaseController;
use backup\Backup;
use think\facade\Session;
use think\facade\Db;
use think\facade\View;
class Database extends BaseController
{
protected $db = '', $datadir;
public function initialize()
//数据表列表
public function database()
{
parent::initialize();
$this->config = array(
'path' => './backup/', // 数据库备份路径
'part' => 20971520, // 数据库备份卷大小
'compress' => 0, // 数据库备份文件是否启用压缩 0不压缩 1 压缩
'level' => 9, // 数据库备份文件压缩级别 1普通 4 一般 9最高
);
$this->db = new Backup($this->config);
}
// 数据列表
public function database()
{
if (request()->isAjax()) {
// 数据信息
$list = $this->db->dataList();
// 计算总大小
$total = 0;
foreach ($list as $k => $v) {
$total += $v['data_length'];
$list[$k]['data_size'] = $v['data_length'];
$list[$k]['data_length'] = format_bytes($v['data_length']);
if (request()->isAjax()) {
// 数据信息
$db = new Backup();
$list = $db->dataList();
// 计算总大小
$total = 0;
foreach ($list as $k => $v) {
$total += $v['data_length'];
$list[$k]['data_size'] = $v['data_length'];
$list[$k]['data_length'] = format_bytes($v['data_length']);
}
// 提示信息
$dataTips = '数据库中共有<strong> ' . count($list) . '</strong> 张表,共计 <strong>' . format_bytes($total) . '</strong>大小。';
$data['data'] = $list;
return table_assign(0, $dataTips, $data);
}
return view();
}
//备份数据
public function backup()
{
$db= new Backup();
if(request()->isPost()){
$tables=get_params('tables');
$fileinfo =$db->getFile();
//检查是否有正在执行的任务
$lock = "{$fileinfo['filepath']}backup.lock";
if(is_file($lock)){
return to_assign(2, '检测到有一个备份任务未完成');
} else {
//创建锁文件
file_put_contents($lock,time());
}
// 提示信息
$dataTips = '数据库中共有<strong> ' . count($list) . '</strong> 张表,共计 <strong>' . format_bytes($total) . '</strong>大小。';
$data['data'] = $list;
return table_assign(0, $dataTips, $data);
}
return view();
}
// 备份
public function backup()
{
$tables = get_params('id');
if (!empty($tables)) {
$tables = explode(',', $tables);
foreach ($tables as $table) {
$this->db->setFile()->backup($table, 0);
// 检查备份目录是否可写
if(!is_writeable($fileinfo['filepath'])){
return to_assign(1, '备份目录不存在或不可写,请检查后重试!');
}
add_log('bak');
return to_assign(0, '备份成功!');
} else {
return to_assign(1, '请选择要备份的表');
}
}
//缓存锁文件
Session::set('lock', $lock);
//缓存备份文件信息
Session::set('backup_file', $fileinfo['file']);
//缓存要备份的表
Session::set('backup_tables', $tables);
//创建备份文件
if(false !== $db->Backup_Init()){
return to_assign(0, '初始化成功',['tab'=>['id' => 0, 'start' => 0,'table'=>$tables[0]]]);
}else{
return to_assign(1, '初始化失败,备份文件创建失败!');
}
}else if(request()->isGet()){
$tables = Session::get('backup_tables');
$file=Session::get('backup_file');
$id=get_params('id');
$start=get_params('start');
$start= $db->setFile($file)->backup($tables[$id], $start);
if(false === $start){
return to_assign(1, '备份出错!');
}else if(0 === $start){
if(isset($tables[++$id])){
return to_assign(0, '备份完成',['tab'=>['id' => $id, 'start' => 0,'table'=>$tables[$id-1]]]);
} else { //备份完成,清空缓存
unlink(Session::get('lock'));
Session::delete('backup_tables');
Session::delete('backup_file');
add_log('bak');
return to_assign(0, '备份完成',['tab'=>['start' => 'ok','table'=>$tables[$id-1]]]);
}
}
}else{
return to_assign(1, '参数错误!');
}
}
// 优化
public function optimize()
{
$tables = get_params('id');
if (empty($tables)) {
return to_assign(0, '请选择要优化的表');
//优化表
public function optimize($tables= null)
{
$db= new Backup();
//return to_assign(0, $db->optimize($tables));
if($db->optimize($tables)){
add_log('optimize');
return to_assign(0, '数据表优化完成');
}else{
return to_assign(1, '数据表优化出错请重试');
}
$tables = explode(',', $tables);
if ($this->db->optimize($tables)) {
add_log('optimize');
return to_assign(0, '数据表优化成功!');
} else {
return to_assign(1, '数据表优化出错请重试');
}
}
// 修复
public function repair()
{
$tables = get_params('id');
if (empty($tables)) {
return to_assign(1, '请选择要修复的表');
}
$tables = explode(',', $tables);
if ($this->db->repair($tables)) {
add_log('repair');
return to_assign(0, '数据表修复成功');
} else {
return to_assign(1, '数据表修复出错请重试');
}
}
// 还原列表
public function backuplist()
{
// 数据信息
$list = $this->db->fileList();
$listNew = [];
$indx = 0;
}
//修复表
public function repair($tables= null)
{
$db= new Backup();
//return to_assign(0, $db->repair($tables));
if($db->repair($tables)){
add_log('repair');
return to_assign(0, '数据表修复完成');
}else{
return to_assign(1, '数据表修复出错请重试');
}
}
//备份文件列表
public function backuplist()
{
$db= new Backup();
$list = $db->fileList();
$fileinfo =$db->getFile();
$lock = "{$fileinfo['filepath']}backup.lock";
$lock_time = 0;
if(is_file($lock)){
$lock_time = file_get_contents($lock);
}
$listNew = [];
$indx = 0;
foreach ($list as $k => $v) {
$listNew[$indx]['time'] = $k;
$listNew[$indx]['data'][] = $v;
$listNew[$indx]['timespan'] = $v['time'];
$listNew[$indx]['data'] = $v;
$indx++;
// $listNew[$k]['list'] = $list[$k];
}
$list = $listNew;
array_multisort(array_column($list, 'time'), SORT_DESC, $list);
return view('', ['list' => $list]);
}
// 执行还原数据库操作
public function import(int $id)
{
$list = $this->db->getFile('timeverif', $id);
$this->db->setFile($list)->import(1);
add_log('reduction');
return to_assign(0, '还原成功!');
}
// 下载
public function downfile(string $name)
{
$file_name = $name; //得到文件名
header("Content-type:text/html;charset=utf-8");
$file_name = iconv("utf-8", "gb2312", $file_name); // 转换编码
$file_sub_path = $this->config['path']; //确保文件在这个路径下面,换成你文件所在的路径
$file_path = $file_sub_path . $file_name;
# 将反斜杠 替换成正斜杠
$file_path = str_replace('\\', '/', $file_path);
if (!file_exists($file_path)) {
$this->error($file_path);exit; //如果提示这个错误,很可能你的路径不对,可以打印$file_sub_path查看
}
$fp = fopen($file_path, "r"); // 以可读的方式打开这个文件
# 如果出现图片无法打开,可以在这个位置添加函数
ob_clean(); # 清空擦掉,输出缓冲区。
$file_size = filesize($file_path);
//下载文件需要用到的头
Header("Content-type: application/octet-stream");
Header("Accept-Ranges: bytes");
Header("Accept-Length:" . $file_size);
Header("Content-Disposition: attachment; filename = " . $file_name);
$buffer = 1024000;
$file_count = 0;
while (!feof($fp) && $file_count < $file_size) {
$file_con = fread($fp, $buffer);
$file_count += $buffer;
echo $file_con;
}
fclose($fp); //关闭这个打开的文件
}
// 删除sql文件
public function del(string $id)
{
if (request()->isAjax()) {
if (strpos($id, ',') !== false) {
$idArr = explode(',', $id);
foreach ($idArr as $k => $v) {
$this->db->delFile($v);
return view('',['list'=>$list,'lock_time' => $lock_time]);
}
//数据还原
public function import($time = 0, $part = null, $start = null)
{
$db= new Backup();
$time =(int)$time;
if(is_numeric($time) && is_null($part) && is_null($start)){
$list = $db->getFile('timeverif',$time);
if(is_array($list)){
Session::set('backup_list', $list);
return to_assign(0, '初始化完成',array('part' => 1, 'start' => 0,'time' => $time));
}else{
return to_assign(1, '备份文件可能已经损坏,请检查');
}
}else if(is_numeric($part) && is_numeric($start)){
$list=Session::get('backup_list');
$part =(int)$part;
$start =(int)$start;
$start= $db->setFile($list)->import($start,$time,$part);
if(false===$start){
return to_assign(1, '还原数据出错,请重试');
}elseif(0 === $start){
if(isset($list[++$part])){
$data = array('part' => $part, 'start' => 0,'time' => $time);
return to_assign(0, "正在还原...卷{$part},请勿关闭当前页面",$data);
} else {
Session::delete('backup_list');
return to_assign(0, '还原数据成功');
}
add_log('delete');
return to_assign(0, "删除成功");
}
if ($this->db->delFile($id)) {
add_log('delete');
return to_assign(0, "删除成功");
} else {
return to_assign(1, "备份文件删除失败,请检查文件权限");
}
}
}
}
}else{
$data = array('part' => $part, 'start' => $start[0],'time' => $time);
if($start[1]){
$rate = floor(100 * ($start[0] / $start[1]));
return to_assign(0, "正在还原...卷{$part} ({$rate}%),请勿关闭当前页面",$data);
} else {
$data['gz'] = 1;
return to_assign(0, "正在还原...卷{$part},请勿关闭当前页面",$data);
}
return to_assign(0, "正在还原...卷{$part},请勿关闭当前页面");
}
}else{
return to_assign(1, "参数错误");
}
}
/**
* 删除备份文件
*/
public function del($time = 0,$lock=0){
$db= new Backup();
if($lock==1){
$fileinfo =$db->getFile();
$lock = "{$fileinfo['filepath']}backup.lock";
if(is_file($lock)){
$time = file_get_contents($lock);
unlink($lock);
}
}
if($db->delFile((int)$time)){
add_log('delete');
return to_assign(0, '删除成功');
}else{
return to_assign(0, '删除失败,请检查权限');
}
}
/**
* 下载备份文件
*/
public function downfile($time = 0,$part=0){
$db= new Backup();
add_log('down');
$db->downloadFile((int)$time,$part-1);
}
}

View File

@ -5,10 +5,10 @@
<table cellspacing="0" cellpadding="0" border="0" class="layui-table">
<tr style="background-color: #f5f5f5; text-align: center;">
<th style=" text-align: center; font-weight: 800;"><span>文件名称</span></th>
<th style=" text-align: center; font-weight: 800;"><span>分卷</span></th>
<th style=" text-align: center; font-weight: 800;"><span>文件大小</span></th>
<th style=" text-align: center; font-weight: 800;"><span>文件格式</span></th>
<th style=" text-align: center; font-weight: 800;"><span>分隔符</span></th>
<th style=" text-align: center; font-weight: 800;"><span>文件总大小</span></th>
<th style=" text-align: center; font-weight: 800;"><span>分卷总数</span></th>
<th style=" text-align: center; font-weight: 800; width:222px"><span>操作</span></th>
</tr>
{empty name="list"}
@ -18,26 +18,32 @@
{/empty}
{volist name="list" id="vo" key="k"}
<tr style="background-color: #fafafa;">
<td colspan="6"><strong>备份时间:{$vo.time}</strong></td>
</tr>
{volist name="vo.data" id="voo"}
<tr>
<td>
{:date("Ymd",$voo.time)}{$voo.compress}{:date("His",$voo.time)}{$voo.compress}{$voo.part}.sql
</td>
<td align="center"><span>{$voo.part}</span></td>
<td align="center"><span>{:format_bytes($voo.size)}</span></td>
<td><strong>备份时间:{$vo.time}</strong>{if $vo.timespan == $lock_time}<span style="color:red; margin-left:20px;">该备份不是完整备份,请删除重新备份</span>{/if}</td>
<td align="center"><span>.sql</span></td>
<td align="center"><span>{$voo.compress}</span></td>
<td align="center"><span>{$vo.data.compress}</span></td>
<td align="center"><span>{:format_bytes($vo.data.size)}</span></td>
<td align="center"><span>{$vo.data.part}</span></td>
<td align="center">
<div class="layui-btn-group" data-id='{$voo.time}'>
<div class="layui-btn-group" data-time='{$vo.timespan}'>
{if $vo.timespan == $lock_time}
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="reset">清除不完整的备份</a>
{else/}
<a class="layui-btn layui-btn-xs layui-btn-normal" lay-event="import">数据还原</a>
<a class="layui-btn layui-btn-xs" href='/admin/database/downfile?name={:date("Ymd",$voo.time)}{$voo.compress}{:date("His",$voo.time)}{$voo.compress}{$voo.part}.sql'>备份下载</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="del">备份删除</a>
{/if}
</div>
</td>
</tr>
{/volist}
{for start="0" end="$vo.data.part" step="1"}
<tr>
<td colspan="5">
{:date("Ymd",$vo.timespan)}{$vo.data.compress}{:date("His",$vo.timespan)}{$vo.data.compress}{$i+1}.sql
</td>
<td align="center">
<a class="layui-btn layui-btn-xs" href='/admin/database/downfile?time={$vo.data.time}&part={$i+1}'>下载备份(分卷{$i+1})</a>
</td>
</tr>
{/for}
{/volist}
</table>
</div>
@ -49,44 +55,93 @@
{block name="script"}
<script>
function init(layui) {
var table = layui.table,
form = layui.form;
function importData(data){
if(data.code==0){
console.log(data.msg);
layer.closeAll();
layer.msg(data.msg,{time: 200000});
if($.isPlainObject(data.data)){
$.ajax({
url: "/admin/database/import",
type:'get',
data:{"part" : data.data.part, "start" : data.data.start,time:data.data.time},
success: function (res) {
importData(res);
}
})
}else {
layer.msg(data.msg);
window.onbeforeunload = function(){ return null; }
}
} else {
layer.msg(data.msg);
}
}
//监听行工具事件
$('[lay-event="import"]').on('click',function(){
var id=$(this).parent().data('id');
var time=$(this).parent().data('time');
layer.confirm('确认要还原该备份吗?', {
icon: 3,
title: '提示'
}, function (index) {
layer.msg("数据还原中...");
layer.msg("数据还原中...",{time: 200000});
$.ajax({
url: "/admin/database/import?id="+id,
url: "/admin/database/import?time="+time,
type:'get',
success: function (res) {
layer.msg(res.msg);
importData(res);
}
})
layer.close(index);
});
window.onbeforeunload = function(){ return "正在还原数据库,请不要关闭!" }
layer.close(index);
});
return false;
})
$('[lay-event="del"]').on('click',function(){
var id=$(this).parent().data('id');
var time=$(this).parent().data('time');
layer.confirm('确认要删除该备份吗?', {
icon: 3,
title: '提示'
}, function (index) {
$.ajax({
url: "/admin/database/del",
data: {'id':id},
data: {'time':time},
success: function (res) {
layer.msg(res.msg);
if (res.code == 0) {
location.reload();
setTimeout(function(){
location.reload();
},2000)
}
}
})
layer.close(index);
});
return false;
})
$('[lay-event="reset"]').on('click',function(){
var time=$(this).parent().data('time');
layer.confirm('确认要清除该备份吗?', {
icon: 3,
title: '提示'
}, function (index) {
$.ajax({
url: "/admin/database/del",
data: {'time':time,'lock':1},
success: function (res) {
layer.msg(res.msg);
if (res.code == 0) {
setTimeout(function(){
location.reload();
},2000)
}
}
})
layer.close(index);
});
return false;
})
}
</script>

View File

@ -2,18 +2,19 @@
<!-- 主体 -->
{block name="body"}
<div class="body-table">
<table class="layui-hide" id="test" lay-filter="test"></table>
<table class="layui-hide" id="backup" lay-filter="test"></table>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-group">
<span class="layui-btn layui-btn-sm layui-btn-normal" lay-event="backup">数据备份</span><span class="layui-btn layui-btn-sm" lay-event="optimize">数据优化</span><span class="layui-btn layui-btn-danger layui-btn-sm" lay-event="repair">数据修复</span>
<span class="layui-btn layui-btn-sm layui-btn-normal" lay-event="backup">数据备份</span><span class="layui-btn layui-btn-sm" lay-event="optimize">数据优化</span><span class="layui-btn layui-btn-danger layui-btn-sm" lay-event="repair">数据修复</span>
</div>
<span id="dataTips" style="font-size:12px; margin-left:10px"></span>
</script>
{/block}
<!-- /主体 -->
<script>
<!-- 脚本 -->
{block name="script"}
<script>
@ -22,7 +23,7 @@
form = layui.form;
var tableIns = table.render({
elem: '#test',
elem: '#backup',
title: '数据备份',
toolbar: '#toolbarDemo',
url: "/admin/database/database", //数据接口
@ -34,7 +35,16 @@
field: 'name',
title: '数据表',
width: 220
}, {
},
{
field: 'status',
title: '状态',
width: 120,
templet:function(d){
return '<span id="table_'+d.name+'">-</span>';
}
},
{
field: 'engine',
title: '存储引擎',
align: 'center',
@ -75,22 +85,42 @@
}
});
      //递归备份表
       function backup(tab,status,table){
layer.closeAll();
layer.msg("备份中...,请勿关闭本页面",{time: 200000});
        $.get("/admin/database/backup", tab, function(data){
           // console.log(data)
if(data.code==0){
             showmsg(data.data.tab.table, data.msg);
              if(data.data.tab.start=='ok'){
layer.msg('备份完成');
                    window.onbeforeunload = function(){ return null }
                    return;
                } 
                backup(data.data.tab, tab.id != data.data.tab.id);
            } else {
layer.msg('立即备份');
            }
        }, "json"); 
       }
    //修改备份状态
    function showmsg(table, msg){
console.log(table);
     $("#table_"+table).addClass('span-color-2').html(msg);
    }
//监听行工具事件
table.on('toolbar(test)', function (obj) {
var checkData = table.checkStatus(obj.config.id).data;
var len = checkData.length;
var ids='';
var tables=[];
if(len==0){
layer.msg('请先选择表');
return false;
}
for(var i=0;i<len;i++){
if(i==0){
ids+=checkData[i].name;
}
else{
ids+=','+checkData[i].name;
}
tables.push(checkData[i].name);
}
if (obj.event === 'backup') {
layer.confirm('确认要备份选中的'+len+'个数据表吗?', {
@ -99,11 +129,30 @@
}, function (index) {
$.ajax({
url: "/admin/database/backup",
data: {'id':ids},
type:'post',
data: {'tables':tables},
success: function (res) {
layer.msg(res.msg);
if (res.code == 0) {
location.reload();
layer.msg( "开始备份,请不要关闭本页面!");
                window.onbeforeunload = function(){ return "正在备份数据库,请不要关闭!" }
backup(res.data.tab);
}
if (res.code == 2) {
layer.confirm('检测到有一个备份任务未完成,请先清除未完成的备份', {
icon: 3,
title: '提示'
}, function (index) {
$.ajax({
url: "/admin/database/del",
data: {'lock':1},
success: function (res) {
layer.msg(res.msg);
}
})
})
}
else{
layer.msg(res.msg);
}
}
})
@ -116,11 +165,13 @@
}, function (index) {
$.ajax({
url: "/admin/database/optimize",
data: {'id':ids},
data: {'tables':tables},
success: function (res) {
layer.msg(res.msg);
if (res.code == 0) {
location.reload();
setTimeout(function(){
location.reload();
},2000)
}
}
})
@ -133,11 +184,13 @@
}, function (index) {
$.ajax({
url: "/admin/database/repair",
data: {'id':ids},
data: {'tables':tables},
success: function (res) {
layer.msg(res.msg);
if (res.code == 0) {
location.reload();
setTimeout(function(){
location.reload();
},2000)
}
}
})

View File

@ -49,17 +49,13 @@
width: 80
}, {
field: 'content',
title: '操作描述',
width: 348
title: '操作描述'
}, {
field: 'param_id',
title: '操作数据ID',
align: 'center',
width: 100
}, {
field: 'param',
title: '操作数据'
}, {
},{
field: 'nickname',
title: '操作用户',
align: 'center',

View File

@ -1,118 +1,139 @@
<?php
declare (strict_types = 1);
namespace backup;
use think\facade\Db;
use think\facade\Config;
class Backup
{
/**
* 文件指针
* @var resource
*/
private $fp;
/**
* 备份文件信息 part - 卷号name - 文件名
* @var array
*/
private $file;
/**
* 当前打开文件大小
* @var integer
*/
private $size = 0;
/**
* 数据库配置
* @var integer
*/
private $dbconfig=array();
private $dbconfig = array();
/**
* 备份配置
* @var integer
*/
private $config=array(
'path' => './backup/',//数据库备份路径
'part' => 20971520,//数据库备份卷大小
'compress' => 0,//数据库备份文件是否启用压缩 0不压缩 1 压缩
'level' => 9 //数据库备份文件压缩级别 1普通 4 一般 9最高
private $config = array(
//数据库备份路径
'path' => './backup/',
//数据库备份卷大小,默认10MB
'part' => 10485760,
//数据库备份文件是否启用压缩 0不压缩 1 压缩
'compress' => 0,
//数压缩级别
'level' => 9,
);
/**
* 数据库备份构造方法
* @param array $file 备份或还原的文件信息
* @param array $config 备份配置信息
*/
public function __construct($config=[]){
public function __construct($config = [])
{
$this->config = array_merge($this->config, $config);
//初始化文件名
$this->setFile();
//初始化数据库连接参数
$this->setDbConn();
//检查文件是否可写
if(!$this->checkPath($this->config['path'])){
throw new \Exception("The current directory is not writable");
if (!$this->checkPath($this->config['path'])) {
throw new \think\Exception("The current directory is not writable");
}
}
/**
* 设置脚本运行超时时间
* 0表示不限制,支持连贯操作
*/
public function setTimeout($time=null)
{
if (!is_null($time)) {
set_time_limit($time)||ini_set("max_execution_time", $time);
}
return $this;
}
/**
* 设置数据库连接必备参数
* @param array $dbconfig 数据库连接配置信息
* @return object
* @return object
*/
public function setDbConn($dbconfig=[])
public function setDbConn($dbconfig = [])
{
if (empty($dbconfig)) {
$this->dbconfig = Config::get('database');
}else{
$this->dbconfig=$dbconfig;
$this->dbconfig = Config::get('database');
} else {
$this->dbconfig = $dbconfig;
}
return $this;
}
/**
* 设置备份文件名
* @param String $file 文件名字
* @return object
* @param Array $file 文件名字
* @return object
*/
public function setFile($file=null)
public function setFile($file = null)
{
if(is_null($file)){
$this->file=['name'=>date('Ymd-His'),'part'=>1];
}else{
if(!array_key_exists("name",$file) && !array_key_exists("part",$file)){
$this->file=$file['1'];
}else{
$this->file=$file;
if (is_null($file)) {
$this->file = ['name' => date('Ymd-His'), 'part' => 1];
} else {
if (!array_key_exists("name", $file) && !array_key_exists("part", $file)) {
$this->file = $file['1'];
} else {
$this->file = $file;
}
}
return $this;
}
//数据类连接
public static function connect()
{
return Db::connect();
return \think\facade\Db::connect();
}
//数据库表列表
public function dataList($table = null,$type=1)
{
$db = self::connect();
if (is_null($table)) {
$list = $db->query("SHOW TABLE STATUS");
} else {
if ($type) {
$list = $db->query("SHOW FULL COLUMNS FROM {$table}");
}else{
$list = $db->query("show columns from {$table}");
}
}
return array_map('array_change_key_case', $list);
//$list;
}
//数据库备份文件列表
public function fileList()
{
if(!is_dir($this->config['path'])){
mkdir($this->config['path'], 0755, true);
}
$path = realpath($this->config['path']);
$flag = \FilesystemIterator::KEY_AS_FILENAME;
$glob = new \FilesystemIterator($path, $flag);
$list = array();
foreach ($glob as $name => $file) {
if(preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql(?:\.gz)?$/', $name)){
if (!is_dir($this->config['path'])) {
mkdir($this->config['path'], 0755, true);
}
$path = realpath($this->config['path']);
$flag = \FilesystemIterator::KEY_AS_FILENAME;
$glob = new \FilesystemIterator($path, $flag);
$list = array();
foreach ($glob as $name => $file) {
if (preg_match('/^\\d{8,8}-\\d{6,6}-\\d+\\.sql(?:\\.gz)?$/', $name)) {
$name1= $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}"])){
if (isset($list["{$date} {$time}"])) {
$info = $list["{$date} {$time}"];
$info['part'] = max($info['part'], $part);
$info['size'] = $info['size'] + $file->getSize();
@ -120,104 +141,121 @@ class Backup
$info['part'] = $part;
$info['size'] = $file->getSize();
}
$extension = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
$info['compress'] = ($extension === 'SQL') ? '-' : $extension;
$info['time'] = strtotime("{$date} {$time}");
$extension = strtoupper(pathinfo($file->getFilename(), PATHINFO_EXTENSION));
$info['name']=$name1;
$info['compress'] = $extension === 'SQL' ? '-' : $extension;
$info['time'] = strtotime("{$date} {$time}");
$list["{$date} {$time}"] = $info;
}
}
return $list;
return $list;
}
public function getFile($type='',$time=0)
{ //
if(!is_numeric($time) ){
throw new \Exception("{$time} Illegal data type");
public function getFile($type = '', $time = 0)
{
//
if (!is_numeric($time)) {
throw new \think\Exception("{$time} Illegal data type");
}
switch ($type)
{
switch ($type) {
case 'time':
$time = intval($time);
$name = date('Ymd-His', $time) . '-*.sql*';
$path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
$name = date('Ymd-His', $time) . '-*.sql*';
$path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
return glob($path);
break;
case 'timeverif':
$time = intval($time);
$name = date('Ymd-His', $time) . '-*.sql*';
$path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
$name = date('Ymd-His', $time) . '-*.sql*';
$path = realpath($this->config['path']) . DIRECTORY_SEPARATOR . $name;
$files = glob($path);
$list = array();
foreach($files as $name){
$list = array();
foreach ($files as $name) {
$basename = basename($name);
$match = sscanf($basename, '%4s%2s%2s-%2s%2s%2s-%d');
$gz = preg_match('/^\d{8,8}-\d{6,6}-\d+\.sql.gz$/', $basename);
$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], $name, $gz);
}
$last = end($list);
if(count($list) === $last[0]){
if (count($list) === $last[0]) {
return $list;
} else {
throw new \Exception("File {$files['0']} may be damaged, please check again");
throw new \think\Exception("File {$files['0']} may be damaged, please check again");
}
break;
case 'pathname':
return "{$this->config['path']}{$this->file['name']}-{$this->file['part']}.sql";
break;
break;
case 'filename':
return "{$this->file['name']}-{$this->file['part']}.sql";
break;
break;
case 'filepath':
return $this->config['path'];
break;
default:
$arr=array(
'pathname'=>"{$this->config['path']}{$this->file['name']}-{$this->file['part']}.sql",
'filename'=>"{$this->file['name']}-{$this->file['part']}.sql",
'filepath'=>$this->config['path'],
'file'=>$this->file
);
return $arr;
$arr = array('pathname' => "{$this->config['path']}{$this->file['name']}-{$this->file['part']}.sql", 'filename' => "{$this->file['name']}-{$this->file['part']}.sql", 'filepath' => $this->config['path'], 'file' => $this->file);
return $arr;
}
}
//删除备份文件
public function delFile($time)
{
if($time){
$file=$this->getFile('time',$time);
array_map("unlink", $this->getFile('time',$time));
if(count( $this->getFile('time',$time) )){
throw new \Exception("File {$path} deleted failed");
if ($time) {
$file = $this->getFile('time', $time);
array_map("unlink", $this->getFile('time', $time));
if (count($this->getFile('time', $time))) {
throw new \think\Exception("File {$path} deleted failed");
} else {
return $time;
return $time;
}
} else {
throw new \Exception("{$time} Time parameter is incorrect");
throw new \think\Exception("{$time} Time parameter is incorrect");
}
}
public function import($start){
/**
* 下载备份
* @param string $time
* @param integer $part
* @return array|mixed|string
*/
public function downloadFile($time, $part = 0)
{
$file = $this->getFile('time', $time);
$fileName = $file[$part];
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 {
throw new \think\Exception("{$time} File is abnormal");
}
}
public function import($start,$time,$part)
{
//还原数据
$db = self::connect();
if($this->config['compress']){
$gz = gzopen($this->file[1], 'r');
$db = self::connect();
//$this->file=$this->getFile('time',$time);
$file = $this->getFile('time', $time);
$fileName = $file[$part-1];
if (!file_exists($fileName)) {
return false;
}
if ($this->config['compress']) {
$gz = gzopen($fileName, 'r');
$size = 0;
} else {
$size = filesize($this->file[1]);
$gz = fopen($this->file[1], 'r');
$size = filesize($fileName);
$gz = fopen($fileName, 'r');
}
$sql = '';
if($start){
$sql = '';
if ($start) {
$this->config['compress'] ? gzseek($gz, $start) : fseek($gz, $start);
}
for($i = 0; $i < 1000; $i++){
$sql .= $this->config['compress'] ? gzgets($gz) : fgets($gz);
if(preg_match('/.*;$/', trim($sql))){
if(false !== $db->execute($sql)){
for ($i = 0; $i < 1000; $i++) {
$sql .= $this->config['compress'] ? gzgets($gz) : fgets($gz);
if (preg_match('/.*;$/', trim($sql))) {
if (false !== $db->execute($sql)) {
$start += strlen($sql);
} else {
return false;
@ -227,95 +265,71 @@ class Backup
return 0;
}
}
return array($start, $size);
}
//数据库表列表
public function dataList($table=null)
{
$db = self::connect();
if(is_null($table)){
$list = $db->query("SHOW TABLE STATUS");
}else{
$list = $db->query("show columns from {$table}");
}
return array_map('array_change_key_case', $list);//$list;
}
/**
* 写入初始数据
* @return boolean true - 写入成功false - 写入失败
*/
public function Backup_Init(){
$sql = "-- -----------------------------\n";
public function Backup_Init()
{
$sql = "-- -----------------------------\n";
$sql .= "-- Think MySQL Data Transfer \n";
$sql .= "-- \n";
$sql .= "-- Host : " .$this->dbconfig['hostname']. "\n";
$sql .= "-- Port : " .$this->dbconfig['hostport']. "\n";
$sql .= "-- Database : " .$this->dbconfig['database']. "\n";
$sql .= "-- \n";
$sql .= "-- Part : #{$this->file['part']}\n";
$sql .= "-- Date : " . date("Y-m-d H:i:s") . "\n";
$sql .= "-- -----------------------------\n\n";
$sql .= "SET FOREIGN_KEY_CHECKS = 0;\n\n";
return $this->write($sql);
return $this->write($sql);
}
/**
* 备份表结构
* @param string $table 表名
* @param integer $start 起始行数
* @return boolean false - 备份失败
*/
public function backup($table, $start){
public function backup($table, $start)
{
$db = self::connect();
// 备份表结构
if(0 == $start){
if (0 == $start) {
$result = $db->query("SHOW CREATE TABLE `{$table}`");
$sql = "\n";
$sql = "\n";
$sql .= "-- -----------------------------\n";
$sql .= "-- Table structure for `{$table}`\n";
$sql .= "-- -----------------------------\n";
$sql .= "DROP TABLE IF EXISTS `{$table}`;\n";
$sql .= trim($result[0]['Create Table']) . ";\n\n";
if(false === $this->write($sql)){
if (false === $this->write($sql)) {
return false;
}
}
//数据总数
$result = $db->query("SELECT COUNT(*) AS count FROM `{$table}`");
$count = $result['0']['count'];
$count = $result['0']['count'];
//备份表数据
if($count){
if ($count) {
//写入数据注释
if(0 == $start){
$sql = "-- -----------------------------\n";
if (0 == $start) {
$sql = "-- -----------------------------\n";
$sql .= "-- Records of `{$table}`\n";
$sql .= "-- -----------------------------\n";
$this->write($sql);
}
//备份数据记录
$result = $db->query("SELECT * FROM `{$table}` LIMIT {$start}, 1000");
foreach ($result as $row) {
$row = array_map('addslashes', $row);
$sql = "INSERT INTO `{$table}` VALUES ('" . str_replace(array("\r","\n"),array('\r','\n'),implode("', '", $row)) . "');\n";
if(false === $this->write($sql)){
$sql = "INSERT INTO `{$table}` VALUES ('" . str_replace(array("\r", "\n"), array('\\r', '\\n'), implode("', '", $row)) . "');\n";
if (false === $this->write($sql)) {
return false;
}
}
//还有更多数据
if($count > $start + 1000){
return array($start + 1000, $count);
if ($count > $start + 1000) {
//return array($start + 1000, $count);
return $this->backup($table, $start + 1000);
}
}
//备份下一表
@ -324,81 +338,84 @@ class Backup
/**
* 优化表
* @param String $tables 表名
* @return String $tables
* @return String $tables
*/
public function optimize($tables = null){
if($tables) {
public function optimize($tables = null)
{
if ($tables) {
$db = self::connect();
if(is_array($tables)){
if (is_array($tables)) {
$tables = implode('`,`', $tables);
$list = $db->query("OPTIMIZE TABLE `{$tables}`");
} else {
$list = $db->query("OPTIMIZE TABLE `{$tables}`");
}
if($list){
if ($list) {
return $tables;
} else {
throw new \Exception("data sheet'{$tables}'Repair mistakes please try again!");
throw new \think\Exception("data sheet'{$tables}'Repair mistakes please try again!");
}
} else {
throw new \Exception("Please specify the table to be repaired!");
throw new \think\Exception("Please specify the table to be repaired!");
}
}
/**
* 修复表
* @param String $tables 表名
* @return String $tables
* @return String $tables
*/
public function repair($tables = null){
if($tables) {
public function repair($tables = null)
{
if ($tables) {
$db = self::connect();
if(is_array($tables)){
if (is_array($tables)) {
$tables = implode('`,`', $tables);
$list = $db->query("REPAIR TABLE `{$tables}`");
} else {
$list = $db->query("REPAIR TABLE `{$tables}`");
$list = $db->query("REPAIR TABLE `{$tables}`");
}
if($list){
if ($list) {
return $list;
} else {
throw new \Exception("data sheet'{$tables}'Repair mistakes please try again!");
throw new \think\Exception("data sheet'{$tables}'Repair mistakes please try again!");
}
} else {
throw new \Exception("Please specify the table to be repaired!");
throw new \think\Exception("Please specify the table to be repaired!");
}
}
/**
* 写入SQL语句
* @param string $sql 要写入的SQL语句
* @return boolean true - 写入成功false - 写入失败!
*/
private function write($sql){
private function write($sql)
{
$size = strlen($sql);
//由于压缩原因无法计算出压缩后的长度这里假设压缩率为50%
//一般情况压缩率都会高于50%
$size = $this->config['compress'] ? $size / 2 : $size;
$this->open($size);
$this->open($size);
return $this->config['compress'] ? @gzwrite($this->fp, $sql) : @fwrite($this->fp, $sql);
}
/**
* 打开一个卷,用于写入数据
* @param integer $size 写入数据的大小
*/
private function open($size){
if($this->fp){
private function open($size)
{
if ($this->fp) {
$this->size += $size;
if($this->size > $this->config['part']){
if ($this->size > $this->config['part']) {
$this->config['compress'] ? @gzclose($this->fp) : @fclose($this->fp);
$this->fp = null;
$this->file['part']++;
session('backup_file', $this->file);
$this->create();
$this->Backup_Init();
}
} else {
$backuppath = $this->config['path'];
$filename = "{$backuppath}{$this->file['name']}-{$this->file['part']}.sql";
if($this->config['compress']){
$filename = "{$backuppath}{$this->file['name']}-{$this->file['part']}.sql";
if ($this->config['compress']) {
$filename = "{$filename}.gz";
$this->fp = @gzopen($filename, "a{$this->config['level']}");
} else {
@ -426,10 +443,10 @@ class Backup
/**
* 析构方法,用于关闭文件资源
*/
public function __destruct(){
public function __destruct()
{
if($this->fp){
$this->config['compress'] ? @gzclose($this->fp) : @fclose($this->fp);
}
}
}
}