{"record":{"id":"9ea58f2a5e551622","repo":"flipped-aurora/gin-vue-admin","slug":"w-9ea58f","errorCode":null,"errorMessage":"构建下载请求失败: %w","messagePattern":"构建下载请求失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"server/service/system/sys_skills.go","lineNumber":399,"sourceCode":"\t\tif err := writeConstraint(tool, req.Content); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc (s *SkillsService) DownloadOnlineSkill(_ context.Context, req request.DownloadOnlineSkillReq) error {\n\tskillsDir, err := s.toolSkillsDir(req.Tool)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tbody, err := json.Marshal(map[string]interface{}{\n\t\t\"plugin_id\": req.ID,\n\t\t\"version\":   req.Version,\n\t})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"构建下载请求失败: %w\", err)\n\t}\n\n\tdownloadReq, err := http.NewRequest(http.MethodPost, \"https://plugin.gin-vue-admin.com/api/shopPlugin/downloadSkill\", bytes.NewReader(body))\n\tif err != nil {\n\t\treturn fmt.Errorf(\"构建下载请求失败: %w\", err)\n\t}\n\tdownloadReq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tdownloadResp, err := http.DefaultClient.Do(downloadReq)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"下载技能失败: %w\", err)\n\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","sourceCodeStart":381,"sourceCodeEnd":417,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/service/system/sys_skills.go#L381-L417","documentation":"DownloadOnlineSkill serializes {plugin_id, version} to JSON before POSTing to the skill marketplace. json.Marshal on a map[string]interface{} with string values essentially never fails, so this wrap is a defensive guard; it fires only if a value in the map cannot be marshaled (e.g. a channel, func, or unsupported type injected into the map).","triggerScenarios":"Practically unreachable with the current hardcoded map of two string fields; would fire only if req.ID or req.Version were changed to an unmarshalable type, or the map construction were modified to include unsupported values.","commonSituations":"Code changes introducing a non-serializable value (func/chan/custom type without MarshalJSON) into the request map.","solutions":["Check the wrapped %v message for 'unsupported type' to find the offending field","Keep req.ID and req.Version as plain strings and convert before building the map","Implement json.Marshaler on any custom type placed into the payload"],"exampleFix":"// before\n\"plugin_id\": req.ID, // ID became chan string after a refactor\n// after\n\"plugin_id\": fmt.Sprintf(\"%v\", req.ID), // ensure serializable primitives","handlingStrategy":"validation","validationCode":"payload := map[string]interface{}{\"plugin_id\": req.ID, \"version\": req.Version}\nif _, err := json.Marshal(payload); err != nil {\n    // reject before calling the service\n}","typeGuard":"func isJSONSerializable(v interface{}) bool { _, err := json.Marshal(v); return err == nil }","tryCatchPattern":"if err := DownloadOnlineSkill(ctx, req); err != nil {\n    if strings.Contains(err.Error(), \"unsupported type\") {\n        // marshal bug in payload construction, fix the map values\n    }\n}","preventionTips":["Keep request payload fields as plain primitives (string)","Add a unit test marshaling the download payload","Use typed structs instead of map[string]interface{} for request bodies","Never put func/chan values into JSON payloads"],"tags":["go","json","serialization"],"backgroundTag":"json-marshal-unsupported-type","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}