windows下 利用 dns 解析IP下载 github 文件

# 定义公共 DNS 服务器列表
 $Global:DnsServers = @("1.1.1.1", "8.8.8.8", "9.9.9.9", "223.5.5.5")

function Get-FastestIP {
    param([string]$HostName)

    Write-Host "正在为 [$HostName] 寻找最优 IP..." -ForegroundColor Cyan

    $ipList = New-Object System.Collections.Generic.HashSet[string]
    
    # 并行DNS查询
    $dnsJobs = @()
    foreach ($dns in $Global:DnsServers) {
        $dnsJobs += Start-Job -ScriptBlock {
            param($HostName, $DnsServer)
            try {
                $results = Resolve-DnsName -Name $HostName -Server $DnsServer -Type A -ErrorAction SilentlyContinue
                if ($results) {
                    return $results | Where-Object { $_.IPAddress } | Select-Object -ExpandProperty IPAddress
                }
            } catch {
                return $null
            }
        } -ArgumentList $HostName, $dns
    }
    
    # 等待所有DNS查询完成
    $timeoutSeconds = 5
    $completedJobs = Wait-Job -Job $dnsJobs -Timeout $timeoutSeconds
    
    # 收集所有IP地址
    foreach ($job in $completedJobs) {
        $ips = Receive-Job -Job $job
        if ($ips) {
            foreach ($ip in $ips) {
                [void]$ipList.Add($ip)
            }
        }
    }
    
    # 清理作业
    Remove-Job -Job $dnsJobs -Force

    # 如果公共 DNS 失败,尝试本地 DNS
    if ($ipList.Count -eq 0) {
        try {
            $results = Resolve-DnsName -Name $HostName -Type A -ErrorAction SilentlyContinue
            foreach ($res in $results) { if ($res.IPAddress) { [void]$ipList.Add($res.IPAddress) } }
        } catch {}
    }

    if ($ipList.Count -eq 0) { throw "无法解析域名: $HostName" }

    Write-Host "找到候选 IP: $($ipList -join ', ')" -ForegroundColor Gray

    # 并行测速
    $pingJobs = @()
    foreach ($ip in $ipList) {
        $pingJobs += Start-Job -ScriptBlock {
            param($IpAddress)
            try {
                $tcpClient = New-Object System.Net.Sockets.TcpClient
                $stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
                
                # 尝试连接443端口(HTTPS)
                $result = $tcpClient.BeginConnect($IpAddress, 443, $null, $null)
                $wait = $result.AsyncWaitHandle.WaitOne(1000, $false)  # 1秒超时
                
                if ($wait) {
                    $latency = $stopwatch.ElapsedMilliseconds
                    $tcpClient.EndConnect($result)
                    $tcpClient.Close()
                    $stopwatch.Stop()
                    return @{ IP = $IpAddress; Latency = $latency }
                } else {
                    $tcpClient.Close()
                    $stopwatch.Stop()
                    return $null
                }
            } catch {
                return $null
            }
        } -ArgumentList $ip
    }
    
    # 等待所有测速完成
    $pingResults = @()
    foreach ($job in $pingJobs) {
        $result = Receive-Job -Job $job -Wait
        if ($result) {
            $pingResults += $result
        }
    }
    
    # 清理作业
    Remove-Job -Job $pingJobs -Force

    # 选择最快的IP
    $bestIp = $null
    $minPing = [int]::MaxValue
    
    if ($pingResults.Count -gt 0) {
        $pingResults = $pingResults | Sort-Object -Property Latency
        $bestIp = $pingResults[0].IP
        $minPing = $pingResults[0].Latency
        Write-Host "选定最优 IP: $bestIp (延迟: ${minPing}ms)" -ForegroundColor Green
    } else {
        $bestIp = $ipList | Select-Object -First 1
        Write-Warning "无法连接任何IP,随机选择: $bestIp"
    }

    return $bestIp
}

