installService === null) {
try {
$this->installService = new InstallService();
} catch (\Throwable $e) {
throw new \RuntimeException(
'Failed to initialize InstallService: ' . $e->getMessage(),
0,
$e
);
}
}
return $this->installService;
}
/**
* 配置命令参数和选项
*/
protected function configure(): void
{
$this
->setDescription('安装 MDAdmin 框架')
->setHelp('此命令用于安装 MDAdmin 框架,支持交互式和参数化两种方式')
// 数据库参数
->addOption('db-host', null, InputOption::VALUE_OPTIONAL, '数据库主机')
->addOption('db-port', null, InputOption::VALUE_OPTIONAL, '数据库端口', '3306')
->addOption('db-name', null, InputOption::VALUE_OPTIONAL, '数据库名称')
->addOption('db-user', null, InputOption::VALUE_OPTIONAL, '数据库用户名')
->addOption('db-pass', null, InputOption::VALUE_OPTIONAL, '数据库密码')
->addOption('db-prefix', null, InputOption::VALUE_OPTIONAL, '数据库表前缀', 'ma_')
// 管理员参数
->addOption('admin-username', null, InputOption::VALUE_OPTIONAL, '管理员用户名')
->addOption('admin-password', null, InputOption::VALUE_OPTIONAL, '管理员密码')
->addOption('admin-email', null, InputOption::VALUE_OPTIONAL, '管理员邮箱')
// 安装选项
->addOption('install-database', null, InputOption::VALUE_OPTIONAL, '是否安装数据库表(1=是,0=否)', '1')
->addOption('build-project', null, InputOption::VALUE_OPTIONAL, '是否构建前端项目(1=是,0=否)', '1')
->addOption('non-interactive', 'y', InputOption::VALUE_NONE, '非交互模式(使用所有默认值)')
->addOption('skip-env-check', null, InputOption::VALUE_NONE, '跳过环境检查');
}
/**
* 执行安装命令
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
// 显示欢迎信息
$io->title('MDAdmin 框架安装程序');
$io->text([
'',
'欢迎使用 MDAdmin 框架安装程序!',
'此程序将引导您完成框架的安装配置。',
'',
'提示: 密码输入时不会显示字符(出于安全考虑),输入完成后会显示确认信息',
'',
]);
// 检查是否已安装
if ($this->getInstallService()->checkInstalled()) {
$io->error('系统已安装,请勿重复安装!');
$io->note('如需重新安装,请先删除 ' . base_path('install.lock') . ' 文件');
return Command::FAILURE;
}
// 交互式或非交互式
$isInteractive = !$input->getOption('non-interactive');
$skipEnvCheck = $input->getOption('skip-env-check');
// 1. 环境检测
if (!$skipEnvCheck) {
$io->section('1. 环境检测');
$this->handleEnvironmentCheck($io);
}
// 2. 数据库配置
$io->section('2. 数据库配置');
$installParams = $this->collectInstallParams($io, $input, $isInteractive);
// 3. 确认安装
$io->section('3. 安装确认');
if (!$this->confirmInstallation($io, $installParams, $isInteractive)) {
$io->note('安装已取消');
return Command::SUCCESS;
}
// 4. 执行安装
$io->section('4. 执行安装');
$io->text('开始安装...');
try {
// 调用安装服务(共享在线安装逻辑)
$generator = $this->getInstallService()->install($installParams);
// 处理 SSE 事件输出
foreach ($generator as $chunk) {
$result = $this->handleSseEvent($chunk, $io, null);
if ($result !== null) {
return $result;
}
}
return Command::SUCCESS;
} catch (\Throwable $e) {
$this->outputError($io, '安装失败: ' . $e->getMessage(), $e);
return Command::FAILURE;
}
}
/**
* 处理环境检查
*/
private function handleEnvironmentCheck(SymfonyStyle $io): void
{
$environment = $this->getInstallService()->checkEnvironment();
// 显示系统信息
$io->text('系统信息:');
$io->definitionList(
['PHP 版本' => $environment['php_version']],
['服务器软件' => $environment['server_software']],
['操作系统' => $environment['operating_system']],
['内存限制' => $environment['memory_limit']],
['最大执行时间' => $environment['max_execution_time'] . '秒'],
);
$io->newLine();
// 检查系统要求
$allOk = $environment['all_requirements_met'];
if (!$allOk) {
$io->warning('环境检查未通过,请检查以下项目:');
// 显示失败的检查项
foreach ($environment['check_items'] as $item) {
if (!$item['status']) {
$io->error($item['message']);
}
}
foreach ($environment['directory_check_items'] as $item) {
if (!$item['status'] && $item['required']) {
$io->error($item['message']);
}
}
// 确认是否继续
if (!$this->confirm($io, '环境检查未通过,是否继续安装?', false)) {
throw new \Exception('环境检查未通过,安装已取消');
}
} else {
$io->success('环境检查通过!');
}
$io->newLine();
}
/**
* 收集安装参数
*/
private function collectInstallParams(SymfonyStyle $io, InputInterface $input, bool $isInteractive): array
{
$params = [];
// 数据库配置
$io->text('数据库配置');
$dbHost = $input->getOption('db-host');
if (!$dbHost && $isInteractive) {
$dbHost = $io->ask('请输入数据库主机', '127.0.0.1');
}
$params['db_host'] = $dbHost ?? '127.0.0.1';
$dbPort = $input->getOption('db-port');
if (!$dbPort && $isInteractive) {
$dbPort = $io->ask('请输入数据库端口', '3306');
}
$params['db_port'] = $dbPort ?? '3306';
$dbName = $input->getOption('db-name');
if (!$dbName && $isInteractive) {
$dbName = $io->ask('请输入数据库名称', 'md_admin');
}
$params['db_database'] = $dbName ?? 'md_admin';
$dbUser = $input->getOption('db-user');
if (!$dbUser && $isInteractive) {
$dbUser = $io->ask('请输入数据库用户名', 'root');
}
$params['db_username'] = $dbUser ?? 'root';
$dbPass = $input->getOption('db-pass');
if (!$dbPass && $isInteractive) {
$io->text('提示: 密码输入时显示掩码字符 *');
$dbPass = $this->askPassword($io, '请输入数据库密码: ');
if ($dbPass !== '') {
$io->text('✓ 数据库密码已设置');
}
}
$params['db_password'] = $dbPass ?? '';
$dbPrefix = $input->getOption('db-prefix');
if (!$dbPrefix && $isInteractive) {
$dbPrefix = $io->ask('请输入数据库表前缀', 'ma_');
}
$params['db_prefix'] = $dbPrefix ?? 'ma_';
// 管理员配置
$io->newLine();
$io->text('管理员配置');
$adminUsername = $input->getOption('admin-username');
if (!$adminUsername && $isInteractive) {
$adminUsername = $io->ask('请输入管理员用户名', 'admin');
}
$params['admin_username'] = $adminUsername ?? 'admin';
$adminPassword = $input->getOption('admin-password');
if (!$adminPassword && $isInteractive) {
$io->text('提示: 密码输入时显示掩码字符 *');
do {
$adminPassword = $this->askPassword($io, '请输入管理员密码(至少6位): ');
if (strlen($adminPassword) < 6) {
$io->error('密码长度不能少于6位!');
}
} while (strlen($adminPassword) < 6);
$io->text('✓ 管理员密码已设置');
}
$params['admin_password'] = $adminPassword ?? '123456';
$adminEmail = $input->getOption('admin-email');
if (!$adminEmail && $isInteractive) {
$adminEmail = $io->ask('请输入管理员邮箱', 'admin@example.com');
}
$params['admin_email'] = $adminEmail ?? 'admin@example.com';
// 安装选项
$io->newLine();
$io->text('安装选项');
$installDatabase = $input->getOption('install-database');
if ($installDatabase === null && $isInteractive) {
$installDatabase = $this->confirm($io, '是否安装数据库表?', true) ? '1' : '0';
}
$params['install_database'] = $installDatabase ?? '1';
$buildProject = $input->getOption('build-project');
if ($buildProject === null && $isInteractive) {
$buildProject = $this->confirm($io, '是否构建前端项目?', false) ? '1' : '0';
}
$params['build_project'] = $buildProject ?? '0';
return $params;
}
/**
* 确认安装信息
*/
private function confirmInstallation(SymfonyStyle $io, array $params, bool $isInteractive): bool
{
// 显示配置摘要
$io->text('安装配置摘要:');
$io->definitionList(
['数据库主机' => $params['db_host']],
['数据库端口' => $params['db_port']],
['数据库名称' => $params['db_database']],
['数据库用户' => $params['db_username']],
['表前缀' => $params['db_prefix']],
['管理员用户名' => $params['admin_username']],
['管理员邮箱' => $params['admin_email']],
['安装数据库' => $params['install_database'] == '1' ? '是' : '否'],
['构建前端' => $params['build_project'] == '1' ? '是' : '否'],
);
$io->newLine();
// 确认安装
if ($isInteractive) {
return $this->confirm($io, '确认开始安装?安装前请确保数据库配置正确!', false);
}
return true;
}
}