{"record":{"id":"8ca0dedd4f834540","repo":"flipped-aurora/gin-vue-admin","slug":"w-8ca0de","errorCode":null,"errorMessage":"解析下载结果失败: %w","messagePattern":"解析下载结果失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/service/system/sys_skills.go","lineNumber":429,"sourceCode":"\t}\n\tdefer downloadResp.Body.Close()\n\n\tif downloadResp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"下载技能失败, HTTP状态码: %d\", downloadResp.StatusCode)\n\t}\n\n\tmetaBody, err := io.ReadAll(downloadResp.Body)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"读取下载结果失败: %w\", err)\n\t}\n\n\tvar meta struct {\n\t\tData struct {\n\t\t\tURL string `json:\"url\"`\n\t\t} `json:\"data\"`\n\t}\n\tif err = json.Unmarshal(metaBody, &meta); err != nil {\n\t\treturn fmt.Errorf(\"解析下载结果失败: %w\", err)\n\t}\n\n\trealDownloadURL := strings.TrimSpace(meta.Data.URL)\n\tif realDownloadURL == \"\" {\n\t\treturn errors.New(\"下载结果缺少 url\")\n\t}\n\n\tzipResp, err := http.Get(realDownloadURL)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"下载压缩包失败: %w\", err)\n\t}\n\tdefer zipResp.Body.Close()\n\n\tif zipResp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"下载压缩包失败, HTTP状态码: %d\", zipResp.StatusCode)\n\t}\n\n\ttmpFile, err := os.CreateTemp(\"\", \"gva-skill-*.zip\")","sourceCodeStart":411,"sourceCodeEnd":447,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/sys_skills.go#L411-L447","documentation":"The marketplace's 200 response body could not be parsed as the expected JSON shape {\"data\":{\"url\":\"...\"}}. json.Unmarshal failed, meaning the response is not valid JSON or doesn't match the meta struct.","triggerScenarios":"Calling DownloadOnlineSkill when the marketplace returns HTML (error page via proxy), an empty body, a different envelope (e.g. top-level url instead of data.url), or a captcha/WAF challenge page with 200 status.","commonSituations":"Marketplace API contract changed after an upgrade; transparent proxies returning HTML; hitting a wrong endpoint variant; CDN serving cached error pages.","solutions":["Dump/log the raw metaBody to see what was actually returned","Verify the marketplace API response contract still nests the URL under data.url","Check for proxy/WAF HTML responses and exclude them (Content-Type check before Unmarshal)","Upgrade/redeploy if the server-side struct is stale relative to the marketplace API"],"exampleFix":"// add diagnostics before unmarshal\nct := downloadResp.Header.Get(\"Content-Type\")\nif !strings.Contains(ct, \"application/json\") {\n    return fmt.Errorf(\"unexpected content-type %s: %s\", ct, metaBody)\n}\nif err = json.Unmarshal(metaBody, &meta); err != nil { ... }","handlingStrategy":"type-guard","validationCode":"resp, err := http.Post(downloadSkillURL, \"application/json\", body)\nif err == nil && !strings.Contains(resp.Header.Get(\"Content-Type\"), \"application/json\") {\n    // response is not JSON (HTML error page/captcha) — abort early\n}","typeGuard":"func isDownloadMeta(v interface{}) bool {\n    m, ok := v.(map[string]interface{})\n    if !ok { return false }\n    d, ok := m[\"data\"].(map[string]interface{})\n    if !ok { return false }\n    url, ok := d[\"url\"].(string)\n    return ok && url != \"\"\n}","tryCatchPattern":"if err := DownloadOnlineSkill(ctx, req); err != nil && strings.Contains(err.Error(), \"解析下载结果失败\") {\n    // marketplace contract changed or returned HTML; log body and check API version\n}","preventionTips":["Check Content-Type before json.Unmarshal","Log a bounded sample of unexpected bodies for diagnosis","Pin/track the marketplace API version in tests","Detect WAF/captcha HTML responses explicitly"],"tags":["go","json","api-contract"],"backgroundTag":"json-unmarshal-failed","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}