Files
webman-buildadmin-mall/app/api/controller/Install.php
2026-03-18 17:19:03 +08:00

769 lines
30 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace app\api\controller;
use Throwable;
use ba\Random;
use ba\Version;
use ba\Terminal;
use ba\Filesystem;
use app\common\controller\Api;
use app\admin\model\Admin as AdminModel;
use app\admin\model\User as UserModel;
use support\Response;
use Webman\Http\Request;
use Phinx\Config\Config as PhinxConfig;
use Phinx\Migration\Manager as PhinxManager;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
/**
* 安装控制器
*/
class Install extends Api
{
public const X64 = 'x64';
public const X86 = 'x86';
protected bool $useSystemSettings = false;
/**
* 环境检查状态
*/
static string $ok = 'ok';
static string $fail = 'fail';
static string $warn = 'warn';
/**
* 安装锁文件名称
*/
static string $lockFileName = 'install.lock';
/**
* 配置文件
*/
static string $dbConfigFileName = 'thinkorm.php';
static string $buildConfigFileName = 'buildadmin.php';
/**
* 自动构建的前端文件的 outDir 相对于根目录
*/
static string $distDir = 'web' . DIRECTORY_SEPARATOR . 'dist';
/**
* 需要的依赖版本
*/
static array $needDependentVersion = [
'php' => '8.2.0',
'npm' => '9.8.1',
'cnpm' => '7.1.0',
'node' => '20.14.0',
'yarn' => '1.2.0',
'pnpm' => '6.32.13',
];
/**
* 安装完成标记
*/
static string $InstallationCompletionMark = 'install-end';
/**
* 命令执行窗口exec 为 SSE 长连接,不会返回)
* @throws Throwable
*/
public function terminal(Request $request): Response
{
$this->setRequest($request);
if ($this->isInstallComplete()) {
return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName]));
}
(new Terminal())->exec(false);
return $this->success(); // unreachable: exec() blocks with SSE stream
}
public function changePackageManager(Request $request): Response
{
$this->setRequest($request);
if ($this->isInstallComplete()) {
return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName]));
}
$newPackageManager = $request->post('manager', config('terminal.npm_package_manager'));
if (Terminal::changeTerminalConfig()) {
return $this->success('', [
'manager' => $newPackageManager
]);
}
return $this->error(__('Failed to switch package manager. Please modify the configuration file manually:%s', ['config/terminal.php']));
}
/**
* 环境基础检查
*/
public function envBaseCheck(Request $request): Response
{
$this->setRequest($request);
if ($this->isInstallComplete()) {
return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName]), []);
}
if (($_ENV['DATABASE_TYPE'] ?? getenv('DATABASE_TYPE'))) {
return $this->error(__('The .env file with database configuration was detected. Please clean up and try again!'));
}
// php版本-start
$phpVersion = phpversion();
$phpBit = PHP_INT_SIZE == 8 ? self::X64 : self::X86;
$phpVersionCompare = Version::compare(self::$needDependentVersion['php'], $phpVersion);
if (!$phpVersionCompare) {
$phpVersionLink = [
[
'name' => __('need') . ' >= ' . self::$needDependentVersion['php'],
'type' => 'text'
],
[
'name' => __('How to solve?'),
'title' => __('Click to see how to solve it'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/preparePHP.html'
]
];
} elseif ($phpBit != self::X64) {
$phpVersionLink = [
[
'name' => __('need') . ' x64 PHP',
'type' => 'text'
],
[
'name' => __('How to solve?'),
'title' => __('Click to see how to solve it'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/preparePHP.html'
]
];
}
// php版本-end
// 配置文件-start分别检测目录和文件便于定位问题
$configDir = rtrim(config_path(), '/\\');
$dbConfigFile = $configDir . DIRECTORY_SEPARATOR . self::$dbConfigFileName;
$configDirWritable = Filesystem::pathIsWritable($configDir);
$dbConfigWritable = Filesystem::pathIsWritable($dbConfigFile);
$configIsWritable = $configDirWritable && $dbConfigWritable;
if (!$configIsWritable) {
$configIsWritableLink = [
[
'name' => __('View reason'),
'title' => __('Click to view the reason'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/dirNoPermission.html'
]
];
}
// 配置文件-end
// public-start
$publicIsWritable = Filesystem::pathIsWritable(public_path());
if (!$publicIsWritable) {
$publicIsWritableLink = [
[
'name' => __('View reason'),
'title' => __('Click to view the reason'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/dirNoPermission.html'
]
];
}
// public-end
// PDO-start
$phpPdo = extension_loaded("PDO") && extension_loaded('pdo_mysql');
if (!$phpPdo) {
$phpPdoLink = [
[
'name' => __('PDO extensions need to be installed'),
'type' => 'text'
],
[
'name' => __('How to solve?'),
'title' => __('Click to see how to solve it'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/missingExtension.html'
]
];
}
// PDO-end
// GD2和freeType-start
$phpGd2 = extension_loaded('gd') && function_exists('imagettftext');
if (!$phpGd2) {
$phpGd2Link = [
[
'name' => __('The gd extension and freeType library need to be installed'),
'type' => 'text'
],
[
'name' => __('How to solve?'),
'title' => __('Click to see how to solve it'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/gdFail.html'
]
];
}
// GD2和freeType-end
// proc_open
$phpProc = function_exists('proc_open') && function_exists('proc_close') && function_exists('proc_get_status');
if (!$phpProc) {
$phpProcLink = [
[
'name' => __('View reason'),
'title' => __('proc_open or proc_close functions in PHP Ini is disabled'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/disablement.html'
],
[
'name' => __('How to modify'),
'title' => __('Click to view how to modify'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/disablement.html'
],
[
'name' => __('Security assurance?'),
'title' => __('Using the installation service correctly will not cause any potential security problems. Click to view the details'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/senior.html'
],
];
}
// proc_open-end
return $this->success('', [
'php_version' => [
'describe' => $phpVersion . " ($phpBit)",
'state' => $phpVersionCompare && $phpBit == self::X64 ? self::$ok : self::$fail,
'link' => $phpVersionLink ?? [],
],
'config_is_writable' => [
'describe' => $configIsWritable
? self::writableStateDescribe(true)
: (self::writableStateDescribe(false) . ' [' . $configDir . ']'),
'state' => $configIsWritable ? self::$ok : self::$fail,
'link' => $configIsWritableLink ?? []
],
'public_is_writable' => [
'describe' => self::writableStateDescribe($publicIsWritable),
'state' => $publicIsWritable ? self::$ok : self::$fail,
'link' => $publicIsWritableLink ?? []
],
'php_pdo' => [
'describe' => $phpPdo ? __('already installed') : __('Not installed'),
'state' => $phpPdo ? self::$ok : self::$fail,
'link' => $phpPdoLink ?? []
],
'php_gd2' => [
'describe' => $phpGd2 ? __('already installed') : __('Not installed'),
'state' => $phpGd2 ? self::$ok : self::$fail,
'link' => $phpGd2Link ?? []
],
'php_proc' => [
'describe' => $phpProc ? __('Allow execution') : __('disabled'),
'state' => $phpProc ? self::$ok : self::$warn,
'link' => $phpProcLink ?? []
],
]);
}
/**
* npm环境检查
*/
public function envNpmCheck(Request $request): Response
{
$this->setRequest($request);
if ($this->isInstallComplete()) {
return $this->error('', [], 2);
}
$packageManager = $request->post('manager', 'none');
// npm
$npmVersion = Version::getVersion('npm');
$npmVersionCompare = Version::compare(self::$needDependentVersion['npm'], $npmVersion);
if (!$npmVersionCompare || !$npmVersion) {
$npmVersionLink = [
[
'name' => __('need') . ' >= ' . self::$needDependentVersion['npm'],
'type' => 'text'
],
[
'name' => __('How to solve?'),
'title' => __('Click to see how to solve it'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/prepareNpm.html'
]
];
}
// 包管理器
$pmVersionLink = [];
$pmVersion = __('nothing');
$pmVersionCompare = false;
if (in_array($packageManager, ['npm', 'cnpm', 'pnpm', 'yarn'])) {
$pmVersion = Version::getVersion($packageManager);
$pmVersionCompare = Version::compare(self::$needDependentVersion[$packageManager], $pmVersion);
if (!$pmVersion) {
$pmVersionLink[] = [
'name' => __('need') . ' >= ' . self::$needDependentVersion[$packageManager],
'type' => 'text'
];
if ($npmVersionCompare) {
$pmVersionLink[] = [
'name' => __('Click Install %s', [$packageManager]),
'title' => '',
'type' => 'install-package-manager'
];
} else {
$pmVersionLink[] = [
'name' => __('Please install NPM first'),
'type' => 'text'
];
}
} elseif (!$pmVersionCompare) {
$pmVersionLink[] = [
'name' => __('need') . ' >= ' . self::$needDependentVersion[$packageManager],
'type' => 'text'
];
$pmVersionLink[] = [
'name' => __('Please upgrade %s version', [$packageManager]),
'type' => 'text'
];
}
} elseif ($packageManager == 'ni') {
$pmVersionCompare = true;
}
// nodejs
$nodejsVersion = Version::getVersion('node');
$nodejsVersionCompare = Version::compare(self::$needDependentVersion['node'], $nodejsVersion);
if (!$nodejsVersionCompare || !$nodejsVersion) {
$nodejsVersionLink = [
[
'name' => __('need') . ' >= ' . self::$needDependentVersion['node'],
'type' => 'text'
],
[
'name' => __('How to solve?'),
'title' => __('Click to see how to solve it'),
'type' => 'faq',
'url' => 'https://doc.buildadmin.com/guide/install/prepareNodeJs.html'
]
];
}
return $this->success('', [
'npm_version' => [
'describe' => $npmVersion ?: __('Acquisition failed'),
'state' => $npmVersionCompare ? self::$ok : self::$warn,
'link' => $npmVersionLink ?? [],
],
'nodejs_version' => [
'describe' => $nodejsVersion ?: __('Acquisition failed'),
'state' => $nodejsVersionCompare ? self::$ok : self::$warn,
'link' => $nodejsVersionLink ?? []
],
'npm_package_manager' => [
'describe' => $pmVersion ?: __('Acquisition failed'),
'state' => $pmVersionCompare ? self::$ok : self::$warn,
'link' => $pmVersionLink ?? [],
]
]);
}
/**
* 测试数据库连接
*/
public function testDatabase(Request $request): Response
{
$this->setRequest($request);
$database = [
'hostname' => $request->post('hostname'),
'username' => $request->post('username'),
'password' => $request->post('password'),
'hostport' => $request->post('hostport'),
'database' => '',
];
$conn = $this->connectDb($database);
if ($conn['code'] == 0) {
return $this->error($conn['msg']);
}
return $this->success('', [
'databases' => $conn['databases']
]);
}
/**
* 系统基础配置
* post请求=开始安装
*/
public function baseConfig(Request $request): Response
{
$this->setRequest($request);
if ($this->isInstallComplete()) {
return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName]));
}
$envOk = $this->commandExecutionCheck();
$rootPath = str_replace('\\', '/', root_path());
$migrateCommand = 'php vendor/bin/phinx migrate';
if ($request->isGet()) {
return $this->success('', [
'rootPath' => $rootPath,
'executionWebCommand' => $envOk,
'migrateCommand' => $migrateCommand,
]);
}
$connectData = $databaseParam = $request->only(['hostname', 'username', 'password', 'hostport', 'database', 'prefix']);
// 数据库配置测试
$connectData['database'] = '';
$connect = $this->connectDb($connectData, true);
if ($connect['code'] == 0) {
return $this->error($connect['msg']);
}
// 建立数据库
if (!in_array($databaseParam['database'], $connect['databases'])) {
$sql = "CREATE DATABASE IF NOT EXISTS `{$databaseParam['database']}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
$connect['pdo']->exec($sql);
}
// 写入数据库配置文件thinkorm.php 使用 $env('database.xxx', 'default') 格式)
$dbConfigFile = config_path(self::$dbConfigFileName);
$dbConfigContent = @file_get_contents($dbConfigFile);
if ($dbConfigContent === false || $dbConfigContent === '') {
return $this->error(__('File has no write permission:%s', ['config/' . self::$dbConfigFileName]));
}
$callback = function ($matches) use ($databaseParam) {
$key = $matches[1];
$value = (string) ($databaseParam[$key] ?? '');
return "\$env('database.{$key}', '" . addslashes($value) . "')";
};
$dbConfigText = preg_replace_callback("/\\\$env\('database\.(hostname|database|username|password|hostport|prefix)',\s*'[^']*'\)/", $callback, $dbConfigContent);
$result = @file_put_contents($dbConfigFile, $dbConfigText);
if (!$result) {
return $this->error(__('File has no write permission:%s', ['config/' . self::$dbConfigFileName]));
}
// 写入 .env 和 .env-example仅使用 Dotenv 可解析的 DATABASE_XXX 格式,避免 [DATABASE] 导致解析失败)
$databaseBlock = "\n# Database\n"
. 'DATABASE_TYPE = mysql' . "\n"
. 'DATABASE_HOSTNAME = ' . $databaseParam['hostname'] . "\n"
. 'DATABASE_DATABASE = ' . $databaseParam['database'] . "\n"
. 'DATABASE_USERNAME = ' . $databaseParam['username'] . "\n"
. 'DATABASE_PASSWORD = ' . $databaseParam['password'] . "\n"
. 'DATABASE_HOSTPORT = ' . $databaseParam['hostport'] . "\n"
. 'DATABASE_CHARSET = utf8mb4' . "\n"
. 'DATABASE_PREFIX = ' . ($databaseParam['prefix'] ?? '') . "\n";
foreach (['.env', '.env-example'] as $envName) {
$envFile = root_path() . $envName;
$envFileContent = is_file($envFile) ? @file_get_contents($envFile) : '';
if ($envFileContent !== false) {
$cutPos = strlen($envFileContent);
foreach (['[DATABASE]', "\n# Database\n", "\n# 数据库", "\nDATABASE_DRIVER", "\nDATABASE_TYPE"] as $marker) {
$pos = stripos($envFileContent, $marker);
if ($pos !== false && $pos < $cutPos) {
$cutPos = $pos;
}
}
$envFileContent = rtrim(substr($envFileContent, 0, $cutPos)) . $databaseBlock;
$result = @file_put_contents($envFile, $envFileContent);
if (!$result && is_file($envFile)) {
return $this->error(__('File has no write permission:%s', ['%s' => $envName]));
}
}
}
// 设置新的Token随机密钥key
$oldTokenKey = config('buildadmin.token.key');
$newTokenKey = Random::build('alnum', 32);
$buildConfigFile = config_path(self::$buildConfigFileName);
$buildConfigContent = @file_get_contents($buildConfigFile);
if ($buildConfigContent === false || $buildConfigContent === '') {
return $this->error(__('File has no write permission:%s', ['config/' . self::$buildConfigFileName]));
}
$buildConfigContent = preg_replace("/'key'(\s+)=>(\s+)'$oldTokenKey'/", "'key'\$1=>\$2'$newTokenKey'", $buildConfigContent);
$result = @file_put_contents($buildConfigFile, $buildConfigContent);
if (!$result) {
return $this->error(__('File has no write permission:%s', ['config/' . self::$buildConfigFileName]));
}
// 建立安装锁文件
$result = @file_put_contents(public_path(self::$lockFileName), date('Y-m-d H:i:s'));
if (!$result) {
return $this->error(__('File has no write permission:%s', ['public/' . self::$lockFileName]));
}
// 自动执行数据库迁移(无需手动运行 phinx 命令)
$migrateResult = $this->runPhinxMigrate($databaseParam);
if ($migrateResult !== true) {
return $this->error($migrateResult);
}
return $this->success('', [
'rootPath' => $rootPath,
'executionWebCommand' => $envOk,
'migrateCommand' => $migrateCommand,
'migrationCompleted' => true,
]);
}
/**
* 程序化执行 Phinx 数据库迁移
* @param array $databaseParam 数据库连接参数
* @return true|string 成功返回 true失败返回错误信息
*/
private function runPhinxMigrate(array $databaseParam): true|string
{
try {
$baseDir = root_path();
$phinxConfigPath = $baseDir . 'phinx.php';
if (!is_file($phinxConfigPath)) {
return __('Failed to install SQL execution:%msg%', ['%msg%' => 'phinx.php not found']);
}
// 临时设置环境变量,供 phinx 读取数据库配置
$_ENV['DATABASE_HOSTNAME'] = $databaseParam['hostname'] ?? '127.0.0.1';
$_ENV['DATABASE_DATABASE'] = $databaseParam['database'] ?? '';
$_ENV['DATABASE_USERNAME'] = $databaseParam['username'] ?? 'root';
$_ENV['DATABASE_PASSWORD'] = $databaseParam['password'] ?? '';
$_ENV['DATABASE_HOSTPORT'] = $databaseParam['hostport'] ?? '3306';
$_ENV['DATABASE_PREFIX'] = $databaseParam['prefix'] ?? '';
putenv('DATABASE_HOSTNAME=' . $_ENV['DATABASE_HOSTNAME']);
putenv('DATABASE_DATABASE=' . $_ENV['DATABASE_DATABASE']);
putenv('DATABASE_USERNAME=' . $_ENV['DATABASE_USERNAME']);
putenv('DATABASE_PASSWORD=' . $_ENV['DATABASE_PASSWORD']);
putenv('DATABASE_HOSTPORT=' . $_ENV['DATABASE_HOSTPORT']);
putenv('DATABASE_PREFIX=' . $_ENV['DATABASE_PREFIX']);
$config = PhinxConfig::fromPhp($phinxConfigPath);
$input = new ArrayInput([]);
$output = new NullOutput();
$manager = new PhinxManager($config, $input, $output);
$environment = $config->getDefaultEnvironment();
$manager->migrate($environment);
return true;
} catch (Throwable $e) {
$msg = $e->getMessage();
if ($e->getPrevious()) {
$msg .= ' | ' . $e->getPrevious()->getMessage();
}
return __('Failed to install SQL execution:%msg%', ['%msg%' => $msg]);
}
}
protected function isInstallComplete(): bool
{
if (is_file(public_path(self::$lockFileName))) {
$contents = @file_get_contents(public_path(self::$lockFileName));
if ($contents == self::$InstallationCompletionMark) {
return true;
}
}
return false;
}
/**
* 标记命令执行完毕
* @throws Throwable
*/
public function commandExecComplete(Request $request): Response
{
$this->setRequest($request);
if ($this->isInstallComplete()) {
return $this->error(__('The system has completed installation. If you need to reinstall, please delete the %file% file first', ['%file%' => 'public/' . self::$lockFileName]));
}
$param = $request->only(['type', 'adminname', 'adminpassword', 'sitename']);
if ($param['type'] == 'web') {
$result = @file_put_contents(public_path(self::$lockFileName), self::$InstallationCompletionMark);
if (!$result) {
return $this->error(__('File has no write permission:%s', ['public/' . self::$lockFileName]));
}
} else {
// 管理员配置入库
$adminModel = new AdminModel();
$defaultAdmin = $adminModel->where('username', 'admin')->find();
$defaultAdmin->username = $param['adminname'];
$defaultAdmin->nickname = ucfirst($param['adminname']);
$defaultAdmin->save();
if (isset($param['adminpassword']) && $param['adminpassword']) {
$adminModel->resetPassword($defaultAdmin->id, $param['adminpassword']);
}
// 默认用户密码修改
$user = new UserModel();
$user->resetPassword(1, Random::build());
// 修改站点名称
if (class_exists(\app\admin\model\Config::class)) {
\app\admin\model\Config::where('name', 'site_name')->update([
'value' => $param['sitename']
]);
}
}
return $this->success();
}
/**
* 获取命令执行检查的结果
* @return bool 是否拥有执行命令的条件
*/
private function commandExecutionCheck(): bool
{
$pm = config('terminal.npm_package_manager');
if ($pm == 'none') {
return false;
}
$check['phpPopen'] = function_exists('proc_open') && function_exists('proc_close');
$check['npmVersionCompare'] = Version::compare(self::$needDependentVersion['npm'], Version::getVersion('npm'));
$check['pmVersionCompare'] = Version::compare(self::$needDependentVersion[$pm], Version::getVersion($pm));
$check['nodejsVersionCompare'] = Version::compare(self::$needDependentVersion['node'], Version::getVersion('node'));
$envOk = true;
foreach ($check as $value) {
if (!$value) {
$envOk = false;
break;
}
}
return $envOk;
}
/**
* 获取安装完成后的访问地址(根据请求来源区分 API 与前端开发模式)
* - 通过 API 访问8787index.html#/admin、index.html#/
* - 通过前端开发服务访问1818/#/admin、/#/
*/
public function accessUrls(Request $request): Response
{
$this->setRequest($request);
$host = $request->header('host', '127.0.0.1:8787');
$port = '8787';
if (str_contains($host, ':')) {
$port = substr($host, strrpos($host, ':') + 1);
}
$scheme = $request->header('x-forwarded-proto', 'http');
$base = rtrim($scheme . '://' . $host, '/');
if ($port === '1818') {
$adminUrl = $base . '/#/admin';
$frontUrl = $base . '/#/';
} else {
$adminUrl = $base . '/index.html#/admin';
$frontUrl = $base . '/index.html#/';
}
return $this->success('', [
'adminUrl' => $adminUrl,
'frontUrl' => $frontUrl,
]);
}
/**
* 安装指引
*/
public function manualInstall(Request $request): Response
{
$this->setRequest($request);
return $this->success('', [
'webPath' => str_replace('\\', '/', root_path() . 'web')
]);
}
public function mvDist(Request $request): Response
{
$this->setRequest($request);
if (!is_file(root_path() . self::$distDir . DIRECTORY_SEPARATOR . 'index.html')) {
return $this->error(__('No built front-end file found, please rebuild manually!'));
}
if (Terminal::mvDist()) {
return $this->success();
}
return $this->error(__('Failed to move the front-end file, please move it manually!'));
}
/**
* 目录是否可写
* @param $writable
* @return string
*/
private static function writableStateDescribe($writable): string
{
return $writable ? __('Writable') : __('No write permission');
}
/**
* 数据库连接-获取数据表列表(使用 raw PDO
* @param array $database hostname, hostport, username, password, database
* @param bool $returnPdo
* @return array
*/
private function connectDb(array $database, bool $returnPdo = false): array
{
$host = $database['hostname'] ?? '127.0.0.1';
$port = $database['hostport'] ?? '3306';
$user = $database['username'] ?? '';
$pass = $database['password'] ?? '';
$db = $database['database'] ?? '';
$dsn = "mysql:host={$host};port={$port};charset=utf8mb4";
if ($db) {
$dsn .= ";dbname={$db}";
}
try {
$pdo = new \PDO($dsn, $user, $pass, [
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true,
]);
$pdo->query("SELECT 1")->fetchAll(\PDO::FETCH_ASSOC);
} catch (\PDOException $e) {
$errorMsg = mb_convert_encoding($e->getMessage() ?: 'unknown', 'UTF-8', 'UTF-8,GBK,GB2312,BIG5');
$template = __('Database connection failed:%s');
return [
'code' => 0,
'msg' => strpos($template, '%s') !== false ? sprintf($template, $errorMsg) : $template . $errorMsg,
];
}
$databases = [];
$databasesExclude = ['information_schema', 'mysql', 'performance_schema', 'sys'];
$stmt = $pdo->query("SHOW DATABASES");
$rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$stmt->closeCursor();
foreach ($rows as $row) {
$dbName = $row['Database'] ?? $row['database'] ?? '';
if ($dbName && !in_array($dbName, $databasesExclude)) {
$databases[] = $dbName;
}
}
return [
'code' => 1,
'msg' => '',
'databases' => $databases,
'pdo' => $returnPdo ? $pdo : null,
];
}
}