69 lines
3.0 KiB
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) . "'";
|
|
}
|
|
}
|