diff --git a/modules/EcommerceFinance/Services/UpstreamOdbcConnection.php b/modules/EcommerceFinance/Services/UpstreamOdbcConnection.php index d5c05cfd..dc454cf7 100644 --- a/modules/EcommerceFinance/Services/UpstreamOdbcConnection.php +++ b/modules/EcommerceFinance/Services/UpstreamOdbcConnection.php @@ -6,6 +6,8 @@ use Illuminate\Support\Facades\Log; use PDO; use PDOException; use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; +use Symfony\Component\Process\PhpExecutableFinder; +use Symfony\Component\Process\Process; /** * 按来源隔离的 SQL Server PDO_ODBC 连接管理。 @@ -41,6 +43,67 @@ class UpstreamOdbcConnection } public static function probeConfiguration(array $connection, string $label, string $source = 'custom'): void + { + try { + $payload = json_encode([ + 'connection' => $connection, + 'label' => $label, + 'source' => $source, + ], JSON_THROW_ON_ERROR); + $php = (new PhpExecutableFinder())->find(false); + + if (! is_string($php) || $php === '') { + throw new \RuntimeException('PHP CLI 不可用。'); + } + + $script = <<<'PHP' +require getcwd() . '/vendor/autoload.php'; +$app = require getcwd() . '/bootstrap/app.php'; +$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class); +$kernel->bootstrap(); +$payload = json_decode(stream_get_contents(STDIN), true, 512, JSON_THROW_ON_ERROR); +Modules\EcommerceFinance\Services\UpstreamOdbcConnection::probeConfigurationInProcess( + (array) $payload['connection'], + (string) $payload['label'], + (string) $payload['source'], +); +PHP; + $connectTimeout = min(max((int) ($connection['connect_timeout'] ?? 5), 1), 30); + $process = new Process( + [$php, '-d', 'display_errors=0', '-r', $script], + base_path(), + null, + $payload, + min(max($connectTimeout + 3, 5), 15), + ); + $process->run(); + + if ($process->isSuccessful()) { + return; + } + + Log::warning('上游数据库隔离探测失败。', [ + 'source' => $source, + 'source_label' => $label, + 'exit_code' => $process->getExitCode(), + ]); + } catch (\Throwable $exception) { + Log::warning('上游数据库隔离探测无法完成。', [ + 'source' => $source, + 'source_label' => $label, + 'exception' => $exception::class, + ]); + } + + throw new ServiceUnavailableHttpException(60, '上游数据库连接探测失败,请检查配置。'); + } + + /** + * 仅由隔离的 PHP CLI 子进程调用,避免 ODBC 原生崩溃拖垮 Web 请求进程。 + * + * @internal + */ + public static function probeConfigurationInProcess(array $connection, string $label, string $source = 'custom'): void { $pdo = self::open($connection, $source, $label);