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 */ 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, ]); } }