{"record":{"id":"c4eaacd463322476","repo":"fish2018/pansou","slug":"s-w-c4eaac","errorCode":null,"errorMessage":"[%s] 详情请求失败: %w","messagePattern":"\\[(.+?)\\] 详情请求失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/lingjisp/lingjisp.go","lineNumber":221,"sourceCode":"\t}\n\n\titems := resp.Data.Data\n\tif len(items) == 0 {\n\t\titems = resp.Data.List\n\t}\n\treturn dedupeLingjiItems(items), nil\n}\n\nfunc (p *LingjiPlugin) fetchDetail(client *http.Client, doubID int) (lingjiVideoItem, error) {\n\tparams := url.Values{}\n\tparams.Set(\"app_id\", lingjiAppID)\n\tparams.Set(\"identity\", lingjiIdentity)\n\tparams.Set(\"id\", fmt.Sprintf(\"%d\", doubID))\n\n\tapiURL := lingjiAPIBase + \"getVideoDetail?\" + params.Encode()\n\tbody, err := doLingjiGET(client, apiURL, lingjiDetailTimeout)\n\tif err != nil {\n\t\treturn lingjiVideoItem{}, fmt.Errorf(\"[%s] 详情请求失败: %w\", p.Name(), err)\n\t}\n\n\tvar resp lingjiDetailResponse\n\tif err := json.Unmarshal(body, &resp); err != nil {\n\t\treturn lingjiVideoItem{}, fmt.Errorf(\"[%s] 解析详情响应失败: %w\", p.Name(), err)\n\t}\n\tif !resp.Success || resp.Code != http.StatusOK {\n\t\treturn lingjiVideoItem{}, fmt.Errorf(\"[%s] 详情接口返回异常: success=%v code=%d\", p.Name(), resp.Success, resp.Code)\n\t}\n\treturn resp.Data, nil\n}\n\nfunc doLingjiGET(client *http.Client, requestURL string, timeout time.Duration) ([]byte, error) {\n\tvar lastErr error\n\n\tfor attempt := 0; attempt < lingjiMaxRetries; attempt++ {\n\t\tctx, cancel := context.WithTimeout(context.Background(), timeout)\n\t\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, requestURL, nil)","sourceCodeStart":203,"sourceCodeEnd":239,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/lingjisp/lingjisp.go#L203-L239","documentation":"fetchDetail failed to GET the getVideoDetail endpoint for a given video id, wrapping the transport error from doLingjiGET (retries already exhausted). Mirrors the search failure path but for the detail API.","triggerScenarios":"doLingjiGET(client, lingjiAPIBase+\"getVideoDetail?identity=...&id=<doubID>\", lingjiDetailTimeout) failed on all attempts — network outage, DNS failure, timeout, or HTTP-level error raised inside doLingjiGET.","commonSituations":"API domain offline or blocked; host network issues; lingjiDetailTimeout too small; invalid id causing server to hang or connection reset; proxy misconfiguration.","solutions":["Test the exact getVideoDetail URL with curl to see whether the endpoint is reachable","Check the wrapped error for timeout vs connection-refused vs HTTP status","Increase lingjiDetailTimeout / confirm retry count","Validate the doubID before calling fetchDetail to avoid requests the API cannot serve","Update lingjiAPIBase if the domain rotated"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Validate the id and endpoint reachability before detail fetch\nif doubID <= 0 {\n    return fmt.Errorf(\"非法详情ID: %d\", doubID)\n}\nctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)\ndefer cancel()\nif !isEndpointReachable(ctx, lingjiAPIBase) {\n    return fmt.Errorf(\"灵集API不可达\")\n}","typeGuard":null,"tryCatchPattern":"item, err := fetchDetail(doubID)\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) || isNetErr(err) {\n        if item, rerr := fetchDetail(doubID); rerr == nil {\n            return item, nil\n        }\n    }\n    return emptyItem, err\n}","preventionTips":["Validate ids before requesting details","Rely on doLingjiGET retries; keep timeouts sane","Check the wrapped cause (timeout vs refused vs HTTP status) in logs","Update the base URL when the domain rotates"],"tags":["network","http","api","timeout"],"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"}