{"record":{"id":"03b4f8b5346f6a1f","repo":"flipped-aurora/gin-vue-admin","slug":"w-03b4f8","errorCode":null,"errorMessage":"解析响应失败: %w","messagePattern":"解析响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/mcp/http_client.go","lineNumber":134,"sourceCode":"\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, err := http.DefaultClient.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"请求上游接口失败: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\trawBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"读取响应失败: %w\", err)\n\t}\n\tif resp.StatusCode >= http.StatusBadRequest {\n\t\treturn nil, fmt.Errorf(\"上游接口返回状态码 %d: %s\", resp.StatusCode, string(rawBody))\n\t}\n\n\tvar result upstreamEnvelope[T]\n\tif err := json.Unmarshal(rawBody, &result); err != nil {\n\t\treturn nil, fmt.Errorf(\"解析响应失败: %w\", err)\n\t}\n\tif result.Code != 0 {\n\t\treturn nil, fmt.Errorf(\"上游接口业务错误: %s\", result.Msg)\n\t}\n\treturn &result, nil\n}\n\nfunc getUpstream[T any](ctx context.Context, endpoint string, query url.Values) (*upstreamEnvelope[T], error) {\n\treturn doUpstream[T](ctx, http.MethodGet, endpoint, query, nil)\n}\n\nfunc postUpstream[T any](ctx context.Context, endpoint string, body any) (*upstreamEnvelope[T], error) {\n\treturn doUpstream[T](ctx, http.MethodPost, endpoint, nil, body)\n}\n\nfunc deleteUpstream[T any](ctx context.Context, endpoint string, body any) (*upstreamEnvelope[T], error) {\n\treturn doUpstream[T](ctx, http.MethodDelete, endpoint, nil, body)\n}","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/mcp/http_client.go#L116-L152","documentation":"The upstream returned HTTP 200 but its JSON body could not be unmarshaled into upstreamEnvelope[T]; the error is wrapped as '解析响应失败'. This means the response is not the expected {code,msg,data} envelope shape (or data does not match T).","triggerScenarios":"Upstream returns HTML (login page/proxy error page) with 200; upstream response shape changed; generic type T does not match the actual data field type (e.g. expecting array but getting object); empty body with 200.","commonSituations":"Reverse proxy intercepting requests and serving an HTML error/login page; a GVA endpoint updated its response model; wrong generic type parameter passed to fetchPublicUpstream; gzip/charset issues served by a misconfigured proxy.","solutions":["Log/inspect string(rawBody) to see what the upstream actually returned","Verify the generic type parameter T matches the endpoint's response data model","Check whether a proxy or auth middleware is returning HTML instead of JSON","Align T with the current server response struct after any API change"],"exampleFix":"// before\ntype upstreamEnvelope[T any] struct {\n\tCode int    `json:\"code\"`\n\tMsg  string `json:\"msg\"`\n\tData T      `json:\"data\"`\n}\n// after (tolerate data being absent)\nvar result upstreamEnvelope[T]\nif len(bytes.TrimSpace(rawBody)) == 0 {\n\treturn nil, fmt.Errorf(\"解析响应失败: 空响应体\")\n}\nif err := json.Unmarshal(rawBody, &result); err != nil {\n\treturn nil, fmt.Errorf(\"解析响应失败: %w\", err)\n}","handlingStrategy":"type-guard","validationCode":"// sniff the response shape before trusting it\nif !strings.HasPrefix(strings.TrimSpace(string(rawBody)), \"{\") {\n\t// not JSON — likely an HTML proxy/login page; fix base URL or auth","typeGuard":"func isValidEnvelope[T any](raw []byte) (*upstreamEnvelope[T], bool) {\n\tvar probe struct {\n\t\tCode *int `json:\"code\"`\n\t}\n\tif err := json.Unmarshal(raw, &probe); err != nil || probe.Code == nil {\n\t\treturn nil, false\n\t}\n\tvar env upstreamEnvelope[T]\n\tif err := json.Unmarshal(raw, &env); err != nil {\n\t\treturn nil, false\n\t}\n\treturn &env, true\n}","tryCatchPattern":"data, err := fetchPublicUpstream[Item](ctx, endpoint)\nif err != nil {\n\tif strings.Contains(err.Error(), \"解析响应失败\") {\n\t\t// log raw body upstream of here; treat as upstream contract violation\n\t}\n\treturn err\n}","preventionTips":["Keep generic type T aligned with the endpoint's data model after API changes","Add a contract test per endpoint asserting unmarshal succeeds","Ensure no proxy sits between MCP and GVA serving HTML pages","Return an explicit error on empty response bodies"],"tags":["json","unmarshal","schema"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}