Install.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. <?php
  2. declare (strict_types=1);
  3. namespace app\api\controller;
  4. use ba\Random;
  5. use ba\Version;
  6. use app\common\controller\Api;
  7. use think\App;
  8. use ba\Terminal;
  9. use think\Exception;
  10. use think\facade\Config;
  11. use think\facade\Db;
  12. use think\db\exception\PDOException;
  13. use app\admin\model\Admin as AdminModel;
  14. use app\admin\model\User as UserModel;
  15. /**
  16. * 安装控制器
  17. */
  18. class Install extends Api
  19. {
  20. protected $useSystemSettings = false;
  21. /**
  22. * 环境检查状态
  23. */
  24. static $ok = 'ok';
  25. static $fail = 'fail';
  26. static $warn = 'warn';
  27. /**
  28. * 安装锁文件名称
  29. */
  30. static $lockFileName = 'install.lock';
  31. /**
  32. * 配置文件
  33. */
  34. static $dbConfigFileName = 'database.php';
  35. static $buildConfigFileName = 'buildadmin.php';
  36. /**
  37. * 自动构建的前端文件的 outDir 相对于根目录
  38. */
  39. static $distDir = 'web' . DIRECTORY_SEPARATOR . 'dist';
  40. /**
  41. * 需要的依赖版本
  42. */
  43. static $needDependentVersion = [
  44. 'php' => '7.1.0',
  45. 'npm' => '6.14.0',
  46. 'cnpm' => '7.1.0',
  47. 'node' => '14.13.1',
  48. 'yarn' => '1.2.0',
  49. 'pnpm' => '6.32.13',
  50. ];
  51. /**
  52. * 安装完成标记
  53. * 配置完成则建立lock文件
  54. * 执行命令成功执行再写入标记到lock文件
  55. * 实现命令执行失败,重载页面可重新执行
  56. */
  57. static $InstallationCompletionMark = 'install-end';
  58. /**
  59. * 构造方法
  60. * @param App $app
  61. */
  62. public function __construct(App $app)
  63. {
  64. parent::__construct($app);
  65. }
  66. /**
  67. * 命令执行窗口
  68. */
  69. public function terminal()
  70. {
  71. // 安装锁
  72. if (is_file(public_path() . self::$lockFileName)) {
  73. $contents = @file_get_contents(public_path() . self::$lockFileName);
  74. if ($contents == self::$InstallationCompletionMark) {
  75. return;
  76. }
  77. }
  78. Terminal::instance()->exec(false);
  79. }
  80. public function changePackageManager()
  81. {
  82. // 安装锁
  83. if (is_file(public_path() . self::$lockFileName)) {
  84. $contents = @file_get_contents(public_path() . self::$lockFileName);
  85. if ($contents == self::$InstallationCompletionMark) {
  86. return;
  87. }
  88. }
  89. $newPackageManager = request()->post('manager', Config::get('terminal.npm_package_manager'));
  90. if (Terminal::changeTerminalConfig()) {
  91. $this->success('', [
  92. 'manager' => $newPackageManager
  93. ]);
  94. } else {
  95. $this->error(__('Failed to switch package manager. Please modify the configuration file manually:%s', ['根目录/config/buildadmin.php']));
  96. }
  97. }
  98. /**
  99. * 环境基础检查
  100. */
  101. public function envBaseCheck()
  102. {
  103. // 安装锁
  104. if (is_file(public_path() . self::$lockFileName)) {
  105. $this->error(__('The system has completed installation. If you need to reinstall, please delete the %s file first', ['public/' . self::$lockFileName]), []);
  106. }
  107. // php版本-start
  108. $phpVersion = phpversion();
  109. $phpVersionCompare = Version::compare(self::$needDependentVersion['php'], $phpVersion);
  110. if (!$phpVersionCompare) {
  111. $phpVersionLink = [
  112. [
  113. // 需要PHP版本
  114. 'name' => __('need') . ' >= ' . self::$needDependentVersion['php'],
  115. 'type' => 'text'
  116. ],
  117. [
  118. // 如何解决
  119. 'name' => __('How to solve?'),
  120. 'title' => __('Click to see how to solve it'),
  121. 'type' => 'faq',
  122. 'url' => 'https://wonderful-code.gitee.io/guide/install/preparePHP.html'
  123. ]
  124. ];
  125. }
  126. // php版本-end
  127. // 配置文件-start
  128. $dbConfigFile = config_path() . self::$dbConfigFileName;
  129. $configIsWritable = path_is_writable(config_path()) && path_is_writable($dbConfigFile);
  130. if (!$configIsWritable) {
  131. $configIsWritableLink = [
  132. [
  133. // 查看原因
  134. 'name' => __('View reason'),
  135. 'title' => __('Click to view the reason'),
  136. 'type' => 'faq',
  137. 'url' => 'https://wonderful-code.gitee.io/guide/install/dirNoPermission.html'
  138. ]
  139. ];
  140. }
  141. // 配置文件-end
  142. // public-start
  143. $publicIsWritable = path_is_writable(public_path());
  144. if (!$publicIsWritable) {
  145. $publicIsWritableLink = [
  146. [
  147. 'name' => __('View reason'),
  148. 'title' => __('Click to view the reason'),
  149. 'type' => 'faq',
  150. 'url' => 'https://wonderful-code.gitee.io/guide/install/dirNoPermission.html'
  151. ]
  152. ];
  153. }
  154. // public-end
  155. // PDO-start
  156. $phpPdo = extension_loaded("PDO");
  157. if (!$phpPdo) {
  158. $phpPdoLink = [
  159. [
  160. 'name' => __('PDO extensions need to be installed'),
  161. 'type' => 'text'
  162. ],
  163. [
  164. 'name' => __('How to solve?'),
  165. 'title' => __('Click to see how to solve it'),
  166. 'type' => 'faq',
  167. 'url' => 'https://wonderful-code.gitee.io/guide/install/missingExtension.html'
  168. ]
  169. ];
  170. }
  171. // PDO-end
  172. // proc_open
  173. $phpProc = function_exists('proc_open') && function_exists('proc_close') && function_exists('proc_get_status');
  174. if (!$phpProc) {
  175. $phpProcLink = [
  176. [
  177. 'name' => __('View reason'),
  178. 'title' => __('proc_open or proc_close functions in PHP Ini is disabled'),
  179. 'type' => 'faq',
  180. 'url' => 'https://wonderful-code.gitee.io/guide/install/disablement.html'
  181. ],
  182. [
  183. 'name' => __('How to modify'),
  184. 'title' => __('Click to view how to modify'),
  185. 'type' => 'faq',
  186. 'url' => 'https://wonderful-code.gitee.io/guide/install/disablement.html'
  187. ],
  188. [
  189. 'name' => __('Security assurance?'),
  190. 'title' => __('Using the installation service correctly will not cause any potential security problems. Click to view the details'),
  191. 'type' => 'faq',
  192. 'url' => 'https://wonderful-code.gitee.io/guide/install/senior.html'
  193. ],
  194. ];
  195. }
  196. // proc_open-end
  197. $this->success('', [
  198. 'php_version' => [
  199. 'describe' => $phpVersion,
  200. 'state' => $phpVersionCompare ? self::$ok : self::$fail,
  201. 'link' => $phpVersionLink ?? [],
  202. ],
  203. 'config_is_writable' => [
  204. 'describe' => self::writableStateDescribe($configIsWritable),
  205. 'state' => $configIsWritable ? self::$ok : self::$fail,
  206. 'link' => $configIsWritableLink ?? []
  207. ],
  208. 'public_is_writable' => [
  209. 'describe' => self::writableStateDescribe($publicIsWritable),
  210. 'state' => $publicIsWritable ? self::$ok : self::$fail,
  211. 'link' => $publicIsWritableLink ?? []
  212. ],
  213. 'php_pdo' => [
  214. 'describe' => $phpPdo ? __('already installed') : __('Not installed'),
  215. 'state' => $phpPdo ? self::$ok : self::$fail,
  216. 'link' => $phpPdoLink ?? []
  217. ],
  218. 'php_proc' => [
  219. 'describe' => $phpProc ? __('Allow execution') : __('disabled'),
  220. 'state' => $phpProc ? self::$ok : self::$warn,
  221. 'link' => $phpProcLink ?? []
  222. ],
  223. ]);
  224. }
  225. /**
  226. * npm环境检查
  227. */
  228. public function envNpmCheck()
  229. {
  230. if (is_file(public_path() . self::$lockFileName)) {
  231. $this->error('', [], 2);
  232. }
  233. $packageManager = request()->post('manager', 'none');
  234. // npm
  235. $npmVersion = Version::getVersion('npm');
  236. $npmVersionCompare = Version::compare(self::$needDependentVersion['npm'], $npmVersion);
  237. if (!$npmVersionCompare || !$npmVersion) {
  238. $npmVersionLink = [
  239. [
  240. // 需要版本
  241. 'name' => __('need') . ' >= ' . self::$needDependentVersion['npm'],
  242. 'type' => 'text'
  243. ],
  244. [
  245. // 如何解决
  246. 'name' => __('How to solve?'),
  247. 'title' => __('Click to see how to solve it'),
  248. 'type' => 'faq',
  249. 'url' => 'https://wonderful-code.gitee.io/guide/install/prepareNpm.html'
  250. ]
  251. ];
  252. }
  253. // 包管理器
  254. if (in_array($packageManager, ['npm', 'cnpm', 'pnpm', 'yarn'])) {
  255. $pmVersion = Version::getVersion($packageManager);
  256. $pmVersionCompare = Version::compare(self::$needDependentVersion[$packageManager], $pmVersion);
  257. if (!$pmVersion) {
  258. // 安装
  259. $pmVersionLink[] = [
  260. // 需要版本
  261. 'name' => __('need') . ' >= ' . self::$needDependentVersion[$packageManager],
  262. 'type' => 'text'
  263. ];
  264. if ($npmVersionCompare) {
  265. $pmVersionLink[] = [
  266. // 点击安装
  267. 'name' => __('Click Install %s', [$packageManager]),
  268. 'title' => '',
  269. 'type' => 'install-package-manager'
  270. ];
  271. } else {
  272. $pmVersionLink[] = [
  273. // 请先安装npm
  274. 'name' => __('Please install NPM first'),
  275. 'type' => 'text'
  276. ];
  277. }
  278. } elseif (!$pmVersionCompare) {
  279. // 版本不足
  280. $pmVersionLink[] = [
  281. // 需要版本
  282. 'name' => __('need') . ' >= ' . self::$needDependentVersion[$packageManager],
  283. 'type' => 'text'
  284. ];
  285. $pmVersionLink[] = [
  286. // 请升级
  287. 'name' => __('Please upgrade %s version', [$packageManager]),
  288. 'type' => 'text'
  289. ];
  290. }
  291. } elseif ($packageManager == 'ni') {
  292. $pmVersion = __('nothing');
  293. $pmVersionCompare = true;
  294. } else {
  295. $pmVersion = __('nothing');
  296. $pmVersionCompare = false;
  297. }
  298. // nodejs
  299. $nodejsVersion = Version::getVersion('node');
  300. $nodejsVersionCompare = Version::compare(self::$needDependentVersion['node'], $nodejsVersion);
  301. if (!$nodejsVersionCompare || !$nodejsVersion) {
  302. $nodejsVersionLink = [
  303. [
  304. // 需要版本
  305. 'name' => __('need') . ' >= ' . self::$needDependentVersion['node'],
  306. 'type' => 'text'
  307. ],
  308. [
  309. // 如何解决
  310. 'name' => __('How to solve?'),
  311. 'title' => __('Click to see how to solve it'),
  312. 'type' => 'faq',
  313. 'url' => 'https://wonderful-code.gitee.io/guide/install/prepareNodeJs.html'
  314. ]
  315. ];
  316. }
  317. $this->success('', [
  318. 'npm_version' => [
  319. 'describe' => $npmVersion ?: __('Acquisition failed'),
  320. 'state' => $npmVersionCompare ? self::$ok : self::$warn,
  321. 'link' => $npmVersionLink ?? [],
  322. ],
  323. 'nodejs_version' => [
  324. 'describe' => $nodejsVersion ?: __('Acquisition failed'),
  325. 'state' => $nodejsVersionCompare ? self::$ok : self::$warn,
  326. 'link' => $nodejsVersionLink ?? []
  327. ],
  328. 'npm_package_manager' => [
  329. 'describe' => $pmVersion ?: __('Acquisition failed'),
  330. 'state' => $pmVersionCompare ? self::$ok : self::$warn,
  331. 'link' => $pmVersionLink ?? [],
  332. ]
  333. ]);
  334. }
  335. /**
  336. * 测试数据库连接
  337. */
  338. public function testDatabase()
  339. {
  340. $database = [
  341. 'hostname' => $this->request->post('hostname'),
  342. 'username' => $this->request->post('username'),
  343. 'password' => $this->request->post('password'),
  344. 'hostport' => $this->request->post('hostport'),
  345. 'database' => '',
  346. ];
  347. $conn = $this->testConnectDatabase($database);
  348. if ($conn['code'] == 0) {
  349. $this->error($conn['msg']);
  350. } else {
  351. $this->success('', [
  352. 'databases' => $conn['databases']
  353. ]);
  354. }
  355. }
  356. /**
  357. * 系统基础配置
  358. * post请求=开始安装
  359. */
  360. public function baseConfig()
  361. {
  362. if (is_file(public_path() . self::$lockFileName)) {
  363. $contents = @file_get_contents(public_path() . self::$lockFileName);
  364. if ($contents != self::$InstallationCompletionMark) {
  365. $this->error('Retry Build', [], 302);
  366. }
  367. $this->error(__('The system has completed installation. If you need to reinstall, please delete the %s file first', ['public/' . self::$lockFileName]));
  368. }
  369. $envOk = $this->commandExecutionCheck();
  370. if ($this->request->isGet()) {
  371. $this->success('', ['envOk' => $envOk]);
  372. }
  373. $param = $this->request->only(['hostname', 'username', 'password', 'hostport', 'database', 'prefix', 'adminname', 'adminpassword', 'sitename']);
  374. // 数据库配置测试
  375. try {
  376. $dbConfig = Config::get('database');
  377. $dbConfig['connections']['mysql']['hostname'] = $param['hostname'];
  378. $dbConfig['connections']['mysql']['database'] = $param['database'];
  379. $dbConfig['connections']['mysql']['username'] = $param['username'];
  380. $dbConfig['connections']['mysql']['password'] = $param['password'];
  381. $dbConfig['connections']['mysql']['hostport'] = $param['hostport'];
  382. $dbConfig['connections']['mysql']['prefix'] = $param['prefix'];
  383. Config::set(['connections' => $dbConfig['connections']], 'database');
  384. $connect = Db::connect('mysql');
  385. $connect->execute("SELECT 1");
  386. } catch (PDOException $e) {
  387. $this->error(__('Database connection failed:%s', [mb_convert_encoding($e->getMessage(), 'UTF-8', 'UTF-8,GBK,GB2312,BIG5')]));
  388. }
  389. // 导入安装sql
  390. try {
  391. $sql = file_get_contents(root_path() . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR . 'buildadmin.sql');
  392. $sql = str_replace("__PREFIX__", $param['prefix'], $sql);
  393. $connect->getPdo()->exec($sql);
  394. } catch (PDOException $e) {
  395. $errorMsg = $e->getMessage();
  396. $this->error(__('Failed to install SQL execution:%s', [mb_convert_encoding($errorMsg ?: 'unknown', 'UTF-8', 'UTF-8,GBK,GB2312,BIG5')]));
  397. } catch (Exception $e) {
  398. $errorMsg = $e->getMessage();
  399. $this->error(__('Installation error:%s', [mb_convert_encoding($errorMsg ?: 'unknown', 'UTF-8', 'UTF-8,GBK,GB2312,BIG5')]));
  400. }
  401. // 写入数据库配置文件
  402. $dbConfigFile = config_path() . self::$dbConfigFileName;
  403. $dbConfigContent = @file_get_contents($dbConfigFile);
  404. $callback = function ($matches) use ($param) {
  405. $value = $param[$matches[1]] ?? '';
  406. return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}env('database.{$matches[1]}', '{$value}'),";
  407. };
  408. $dbConfigText = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)env\('database\.(.*)',\s+'(.*)'\)\,/", $callback, $dbConfigContent);
  409. $result = @file_put_contents($dbConfigFile, $dbConfigText);
  410. if (!$result) {
  411. $this->error(__('File has no write permission:%s', ['config/' . self::$dbConfigFileName]));
  412. }
  413. // 写入.env-example文件
  414. $envFile = root_path() . '.env-example';
  415. $envFileContent = @file_get_contents($envFile);
  416. if ($envFileContent && stripos($envFileContent, '[DATABASE]') === false) {
  417. $envFileContent .= "\n" . '[DATABASE]' . "\n";
  418. $envFileContent .= 'TYPE = mysql' . "\n";
  419. $envFileContent .= 'HOSTNAME = ' . $param['hostname'] . "\n";
  420. $envFileContent .= 'DATABASE = ' . $param['database'] . "\n";
  421. $envFileContent .= 'USERNAME = ' . $param['username'] . "\n";
  422. $envFileContent .= 'PASSWORD = ' . $param['password'] . "\n";
  423. $envFileContent .= 'HOSTPORT = ' . $param['hostport'] . "\n";
  424. $envFileContent .= 'CHARSET = utf8' . "\n";
  425. $envFileContent .= 'DEBUG = true' . "\n";
  426. $result = @file_put_contents($envFile, $envFileContent);
  427. if (!$result) {
  428. $this->error(__('File has no write permission:%s', ['/' . $envFile]));
  429. }
  430. }
  431. // 设置新的Token随机密钥key
  432. $oldTokenKey = Config::get('buildadmin.token.key');
  433. $newTokenKey = Random::build('alnum', 32);
  434. $buildConfigFile = config_path() . self::$buildConfigFileName;
  435. $buildConfigContent = @file_get_contents($buildConfigFile);
  436. $buildConfigContent = preg_replace("/'key'(\s+)=>(\s+)'{$oldTokenKey}'/", "'key'\$1=>\$2'{$newTokenKey}'", $buildConfigContent);
  437. $result = @file_put_contents($buildConfigFile, $buildConfigContent);
  438. if (!$result) {
  439. $this->error(__('File has no write permission:%s', ['config/' . self::$buildConfigFileName]));
  440. }
  441. // 管理员配置入库
  442. $adminModel = new AdminModel();
  443. $defaultAdmin = $adminModel->where('username', 'admin')->find();
  444. $defaultAdmin->username = $param['adminname'];
  445. $defaultAdmin->nickname = ucfirst($param['adminname']);
  446. $defaultAdmin->save();
  447. if (isset($param['adminpassword']) && $param['adminpassword']) {
  448. $adminModel->resetPassword($defaultAdmin->id, $param['adminpassword']);
  449. }
  450. // 默认用户密码修改
  451. $user = new UserModel();
  452. $user->resetPassword(1, Random::build());
  453. // 修改站点名称
  454. $connect->table($param['prefix'] . 'config')->where('name', 'site_name')->update([
  455. 'value' => $param['sitename']
  456. ]);
  457. // 建立安装锁文件
  458. $result = @file_put_contents(public_path() . self::$lockFileName, date('Y-m-d H:i:s'));
  459. if (!$result) {
  460. $this->error(__('File has no write permission:%s', ['public/' . self::$lockFileName]));
  461. }
  462. $this->success('', [
  463. 'execution' => $envOk
  464. ]);
  465. }
  466. /**
  467. * 标记命令执行完毕
  468. */
  469. public function commandExecComplete()
  470. {
  471. if (is_file(public_path() . self::$lockFileName)) {
  472. $contents = @file_get_contents(public_path() . self::$lockFileName);
  473. if ($contents == self::$InstallationCompletionMark) {
  474. $this->error(__('The system has completed installation. If you need to reinstall, please delete the %s file first', ['public/' . self::$lockFileName]));
  475. }
  476. }
  477. $result = @file_put_contents(public_path() . self::$lockFileName, self::$InstallationCompletionMark);
  478. if (!$result) {
  479. $this->error(__('File has no write permission:%s', ['public/' . self::$lockFileName]));
  480. }
  481. $this->success();
  482. }
  483. /**
  484. * 获取命令执行检查的结果
  485. * @return bool 是否拥有执行命令的条件
  486. */
  487. private function commandExecutionCheck(): bool
  488. {
  489. $pm = Config::get('terminal.npm_package_manager');
  490. if ($pm == 'none') {
  491. return false;
  492. }
  493. $check['phpPopen'] = function_exists('proc_open') && function_exists('proc_close');
  494. $check['npmVersionCompare'] = Version::compare(self::$needDependentVersion['npm'], Version::getVersion('npm'));
  495. $check['pmVersionCompare'] = Version::compare(self::$needDependentVersion[$pm], Version::getVersion($pm));
  496. $check['nodejsVersionCompare'] = Version::compare(self::$needDependentVersion['node'], Version::getVersion('node'));
  497. $envOk = true;
  498. foreach ($check as $value) {
  499. if (!$value) {
  500. $envOk = false;
  501. break;
  502. }
  503. }
  504. return $envOk;
  505. }
  506. /**
  507. * 安装指引
  508. */
  509. public function manualInstall()
  510. {
  511. $this->success('', [
  512. 'webPath' => str_replace('\\', '/', root_path() . 'web')
  513. ]);
  514. }
  515. public function mvDist()
  516. {
  517. if (!is_file(root_path() . self::$distDir . DIRECTORY_SEPARATOR . 'index.html')) {
  518. $this->error(__('No built front-end file found, please rebuild manually!'));
  519. }
  520. if (Terminal::mvDist()) {
  521. $this->success();
  522. } else {
  523. $this->error(__('Failed to move the front-end file, please move it manually!'));
  524. }
  525. }
  526. /**
  527. * 目录是否可写
  528. * @param $writable
  529. * @return string
  530. */
  531. private static function writableStateDescribe($writable): string
  532. {
  533. return $writable ? __('Writable') : __('No write permission');
  534. }
  535. /**
  536. * 数据库连接-获取数据表列表
  537. * @param $database
  538. * @return array
  539. */
  540. private function testConnectDatabase($database)
  541. {
  542. try {
  543. $dbConfig = Config::get('database');
  544. $dbConfig['connections']['mysql'] = array_merge($dbConfig['connections']['mysql'], $database);
  545. Config::set(['connections' => $dbConfig['connections']], 'database');
  546. $connect = Db::connect('mysql');
  547. $connect->execute("SELECT 1");
  548. } catch (PDOException $e) {
  549. $errorMsg = $e->getMessage();
  550. return [
  551. 'code' => 0,
  552. 'msg' => __('Database connection failed:%s', [mb_convert_encoding($errorMsg ?: 'unknown', 'UTF-8', 'UTF-8,GBK,GB2312,BIG5')])
  553. ];
  554. }
  555. $databases = [];
  556. // 不需要的数据表
  557. $databasesExclude = ['information_schema', 'mysql', 'performance_schema', 'sys'];
  558. $res = $connect->query("SHOW DATABASES");
  559. foreach ($res as $row) {
  560. if (!in_array($row['Database'], $databasesExclude)) {
  561. $databases[] = $row['Database'];
  562. }
  563. }
  564. return [
  565. 'code' => 1,
  566. 'msg' => '',
  567. 'databases' => $databases,
  568. ];
  569. }
  570. }