677 lines
26 KiB
PHP
677 lines
26 KiB
PHP
<?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;
|
||
|
||
/**
|
||
* 安装控制器
|
||
*/
|
||
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 %s file first', ['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 %s file first', ['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 %s file first', ['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 %s file first', ['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]));
|
||
}
|
||
|
||
// 写入 dafuweng-webman/.env-example
|
||
$envFile = root_path() . '.env-example';
|
||
$envFileContent = @file_get_contents($envFile);
|
||
if ($envFileContent) {
|
||
$databasePos = stripos($envFileContent, '[DATABASE]');
|
||
if ($databasePos !== false) {
|
||
$envFileContent = substr($envFileContent, 0, $databasePos);
|
||
}
|
||
$envFileContent .= "\n" . '[DATABASE]' . "\n";
|
||
$envFileContent .= 'TYPE = mysql' . "\n";
|
||
$envFileContent .= 'HOSTNAME = ' . $databaseParam['hostname'] . "\n";
|
||
$envFileContent .= 'DATABASE = ' . $databaseParam['database'] . "\n";
|
||
$envFileContent .= 'USERNAME = ' . $databaseParam['username'] . "\n";
|
||
$envFileContent .= 'PASSWORD = ' . $databaseParam['password'] . "\n";
|
||
$envFileContent .= 'HOSTPORT = ' . $databaseParam['hostport'] . "\n";
|
||
$envFileContent .= 'PREFIX = ' . ($databaseParam['prefix'] ?? '') . "\n";
|
||
$envFileContent .= 'CHARSET = utf8mb4' . "\n";
|
||
$envFileContent .= 'DEBUG = true' . "\n";
|
||
$result = @file_put_contents($envFile, $envFileContent);
|
||
if (!$result) {
|
||
return $this->error(__('File has no write permission:%s', ['/' . $envFile]));
|
||
}
|
||
}
|
||
|
||
// 设置新的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]));
|
||
}
|
||
|
||
return $this->success('', [
|
||
'rootPath' => $rootPath,
|
||
'executionWebCommand' => $envOk,
|
||
'migrateCommand' => $migrateCommand,
|
||
]);
|
||
}
|
||
|
||
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 %s file first', ['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;
|
||
}
|
||
|
||
/**
|
||
* 安装指引
|
||
*/
|
||
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,
|
||
];
|
||
}
|
||
}
|