# Invoke-GitHubSmartDownload 
function Invoke-GitHubSmartDownload {
    <#
    .SYNOPSIS
        全自动动态测速下载 GitHub 资源 (修复版)
    #>
    param(
        [Parameter(Mandatory=$true)]
        [string]$Url,
        [string]$OutFile
    )

    $SessionIpCache = @{}

    # 全局忽略证书错误
    [System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
    [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls11 -bor [System.Net.SecurityProtocolType]::Tls

    # === 主逻辑 ===
    $currentUrl = $Url
    $maxRedirects = 10
    $count = 0

    while ($count -lt $maxRedirects) {
        Write-Host "`n[$count] 请求: $currentUrl" -ForegroundColor White
        
        $uri = [Uri]$currentUrl
        $hostName = $uri.Host
        
        if (-not $SessionIpCache.ContainsKey($hostName)) {
            $SessionIpCache[$hostName] = Get-FastestIP -HostName $hostName
        }
        $targetIp = $SessionIpCache[$hostName]

        try {
            # 创建请求对象
            $webRequest = [System.Net.HttpWebRequest]::Create($currentUrl)
            $webRequest.Method = "HEAD"
            $webRequest.UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
            $webRequest.AllowAutoRedirect = $false
            $webRequest.Timeout = 15000
            
            # 设置Host属性而不是添加Host头
            $webRequest.Host = $hostName
            
            $response = $webRequest.GetResponse()
        } catch [System.Net.WebException] {
            $response = $_.Exception.Response
            if (-not $response) {
                Write-Error "请求失败: $($_.Exception.Message)"
                return
            }
        }

        $statusCode = [int]$response.StatusCode
        $headers = $response.Headers

        # 处理重定向 (300-399)
        if ($statusCode -ge 300 -and $statusCode -lt 400) {
            $newUrl = $headers["Location"]
            
            if ([string]::IsNullOrEmpty($newUrl)) {
                Write-Error "重定向失败:服务器返回了 $statusCode 但没有 Location 头"
                $response.Close()
                return
            }

            # 处理相对路径
            if (-not ([Uri]$newUrl).IsAbsoluteUri) {
                $baseUri = [Uri]$currentUrl
                $newUrl = (new-object Uri($baseUri, $newUrl)).AbsoluteUri
            }
            
            $currentUrl = $newUrl
            $count++
            Write-Host "  -> 重定向 ($statusCode) 到: $newUrl" -ForegroundColor Yellow
            $response.Close()
            continue
        }

        # 准备下载 (200 OK)
        if ($statusCode -eq 200) {
            $response.Close()
            
            if (-not $OutFile) {
                if ($headers["Content-Disposition"] -match 'filename="?([^"]+)"?') {
                    $OutFile = $matches[1]
                } else {
                    $OutFile = [IO.Path]::GetFileName(([Uri]$currentUrl).LocalPath)
                }
                if (-not $OutFile) { $OutFile = "download.bin" }
            }
            
            $OutFile = [IO.Path]::GetFullPath($OutFile)
            Write-Host "开始下载内容到: $OutFile" -ForegroundColor Cyan

            # 修复:使用curl.exe而不是Invoke-WebRequest,更可靠
            if (Get-Command "curl.exe" -ErrorAction SilentlyContinue) {
                Write-Host "使用curl下载..." -ForegroundColor Gray
                $curlArgs = @(
                    "-L",  # 跟随重定向
                    "--resolve", "$($hostName):443:$($targetIp)",
                    "-k",  # 忽略证书错误
                    "-o", "`"$OutFile`"",
                    "-#",  # 显示进度条
                    "`"$currentUrl`""
                )
                
                & curl.exe $curlArgs
                
                if ($LASTEXITCODE -eq 0) {
                    Write-Host "下载完成!" -ForegroundColor Green
                    return
                } else {
                    Write-Error "curl下载失败,退出码: $LASTEXITCODE"
                }
            }
            
            # 备用方案:使用WebClient
            Write-Host "使用WebClient下载..." -ForegroundColor Gray
            try {
                $webClient = New-Object System.Net.WebClient
                $webClient.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
                
                # 设置代理(如果需要)
                # $webClient.Proxy = [System.Net.WebRequest]::GetSystemWebProxy()
                # $webClient.Proxy.Credentials = [System.Net.CredentialCache]::DefaultCredentials
                
                $webClient.DownloadFile($currentUrl, $OutFile)
                Write-Host "下载完成!" -ForegroundColor Green
                return
            } catch {
                Write-Error "WebClient下载失败: $($_.Exception.Message)"
            }
            
            # 最后备用方案:Invoke-WebRequest
            Write-Host "使用Invoke-WebRequest下载..." -ForegroundColor Gray
            try {
                $params = @{
                    Uri = $currentUrl
                    OutFile = $OutFile
                    UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
                    TimeoutSec = 300
                }
                
                if ([System.Environment]::Version.Major -ge 7) {
                    $params["SkipCertificateCheck"] = $true
                }
                
                Invoke-WebRequest @params
                Write-Host "下载完成!" -ForegroundColor Green
                return
            } catch {
                Write-Error "所有下载方法均失败: $($_.Exception.Message)"
            }
        }

        Write-Error "请求结束,状态码: $statusCode"
        if ($response) { $response.Close() }
        return
    }
}

Invoke-GitHubSmartDownload https://github.com/MoonshotAI/kimi-cli/releases/download/0.75/kimi-0.75-x86_64-pc-windows-msvc.zip

posted @ 2026-01-11 16:54  geyee  阅读(19)  评论(0)    收藏  举报