{"record":{"id":"a94e6b10b973f950","repo":"fish2018/pansou","slug":"s-w-a94e6b","errorCode":null,"errorMessage":"[%s] 获取内容失败: %w","messagePattern":"\\[(.+?)\\] 获取内容失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/jsnoteclub/jsnoteclub.go","lineNumber":284,"sourceCode":"\tparams.Set(\"key\", dataKey)\n\tparams.Set(\"limit\", \"10000\")\n\tparams.Set(\"fields\", \"id,slug,title,excerpt,url,updated_at,visibility\")\n\tparams.Set(\"order\", \"updated_at DESC\")\n\n\treqURL := fmt.Sprintf(\"https://jsnoteclub.com/ghost/api/content/posts/?%s\", params.Encode())\n\n\tctx, cancel := context.WithTimeout(context.Background(), requestTimeout)\n\tdefer cancel()\n\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, reqURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建内容请求失败: %w\", p.Name(), err)\n\t}\n\tsetAPIHeaders(req, \"https://jsnoteclub.com/\")\n\n\tresp, err := p.doRequestWithRetry(req, client, maxRequestRetries)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 获取内容失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[%s] 内容接口返回状态码: %d\", p.Name(), resp.StatusCode)\n\t}\n\n\tvar payload ghostPostsResponse\n\tif err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析内容数据失败: %w\", p.Name(), err)\n\t}\n\n\treturn payload.Posts, nil\n}\n\nfunc (p *JsNoteClubPlugin) fetchDetailLinks(client *http.Client, detailURL string) []model.Link {\n\tif cached, ok := detailCache.Load(detailURL); ok {\n\t\tif entry, valid := cached.(detailCacheEntry); valid && time.Now().Before(entry.expiresAt) {","sourceCodeStart":266,"sourceCodeEnd":302,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/jsnoteclub/jsnoteclub.go#L266-L302","documentation":"fetchPosts returns this error when p.doRequestWithRetry exhausts its 3 attempts (with exponential backoff) to GET the Ghost Content API posts endpoint and every attempt failed with a transport error or a non-200 status. The underlying cause (network failure, timeout, or 'HTTP 状态码 %d') is wrapped with %w. This means the plugin could not retrieve the post list at all, so the search fails.","triggerScenarios":"All 3 attempts of client.Do inside doRequestWithRetry fail: DNS resolution failure, connection refused/reset, TLS errors, the 12s requestTimeout context expiring, or persistent non-200 responses (e.g. 403/429/5xx) from https://jsnoteclub.com/ghost/api/content/posts/.","commonSituations":"Server offline or DNS broken; the site rate-limiting or blocking the plugin's IP/User-Agent (Cloudflare challenges returning 403); a data-key that has been rotated making the API reject the request; corporate proxy/firewall blocking outbound HTTPS; transient network outage during all retries.","solutions":["Test reachability of https://jsnoteclub.com/ghost/api/content/posts/ with curl using the same headers (browser User-Agent, Referer) to see the actual status code.","Check network/DNS/proxy on the host running the plugin; fix connectivity or configure proxy environment correctly.","Unwrap the error (errors.Unwrap) to distinguish a transport error from 'HTTP 状态码 %d'; if 403/429, the site is blocking — update User-Agent/referer headers or add longer backoff.","Verify the data-key passed in fetchPosts is fresh; re-run after the posts cache (1h TTL) expires or restart to re-fetch the key.","Increase maxRequestRetries or retryBaseDelay if failures are transient rate-limiting."],"exampleFix":"// before\nresp, err := p.doRequestWithRetry(req, client, maxRequestRetries)\nif err != nil {\n    return nil, fmt.Errorf(\"[%s] 获取内容失败: %w\", p.Name(), err)\n}\n// after\nresp, err := p.doRequestWithRetry(req, client, maxRequestRetries)\nif err != nil {\n    var statusErr error\n    if errors.As(err, &statusErr) && strings.Contains(err.Error(), \"状态码 403\") {\n        // site is blocking us — refresh headers/UA or back off\n    }\n    return nil, fmt.Errorf(\"[%s] 获取内容失败: %w\", p.Name(), err)\n}","handlingStrategy":"retry","validationCode":"// check upstream reachability before invoking the plugin\nresp, err := http.Head(\"https://jsnoteclub.com/\")\nif err != nil || resp.StatusCode != http.StatusOK {\n    log.Printf(\"jsnoteclub.com unreachable, skipping: %v\", err)\n    return\n}","typeGuard":null,"tryCatchPattern":"posts, err := p.fetchPosts(client, dataKey)\nif err != nil {\n    var netErr net.Error\n    if errors.As(err, &netErr) && netErr.Timeout() {\n        // transient — schedule retry with backoff\n    }\n    return nil, fmt.Errorf(\"upstream unavailable: %w\", err)\n}","preventionTips":["Monitor jsnoteclub.com availability; treat this error as upstream downtime rather than a local bug.","Keep the built-in retry (3 attempts, exponential backoff) and consider raising it for flaky networks.","Send browser-like headers (User-Agent, Referer) to avoid bot blocks, as setAPIHeaders does.","Cache successful post lists (plugin already caches 1h) so transient failures don't break searches."],"tags":["network","http","retry","api"],"backgroundTag":"http-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"}