Database.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. <?php
  2. namespace app\admin\controller\general;
  3. use addons\database\library\Backup;
  4. use app\common\controller\Backend;
  5. use think\Db;
  6. use think\Debug;
  7. use think\Exception;
  8. use think\exception\PDOException;
  9. use ZipArchive;
  10. /**
  11. * 数据库管理
  12. *
  13. * @icon fa fa-database
  14. * @remark 可在线进行一些简单的数据库表优化或修复,查看表结构和数据。也可以进行SQL语句的操作
  15. */
  16. class Database extends Backend
  17. {
  18. protected $noNeedRight = ['backuplist','query'];
  19. public function _initialize()
  20. {
  21. // if (!config("app_debug")) {
  22. // $this->error("数据库管理插件只允许在开发环境下使用");
  23. // }
  24. return parent::_initialize();
  25. }
  26. /**
  27. * 查看
  28. */
  29. public function index()
  30. {
  31. $tables_data_length = $tables_index_length = $tables_free_length = $tables_data_count = 0;
  32. $tables = $list = [];
  33. $list = Db::query("SHOW TABLES");
  34. foreach ($list as $key => $row) {
  35. $tables[] = ['name' => reset($row), 'rows' => 0];
  36. }
  37. $data['tables'] = $tables;
  38. $data['saved_sql'] = [];
  39. $this->view->assign($data);
  40. return $this->view->fetch();
  41. }
  42. /**
  43. * SQL查询
  44. */
  45. public function query()
  46. {
  47. $do_action = $this->request->post('do_action');
  48. echo '<style type="text/css">
  49. xmp,body{margin:0;padding:0;line-height:18px;font-size:13px;font-family:"Helvetica Neue", Helvetica, Microsoft Yahei, Hiragino Sans GB, WenQuanYi Micro Hei, sans-serif;}
  50. hr{height:1px;margin:5px 1px;background:#e3e3e3;border:none;}
  51. </style>';
  52. if ($do_action == '') {
  53. exit(__('Invalid parameters'));
  54. }
  55. $tablename = $this->request->post("tablename/a");
  56. if(empty($tablename)){
  57. }
  58. if (in_array($do_action, array('doquery', 'optimizeall', 'repairall'))) {
  59. $this->$do_action();
  60. } elseif (count($tablename) == 0) {
  61. exit(__('Invalid parameters'));
  62. } else {
  63. foreach ($tablename as $table) {
  64. $this->$do_action($table);
  65. echo "<br />";
  66. }
  67. }
  68. }
  69. /**
  70. * 备份列表
  71. * @internal
  72. */
  73. public function backuplist()
  74. {
  75. $config = get_addon_config('database');
  76. $backupDir = ROOT_PATH . 'public' . DS . $config['backupDir'];
  77. $backuplist = [];
  78. foreach (glob($backupDir . "*.zip") as $filename) {
  79. $time = filemtime($filename);
  80. $backuplist[$time] =
  81. [
  82. 'file' => str_replace($backupDir, '', $filename),
  83. 'date' => date("Y-m-d H:i:s", $time),
  84. 'size' => format_bytes(filesize($filename))
  85. ];
  86. }
  87. krsort($backuplist);
  88. $this->success("", null, ['backuplist' => array_values($backuplist)]);
  89. }
  90. /**
  91. * 还原
  92. */
  93. public function restore($ids = '')
  94. {
  95. $config = get_addon_config('database');
  96. $backupDir = ROOT_PATH . 'public' . DS . $config['backupDir'];
  97. if ($this->request->isPost()) {
  98. $action = $this->request->request('action');
  99. $file = $this->request->request('file');
  100. if (!preg_match("/^backup\-([a-z0-9\-_\.]+)\.zip$/i", $file)) {
  101. $this->error(__("Invalid parameters"));
  102. }
  103. $file = $backupDir . $file;
  104. if ($action == 'restore') {
  105. if (!class_exists('ZipArchive')) {
  106. $this->error("服务器缺少php-zip组件,无法进行还原操作");
  107. }
  108. try {
  109. $dir = RUNTIME_PATH . 'database' . DS;
  110. if (!is_dir($dir)) {
  111. mkdir($dir, 0755);
  112. }
  113. $zip = new ZipArchive;
  114. if ($zip->open($file) !== true) {
  115. throw new Exception(__('Can not open zip file'));
  116. }
  117. if (!$zip->extractTo($dir)) {
  118. $zip->close();
  119. throw new Exception(__('Can not unzip file'));
  120. }
  121. $zip->close();
  122. $filename = basename($file);
  123. $sqlFile = $dir . str_replace('.zip', '.sql', $filename);
  124. if (!is_file($sqlFile)) {
  125. throw new Exception(__('Sql file not found'));
  126. }
  127. $filesize = filesize($sqlFile);
  128. $list = Db::query('SELECT @@global.max_allowed_packet');
  129. if (isset($list[0]['@@global.max_allowed_packet']) && $filesize >= $list[0]['@@global.max_allowed_packet']) {
  130. Db::execute('SET @@global.max_allowed_packet = ' . ($filesize + 1024));
  131. //throw new Exception('备份文件超过配置max_allowed_packet大小,请修改Mysql服务器配置');
  132. }
  133. $sql = file_get_contents($sqlFile);
  134. Db::clear();
  135. //必须重连一次
  136. Db::connect([], true)->query("select 1");
  137. Db::getPdo()->exec($sql);
  138. } catch (Exception $e) {
  139. $this->error($e->getMessage());
  140. } catch (PDOException $e) {
  141. $this->error($e->getMessage());
  142. }
  143. $this->success(__('Restore successful'));
  144. } elseif ($action == 'delete') {
  145. unlink($file);
  146. $this->success(__('Delete successful'));
  147. }
  148. }
  149. }
  150. /**
  151. * 备份
  152. */
  153. public function backup()
  154. {
  155. $config = get_addon_config('database');
  156. $backupDir = ROOT_PATH . 'public' . DS . $config['backupDir'];
  157. if ($this->request->isPost()) {
  158. if (!class_exists('ZipArchive')) {
  159. $this->error("服务器缺少php-zip组件,无法进行备份操作");
  160. }
  161. $database = config('database');
  162. try {
  163. $backup = new Backup($database['hostname'], $database['username'], $database['database'], $database['password'], $database['hostport']);
  164. $backup->setIgnoreTable($config['backupIgnoreTables'])->backup($backupDir);
  165. } catch (Exception $e) {
  166. $this->error($e->getMessage());
  167. }
  168. $this->success(__('Backup successful'));
  169. }
  170. return;
  171. }
  172. private function viewinfo($name)
  173. {
  174. $row = Db::query("SHOW CREATE TABLE `{$name}`");
  175. $row = array_values($row[0]);
  176. $info = $row[1];
  177. echo "<xmp>{$info};</xmp>";
  178. }
  179. private function viewdata($name = '')
  180. {
  181. $sqlquery = "SELECT * FROM `{$name}`";
  182. $this->doquery($sqlquery);
  183. }
  184. private function optimize($name = '')
  185. {
  186. if (Db::execute("OPTIMIZE TABLE `{$name}`")) {
  187. echo __('Optimize table %s done', $name);
  188. } else {
  189. echo __('Optimize table %s fail', $name);
  190. }
  191. }
  192. private function optimizeall($name = '')
  193. {
  194. $list = Db::query("SHOW TABLES");
  195. foreach ($list as $key => $row) {
  196. $name = reset($row);
  197. if (Db::execute("OPTIMIZE TABLE {$name}")) {
  198. echo __('Optimize table %s done', $name);
  199. } else {
  200. echo __('Optimize table %s fail', $name);
  201. }
  202. echo "<br />";
  203. }
  204. }
  205. private function repair($name = '')
  206. {
  207. if (Db::execute("REPAIR TABLE `{$name}`")) {
  208. echo __('Repair table %s done', $name);
  209. } else {
  210. echo __('Repair table %s fail', $name);
  211. }
  212. }
  213. private function repairall($name = '')
  214. {
  215. $list = Db::query("SHOW TABLES");
  216. foreach ($list as $key => $row) {
  217. $name = reset($row);
  218. if (Db::execute("REPAIR TABLE {$name}")) {
  219. echo __('Repair table %s done', $name);
  220. } else {
  221. echo __('Repair table %s fail', $name);
  222. }
  223. echo "<br />";
  224. }
  225. }
  226. private function doquery($sql = null)
  227. {
  228. $sqlquery = $sql ? $sql : $this->request->post('sqlquery');
  229. if ($sqlquery == '') {
  230. exit(__('SQL can not be empty'));
  231. }
  232. $sqlquery = str_replace('__PREFIX__', config('database.prefix'), $sqlquery);
  233. $sqlquery = str_replace("\r", "", $sqlquery);
  234. $sqls = preg_split("/;[ \t]{0,}\n/i", $sqlquery);
  235. $maxreturn = 100;
  236. $r = '';
  237. foreach ($sqls as $key => $val) {
  238. if (trim($val) == '') {
  239. continue;
  240. }
  241. $val = rtrim($val, ';');
  242. $r .= "SQL:<span style='color:green;'>{$val}</span> ";
  243. if (preg_match("/^(select|explain)(.*)/i ", $val)) {
  244. Debug::remark("begin");
  245. $limit = stripos(strtolower($val), "limit") !== false ? true : false;
  246. try {
  247. $count = Db::execute($val);
  248. if ($count > 0) {
  249. $resultlist = Db::query($val . (!$limit && $count > $maxreturn ? ' LIMIT ' . $maxreturn : ''));
  250. } else {
  251. $resultlist = [];
  252. }
  253. } catch (\PDOException $e) {
  254. continue;
  255. }
  256. Debug::remark("end");
  257. $time = Debug::getRangeTime('begin', 'end', 4);
  258. $usedseconds = __('Query took %s seconds', $time) . "<br />";
  259. if ($count <= 0) {
  260. $r .= __('Query returned an empty result');
  261. } else {
  262. $r .= (__('Total:%s', $count) . (!$limit && $count > $maxreturn ? ',' . __('Max output:%s', $maxreturn) : ""));
  263. }
  264. $r = $r . ',' . $usedseconds;
  265. $j = 0;
  266. foreach ($resultlist as $m => $n) {
  267. $j++;
  268. if (!$limit && $j > $maxreturn) {
  269. break;
  270. }
  271. $r .= "<hr/>";
  272. $r .= "<font color='red'>" . __('Row:%s', $j) . "</font><br />";
  273. foreach ($n as $k => $v) {
  274. $r .= "<font color='blue'>{$k}:</font>{$v}<br/>\r\n";
  275. }
  276. }
  277. } else {
  278. try {
  279. Debug::remark("begin");
  280. $count = Db::getPdo()->exec($val);
  281. Debug::remark("end");
  282. } catch (\PDOException $e) {
  283. continue;
  284. }
  285. $time = Debug::getRangeTime('begin', 'end', 4);
  286. $r .= __('Query affected %s rows and took %s seconds', $count, $time) . "<br />";
  287. }
  288. }
  289. echo $r;
  290. }
  291. }