{"record":{"id":"38e7c1f178a94899","repo":"fish2018/pansou","slug":"d-w-38e7c1","errorCode":null,"errorMessage":"重试 %d 次后失败: %w","messagePattern":"重试 (.+?) 次后失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/jsnoteclub/jsnoteclub.go","lineNumber":516,"sourceCode":"\n\tfor attempt := 0; attempt < maxRetries; attempt++ {\n\t\tresp, err := client.Do(req.Clone(req.Context()))\n\t\tif err == nil && resp.StatusCode == http.StatusOK {\n\t\t\treturn resp, nil\n\t\t}\n\t\tif resp != nil {\n\t\t\tresp.Body.Close()\n\t\t}\n\t\tlastErr = err\n\t\tif err == nil {\n\t\t\tlastErr = fmt.Errorf(\"HTTP 状态码 %d\", resp.StatusCode)\n\t\t}\n\t\tif attempt < maxRetries-1 {\n\t\t\ttime.Sleep(retryBaseDelay * time.Duration(1<<attempt))\n\t\t}\n\t}\n\n\treturn nil, fmt.Errorf(\"重试 %d 次后失败: %w\", maxRetries, lastErr)\n}\n\nfunc newHTTPClient() *http.Client {\n\treturn &http.Client{\n\t\tTimeout: requestTimeout,\n\t\tTransport: &http.Transport{\n\t\t\tMaxIdleConns:        httpMaxIdleConns,\n\t\t\tMaxIdleConnsPerHost: httpMaxIdlePerHost,\n\t\t\tMaxConnsPerHost:     httpMaxConnsPerHost,\n\t\t\tIdleConnTimeout:     90 * time.Second,\n\t\t\tTLSHandshakeTimeout: 10 * time.Second,\n\t\t},\n\t}\n}\n\nfunc setHTMLHeaders(req *http.Request, referer string) {\n\treq.Header.Set(\"User-Agent\", \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36\")\n\treq.Header.Set(\"Accept\", \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\")","sourceCodeStart":498,"sourceCodeEnd":534,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/jsnoteclub/jsnoteclub.go#L498-L534","documentation":"This error is returned by doRequestWithRetry after exhausting maxRetries attempts to fetch remote data. lastErr is wrapped with %w so the underlying cause (timeout, connection failure, non-2xx, etc.) is preserved. The plugin's callers fetchDataKey, fetchPosts, and fetchDetailLinks all surface it when the upstream source is unreachable or persistently failing.","triggerScenarios":"Any of fetchDataKey, fetchPosts, or fetchDetailLinks calling doRequestWithRetry while the remote endpoint keeps failing on every attempt (network errors, timeouts, repeated bad status codes), after exponential backoff sleeps of retryBaseDelay<<attempt between attempts.","commonSituations":"Upstream site is down or blocked (e.g. by GFW or geo-restriction), DNS failure in the deployment environment, no outbound internet in a container, or the source raising rate limits that persist across all retries.","solutions":["Test connectivity to the upstream host from the deployment machine (curl the source URL) to confirm the network path works.","Check the wrapped lastErr chain (errors.Unwrap / %v of the message) to identify the root cause — timeout vs status code vs DNS.","Increase maxRetries or retryBaseDelay if the upstream is intermittently slow.","Configure an HTTP proxy if the host requires one to be reachable.","Verify the source URL is still valid; upstream APIs change or shut down over time."],"exampleFix":"// before: retries fail with no diagnosis\nresult, err := fetchPosts(client)\n// after: inspect the wrapped cause\nif err != nil {\n    log.Printf(\"fetchPosts failed: %v\", errors.Unwrap(err))\n    // e.g. context deadline exceeded -> raise requestTimeout or add proxy\n}","handlingStrategy":"retry","validationCode":"url, err := url.Parse(sourceURL)\nif err != nil || url == nil || url.Host == \"\" { return err }\nconn, err := net.DialTimeout(\"tcp\", net.JoinHostPort(url.Hostname(), portOr(url, \"443\")), 3*time.Second)\nif err != nil { return fmt.Errorf(\"upstream unreachable: %w\", err) }\nconn.Close()","typeGuard":null,"tryCatchPattern":"results, err := fetchPosts(client)\nif err != nil {\n    var retryErr = err\n    for unwrapped := errors.Unwrap(err); unwrapped != nil; unwrapped = errors.Unwrap(unwrapped) {\n        retryErr = unwrapped\n    }\n    log.Printf(\"upstream fetch failed after retries, root cause: %v\", retryErr)\n    results = fallbackResults // serve cached/stale data\n}","preventionTips":["Add health monitoring for upstream sources and alert before users hit failures.","Cache successful responses so retries can fall back to stale data.","Keep retry counts and delays configurable per environment.","Log the fully unwrapped error chain, not just the wrapper message."],"tags":["network","retry","http","upstream"],"backgroundTag":"api-request-failed","analyzedSha":"beaa56133755a548ebc51b090b3816e2ae044aa6","analyzedAt":"2026-09-07T00:31:18.025Z","contentChangedAt":"2026-09-07T00:31:18.025Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}