{"record":{"id":"d98bd69c8a2db4d4","repo":"fish2018/pansou","slug":"s-w-d98bd6","errorCode":null,"errorMessage":"[%s] 解析搜索响应失败: %w","messagePattern":"\\[(.+?)\\] 解析搜索响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/lingjisp/lingjisp.go","lineNumber":199,"sourceCode":"}\n\nfunc (p *LingjiPlugin) fetchSearchItems(client *http.Client, keyword string) ([]lingjiVideoItem, error) {\n\tparams := url.Values{}\n\tparams.Set(\"app_id\", lingjiAppID)\n\tparams.Set(\"identity\", lingjiIdentity)\n\tparams.Set(\"sb\", keyword)\n\tparams.Set(\"page\", \"1\")\n\tparams.Set(\"limit\", \"20\")\n\n\tapiURL := lingjiAPIBase + \"getVideoList?\" + params.Encode()\n\tbody, err := doLingjiGET(client, apiURL, lingjiSearchTimeout)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索请求失败: %w\", p.Name(), err)\n\t}\n\n\tvar resp lingjiSearchResponse\n\tif err := json.Unmarshal(body, &resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析搜索响应失败: %w\", p.Name(), err)\n\t}\n\tif !resp.Success || resp.Code != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"[%s] 搜索接口返回异常: success=%v code=%d\", p.Name(), resp.Success, resp.Code)\n\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","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/lingjisp/lingjisp.go#L181-L217","documentation":"fetchSearchItems received a response body from getVideoList but json.Unmarshal into lingjiSearchResponse failed. The API returned a body that is not JSON or does not match the expected envelope (success/code/data fields). This is a contract mismatch between the plugin's response struct and the actual API payload.","triggerScenarios":"doLingjiGET succeeded but the body is HTML (error/anti-bot page), empty, or JSON with a different schema than lingjiSearchResponse, causing json.Unmarshal to return an error.","commonSituations":"API domain hijacked/parked returning an HTML page; API version changed its response schema; a CDN error page (502 HTML) was returned with 200/other status; truncated response body.","solutions":["Log the first ~200 bytes of body on unmarshal failure to see the actual payload","Verify the API is reachable and returns JSON via curl","Update lingjiSearchResponse struct to match the current API schema","Add a content-type check before unmarshaling and treat HTML as an upstream failure","Retry, since a transient proxy error page can produce this"],"exampleFix":"// before\nvar resp lingjiSearchResponse\nif err := json.Unmarshal(body, &resp); err != nil {\n    return nil, fmt.Errorf(\"[%s] 解析搜索响应失败: %w\", p.Name(), err)\n}\n// after\nif !json.Valid(body) || len(body) > 0 && body[0] != '{' {\n    return nil, fmt.Errorf(\"[%s] 响应不是JSON: %q\", p.Name(), body[:min(len(body),200)])\n}\nvar resp lingjiSearchResponse\nif err := json.Unmarshal(body, &resp); err != nil {\n    return nil, fmt.Errorf(\"[%s] 解析搜索响应失败: %w\", p.Name(), err)\n}","handlingStrategy":"validation","validationCode":"// Validate JSON shape before unmarshaling into the typed struct\nif len(body) == 0 || body[0] != '{' {\n    return fmt.Errorf(\"响应不是JSON对象: %q\", body[:min(len(body),100)])\n}\nvar probe map[string]json.RawMessage\nif err := json.Unmarshal(body, &probe); err != nil {\n    return fmt.Errorf(\"响应JSON非法: %w\", err)\n}\nif _, ok := probe[\"data\"]; !ok {\n    return fmt.Errorf(\"响应缺少data字段\")\n}","typeGuard":"func looksLikeLingjiSearch(body []byte) bool {\n    var probe struct {\n        Success bool `json:\"success\"`\n        Code    int  `json:\"code\"`\n        Data    struct {\n            Data json.RawMessage `json:\"data\"`\n            List json.RawMessage `json:\"list\"`\n        } `json:\"data\"`\n    }\n    return json.Unmarshal(body, &probe) == nil\n}","tryCatchPattern":"items, err := fetchSearchItems(...)\nif err != nil && strings.Contains(err.Error(), \"解析搜索响应失败\") {\n    log.Printf(\"lingjisp schema drift: %v\", err)\n    return emptyResults, nil // degrade instead of failing whole search\n}","preventionTips":["Log raw bodies on unmarshal failure to catch schema changes early","Keep lingjiSearchResponse in sync with the live API contract","Check Content-Type before parsing","Add json.RawMessage probes for schema drift detection"],"tags":["json","parsing","api","schema"],"backgroundTag":"json-unmarshal-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"}