{"record":{"id":"9c79c22259fb34c2","repo":"fish2018/pansou","slug":"s-api-w-9c79c2","errorCode":null,"errorMessage":"[%s] 解析 API 响应失败: %w","messagePattern":"\\[(.+?)\\] 解析 API 响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugin/dyyj/dyyj.go","lineNumber":429,"sourceCode":"\tdefer cancel()\n\treq, err := http.NewRequestWithContext(ctx, http.MethodGet, apiURL, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 创建 API 请求失败: %w\", p.Name(), err)\n\t}\n\treq.Header.Set(\"User-Agent\", UserAgent)\n\treq.Header.Set(\"Accept\", \"application/vnd.api+json, application/json\")\n\treq.Header.Set(\"Accept-Language\", \"zh-CN,zh;q=0.9,en;q=0.8\")\n\treq.Header.Set(\"Referer\", BaseURL+\"/\")\n\n\tresp, err := p.doRequestWithRetry(req, client)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] API 搜索请求失败: %w\", p.Name(), err)\n\t}\n\tdefer resp.Body.Close()\n\n\tvar payload dyyjAPIResponse\n\tif err := encodingjson.NewDecoder(resp.Body).Decode(&payload); err != nil {\n\t\treturn nil, fmt.Errorf(\"[%s] 解析 API 响应失败: %w\", p.Name(), err)\n\t}\n\n\tposts := make(map[string]dyyjIncluded, len(payload.Included))\n\tfor _, post := range payload.Included {\n\t\tposts[post.ID] = post\n\t}\n\n\tresults := make([]model.SearchResult, 0, len(payload.Data))\n\tfor _, discussion := range payload.Data {\n\t\tpost, ok := posts[discussion.Relationships.MostRelevantPost.Data.ID]\n\t\tif !ok {\n\t\t\tcontinue\n\t\t}\n\t\tlinks := p.extractAPIContentLinks(post.Attributes.ContentHTML)\n\t\tif len(links) == 0 {\n\t\t\tcontinue\n\t\t}\n\t\tcreatedAt, parseErr := time.Parse(time.RFC3339, discussion.Attributes.CreatedAt)","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/fish2018/pansou/blob/beaa56133755a548ebc51b090b3816e2ae044aa6/plugin/dyyj/dyyj.go#L411-L447","documentation":"dyyj.executeSearchAPI received a response but the JSON decoder could not unmarshal it into dyyjAPIResponse (encodingjson.NewDecoder(...).Decode(&payload) failed). This means the body was not valid JSON or its shape broke decoding (e.g. HTML error page returned with 200, truncated JSON).","triggerScenarios":"Flarum API returned non-JSON content (HTML login/challenge page, empty body, or a plain-text error) while the plugin expected application/vnd.api+json; response truncated mid-stream.","commonSituations":"Session expired so the API returns an HTML redirect/login page; WAF interposes an HTML challenge; API version change altering the response envelope; server 500 with text body.","solutions":["Log the first 200 bytes of the raw body before decoding to see what arrived","Check resp.StatusCode and Content-Type; fail early if Content-Type is not JSON","Verify the Flarum API endpoint still exists and matches the dyyjAPIResponse struct","Handle authentication requirements (Flarum may need a token for some queries)","Retry on transient truncation"],"exampleFix":"// before\nvar payload dyyjAPIResponse\nif err := encodingjson.NewDecoder(resp.Body).Decode(&payload); err != nil {\n\treturn nil, fmt.Errorf(\"[%s] 解析 API 响应失败: %w\", p.Name(), err)\n}\n// after\nct := resp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"json\") {\n\theader, _ := io.ReadAll(io.LimitReader(resp.Body, 256))\n\treturn nil, fmt.Errorf(\"[%s] API 返回非 JSON 响应 (Content-Type=%s): %s\", p.Name(), ct, header)\n}\nvar payload dyyjAPIResponse\nif err := encodingjson.NewDecoder(resp.Body).Decode(&payload); err != nil {\n\treturn nil, fmt.Errorf(\"[%s] 解析 API 响应失败: %w\", p.Name(), err)\n}","handlingStrategy":"type-guard","validationCode":"if !strings.Contains(resp.Header.Get(\"Content-Type\"), \"json\") {\n\treturn errors.New(\"API did not return JSON\")\n}","typeGuard":"func isJSONResponse(resp *http.Response) bool {\n\treturn resp.StatusCode == http.StatusOK && strings.Contains(resp.Header.Get(\"Content-Type\"), \"json\")\n}","tryCatchPattern":"var payload dyyjAPIResponse\nif err := encodingjson.NewDecoder(io.LimitReader(resp.Body, maxBodySize)).Decode(&payload); err != nil {\n\theader, _ := io.ReadAll(io.LimitReader(resp.Body, 256))\n\treturn nil, fmt.Errorf(\"decode API response: %w (head=%q)\", err, header)\n}","preventionTips":["Check Content-Type and status before decoding","Capture a raw body prefix on decode failure for diagnosis","Keep the response struct in sync with the Flarum API version","Watch for HTML challenge/login pages arriving with 200 status"],"tags":["json","api","go","unexpected-response"],"backgroundTag":"json-decode-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"}