diff --git a/README.md b/README.md index 75c347a8..89d89811 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,20 @@ -
+# Nl Admin - +## 配置好数据库连接信息后执行 +```aiignore +php artisan sql:run-nl-admin +``` +### 使用示例 +```cmd +# 基本用法 +php artisan sql:run-nl-admin -## About Laravel +# 指定SQL文件路径 +php artisan sql:run-nl-admin --file=storage/sql/setup.sql -Laravel is a web application framework with expressive, elegant syntax. We believe development must be an enjoyable and creative experience to be truly fulfilling. Laravel takes the pain out of development by easing common tasks used in many web projects, such as: +# 强制重置已有数据库 +php artisan sql:run-nl-admin --force -- [Simple, fast routing engine](https://laravel.com/docs/routing). -- [Powerful dependency injection container](https://laravel.com/docs/container). -- Multiple back-ends for [session](https://laravel.com/docs/session) and [cache](https://laravel.com/docs/cache) storage. -- Expressive, intuitive [database ORM](https://laravel.com/docs/eloquent). -- Database agnostic [schema migrations](https://laravel.com/docs/migrations). -- [Robust background job processing](https://laravel.com/docs/queues). -- [Real-time event broadcasting](https://laravel.com/docs/broadcasting). - -Laravel is accessible, powerful, and provides tools required for large, robust applications. - -## Learning Laravel - -Laravel has the most extensive and thorough [documentation](https://laravel.com/docs) and video tutorial library of all modern web application frameworks, making it a breeze to get started with the framework. - -You may also try the [Laravel Bootcamp](https://bootcamp.laravel.com), where you will be guided through building a modern Laravel application from scratch. - -If you don't feel like reading, [Laracasts](https://laracasts.com) can help. Laracasts contains thousands of video tutorials on a range of topics including Laravel, modern PHP, unit testing, and JavaScript. Boost your skills by digging into our comprehensive video library. - -## Laravel Sponsors - -We would like to extend our thanks to the following sponsors for funding Laravel development. If you are interested in becoming a sponsor, please visit the [Laravel Partners program](https://partners.laravel.com). - -### Premium Partners - -- **[Vehikl](https://vehikl.com)** -- **[Tighten Co.](https://tighten.co)** -- **[Kirschbaum Development Group](https://kirschbaumdevelopment.com)** -- **[64 Robots](https://64robots.com)** -- **[Curotec](https://www.curotec.com/services/technologies/laravel)** -- **[DevSquad](https://devsquad.com/hire-laravel-developers)** -- **[Redberry](https://redberry.international/laravel-development)** -- **[Active Logic](https://activelogic.com)** - -## Contributing - -Thank you for considering contributing to the Laravel framework! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). - -## Code of Conduct - -In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). - -## Security Vulnerabilities - -If you discover a security vulnerability within Laravel, please send an e-mail to Taylor Otwell via [taylor@laravel.com](mailto:taylor@laravel.com). All security vulnerabilities will be promptly addressed. - -## License - -The Laravel framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). +# 创建新数据库 +php artisan sql:run-nl-admin --database=my_new_db +``` diff --git a/app/Console/Commands/RunNlAdminSql.php b/app/Console/Commands/RunNlAdminSql.php new file mode 100644 index 00000000..9c12b9ad --- /dev/null +++ b/app/Console/Commands/RunNlAdminSql.php @@ -0,0 +1,661 @@ +info('🚀 开始数据库安装程序'); + + // 1. 确保基础配置加载 + $this->ensureConfigLoaded(); + + // 2. 优先使用.env配置创建数据库 + $filePath = $this->getSqlFilePath(); + + // 3. 配置数据库连接(如果需要) + $this->ensureDatabaseConnection(); + + // 4. 处理目标数据库 + $this->prepareTargetDatabase(); + + // 5. 执行SQL文件 + $this->executeSql($filePath); + + $this->info('✅ 数据库安装成功完成'); + return 0; + } + + /** + * 确保配置已加载 + */ + protected function ensureConfigLoaded(): void + { + $this->call('config:clear'); + + // 确保数据库配置存在 + $envPath = base_path('.env'); + if (!file_exists($envPath)) { + File::copy(base_path('.env.example'), $envPath); + $this->info('📄 .env文件已创建(基于.example)'); + } + } + + /** + * 获取SQL文件路径 + */ + protected function getSqlFilePath(): string + { + $filePath = base_path($this->option('file')); + + // 验证文件是否存在 + if (!file_exists($filePath)) { + $this->error("❌ SQL 文件未找到:{$filePath}"); + $this->info("💡 请使用 --file 选项指定正确的文件路径"); + exit(1); + } + + // 检查文件大小 + $fileSize = filesize($filePath) / 1024 / 1024; // MB + if ($fileSize > $this->maxFileSize) { + $this->warn("⚠️ 注意:SQL文件较大 (" . round($fileSize, 2) . "MB),执行可能需要较长时间"); + } + + $this->info("📁 SQL文件位置: {$filePath}"); + $this->info("📏 文件大小: " . round($fileSize, 2) . 'MB'); + + return $filePath; + } + + /** + * 确保数据库连接可用 + */ + protected function ensureDatabaseConnection(): void + { + $this->line('🔌 检查数据库连接...'); + $retryCount = 0; + + while (true) { + try { + DB::connection()->getPdo(); + $this->info('✅ 数据库连接正常'); + return; + } catch (\Exception $e) { + $this->error("❌ 数据库连接失败: " . $e->getMessage()); + + if ($retryCount > 0 || !$this->confirm('❓ 是否配置数据库连接?', true)) { + $this->error('🔌 无法连接数据库,操作终止'); + exit(1); + } + + $this->configureDatabase(); + $retryCount++; + } + } + } + + /** + * 配置数据库连接信息(优先使用env默认值) + */ + protected function configureDatabase(): void + { + $this->info('🔧 请提供数据库连接信息(留空使用默认值)'); + + $currentConfig = Config::get('database.connections.mysql'); + + $config = [ + 'DB_HOST' => $this->ask('数据库主机', $currentConfig['host'] ?? '127.0.0.1'), + 'DB_PORT' => $this->ask('端口', $currentConfig['port'] ?? '3306'), + 'DB_DATABASE' => $this->ask('数据库名', $currentConfig['database'] ?? 'nl_admin'), + 'DB_USERNAME' => $this->ask('用户名', $currentConfig['username'] ?? 'root'), + 'DB_PASSWORD' => $this->secret('密码') ?: ($currentConfig['password'] ?? ''), + ]; + + // 更新 .env 文件 + $this->updateEnvFile($config); + $this->info('📝 .env 文件已更新'); + + // 刷新配置缓存 + $this->call('config:clear'); + + // 更新运行时配置 + foreach ($config as $key => $value) { + $configKey = strtolower(substr($key, 3)); + Config::set("database.connections.mysql.{$configKey}", $value); + } + + // 测试连接并创建数据库 + $this->testDatabaseConnection(); + } + + /** + * 测试数据库连接并创建数据库(如果需要) + */ + protected function testDatabaseConnection(): void + { + try { + // 测试连接 + DB::connection('mysql')->getPdo(); + $this->info('✅ 数据库连接测试成功'); + } catch (\Exception $e) { + $this->error("❌ 配置后连接测试失败: " . $e->getMessage()); + + if ($this->confirm('是否尝试创建数据库?', true)) { + $this->createDatabaseFromEnv(); + $this->info('✅ 数据库创建成功'); + + // 再次尝试连接 + try { + DB::connection()->getPdo(); + $this->info('✅ 数据库连接成功'); + } catch (\Exception $e) { + $this->error("❌ 创建数据库后连接失败: " . $e->getMessage()); + exit(1); + } + } else { + $this->error('❌ 无法连接数据库,操作终止'); + exit(1); + } + } + } + + /** + * 使用.env配置创建数据库 + */ + protected function createDatabaseFromEnv(): void + { + $dbConfig = [ + 'driver' => 'mysql', + 'host' => config('database.connections.mysql.host'), + 'port' => config('database.connections.mysql.port'), + 'username' => config('database.connections.mysql.username'), + 'password' => config('database.connections.mysql.password'), + 'database' => config('database.connections.mysql.database'), + 'charset' => config('database.connections.mysql.charset', 'utf8mb4'), + 'collation' => config('database.connections.mysql.collation', 'utf8mb4_unicode_ci') + ]; + + $this->createDatabase($dbConfig); + } + + /** + * 更新 .env 文件 + */ + protected function updateEnvFile(array $config): void + { + $envPath = base_path('.env'); + $envContents = file_exists($envPath) ? File::get($envPath) : ''; + + foreach ($config as $key => $value) { + $escapedValue = str_replace('"', '\\"', $value); + $line = "{$key}=\"{$escapedValue}\""; + + if (preg_match("/^{$key}=.*/m", $envContents)) { + $envContents = preg_replace("/^{$key}=.*/m", $line, $envContents); + } else { + $envContents .= PHP_EOL . $line; + } + } + + File::put($envPath, $envContents); + } + + /** + * 准备目标数据库(基于.env配置) + */ + protected function prepareTargetDatabase(): void + { + $this->line('🗄️ 检查目标数据库...'); + + $connection = config('database.default'); + $dbConfig = config("database.connections.{$connection}"); + $driver = $dbConfig['driver']; + + // 处理数据库名称选项(优先使用命令行) + $databaseName = $this->getDatabaseName($dbConfig); + + $this->info("📊 数据库驱动: {$driver}"); + $this->info("🗃️ 目标数据库: {$databaseName}"); + + // 检查数据库是否存在 + if (!$this->databaseExists($dbConfig)) { + $this->warn("❓ 数据库 {$databaseName} 不存在"); + + if ($this->option('force') || $this->confirm("是否创建数据库 {$databaseName}?", true)) { + $this->createDatabase([ + 'driver' => $driver, + 'host' => $dbConfig['host'], + 'port' => $dbConfig['port'], + 'username' => $dbConfig['username'], + 'password' => $dbConfig['password'], + 'database' => $databaseName, + 'charset' => $this->option('charset'), + 'collation' => $this->option('collation') + ]); + $this->info("✅ 数据库 {$databaseName} 创建成功"); + } else { + $this->error('❌ 操作已取消'); + exit(1); + } + } + + // 刷新数据库连接确保使用的是目标数据库 + $this->ensureUsingTargetDatabase($databaseName); + + // 检查数据库表 + $this->checkDatabaseTables($databaseName); + } + + /** + * 获取数据库名称(优先使用命令行选项) + */ + protected function getDatabaseName(array $defaultConfig): string + { + if ($database = $this->option('database')) { + return $database; + } + return $defaultConfig['database']; + } + + /** + * 确保连接到目标数据库 + */ + protected function ensureUsingTargetDatabase(string $databaseName): void + { + // 更新配置为当前目标数据库 + Config::set("database.connections.".config('database.default').".database", $databaseName); + + // 刷新连接 + DB::purge(config('database.default')); + DB::reconnect(config('database.default')); + + $this->line("🔁 已切换至数据库: {$databaseName}"); + } + + /** + * 检查数据库表 + */ + protected function checkDatabaseTables(string $databaseName): void + { + // 优化的表检查逻辑 + $tableCount = $this->getTableCount($databaseName); + + if ($tableCount > 0) { + $this->warn("⚠️ 数据库 {$databaseName} 包含 {$tableCount} 张表"); + + if (!$this->option('force') && + !$this->confirm("此操作将重置数据库 {$databaseName} 中的所有数据。继续执行吗?", false)) { + $this->error('❌ 操作已取消'); + exit(1); + } + } elseif ($tableCount === 0) { + $this->info("📭 数据库 {$databaseName} 为空"); + } else { + $this->warn("⚠️ 无法确定数据库表数量,请谨慎操作"); + } + } + + /** + * 获取数据库中的表数量(优化版) + */ + protected function getTableCount(string $databaseName): int + { + try { + $driver = DB::getDriverName(); + + if ($driver === 'mysql') { + $result = DB::select("SELECT COUNT(*) AS count + FROM information_schema.tables + WHERE table_schema = ?", + [$databaseName]); + return (int) ($result[0]->count ?? 0); + } + + if ($driver === 'sqlite') { + $result = DB::select("SELECT COUNT(*) AS count + FROM sqlite_master + WHERE type='table'"); + return (int) ($result[0]->count ?? 0); + } + + if ($driver === 'pgsql') { + $result = DB::select("SELECT COUNT(*) AS count + FROM information_schema.tables + WHERE table_schema='public'"); + return (int) ($result[0]->count ?? 0); + } + + // 其他数据库使用默认方法 + $result = DB::select("SHOW TABLES"); + return count($result); + } catch (\Exception $e) { + // 出现错误时返回-1表示无法确定 + $this->warn("⚠️ 无法确定表数量: " . $e->getMessage()); + return -1; + } + } + + /** + * 执行SQL + */ + protected function executeSql(string $filePath): void + { + $fileSize = filesize($filePath) / 1024 / 1024; // MB + + if ($fileSize > $this->maxFileSize) { + $this->executeSqlFileChunks($filePath); + } else { + $this->executeSingleSqlFile($filePath); + } + } + + /** + * 单块执行SQL文件 + */ + protected function executeSingleSqlFile(string $filePath): void + { + $this->line('🚀 开始导入SQL文件...'); + + try { + $sql = file_get_contents($filePath); + $this->smartTransactionHandler(function() use ($sql) { + $this->executeRawSql($sql); + }); + + $this->info('✅ SQL文件导入成功'); + } catch (\Exception $e) { + $this->handleSqlError($e, $sql ?? ''); + } + } + + /** + * 分块执行SQL文件 + */ + protected function executeSqlFileChunks(string $filePath): void + { + $this->line('🚀 开始分块导入SQL文件...'); + + $fileSize = filesize($filePath); + $chunkSize = $this->chunkSize * 1024 * 1024; + $chunkCount = ceil($fileSize / $chunkSize); + + $this->info("📦 文件大小: " . round($fileSize / 1024 / 1024, 2) . "MB"); + $this->info("🧩 分块数量: {$chunkCount} (每块 {$this->chunkSize}MB)"); + + $handle = fopen($filePath, 'r'); + $chunkNum = 1; + $position = 0; + + try { + while (!feof($handle)) { + // 定位到下一分块 + fseek($handle, $position); + + // 读取当前分块 + $chunkData = fread($handle, $chunkSize); + + // 确保完整语句 (找到下一个分号) + $lastSemicolon = strrpos($chunkData, ';'); + if ($lastSemicolon !== false) { + $chunkData = substr($chunkData, 0, $lastSemicolon + 1); + } + + $chunkSizeActual = strlen($chunkData); + $this->line("🔧 处理分块 {$chunkNum}/{$chunkCount} (" . round($chunkSizeActual / 1024 / 1024, 2) . "MB)..."); + + $this->smartTransactionHandler(function() use ($chunkData) { + $this->executeRawSql($chunkData); + }); + + $position += $chunkSizeActual; + $chunkNum++; + } + + $this->info('✅ SQL文件导入成功'); + } catch (\Exception $e) { + $this->handleSqlError($e, $chunkData ?? ''); + } finally { + fclose($handle); + } + } + + /** + * 智能事务处理 + */ + protected function smartTransactionHandler(callable $callback): void + { + $driver = DB::getDriverName(); + + $this->prepareDatabaseForExecution($driver); + + try { + // 对于支持事务的数据库驱动使用事务 + if (in_array($driver, ['mysql', 'pgsql', 'sqlsrv', 'sqlite'])) { + DB::beginTransaction(); + $callback(); + DB::commit(); + } else { + // 不支持事务的数据库直接执行 + $callback(); + } + } catch (\Exception $e) { + // 回滚事务(如果已经开启) + if (in_array($driver, ['mysql', 'pgsql', 'sqlsrv', 'sqlite']) && + DB::connection()->transactionLevel() > 0) { + DB::rollBack(); + } + throw $e; + } finally { + $this->restoreDatabaseSettings($driver); + } + } + + /** + * 准备数据库执行环境 + */ + protected function prepareDatabaseForExecution(string $driver): void + { + // MySQL 需要禁用外键检查 + if ($driver === 'mysql') { + DB::statement('SET FOREIGN_KEY_CHECKS=0'); + DB::statement('SET AUTOCOMMIT=0'); + } + + // PostgreSQL 需要禁用触发器 + if ($driver === 'pgsql') { + DB::statement('SET session_replication_role = replica;'); + } + } + + /** + * 恢复数据库设置 + */ + protected function restoreDatabaseSettings(string $driver): void + { + // MySQL 恢复设置 + if ($driver === 'mysql') { + DB::statement('SET FOREIGN_KEY_CHECKS=1'); + DB::statement('SET AUTOCOMMIT=1'); + } + + // PostgreSQL 恢复设置 + if ($driver === 'pgsql') { + DB::statement('SET session_replication_role = DEFAULT;'); + } + } + + /** + * 执行原始SQL语句 + */ + protected function executeRawSql(string $sql): void + { + // 使用原生方法执行以避免事务问题 + try { + DB::getPdo()->exec($sql); + } catch (PDOException $e) { + // 处理部分SQL的错误(记录但不中断) + if (str_contains($e->getMessage(), 'already exists') || + str_contains($e->getMessage(), 'doesn\'t exist')) { + $this->warn("⚠️ SQL警告: " . $e->getMessage()); + } else { + throw $e; + } + } + } + + /** + * 处理SQL错误 + */ + protected function handleSqlError(\Exception $e, string $sqlFragment): void + { + $errorMsg = $e->getMessage(); + $this->error("❌ SQL执行错误: {$errorMsg}"); + + // MySQL错误处理 + if ($this->isMysql() && preg_match('/at line (\d+)/i', $errorMsg, $matches)) { + $lineNum = $matches[1]; + $this->error("📌 错误位置: 大约第 {$lineNum} 行"); + } + + // 创建错误报告 + $this->createErrorReport($e, $sqlFragment); + + $this->error('❌ 数据库安装失败'); + exit(1); + } + + /** + * 创建错误报告 + */ + protected function createErrorReport(\Exception $e, string $sqlFragment): void + { + $timestamp = date('Ymd-His'); + $reportFile = storage_path("logs/sql_error_{$timestamp}.log"); + + $content = "SQL 错误报告: " . date('Y-m-d H:i:s') . PHP_EOL; + $content .= "错误信息: " . $e->getMessage() . PHP_EOL; + $content .= "错误位置: " . $e->getFile() . ':' . $e->getLine() . PHP_EOL; + $content .= "数据库驱动: " . config('database.default') . PHP_EOL; + $content .= "数据库名称: " . config("database.connections.".config('database.default').".database") . PHP_EOL; + + if (!empty($sqlFragment)) { + $content .= PHP_EOL . "相关SQL片段: " . PHP_EOL; + $content .= substr($sqlFragment, -2000) . PHP_EOL; // 最后2000字符 + } + + file_put_contents($reportFile, $content); + $this->warn("📝 错误报告已保存到: {$reportFile}"); + $this->line("💡 请将此文件提供给开发人员以诊断问题"); + } + + /** + * 检查是否为MySQL驱动 + */ + protected function isMysql(): bool + { + return DB::getDriverName() === 'mysql'; + } + + /** + * 检查数据库是否存在(基于env配置) + */ + protected function databaseExists(array $config): bool + { + if ($config['driver'] === 'mysql') { + try { + $dbName = $this->getDatabaseName($config); + $result = DB::select("SELECT SCHEMA_NAME + FROM information_schema.SCHEMATA + WHERE SCHEMA_NAME = ?", + [$dbName]); + return !empty($result); + } catch (\Exception $e) { + $this->warn("⚠️ 数据库检查错误: " . $e->getMessage()); + return false; + } + } + + // SQLite 检查 + if ($config['driver'] === 'sqlite') { + return file_exists($config['database']); + } + + // PostgreSQL 检查 + if ($config['driver'] === 'pgsql') { + try { + DB::statement("SELECT 1 FROM pg_database WHERE datname = ?", [$config['database']]); + return true; + } catch (\Exception) { + return false; + } + } + + // 默认检查方法 + try { + DB::statement("USE {$config['database']}"); + return true; + } catch (\Exception) { + return false; + } + } + + /** + * 创建数据库(基于env配置) + */ + protected function createDatabase(array $config): void + { + $dbName = $this->getDatabaseName($config); + $charset = $this->option('charset'); + $collation = $this->option('collation'); + + $this->line("⚙️ 创建 {$config['driver']} 数据库: {$dbName} (字符集: {$charset}, 排序规则: {$collation})"); + + if ($config['driver'] === 'mysql') { + // 临时连接到系统数据库以创建新数据库 + DB::purge('mysql'); + Config::set('database.connections.mysql.database', null); + DB::reconnect('mysql'); + + $query = "CREATE DATABASE IF NOT EXISTS `{$dbName}` "; + $query .= "CHARACTER SET {$charset} COLLATE {$collation}"; + + DB::statement($query); + + // 恢复配置 + Config::set('database.connections.mysql.database', $dbName); + return; + } + + if ($config['driver'] === 'sqlite') { + file_put_contents($config['database'], ''); + return; + } + + if ($config['driver'] === 'pgsql') { + DB::statement("CREATE DATABASE \"{$dbName}\""); + return; + } + + $this->error("🚫 不支持的数据库驱动: {$config['driver']}"); + exit(1); + } +} diff --git a/app/Http/Controllers/core/SqlInstallController.php b/app/Http/Controllers/core/SqlInstallController.php new file mode 100644 index 00000000..3121a046 --- /dev/null +++ b/app/Http/Controllers/core/SqlInstallController.php @@ -0,0 +1,232 @@ +validate([ + 'host' => 'required|string', + 'port' => 'required|integer', + 'database' => 'required|string', + 'username' => 'required|string', + 'password' => 'required|string', + ]); + + // 创建临时配置 + config([ + 'database.connections.install' => [ + 'driver' => 'mysql', + 'host' => $validated['host'], + 'port' => $validated['port'], + 'database' => $validated['database'], + 'username' => $validated['username'], + 'password' => $validated['password'], + 'charset' => 'utf8mb4', + 'collation' => 'utf8mb4_unicode_ci', + 'prefix' => '', + 'prefix_indexes' => true, + 'strict' => true, + 'engine' => null, + ] + ]); + + try { + // 使用临时连接测试 + DB::connection('install')->getPdo(); + + return response()->json([ + 'success' => true, + 'message' => '✅ 成功连接到数据库' + ]); + } catch (\Exception $e) { + return response()->json([ + 'success' => false, + 'message' => '❌ 连接失败: ' . $e->getMessage() + ], 500); + } + } + + public function startInstallation(Request $request) + { + // 设置超时时间 + set_time_limit(600); + + $validated = $request->validate([ + 'file' => 'required|string', + 'host' => 'required|string', + 'port' => 'required|integer', + 'database' => 'required|string', + 'username' => 'required|string', + 'password' => 'required|string', + 'force' => 'boolean', + 'backup' => 'boolean' + ]); + + $basePath = base_path(); + $logs = ["[开始] SQL安装过程启动"]; + $logs[] = "[项目目录] {$basePath}"; + + try { + // 更新环境变量 + $config = [ + 'DB_HOST' => $validated['host'], + 'DB_PORT' => $validated['port'], + 'DB_DATABASE' => $validated['database'], + 'DB_USERNAME' => $validated['username'], + 'DB_PASSWORD' => $validated['password'], + ]; + + + // 更新 .env 文件 + $this->updateEnvFile($config); + $logs[] = "✅ 环境变量已更新"; + + // 更新运行时配置 + foreach ($config as $key => $value) { + $configKey = strtolower(substr($key, 3)); + Config::set("database.connections.mysql.{$configKey}", $value); + } +// ds($logs); + + $logs[] = "🔧 使用主机: {$validated['host']}"; + $logs[] = "🔧 使用端口: {$validated['port']}"; + $logs[] = "🔧 使用数据库: {$validated['database']}"; + $logs[] = "🔧 SQL文件: {$validated['file']}"; + + // 检查数据库是否存在,如果不存在则创建 + $logs[] = "🔍 检查数据库是否存在"; + + +// $connection = DB::connection('mysql'); +// $databaseName = $validated['database']; +// $result = $connection->select(" +// SELECT SCHEMA_NAME +// FROM INFORMATION_SCHEMA.SCHEMATA +// WHERE SCHEMA_NAME = ? +// ", [$databaseName]); +// +// if (empty($result)) { +// $logs[] = "🔧 数据库不存在,正在创建..."; +// $connection->statement("CREATE DATABASE IF NOT EXISTS `$databaseName`"); +// $logs[] = "✅ 数据库已创建"; +// } + + // 构建 Artisan 命令 - 使用相对路径解决权限问题 + $command = [ + 'php', + 'artisan', + 'sql:run-nl-admin', + '--file=' . $validated['file'], + '--database=' . $validated['database'], + ]; + +// ds($command); + + if ($validated['force'] ?? false) { + $command[] = '--force'; + $logs[] = "⚠️ 强制模式已启用 (将覆盖现有数据)"; + } + + if ($validated['backup'] ?? false) { + $command[] = '--backup'; + $logs[] = "📦 数据库备份已启用"; + } + + $logs[] = "🔄 正在执行: " . implode(' ', $command); + + // 改变工作目录解决路径问题 +// $process = new Process($command, $basePath); + $process = new Process($command, $basePath); + $process->setTimeout(300); + $process->start(); + + // 实时捕获输出 + $process->wait(function ($type, $buffer) use (&$logs) { + if (!empty(trim($buffer))) { + $bufferLines = array_map('trim', explode("\n", trim($buffer))); + foreach ($bufferLines as $line) { + if (!empty($line)) { + $logs[] = $line; + } + } + } + }); + + // 检查执行结果 + if (!$process->isSuccessful()) { + throw new ProcessFailedException($process); + } + + // 添加详细成功日志 + $logs[] = "✅ SQL文件执行成功"; + $logs[] = "📊 执行命令: " . $process->getCommandLine(); + $logs[] = "🆗 退出状态: " . $process->getExitCode(); + + // 获取SQL文件大小 + $filePath = base_path($validated['file']); + $fileSize = file_exists($filePath) ? filesize($filePath) : 0; + + return response()->json([ + 'success' => true, + 'logs' => $logs, + 'file_size' => $fileSize + ]); + + } catch (\Exception $e) { + $logs[] = "❌ 安装过程中出错: " . $e->getMessage(); + + // 添加详细的错误分析 + if ($e instanceof ProcessFailedException) { + $logs[] = "🛠 退出代码: " . $e->getProcess()->getExitCode(); + $logs[] = "📜 错误输出: " . $e->getProcess()->getErrorOutput(); + + // 分析常见权限问题 + $errorOutput = $e->getProcess()->getErrorOutput(); + if (strpos($errorOutput, 'Permission denied') !== false) { + $logs[] = "🔑 权限问题检测:"; + $logs[] = " 1. 确保Web服务器对扩展目录有读取权限"; + $logs[] = " 2. 尝试运行: sudo chmod -R a+r /usr/local/lib/php/extensions/"; + $logs[] = " 3. 检查SELinux/AppArmor设置"; + } + } + + return response()->json([ + 'success' => false, + 'message' => $e->getMessage(), + 'logs' => $logs + ], 500); + } + } + + /** + * 更新 .env 文件 + */ + protected function updateEnvFile(array $config): void + { + $envPath = base_path('.env'); + $envContents = file_exists($envPath) ? File::get($envPath) : ''; + + foreach ($config as $key => $value) { + $escapedValue = str_replace('"', '\\"', $value); + $line = "{$key}=\"{$escapedValue}\""; + + if (preg_match("/^{$key}=.*/m", $envContents)) { + $envContents = preg_replace("/^{$key}=.*/m", $line, $envContents); + } else { + $envContents .= PHP_EOL . $line; + } + } + + File::put($envPath, $envContents); + } +} diff --git a/composer.json b/composer.json index b9d06efa..dd81274b 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "ext-zip": "*", "firebase/php-jwt": "^6.11", "laravel/framework": "^12.0", - "laravel/tinker": "^2.10.1" + "laravel/tinker": "^2.10.1", + "ext-pdo": "*" }, "require-dev": { "fakerphp/faker": "^1.23", diff --git a/resources/views/init.blade.php b/resources/views/init.blade.php new file mode 100644 index 00000000..b5807cd1 --- /dev/null +++ b/resources/views/init.blade.php @@ -0,0 +1,863 @@ + + + + + + +1. 配置数据库连接信息
+2. 设置SQL文件路径(默认为public/nl_admin.sql)
3. 测试数据库连接是否成功
+4. 点击"开始安装"执行SQL文件
+