完善网站备份

This commit is contained in:
liyukun 2021-10-31 01:03:25 +08:00
parent 1934a08c44
commit 09eddda97f
9 changed files with 1044 additions and 22 deletions

View File

@ -0,0 +1,330 @@
<?php
// +----------------------------------------------------------------------
// | HulaCWMS 呼啦企业网站管理系统
// +----------------------------------------------------------------------
// | Copyright (c) 2021 https://www.kaifashu.com All rights reserved.
// +----------------------------------------------------------------------
// | Author: 开发树
// +----------------------------------------------------------------------
namespace app\admin\controller;
use app\Request;
use think\facade\Db;
use app\common\service\Database;
use app\admin\service\UtilService as Util;
/*
* 数据库备份还原控制器
*/
class Databases extends AuthController
{
/**
* 数据库备份/还原列表
* @param null $type
* @return string
* @throws \Exception
* @author 李玉坤
* @date 2021-10-30 12:45
*/
public function index($type = null){
if (!$type){
$type = 'export';
}
switch ($type) {
/* 数据还原 */
case 'import':
$title = '数据还原';
break;
/* 数据备份 */
case 'export':
$title = '数据备份';
break;
default:
$this->error('参数错误!');
}
$this->assign('mate_title',$title);
return $this->fetch($type);
}
/**
* 获取列表
* @param Request $request
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 李玉坤
* @date 2021-10-31 0:12
*/
public function lst(Request $request)
{
$data = Util::postMore([
['type','export'],
]);
switch ($data['type']) {
/* 数据还原 */
case 'import':
//列出备份文件列表
$path = system_config('data_backup_path');
if(!is_dir($path)){
mkdir($path, 0755, true);
}
$path = realpath($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)){
$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}");
$list["{$date} {$time}"] = $info;
}
}
$title = '数据还原';
break;
/* 数据备份 */
case 'export':
$list = Db::query('SHOW TABLE STATUS');
$list = array_map('array_change_key_case', $list);
$title = '数据备份';
break;
default:
$this->error('参数错误!');
}
return app("json")->layui($list);
}
/**
* 优化表
* @author 李玉坤
* @date 2021-10-30 12:46
*/
public function optimize()
{
$data = Util::postMore([
['ids',''],
]);
if ($data['ids'] == "") return app("json")->fail("请指定要优化的表");
$ids = $data['ids'];
if(is_array($ids)){
$ids = implode('`,`', $ids);
$list = Db::query("OPTIMIZE TABLE `{$ids}`");
return $list ? app("json")->success("数据表优化完成",'code') : app("json")->fail("数据表优化出错请重试");
} else {
$list = Db::query("OPTIMIZE TABLE `{$ids}`");
return $list ? app("json")->success("数据表'{$ids}'优化完成",'code') : app("json")->fail("数据表'{$ids}'优化出错请重试");
}
}
/**
* 修复表
* @author 李玉坤
* @date 2021-10-31 0:12
*/
public function repair()
{
$data = Util::postMore([
['ids',''],
]);
if ($data['ids'] == "") return app("json")->fail("请指定要优化的表");
$ids = $data['ids'];
if(is_array($ids)){
$ids = implode('`,`', $ids);
$list = Db::query("REPAIR TABLE `{$ids}`");
return $list ? app("json")->success("数据表修复完成",'code') : app("json")->fail("数据表修复出错请重试");
} else {
$list = Db::query("REPAIR TABLE `{$ids}`");
return $list ? app("json")->success("数据表'{$ids}'修复完成",'code') : app("json")->fail("数据表'{$ids}'修复出错请重试");
}
}
/**
* 删除备份文件
* @return mixed|void
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 李玉坤
* @date 2021-10-30 13:31
*/
public function delOne(){
$data = Util::postMore([
['time',''],
]);
if ($data['time'] == "") return app("json")->fail("参数错误");
$name = date('Ymd-His', $data['time']) . '-*.sql*';
$path = realpath(system_config('data_backup_path')) . DIRECTORY_SEPARATOR . $name;
array_map("unlink", glob($path));
return !count(glob($path)) ? app("json")->success("备份文件删除成功",'code') : app("json")->fail("备份文件删除失败,请检查权限");
}
/**
* 备份数据库
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
* @author 李玉坤
* @date 2021-10-31 0:35
*/
public function export(){
$data = Util::postMore([
['ids',''],
['id',''],
['start',''],
]);
if(request()->isPost() && !empty($data['ids'])){ //初始化
if (!is_array($data['ids'])){
$data['ids'] = explode(',',$data['ids']);
}
$path = system_config('data_backup_path');
if(!is_dir($path)){
mkdir($path, 0755, true);
}
//读取备份配置
$config = array(
'path' => realpath($path) . DIRECTORY_SEPARATOR,
'part' => system_config('data_backup_part_size'),
'compress' => system_config('data_backup_compress'),
'level' => system_config('data_backup_compress_level'),
);
//检查备份目录是否可写
if(!is_writeable($config['path'])) return app("json")->fail('备份目录不存在或不可写,请检查后重试');
//检查是否有正在执行的任务
$lock = "{$config['path']}backup.lock";
if(is_file($lock)){
return app("json")->fail('检测到有一个备份任务正在执行,请稍后再试');
} else {
//创建锁文件
file_put_contents($lock, time());
}
session('backup_config', $config);
//生成备份文件信息
$file = array(
'name' => date('Ymd-His', time()),
'part' => 1,
);
session('backup_file', $file);
//缓存要备份的表
session('backup_tables', $data['ids']);
//创建备份文件
$Database = new Database($file, $config);
$res = $Database->create();
return $res ? app("json")->success("初始化成功",'code') : app("json")->fail("初始化失败,备份文件创建失败!");
} elseif (request()->isGet() && is_numeric($data['id']) && is_numeric($data['start'])) { //备份数据
$data['ids'] = session('backup_tables');
//备份指定表
$Database = new Database(session('backup_file'), session('backup_config'));
$data['start'] = $Database->backup($data['ids'][$data['id']], $data['start']);
if(false === $data['start']){ //出错
$this->error('备份出错!');
} elseif (0 === $data['start']) { //下一表
if(isset($data['ids'][++$data['id']])){
return app("json")->success("备份完成",'code');
} else { //备份完成,清空缓存
unlink(session('backup_config.path') . 'backup.lock');
session('backup_tables', null);
session('backup_file', null);
session('backup_config', null);
return app("json")->success("备份完成",'code');
}
} else {
$rate = floor(100 * ($data['start'][0] / $data['start'][1]));
return app("json")->success("正在备份...({$rate}%)",'code');
}
} else { //出错
return app("json")->fail("参数错误");
}
}
/**
* 还原数据库
* @author 李玉坤
* @date 2021-10-30 12:46
*/
public function import(){
$data = Util::postMore([
['time',''],
['part',''],
['start',''],
]);
if(is_numeric($data['time']) && is_null($data['part']) && is_null($data['start'])){ //初始化
//获取备份文件信息
$name = date('Ymd-His', $data['time']) . '-*.sql*';
$path = realpath(system_config('data_backup_path')) . DIRECTORY_SEPARATOR . $name;
$files = glob($path);
$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);
$list[$match[6]] = array($match[6], $name, $gz);
}
ksort($list);
//检测文件正确性
$last = end($list);
if(count($list) === $last[0]){
session('backup_list', $list); //缓存备份列表
return app("json")->success("初始化完成",'code');
} else {
return app("json")->fail("备份文件可能已经损坏,请检查",'code');
}
} elseif(is_numeric($data['part']) && is_numeric($data['start'])) {
$list = session('backup_list');
$db = new Database($list[$data['part']], array(
'path' => realpath(system_config('data_backup_path')) . DIRECTORY_SEPARATOR,
'compress' => $list[$data['part']][2]));
$data['start'] = $db->import($data['start']);
if(false === $data['start']){
$this->error('还原数据出错!');
} elseif(0 === $data['start']) { //下一卷
if(isset($list[++$data['part']])){
return app("json")->success("正在还原...#{$data['part']}",'code');
} else {
session('backup_list', null);
return app("json")->success("还原完成",'code');
}
} else {
$data = array('part' => $data['part'], 'start' => $data['start'][0]);
if($data['start'][1]){
$rate = floor(100 * ($data['start'][0] / $data['start'][1]));
return app("json")->success("正在还原...#{$data['part']} ({$rate}%)");
} else {
$data['gz'] = 1;
return app("json")->success("正在还原...#{$data['part']}");
}
}
} else {
return app("json")->fail('参数错误!');
}
}
}

