109 lines
3.5 KiB
PHP
109 lines
3.5 KiB
PHP
<?php
|
||
|
||
namespace Modules\EcommerceFinance\Services;
|
||
|
||
use PDO;
|
||
use PDOException;
|
||
use Illuminate\Support\Facades\Log;
|
||
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||
|
||
/**
|
||
* JianSun SQL Server 数据库连接管理
|
||
* 使用 PDO_ODBC 直连 SQL Server(无需 sqlsrv 扩展)
|
||
*/
|
||
class JianSunConnection
|
||
{
|
||
private static ?PDO $instance = null;
|
||
|
||
/**
|
||
* 获取 PDO 连接实例(单例模式)
|
||
*/
|
||
public static function get(): PDO
|
||
{
|
||
if (self::$instance === null) {
|
||
self::$instance = self::create();
|
||
}
|
||
|
||
return self::$instance;
|
||
}
|
||
|
||
/**
|
||
* 创建新的 PDO 连接
|
||
*/
|
||
public static function create(): PDO
|
||
{
|
||
if (! config('jiansun.enabled')) {
|
||
throw new ServiceUnavailableHttpException(
|
||
60,
|
||
'上游数据同步尚未启用,请联系管理员。'
|
||
);
|
||
}
|
||
|
||
$connection = config('jiansun.connection', []);
|
||
$host = trim((string) ($connection['host'] ?? ''));
|
||
$port = (int) ($connection['port'] ?? 1433);
|
||
$db = trim((string) ($connection['database'] ?? ''));
|
||
$user = trim((string) ($connection['username'] ?? ''));
|
||
$pass = (string) ($connection['password'] ?? '');
|
||
$driver = trim((string) ($connection['odbc_driver'] ?? 'FreeTDS'), '{} ');
|
||
$tdsVersion = trim((string) ($connection['tds_version'] ?? '7.4'));
|
||
$connectTimeout = max((int) ($connection['connect_timeout'] ?? 5), 1);
|
||
$queryTimeout = max((int) ($connection['query_timeout'] ?? 15), 1);
|
||
|
||
if ($host === '' || $db === '' || $user === '' || $pass === '' || $driver === '') {
|
||
throw new ServiceUnavailableHttpException(
|
||
60,
|
||
'上游数据库配置不完整,请联系管理员。'
|
||
);
|
||
}
|
||
|
||
$dsn = sprintf(
|
||
'odbc:Driver={%s};Server=%s;Port=%d;Database=%s;UID=%s;PWD=%s;TDS_Version=%s;ClientCharset=UTF-8;Connect Timeout=%d',
|
||
str_replace('}', '}}', $driver),
|
||
self::quoteOdbcValue($host),
|
||
$port,
|
||
self::quoteOdbcValue($db),
|
||
self::quoteOdbcValue($user),
|
||
self::quoteOdbcValue($pass),
|
||
$tdsVersion,
|
||
$connectTimeout
|
||
);
|
||
|
||
try {
|
||
// FreeTDS 的 PDO_ODBC 需要 UID/PWD 位于连接字符串中;异常链不得
|
||
// 携带 DSN,否则日志或调试堆栈可能泄露凭据。
|
||
$pdo = new PDO($dsn, null, null, [
|
||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||
PDO::ATTR_TIMEOUT => $queryTimeout,
|
||
PDO::ATTR_PERSISTENT => false,
|
||
]);
|
||
|
||
return $pdo;
|
||
} catch (PDOException $e) {
|
||
Log::warning('JianSun 数据库连接失败。', [
|
||
'sql_state' => (string) $e->getCode(),
|
||
'exception' => $e::class,
|
||
]);
|
||
|
||
throw new ServiceUnavailableHttpException(
|
||
60,
|
||
'上游数据库暂时无法连接,请稍后再试。'
|
||
);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 重置连接(用于长时间运行或测试场景)
|
||
*/
|
||
public static function reset(): void
|
||
{
|
||
self::$instance = null;
|
||
}
|
||
|
||
private static function quoteOdbcValue(string $value): string
|
||
{
|
||
return '{' . str_replace('}', '}}', $value) . '}';
|
||
}
|
||
}
|