From b7ea6d170120fb29eca9d57ff1e24b2fe065133e Mon Sep 17 00:00:00 2001 From: NewName Date: Tue, 20 May 2025 19:03:15 +0800 Subject: [PATCH] 1 --- ghproxy/search.go | 286 +++++++++++++++++++++++++--------------------- 1 file changed, 157 insertions(+), 129 deletions(-) diff --git a/ghproxy/search.go b/ghproxy/search.go index 43a484f..da2a341 100644 --- a/ghproxy/search.go +++ b/ghproxy/search.go @@ -219,63 +219,179 @@ func searchDockerHub(ctx context.Context, query string, page, pageSize int) (*Se return cached.(*SearchResult), nil } - // 重试逻辑 + // 判断是否是用户/仓库格式的搜索 + isUserRepo := strings.Contains(query, "/") + var namespace, repoName string + + if isUserRepo { + parts := strings.Split(query, "/") + if len(parts) == 2 { + namespace = parts[0] + repoName = parts[1] + } + } + + // 构建搜索URL + baseURL := "https://registry.hub.docker.com/v2/repositories/" + var fullURL string + var params url.Values + + if isUserRepo && namespace != "" { + // 如果是用户/仓库格式,直接搜索用户的仓库 + fullURL = fmt.Sprintf("%s%s/", baseURL, namespace) + params = url.Values{ + "page": {fmt.Sprintf("%d", page)}, + "page_size": {fmt.Sprintf("%d", pageSize)}, + } + if repoName != "" { + params.Set("name", repoName) + } + } else { + // 普通搜索 + fullURL = baseURL + "search/" + params = url.Values{ + "query": {query}, + "page": {fmt.Sprintf("%d", page)}, + "page_size": {fmt.Sprintf("%d", pageSize)}, + } + } + + fullURL = fullURL + "?" + params.Encode() + fmt.Printf("搜索URL: %s\n", fullURL) + + // 发送请求 + req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil) + if err != nil { + return nil, fmt.Errorf("创建请求失败: %v", err) + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") + + client := &http.Client{ + Timeout: 10 * time.Second, + Transport: &http.Transport{ + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + DisableCompression: true, + DisableKeepAlives: false, + MaxIdleConnsPerHost: 10, + }, + } + var result *SearchResult var lastErr error - // 判断是否是用户/仓库格式的搜索 - isUserRepo := strings.Contains(query, "/") - + // 重试逻辑 for retries := 3; retries > 0; retries-- { - if isUserRepo { - // 对于用户/仓库格式,尝试精确搜索和模糊搜索 - parts := strings.Split(query, "/") - if len(parts) == 2 { - // 先尝试精确搜索 - result, lastErr = trySearchDockerHub(ctx, query, page, pageSize) - if lastErr == nil && len(result.Results) == 0 { - // 如果精确搜索没有结果,尝试模糊搜索 - result, lastErr = trySearchDockerHub(ctx, parts[1], page, pageSize) - if lastErr == nil { - // 过滤出属于指定用户的结果 - filteredResults := make([]Repository, 0) - for _, repo := range result.Results { - if strings.EqualFold(repo.Namespace, parts[0]) || - strings.EqualFold(repo.RepoOwner, parts[0]) { - filteredResults = append(filteredResults, repo) - } - } - result.Results = filteredResults - result.Count = len(filteredResults) + resp, err := client.Do(req) + if err != nil { + lastErr = fmt.Errorf("发送请求失败: %v", err) + if !isRetryableError(err) { + break + } + time.Sleep(time.Second * time.Duration(4-retries)) + continue + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + lastErr = fmt.Errorf("读取响应失败: %v", err) + if !isRetryableError(err) { + break + } + time.Sleep(time.Second * time.Duration(4-retries)) + continue + } + + if resp.StatusCode != http.StatusOK { + switch resp.StatusCode { + case http.StatusTooManyRequests: + lastErr = fmt.Errorf("请求过于频繁,请稍后重试") + case http.StatusNotFound: + lastErr = fmt.Errorf("未找到相关镜像") + case http.StatusBadGateway, http.StatusServiceUnavailable: + lastErr = fmt.Errorf("Docker Hub服务暂时不可用,请稍后重试") + default: + lastErr = fmt.Errorf("请求失败: 状态码=%d, 响应=%s", resp.StatusCode, string(body)) + } + if !isRetryableError(lastErr) { + break + } + time.Sleep(time.Second * time.Duration(4-retries)) + continue + } + + // 解析响应 + if isUserRepo && namespace != "" { + // 解析用户仓库列表响应 + var userRepos struct { + Count int `json:"count"` + Next string `json:"next"` + Previous string `json:"previous"` + Results []Repository `json:"results"` + } + if err := json.Unmarshal(body, &userRepos); err != nil { + lastErr = fmt.Errorf("解析响应失败: %v", err) + break + } + + // 转换为SearchResult格式 + result = &SearchResult{ + Count: userRepos.Count, + Next: userRepos.Next, + Previous: userRepos.Previous, + Results: make([]Repository, 0), + } + + // 处理结果 + for _, repo := range userRepos.Results { + // 如果指定了仓库名,只保留匹配的结果 + if repoName == "" || strings.Contains(strings.ToLower(repo.Name), strings.ToLower(repoName)) { + repo.Namespace = namespace + result.Results = append(result.Results, repo) + } + } + result.Count = len(result.Results) + } else { + // 解析普通搜索响应 + result = &SearchResult{} + if err := json.Unmarshal(body, &result); err != nil { + lastErr = fmt.Errorf("解析响应失败: %v", err) + break + } + + // 处理搜索结果 + for i := range result.Results { + if result.Results[i].IsOfficial { + if !strings.Contains(result.Results[i].Name, "/") { + result.Results[i].Name = "library/" + result.Results[i].Name + } + result.Results[i].Namespace = "library" + } else { + parts := strings.Split(result.Results[i].Name, "/") + if len(parts) > 1 { + result.Results[i].Namespace = parts[0] + result.Results[i].Name = parts[1] + } else if result.Results[i].RepoOwner != "" { + result.Results[i].Namespace = result.Results[i].RepoOwner } } } - } else { - result, lastErr = trySearchDockerHub(ctx, query, page, pageSize) } - if lastErr == nil { - break - } - - if !isRetryableError(lastErr) { - return nil, fmt.Errorf("搜索失败: %v", lastErr) - } - - time.Sleep(time.Second * time.Duration(4-retries)) + // 成功获取结果,跳出重试循环 + lastErr = nil + break } if lastErr != nil { - return nil, fmt.Errorf("搜索失败,已重试3次: %v", lastErr) + return nil, fmt.Errorf("搜索失败: %v", lastErr) } - // 过滤和处理搜索结果 - result.Results = filterSearchResults(result.Results, query) - result.Count = len(result.Results) - // 缓存结果 searchCache.Set(cacheKey, result) - return result, nil } @@ -296,94 +412,6 @@ func isRetryableError(err error) bool { return false } -// trySearchDockerHub 执行实际的Docker Hub API请求 -func trySearchDockerHub(ctx context.Context, query string, page, pageSize int) (*SearchResult, error) { - // 构建Docker Hub API请求 - baseURL := "https://registry.hub.docker.com/v2/search/repositories/" - params := url.Values{} - params.Set("query", query) - params.Set("page", fmt.Sprintf("%d", page)) - params.Set("page_size", fmt.Sprintf("%d", pageSize)) - - fullURL := baseURL + "?" + params.Encode() - fmt.Printf("搜索URL: %s\n", fullURL) - - req, err := http.NewRequestWithContext(ctx, "GET", fullURL, nil) - if err != nil { - return nil, fmt.Errorf("创建请求失败: %v", err) - } - - // 添加必要的请求头 - req.Header.Set("Accept", "application/json") - req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") - - // 发送请求 - client := &http.Client{ - Timeout: 10 * time.Second, - Transport: &http.Transport{ - MaxIdleConns: 100, - IdleConnTimeout: 90 * time.Second, - DisableCompression: true, - DisableKeepAlives: false, - MaxIdleConnsPerHost: 10, - }, - } - - resp, err := client.Do(req) - if err != nil { - return nil, fmt.Errorf("发送请求失败: %v", err) - } - defer resp.Body.Close() - - // 读取响应体 - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("读取响应失败: %v", err) - } - - // 检查响应状态码 - if resp.StatusCode != http.StatusOK { - switch resp.StatusCode { - case http.StatusTooManyRequests: - return nil, fmt.Errorf("请求过于频繁,请稍后重试") - case http.StatusNotFound: - return nil, fmt.Errorf("未找到相关镜像") - case http.StatusBadGateway, http.StatusServiceUnavailable: - return nil, fmt.Errorf("Docker Hub服务暂时不可用,请稍后重试") - default: - return nil, fmt.Errorf("请求失败: 状态码=%d, 响应=%s", resp.StatusCode, string(body)) - } - } - - // 解析响应 - var result SearchResult - if err := json.Unmarshal(body, &result); err != nil { - return nil, fmt.Errorf("解析响应失败: %v", err) - } - - // 处理搜索结果 - for i := range result.Results { - if result.Results[i].IsOfficial { - // 确保官方镜像有正确的名称格式 - if !strings.Contains(result.Results[i].Name, "/") { - result.Results[i].Name = "library/" + result.Results[i].Name - } - result.Results[i].Namespace = "library" - } else { - // 从 repo_name 中提取 namespace - parts := strings.Split(result.Results[i].Name, "/") - if len(parts) > 1 { - result.Results[i].Namespace = parts[0] - result.Results[i].Name = parts[1] - } else if result.Results[i].RepoOwner != "" { - result.Results[i].Namespace = result.Results[i].RepoOwner - } - } - } - - return &result, nil -} - // getRepositoryTags 获取仓库标签信息 func getRepositoryTags(ctx context.Context, namespace, name string) ([]TagInfo, error) { if namespace == "" || name == "" {