CaiWuXiTong/modules/EcommerceFinance/Services/UpstreamDatabaseProbe.php

69 lines
3.0 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Services;
use PDO;
use PDOException;
use SensitiveParameter;
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
/** Connection tests only: never used to route business queries or synchronize tables. */
class UpstreamDatabaseProbe
{
public function probe(string $driver, #[SensitiveParameter] array $connection, string $label): void
{
if ($driver === 'sqlsrv') {
UpstreamOdbcConnection::probeConfiguration($connection, $label);
return;
}
if ($driver !== 'pgsql') {
throw new ServiceUnavailableHttpException(30, '不支持此数据库类型,请选择 SQL Server 或 PostgreSQL。');
}
$timeout = min(max((int) ($connection['connect_timeout'] ?? 5), 1), 30);
$queryTimeout = min(max((int) ($connection['query_timeout'] ?? 15), 1), 15) * 1000;
$dsn = 'pgsql:host=' . $this->quote((string) $connection['host'])
. ';port=' . min(max((int) $connection['port'], 1), 65535)
. ';dbname=' . $this->quote((string) $connection['database'])
. ';sslmode=require;connect_timeout=' . $timeout
. ";options='-c default_transaction_read_only=on -c statement_timeout=" . $queryTimeout . "'";
try {
// One short-lived connection, TLS and read-only enforced at session startup.
$pdo = $this->connect($dsn, (string) $connection['username'], (string) $connection['password']);
$statement = $pdo->query('SELECT 1');
if ($statement === false || (int) $statement->fetchColumn() !== 1) {
throw new ServiceUnavailableHttpException(30, 'PostgreSQL 只读测试未返回预期结果。');
}
$statement->closeCursor();
} catch (PDOException) {
// Do not expose raw driver errors, DSNs or credentials in logs/session flashes.
throw new ServiceUnavailableHttpException(30, 'PostgreSQL 连接失败。请检查账号、密码、数据库访问权限,以及服务器网络和 SSL 配置。');
}
}
protected function connect(string $dsn, string $username, #[SensitiveParameter] string $password): PDO
{
if (! in_array('pgsql', PDO::getAvailableDrivers(), true)) {
throw new ServiceUnavailableHttpException(30, '应用缺少 PostgreSQL 驱动,请部署包含 pdo_pgsql 的新版应用镜像。');
}
return new PDO($dsn, $username, $password, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_PERSISTENT => false,
]);
}
private function quote(string $value): string
{
// PDO replaces all DSN semicolons with spaces; reject rather than alter database names.
if (preg_match('/[;\x00-\x1F]/', $value)) {
throw new ServiceUnavailableHttpException(30, '主机或数据库名称包含不支持的字符。');
}
return "'" . str_replace(['\\', "'"], ['\\\\', "\\'"], $value) . "'";
}
}