Database.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | 海豚PHP框架 [ DolphinPHP ]
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2016~2017 河源市卓锐科技有限公司 [ http://www.zrthink.com ]
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: http://dolphinphp.com
  8. // +----------------------------------------------------------------------
  9. // | 开源协议 ( http://www.apache.org/licenses/LICENSE-2.0 )
  10. // +----------------------------------------------------------------------
  11. namespace util;
  12. use think\Db;
  13. /**
  14. * 导出数据表到文件
  15. * @author 麦当苗儿 <zuojiazi@vip.qq.com>
  16. * @alter CaiWeiMing <314013107@qq.com>
  17. */
  18. class Database
  19. {
  20. /**
  21. * 文件指针
  22. * @var resource
  23. */
  24. private $fp;
  25. /**
  26. * 备份文件信息 part - 卷号,name - 文件名
  27. * @var array
  28. */
  29. private $file;
  30. /**
  31. * 当前打开文件大小
  32. * @var integer
  33. */
  34. private $size = 0;
  35. /**
  36. * 备份配置
  37. * @var integer
  38. */
  39. private $config;
  40. /**
  41. * 数据库备份构造方法
  42. * @param array $file 备份或还原的文件信息
  43. * @param array $config 备份配置信息
  44. * @param string $type 执行类型,export - 备份数据, import - 还原数据
  45. */
  46. public function __construct($file, $config, $type = 'export'){
  47. $this->file = $file;
  48. $this->config = $config;
  49. }
  50. /**
  51. * 打开一个卷,用于写入数据
  52. * @param integer $size 写入数据的大小
  53. */
  54. private function open($size = 0){
  55. if($this->fp){
  56. $this->size += $size;
  57. if($this->size > $this->config['part']){
  58. $this->config['compress'] ? @gzclose($this->fp) : @fclose($this->fp);
  59. $this->fp = null;
  60. $this->file['part']++;
  61. session('backup_file', $this->file);
  62. $this->create();
  63. }
  64. } else {
  65. $backup_path = $this->config['path'];
  66. $filename = "{$backup_path}{$this->file['name']}-{$this->file['part']}.sql";
  67. if($this->config['compress']){
  68. $filename = "{$filename}.gz";
  69. $this->fp = @gzopen($filename, "a{$this->config['level']}");
  70. } else {
  71. $this->fp = @fopen($filename, 'a');
  72. }
  73. $this->size = filesize($filename) + $size;
  74. }
  75. }
  76. /**
  77. * 写入初始数据
  78. * @return mixed
  79. */
  80. public function create(){
  81. $sql = "-- -----------------------------\n";
  82. $sql .= "-- MySQL Data Transfer\n";
  83. $sql .= "--\n";
  84. $sql .= "-- Host : " . config('database.hostname') . "\n";
  85. $sql .= "-- Port : " . config('database.hostport') . "\n";
  86. $sql .= "-- Database : " . config('database.database') . "\n";
  87. $sql .= "--\n";
  88. $sql .= "-- Part : #{$this->file['part']}\n";
  89. $sql .= "-- Date : " . date("Y-m-d H:i:s") . "\n";
  90. $sql .= "-- -----------------------------\n\n";
  91. $sql .= "SET FOREIGN_KEY_CHECKS = 0;\n\n";
  92. return $this->write($sql);
  93. }
  94. /**
  95. * 写入SQL语句
  96. * @param string $sql 要写入的SQL语句
  97. * @return int
  98. */
  99. private function write($sql = ''){
  100. $size = strlen($sql);
  101. // 由于压缩原因,无法计算出压缩后的长度,这里假设压缩率为50%,
  102. // 一般情况压缩率都会高于50%;
  103. $size = $this->config['compress'] ? $size / 2 : $size;
  104. $this->open($size);
  105. return $this->config['compress'] ? @gzwrite($this->fp, $sql) : @fwrite($this->fp, $sql);
  106. }
  107. /**
  108. * 备份表结构
  109. * @param string $table 表名
  110. * @param integer $start 起始行数
  111. * @return array|bool|int false - 备份失败
  112. */
  113. public function backup($table = '', $start = 0){
  114. // 备份表结构
  115. if(0 == $start){
  116. $result = Db::query("SHOW CREATE TABLE `{$table}`");
  117. $result = array_map('array_change_key_case', $result);
  118. $sql = "\n";
  119. $sql .= "-- -----------------------------\n";
  120. $sql .= "-- Table structure for `{$table}`\n";
  121. $sql .= "-- -----------------------------\n";
  122. $sql .= "DROP TABLE IF EXISTS `{$table}`;\n";
  123. $sql .= trim($result[0]['create table']) . ";\n\n";
  124. if(false === $this->write($sql)){
  125. return false;
  126. }
  127. }
  128. // 数据总数
  129. $result = Db::query("SELECT COUNT(*) AS count FROM `{$table}`");
  130. $count = $result['0']['count'];
  131. //备份表数据
  132. if($count){
  133. // 写入数据注释
  134. if(0 == $start){
  135. $sql = "-- -----------------------------\n";
  136. $sql .= "-- Records of `{$table}`\n";
  137. $sql .= "-- -----------------------------\n";
  138. $this->write($sql);
  139. }
  140. // 备份数据记录
  141. $result = Db::query("SELECT * FROM `{$table}` LIMIT {$start}, 1000");
  142. foreach ($result as $row) {
  143. $row = array_map('addslashes', $row);
  144. $sql = "INSERT INTO `{$table}` VALUES ('" . str_replace(array("\r","\n"),array('\r','\n'),implode("', '", $row)) . "');\n";
  145. if(false === $this->write($sql)){
  146. return false;
  147. }
  148. }
  149. //还有更多数据
  150. if($count > $start + 1000){
  151. return array($start + 1000, $count);
  152. }
  153. }
  154. // 备份下一表
  155. return 0;
  156. }
  157. /**
  158. * 导入数据
  159. * @param integer $start 起始位置
  160. * @return array|bool|int
  161. */
  162. public function import($start = 0){
  163. if($this->config['compress']){
  164. $gz = gzopen($this->file[1], 'r');
  165. $size = 0;
  166. } else {
  167. $size = filesize($this->file[1]);
  168. $gz = fopen($this->file[1], 'r');
  169. }
  170. $sql = '';
  171. if($start){
  172. $this->config['compress'] ? gzseek($gz, $start) : fseek($gz, $start);
  173. }
  174. for($i = 0; $i < 1000; $i++){
  175. $sql .= $this->config['compress'] ? gzgets($gz) : fgets($gz);
  176. if(preg_match('/.*;$/', trim($sql))){
  177. if(false !== Db::execute($sql)){
  178. $start += strlen($sql);
  179. } else {
  180. return false;
  181. }
  182. $sql = '';
  183. } elseif ($this->config['compress'] ? gzeof($gz) : feof($gz)) {
  184. return 0;
  185. }
  186. }
  187. return array($start, $size);
  188. }
  189. /**
  190. * 导出
  191. * @param array $tables 表名
  192. * @param string $path 导出路径
  193. * @param string $prefix 表前缀
  194. * @param integer $export_data 是否导出数据
  195. * @author 蔡伟明 <314013107@qq.com>
  196. * @return bool
  197. */
  198. public static function export($tables = [], $path = '', $prefix = '', $export_data = 1){
  199. $tables = is_array($tables) ? $tables : explode(',', $tables);
  200. $datetime = date('Y-m-d H:i:s', time());
  201. $sql = "-- -----------------------------\n";
  202. $sql .= "-- 导出时间 `{$datetime}`\n";
  203. $sql .= "-- -----------------------------\n";
  204. if (!empty($tables)) {
  205. foreach ($tables as $table) {
  206. $sql .= self::getSql($prefix.$table, $export_data);
  207. }
  208. // 写入文件
  209. if (file_put_contents($path, $sql)) {
  210. return true;
  211. };
  212. }
  213. return false;
  214. }
  215. /**
  216. * 导出卸载文件
  217. * @param array $tables 表名
  218. * @param string $path 导出路径
  219. * @param string $prefix 表前缀
  220. * @author 蔡伟明 <314013107@qq.com>
  221. * @return bool
  222. */
  223. public static function exportUninstall($tables = [], $path = '', $prefix = ''){
  224. $tables = is_array($tables) ? $tables : explode(',', $tables);
  225. $datetime = date('Y-m-d H:i:s', time());
  226. $sql = "-- -----------------------------\n";
  227. $sql .= "-- 导出时间 `{$datetime}`\n";
  228. $sql .= "-- -----------------------------\n";
  229. if (!empty($tables)) {
  230. foreach ($tables as $table) {
  231. $sql .= "DROP TABLE IF EXISTS `{$prefix}{$table}`;\n";
  232. }
  233. // 写入文件
  234. if (file_put_contents($path, $sql)) {
  235. return true;
  236. };
  237. }
  238. return false;
  239. }
  240. /**
  241. * 获取表结构和数据
  242. * @param string $table 表名
  243. * @param integer $export_data 是否导出数据
  244. * @param integer $start 起始行数
  245. * @author 蔡伟明 <314013107@qq.com>
  246. * @return string
  247. */
  248. private static function getSql($table = '', $export_data = 0, $start = 0)
  249. {
  250. $sql = "";
  251. if (Db::query("SHOW TABLES LIKE '%{$table}%'")) {
  252. // 表结构
  253. if ($start == 0) {
  254. $result = Db::query("SHOW CREATE TABLE `{$table}`");
  255. $sql .= "\n-- -----------------------------\n";
  256. $sql .= "-- 表结构 `{$table}`\n";
  257. $sql .= "-- -----------------------------\n";
  258. $sql .= "DROP TABLE IF EXISTS `{$table}`;\n";
  259. $sql .= trim($result[0]['Create Table']) . ";\n\n";
  260. }
  261. // 表数据
  262. if ($export_data) {
  263. $sql .= "-- -----------------------------\n";
  264. $sql .= "-- 表数据 `{$table}`\n";
  265. $sql .= "-- -----------------------------\n";
  266. // 数据总数
  267. $result = Db::query("SELECT COUNT(*) AS count FROM `{$table}`");
  268. $count = $result['0']['count'];
  269. // 备份数据记录
  270. $result = Db::query("SELECT * FROM `{$table}` LIMIT {$start}, 1000");
  271. foreach ($result as $row) {
  272. $row = array_map('addslashes', $row);
  273. $sql .= "INSERT INTO `{$table}` VALUES ('" . str_replace(array("\r","\n"),array('\r','\n'),implode("', '", $row)) . "');\n";
  274. }
  275. // 还有更多数据
  276. if($count > $start + 1000){
  277. $sql .= self::getSql($table, $export_data, $start + 1000);
  278. }
  279. }
  280. }
  281. return $sql;
  282. }
  283. /**
  284. * 析构方法,用于关闭文件资源
  285. */
  286. public function __destruct(){
  287. $this->config['compress'] ? @gzclose($this->fp) : @fclose($this->fp);
  288. }
  289. }