View File

@ -202,24 +202,6 @@
}
});
}
function test() {
var selRows = $treeTable.bootstrapTable("getSelections");
if(selRows.length == 0){
alert("请至少选择一行");
return;
}
console.log(selRows);
var postData = "";
$.each(selRows,function(i) {
postData += this.id;
if (i < selRows.length - 1) {
postData += " ";
}
});
alert("你选中行的 id 为:"+postData);
}
</script>
</body>
</html>

View File

@ -0,0 +1,251 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>{$mate_title} - {:system_config("title")}</title>
{include file="public/header" /}
<link rel="stylesheet" href="__ADMIN_PATH__css/more.css">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="col-lg-12">
<div class="card">
<ul class="nav nav-tabs page-tabs">
<li class="active"><a href="#!">备份数据库</a> </li>
<li><a href="/admin/databases/index?type=import">还原数据库</a> </li>
</ul>
<div class="tab-content">
<div class="tab-pane active">
<div id="toolbar" class="toolbar-btn-action">
<button type="button" class="btn btn-success btn-primary m-r-5" id="btn-export">
<span aria-hidden="true">立即备份</span>
</button>
<button type="button" class="btn btn-success m-r-5" id="btn-optimize">
<span aria-hidden="true">优化表</span>
</button>
<button type="button" class="btn btn-success m-r-5" id="btn-repair">
<span aria-hidden="true">修复表</span>
</button>
</div>
<table id="tb_departments"></table>
</div>
</div>
</div>
</div>
</div>
{include file="public/footer"/}
<script type="text/javascript">
$('#tb_departments').bootstrapTable({
classes: 'table table-bordered table-hover table-striped',
url: '/admin/databases/Lst',
method: 'post',
dataType : 'json', // 因为本示例中是跨域的调用,所以涉及到ajax都采用jsonp,
uniqueId: 'id',
idField: 'id', // 每行的唯一标识字段
toolbar: '#toolbar', // 工具按钮容器
showColumns: false, // 是否显示所有的列
showRefresh: false, // 是否显示刷新按钮
responseHandler: function (res) {
return {
"total": res.count,
"rows": res.data,
};
},
pagination: true,
queryParams: function(params) {
let temp = toArrayList($(".searchForm").serializeArray());
temp['limit'] = params.limit;
temp['page'] = (params.offset / params.limit) + 1;
return temp;
},
sidePagination: "server",
pageNumber: 1,
pageSize: 10,
pageList: [10, 20, 50, 100],
columns: [{
checkbox: true, // 是否显示复选框
},{
field: 'name',
title: '表名'
}, {
field: 'rows',
title: '数据量',
}, {
field: 'data_length',
title: '数据大小',
formatter:function (value,row,index) {
return renderSize(value);
}
},{
field: 'create_time',
title: '创建时间',
}, {
field: 'operate',
title: '操作',
formatter:function (value,row,index) {
let html = '<a href="#!" class="btn btn-xs btn-default m-r-5 btn-optimize" title="优化表" data-toggle="tooltip" data-trigger="hover"><i class="mdi mdi-rotate-3d"></i></a> \n' +
'<a class="btn btn-xs btn-default btn-repair" href="#!" title="修复表" data-toggle="tooltip" data-trigger="hover"><i class="mdi mdi-rotate-right-variant"></i></a>';
return html;
},
events: {
'click .btn-optimize': function (event, value, row, index) {
$.ajax({
type: "post",
url: "/admin/databases/optimize",
data: {ids:row.name},
success: function (res) {
if (res.code == 200 || res.status == 200) {
parent.lightyear.notify(res.msg, 'success', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');
} else{
parent.lightyear.notify(res.msg, 'danger', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');
}
},
complete: function () {
}
});
},
'click .btn-repair': function (event, value, row, index) {
$.ajax({
type: "post",
url: "/admin/databases/repair",
data: {ids:row.name},
success: function (res) {
if (res.code == 200 || res.status == 200) {
parent.lightyear.notify(res.msg, 'success', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');
} else{
parent.lightyear.notify(res.msg, 'danger', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');
}
},
complete: function () {
}
});
}
}
}],
onLoadSuccess: function(data){
$("[data-toggle='tooltip']").tooltip();
}
});
//格式化文件大小
function renderSize(value){
if(null==value||value==''){
return "0 Bytes";
}
var unitArr = new Array("Bytes","KB","MB","GB","TB","PB","EB","ZB","YB");
var index=0;
var srcsize = parseFloat(value);
index=Math.floor(Math.log(srcsize)/Math.log(1024));
var size =srcsize/Math.pow(1024,index);
size=size.toFixed(2);//保留的小数位数
return size+unitArr[index];
}
$("#btn-export").click(function () {
var checkID = "";
var selectedItem = $('#tb_departments').bootstrapTable('getAllSelections');
if (selectedItem=="") return lightyear.notify("没有选中项", 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
for (var i = 0 ; i< selectedItem.length; i++)
{
checkID += selectedItem[i]['name']+",";
}
if (checkID=="") return lightyear.notify("没有选中项", 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
$.confirm({
title: '重要提醒!',
content: '您确认要执行该操作吗?',
backgroundDismiss: true,
buttons: {
ok: {
text: '确认',
btnClass: 'btn-danger',
action: function () {
$.post("/admin/databases/export",data={ids:checkID},function (res) {
if (res.status == 200) {lightyear.notify(res.msg, 'success', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');location.reload();}
else lightyear.notify(res.msg, 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
})
loadIndex = parent.layer.open({
type: 1,
title:'',
area:'440px',
skin: 'layui-layer-demo', //样式类名
closeBtn: 0, //不显示关闭按钮
anim: 2,
content: '<div id="zz-div-backdata" class="zz-div-backdata"><i class="layui-icon layui-icon-loading layui-icon layui-anim layui-anim-rotate layui-anim-loop"></i><div class="content">正在备份数据库,请稍等...</div></div>'
});
window.onbeforeunload = function(){ return "正在备份数据库,请不要关闭!" }
}
},
cancel: {
text: '取消',
btnClass: 'btn-primary'
}
}
});
})
$("#btn-optimize").click(function () {
var checkID = "";
var selectedItem = $('#tb_departments').bootstrapTable('getAllSelections');
if (selectedItem=="") return lightyear.notify("没有选中项", 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
for (var i = 0 ; i< selectedItem.length; i++)
{
checkID += selectedItem[i]['name']+",";
}
if (checkID=="") return lightyear.notify("没有选中项", 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
$.confirm({
title: '重要提醒!',
content: '您确认要执行该操作吗?',
backgroundDismiss: true,
buttons: {
ok: {
text: '确认',
btnClass: 'btn-danger',
action: function () {
$.post("/admin/databases/optimize",data={ids:checkID},function (res) {
if (res.status == 200) {lightyear.notify(res.msg, 'success', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');location.reload();}
else lightyear.notify(res.msg, 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
})
}
},
cancel: {
text: '取消',
btnClass: 'btn-primary'
}
}
});
})
$("#btn-repair").click(function () {
var checkID = "";
var selectedItem = $('#tb_departments').bootstrapTable('getAllSelections');
if (selectedItem=="") return lightyear.notify("没有选中项", 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
for (var i = 0 ; i< selectedItem.length; i++)
{
checkID += selectedItem[i]['name']+",";
}
if (checkID=="") return lightyear.notify("没有选中项", 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
$.confirm({
title: '重要提醒!',
content: '您确认要执行该操作吗?',
backgroundDismiss: true,
buttons: {
ok: {
text: '确认',
btnClass: 'btn-danger',
action: function () {
$.post("/admin/databases/repair",data={ids:checkID},function (res) {
if (res.status == 200) {lightyear.notify(res.msg, 'success', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');location.reload();}
else lightyear.notify(res.msg, 'danger', 3000, 'mdi mdi-emoticon-neutral', 'top', 'center');
})
}
},
cancel: {
text: '取消',
btnClass: 'btn-primary'
}
}
});
})
</script>
</body>
</html>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<title>{$mate_title} - {:system_config("title")}</title>
{include file="public/header" /}
<link rel="stylesheet" href="__ADMIN_PATH__css/more.css">
</head>
<body>
<div class="container-fluid p-t-15">
<div class="col-lg-12">
<div class="card">
<ul class="nav nav-tabs page-tabs">
<li><a href="/admin/databases/index?type=export">备份数据库</a></li>
<li class="active"><a href="#!">还原数据库</a> </li>
</ul>
<div class="tab-content">
<div class="tab-pane active">
<table id="tb_departments"></table>
</div>
</div>
</div>
</div>
</div>
{include file="public/footer"/}
<script type="text/javascript">
// tree-grid使用
var $treeTable = $('#tree-table');
$treeTable.bootstrapTable({
url: '/admin/category/lst',
method: 'post',
responseHandler: function (res) {
return {
"rows": res.data,
};
},
queryParams: function(params) {
var temp = toArrayList($(".searchForm").serializeArray());
return temp;
},
idField: 'id',
uniqueId: 'id',
dataType: 'json',
toolbar: '#toolbar2',
showColumns: true, // 是否显示所有的列
showRefresh: true, // 是否显示刷新按钮
columns: [
// {
// field: 'check',
// checkbox: true
// },
{
field: 'title',
title: '名称',
},
{
field: 'id',
title: 'ID',
},
{
field: 'display',
title: '可见性',
formatter: function (value, row, index) {
if (value == 0) {
is_checked = '';
} else if (value == 1){
is_checked = 'checked="checked"';
}
var field = "display";
result = '<label class="lyear-switch switch-primary switch-solid lyear-status"><input type="checkbox" '+ is_checked +'><span onClick="updateStatus('+ row.id +', '+ value +', \''+ field +'\')"></span></label>';
return result;
},
},
{
field: 'sort',
title: '排序',
sortable: true,
},
{
field: 'operate',
title: '操作',
align: 'center',
events : {
'click .category-add': function (e, value, row, index) {
location.href = '/admin/category/add?pid='+row.id;
},
'click .article-add': function (e, value, row, index) {
location.href = '/admin/article/add?category_id='+row.id;
},
'click .category-delete': function (e, value, row, index) {
$.alert({
title: '系统提示',
content: '删除提醒',
buttons: {
confirm: {
text: '确认',
btnClass: 'btn-primary',
action: function(){
$.post(url="/admin/category/del",data={"id":row.id},function (res) {
if (res.code == 200) {parent.lightyear.notify('删除成功', 'success', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');$("#tree-table").bootstrapTable('refresh');}
else parent.lightyear.notify('删除失败', 'danger', 3000, 'mdi mdi-emoticon-happy', 'top', 'center');
});
}
},
cancel: {
text: '取消'
}
}
});
},
'click .category-edit': function (e, value, row, index) {
iframe.createIframe('修改分类','/admin/category/edit?id='+row.id)
}
},
formatter: operateFormatter
}
],
treeShowField: 'title',
parentIdField: 'pid',
onResetView: function(data) {
$treeTable.treegrid({
initialState: 'collapsed', // 所有节点都折叠
treeColumn: 1,
//expanderExpandedClass: 'mdi mdi-folder-open', // 可自定义图标样式
//expanderCollapsedClass: 'mdi mdi-folder',
onChange: function() {
$treeTable.bootstrapTable('resetWidth');
}
});
// 只展开树形的第一集节点
$treeTable.treegrid('getRootNodes').treegrid('expand');
},
onCheck: function(row) {
var datas = $treeTable.bootstrapTable('getData');
selectChilds(datas, row, 'id', 'pid', true); // 选择子类
selectParentChecked(datas, row, 'id', 'pid'); // 选择父类
$treeTable.bootstrapTable('load', datas);
},
onUncheck: function(row) {
var datas = $treeTable.bootstrapTable('getData');
selectChilds(datas, row, 'id', 'pid', false);
$treeTable.bootstrapTable('load', datas);
},
});
// 操作按钮
function operateFormatter(value, row, index) {
return [
'<a type="button" class="category-add btn btn-xs btn-default m-r-5" title="编辑" data-toggle="tooltip"><i class="mdi mdi-plus"></i></a>',
'<a type="button" class="article-add btn btn-xs btn-default m-r-5" title="添加文章" data-toggle="tooltip"><i class="mdi mdi-pencil-box"></i></a>',
'<a type="button" class="category-edit btn btn-xs btn-default m-r-5" title="修改" data-toggle="tooltip"><i class="mdi mdi-pencil"></i></a>',
'<a type="button" class="category-delete btn btn-xs btn-default" title="删除" data-toggle="tooltip"><i class="mdi mdi-delete"></i></a>'
].join('');
}
</script>
</body>
</html>

View File

@ -0,0 +1,211 @@
<?php
namespace app\common\service;
use think\db;
//数据导出模型
class Database{
/**
* 文件指针
* @var resource
*/
private $fp;
/**
* 备份文件信息 part - 卷号name - 文件名
* @var array
*/
private $file;
/**
* 当前打开文件大小
* @var integer
*/
private $size = 0;
/**
* 备份配置
* @var integer
*/
private $config;
/**
* 数据库备份构造方法
* @param array $file 备份或还原的文件信息
* @param array $config 备份配置信息
* @param string $type 执行类型export - 备份数据, import - 还原数据
*/
public function __construct($file, $config, $type = 'export'){
$this->file = $file;
$this->config = $config;
}
/**
* 打开一个卷,用于写入数据
* @param integer $size 写入数据的大小
*/
private function open($size){
if($this->fp){
$this->size += $size;
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();
}
} else {
$backuppath = $this->config['path'];
$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 {
$this->fp = @fopen($filename, 'a');
}
$this->size = filesize($filename) + $size;
}
}
/**
* 写入初始数据
* @return boolean true - 写入成功false - 写入失败
*/
public function create(){
$type = config('database.default');
$sql = "-- -----------------------------\n";
$sql .= "-- Think MySQL Data Transfer \n";
$sql .= "-- \n";
$sql .= "-- Host : " . config('database.connections.'.$type.'.hostname') . "\n";
$sql .= "-- Port : " . config('database.connections.'.$type.'.hostport'). "\n";
$sql .= "-- Database : " . config('database.connections.'.$type.'.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);
}
/**
* 写入SQL语句
* @param string $sql 要写入的SQL语句
* @return boolean true - 写入成功false - 写入失败!
*/
private function write($sql){
$size = strlen($sql);
//由于压缩原因无法计算出压缩后的长度这里假设压缩率为50%
//一般情况压缩率都会高于50%
$size = $this->config['compress'] ? $size / 2 : $size;
$this->open($size);
return $this->config['compress'] ? @gzwrite($this->fp, $sql) : @fwrite($this->fp, $sql);
}
/**
* 备份表结构
* @param string $table 表名
* @param integer $start 起始行数
* @return boolean false - 备份失败
*/
public function backup($table, $start){
//创建DB对象
//备份表结构
if(0 == $start){
$result = Db::query("SHOW CREATE TABLE `{$table}`");
$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)){
return false;
}
}
//数据总数
$result = Db::query("SELECT COUNT(*) AS count FROM `{$table}`");
$count = $result['0']['count'];
//备份表数据
if($count){
//写入数据注释
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) {
$sql = "INSERT INTO `{$table}` VALUES (";
foreach ($row as $item){
if($item===null){
$sql=$sql.'null,';
}
else{
$item=addslashes($item);
$sql=$sql."'".str_replace(array("\r","\n"),array('\r','\n'),$item)."',";
}
}
$sql=substr($sql,0,-1);
$sql=$sql. ");\n";
if(false === $this->write($sql)){
return false;
}
}
//还有更多数据
if($count > $start + 1000){
return array($start + 1000, $count);
}
}
//备份下一表
return 0;
}
public function import($start){
//还原数据
if($this->config['compress']){
$gz = gzopen($this->file[1], 'r');
$size = 0;
} else {
$size = filesize($this->file[1]);
$gz = fopen($this->file[1], 'r');
}
$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::query($sql)){
$start += strlen($sql);
} else {
return false;
}
$sql = '';
} elseif ($this->config['compress'] ? gzeof($gz) : feof($gz)) {
return 0;
}
}
return array($start, $size);
}
/**
* 析构方法,用于关闭文件资源
*/
public function __destruct(){
$this->config['compress'] ? @gzclose($this->fp) : @fclose($this->fp);
}
}

View File

@ -0,0 +1,86 @@
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<title>跳转提示</title>
<style type="text/css">
*{ padding: 0; margin: 0; }
body{ background: #FBFBFB; font-family: "Microsoft Yahei","Helvetica Neue",Helvetica,Arial,sans-serif; color: #333; font-size: 16px; }
.system-message{ width:70%;padding: 24px 0; position: absolute;
top:50%;
left:15%;
margin-top: -180px;
}
.system-message h1{ font-size: 100px; font-weight: normal; line-height: 120px; margin-bottom: 12px; color: #333;}
.system-message .msg{
padding: 10px 0;
text-align: center; line-height: 1.8em; font-size: 28px;
}
.system-message .jump{ font-size: 14px; text-align: center; color: #666;}
.system-message .jump a{ color: #666; }
.system-message .detail{ font-size: 12px; line-height: 20px; margin-top: 12px; display: none; }
.icon{
height:120px;
background-position: center center;
background-repeat: no-repeat;
}
.icon-error{
background-image: url('/theme/ico_stop.png');
}
.icon-success{
background-image: url('/theme/ico_success.png');
}
.copyright{
width: 100%;
position: absolute;
bottom: 0;
left:0;
text-align: center;
padding: 30px 0;
font-size:12px;
color: #bbb;
}
.copyright a{
color: #bbb;
text-decoration: none;
}
</style>
</head>
<body>
<div class="system-message">
<?php switch ($code) {?>
<?php case 1:?>
<h1 class="icon icon-success"></h1>
<p class="msg success"><?php echo(strip_tags($msg));?></p>
<?php break;?>
<?php case 0:?>
<h1 class="icon icon-error"></h1>
<p class="msg error"><?php echo(strip_tags($msg));?></p>
<?php break;?>
<?php } ?>
<p class="detail"></p>
<p class="jump">
页面自动 <a id="href" href="<?php echo($url);?>">跳转</a> 等待时间: <b id="wait"><?php echo($wait);?></b>
</p>
</div>
<div class="copyright">
Power by HulaCWMS &copy <a href="http://www.zhuopro.com" target="_blank">灼灼文化</a>
</div>
<script type="text/javascript">
(function(){
var wait = document.getElementById('wait'),
href = document.getElementById('href').href;
var interval = setInterval(function(){
var time = --wait.innerHTML;
if(time <= 0) {
location.href = href;
clearInterval(interval);
};
}, 1000);
})();
</script>
</body>
</html>

View File

@ -34,8 +34,8 @@ return [
// 异常页面的模板文件
'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl',
// 默认跳转页面对应的模板文件
'dispatch_success_tmpl' => root_path(). 'extend/tpl/dispatch_jump.tpl',
'dispatch_error_tmpl' => root_path(). 'extend/tpl/dispatch_jump.tpl',
'dispatch_success_tmpl' => root_path(). 'app/common/tpl/dispatch_jump.tpl',
'dispatch_error_tmpl' => root_path(). 'app/common/tpl/dispatch_jump.tpl',
// 错误显示信息,非调试模式有效
'error_message' => '页面错误!请稍后再试~',

2
extend/.gitignore vendored
View File

@ -1,2 +0,0 @@
*
!.gitignore

View File

@ -0,0 +1 @@
1635613086