导读:
参考了很多前辈们的文章,才最终做出完整的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教程参数这篇文章:blog.csdn.net/qq15577969/…
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('删除备份成功');
}
}