CaiWuXiTong/modules/EcommerceFinance/Services/UpstreamSourceRegistry.php

185 lines
6.0 KiB
PHP

<?php
namespace Modules\EcommerceFinance\Services;
use InvalidArgumentException;
/**
* 统一解析上游数据源定义,避免业务代码直接拼接配置路径。
*/
class UpstreamSourceRegistry
{
public function __construct(
private readonly UpstreamStoredConfiguration $storedConfiguration,
) {
}
/**
* @return array{key:string,label:string,config:string}
*/
public function definition(string $source): array
{
$source = strtolower(trim($source));
if ($source === '' || ! preg_match('/^[a-z0-9_-]+$/', $source)) {
throw new InvalidArgumentException('上游数据源标识不合法。');
}
$definition = config('upstreams.sources.' . $source);
if (! is_array($definition)) {
$connection = $this->storedConfiguration->find($source);
if ($connection === null) {
throw new InvalidArgumentException('未登记的上游数据源:' . $source);
}
$definition = ['label' => $connection->name, 'config' => 'upstream_pipeline.defaults'];
}
$configKey = trim((string) ($definition['config'] ?? ''));
if ($configKey === '' || ! preg_match('/^[a-z0-9_.-]+$/i', $configKey)) {
throw new InvalidArgumentException('上游数据源配置路径不合法:' . $source);
}
return [
'key' => $source,
'label' => trim((string) ($definition['label'] ?? $source)) ?: $source,
'config' => $configKey,
];
}
public function value(string $source, string $path, mixed $default = null): mixed
{
$definition = $this->definition($source);
$suffix = trim($path, '.');
$key = $definition['config'] . ($suffix === '' ? '' : '.' . $suffix);
$configured = config($key, $default);
if ($this->usesLocalConfiguration()) {
return $configured;
}
if ($suffix === 'connection') {
return $this->connection($definition['key'], (array) $configured);
}
if (str_starts_with($suffix, 'connection.')) {
$field = substr($suffix, strlen('connection.'));
$connection = $this->connection(
$definition['key'],
(array) config($definition['config'] . '.connection', []),
);
return $connection[$field] ?? $default;
}
return $this->storedConfiguration->value($definition['key'], $suffix, $configured);
}
public function enabled(string $source): bool
{
return (bool) $this->value($source, 'enabled', false);
}
public function driver(string $source): string
{
$definition = $this->definition($source);
$default = config($definition['config'] . '.connection.driver', 'sqlsrv');
return (string) (($this->usesLocalConfiguration()
? $default : $this->storedConfiguration->value($source, 'connection.driver', $default)) ?: 'sqlsrv');
}
/** @return array<string,array> */
public function all(): array
{
$keys = array_unique(array_merge(array_keys((array) config('upstreams.sources', [])), array_keys($this->storedConfiguration->all())));
$definitions = [];
foreach ($keys as $key) {
$definitions[$key] = $this->definition($key);
}
return $definitions;
}
private function usesLocalConfiguration(): bool
{
return (bool) config('upstreams.local_configuration.enabled', false);
}
private function connection(string $source, array $configured): array
{
$fields = array_values(array_unique(array_merge(array_keys($configured), [
'driver',
'host',
'port',
'database',
'username',
'password',
'odbc_driver',
'tds_version',
'connect_timeout',
'query_timeout',
])));
$resolved = $configured;
foreach ($fields as $field) {
// ODBC 驱动由运行应用的操作系统决定,不能因共享记录中的
// Windows 驱动名而覆盖 Linux 容器中的可用驱动。
if ($field === 'odbc_driver' && trim((string) ($configured[$field] ?? '')) !== '') {
$resolved[$field] = $configured[$field];
continue;
}
$resolved[$field] = $this->storedConfiguration->value(
$source,
'connection.' . $field,
$configured[$field] ?? null,
);
}
return $this->withProductionTunnelEndpoint($source, $resolved);
}
/**
* 生产服务器无法直连上游时,只覆盖到受限 SSH 隧道的私有端点。
* 共享系统数据库仍是账号、密码和业务库信息的唯一来源,开发机则
* 保持原始上游地址直连,避免把生产 Docker 网络地址传播给开发环境。
*/
private function withProductionTunnelEndpoint(string $source, array $connection): array
{
if (
! app()->environment('production')
|| $this->usesLocalConfiguration()
|| ! $this->storedConfiguration->enabled()
) {
return $connection;
}
$endpoint = config('upstreams.tunnel_endpoints.' . $source, []);
if (! is_array($endpoint)) {
return $connection;
}
$host = trim((string) ($endpoint['host'] ?? ''));
$port = $endpoint['port'] ?? null;
if ($host === '' && ($port === null || $port === '')) {
return $connection;
}
if (
$host === ''
|| preg_match('/^[A-Za-z0-9._:-]+$/', $host) !== 1
|| filter_var($port, FILTER_VALIDATE_INT, ['options' => ['min_range' => 1, 'max_range' => 65535]]) === false
) {
throw new \RuntimeException('生产上游隧道端点配置不完整。');
}
return array_replace($connection, [
'host' => $host,
'port' => (int) $port,
]);
}
}