使用Golang构建JSON API的实践指南

使用Golang构建JSON API的实践指南 大家好,我正在尝试从乐天API接收响应。不确定哪里出了问题,因为我收到了这样的响应:

{ "fault": { "code": "403", "type": "Status report", "message": "Runtime Error", "description": "No matching resource found in the API for the given request" }
{"grant_type":"password","password":"jj","scope":342511,"username":"jj"}

代码:

package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"encoding/json"
)

type SearchParams struct {
	Keywords string `json:"keywords"`

}

type SearchRequest struct {
	RegionID string `json:"region"`
	UserID string `json:"uid"`
}
//output API response

type ItemInfo struct {
	ASIN string `json:"asin,omitempty"`
	PartnerID string `json:"pid,omitempty"`
	DetailPageURL string `json:"detailPageURL,omitempty"`
}

type Price struct {
	CurrencyCode string `json:"currencyCode,omitempty"`
	Amount float32 `json:"amount,omitempty"`
}

type PointPrice struct {
	AMEX float32 `json:"AMEX,omitempty"`
	CUR float32 `json:"CUR,omitempty"`
	ThankYou float32 `json:"ThankYou,omitempty"`
	Discover float32 `json:"Discover,omitempty"`
}

type ItemListings struct {
	ItemList []ItemInfo  `json:"items"`
	CashLow string `json:"cashlow,omitempty"`

}



func main() {
	client := &http.Client{
		
	}
	response, err := client.Get("https://api.rakutenmarketing.com/token")
	req, err := http.NewRequest("GET", "https://api.rakutenmarketing.com/token", nil)
	req.Header.Add("Authorization", `"Basic djl6eUp5YUNZX2tZODdOaFRhSmVOZnFqME93YTpGZzJxWHRhNTlvXzUyZ0VNNDFiVThaZjhQa3Nh"`)
    //resp, err := client.Do(req)
	if err != nil {
		fmt.Print(err.Error())
		os.Exit(1)
	}

	payload := map[string]interface{}{"grant_type": "password","username":"jj","password":"pp1234","scope":342511}
	byts, _ := json.Marshal(payload)
	fmt.Println(string(byts)) 
    

	responseData, err := ioutil.ReadAll(response.Body)
	if err != nil {
		log.Fatal(err)
	}

	var responseObject SearchParams
	json.Unmarshal(responseData, &responseObject)

	fmt.Println(string(responseData))

}

---------------------------附注 - 这是乐天API网站的参数指南

配置您的应用程序或常用的Web服务客户端(例如Advanced Rest Client)以发出以下请求: a. URL:https://api.rakutenmarketing.com/token b. HTTP动词:POST c. 头部: i. 授权: d. 负载: i. grant_type=password ii. username= iii. password= iv. scope=

------------------------任何帮助/协助/线索都将非常感激 (:


更多关于使用Golang构建JSON API的实践指南的实战教程也可以访问 https://www.itying.com/category-94-b0.html

14 回复

你发布的说明要求使用POST请求,而你发出的是GET请求。或许你的问题与使用了错误的请求方式有关?

更多关于使用Golang构建JSON API的实践指南的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


诺伯特,你好, 感谢你的回复。 将方法从GET改为POST后结果依然相同……

SearchParams 是您自己起的名字,还是应该作为 json 的一部分?像这样:

{"SearchParams": { "keywords": ..., ...}}

或者 json 应该是:

{ "keywords": ..., ...}

文档是保密的,还是可以分享这部分内容?

感谢!这样就能建立连接了(令牌每小时刷新,所以需要更新授权部分,这可能是您无法测试的原因),但我正在寻找通过打印发送前两种类型 SearchParams 和 SearchRequest,并接收 ItemInfo 和 Price 作为响应。

诚挚问候,

大家好,我仍然卡在这个问题上……不知道如何将 SearchParams 类型的 SearchRequest 作为输入提交到 API,并将其余部分作为输出……虽然获得了完全访问权限并收到了批准令牌,但似乎无法将结构体发送到同一地址…… 😦

我使用SearchParams。我正在发送我正在寻找的API文档。感谢您的帮助 🙂

Uploadfiles.io - ProductSearch_1.0_May2017.pdf

Uploadfiles.io - ProductSearch_1.0_May2017.pdf

免费、安全、匿名、无限制地上传文件。@UploadFilesFree

现在内容已经是 JSON 格式了,尝试将:

res.Header.Add(“Content-Type”, “application/x-www-form-urlencoded”)

改为

res.Header.Add(“Content-Type”, “application/json”)

另外,文档中是否有关于请求在 JSON 中应如何呈现的示例?现在它发送的是:

{"keywords":"Donna"}

您好,非常感谢您的反馈和建议。

我会解释一下GET/POST的困惑…您说的很有道理,代码确实能运行但返回403错误。我认为问题在于第一个请求中缺少刷新令牌。

{ "fault": { "code": "403", "type": "Status report", "message": "Runtime Error", "description": "No matching resource found in the API
for the given request" } }

从这份PDF可以看出:https://ufile.io/dxzwq 其中说明了如何获取第一个令牌(我们对话的开始 - 我们已经实现了这部分),在进行额外请求时需要在有效负载中包含刷新令牌,但初始请求是使用POST方法。

有什么想法吗? 再次表示衷心的感谢!

引用 johandalabacka 的回复:

另外,文档中是否有示例说明请求的 JSON 格式应该如何?现在发送的是:

res.Header.Add(“Content-Type”, “application/json”)

是的,已经尝试过了。 但返回的是 400 错误请求

根据文档,结构体需要如下定义:当然还有其他结构体,但一旦我能成功发送一个请求,后续就会简单很多。

type SearchParams struct {
	Keywords string `json:"keywords"`
	SearchIndex string `json:"index"`
	Page string `json:"page"`
	PriceLow int `json:"price_low"`
	PriceHigh int `json:"price_high"`
	AwardLow int `json:"award_low"`
	AwardHigh int `json:"award_high"`

更新 - 我现在正在使用以下附加代码:

u := SearchParams{Keywords: "Donna"}
t := new(bytes.Buffer)
json.NewEncoder(t).Encode(u)
res, err := http.NewRequest("POST", "https://api.rakutenmarketing.com/token", t)
res.Header.Add("Authorization", "Basicdjl6eUp5YUNZX2tZODdOaFRhSmVOZnFqME93YTpGZzJxWHRhNTlvXzUyZ0VNNDFiVThaZjhQa3Nh")
res.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp1, err := client.Do(res)
if err != nil {
    fmt.Print(err.Error())
    os.Exit(1)

但我收到:500 内部服务器错误。

我通过以下方式取得了一些进展。将有效载荷作为表单数据放入请求体中:

func main() {
	v := url.Values{}
	v.Add("grant_type", "password")
	v.Add("username", "jj")
	v.Add("password", "pp1234")
	v.Add("scope", "342511")
	r := bytes.NewReader([]byte(v.Encode()))

	client := &http.Client{}
	req, err := http.NewRequest("POST", "https://api.rakutenmarketing.com/token", r)
	req.Header.Add("Authorization", `"Basic djl6eUp5YUNZX2tZODdOaFRhSmVOZnFqME93YTpGZzJxWHRhNTlvXzUyZ0VNNDFiVThaZjhQa3Nh"`)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Print(err.Error())
		os.Exit(1)
	}

	responseData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(responseData))

}

感谢您提供的PDF文档。但经过查阅,我发现所有搜索都是通过GET请求实现的,所有参数都放在查询部分,如PDF中的示例所示:

https://api.rakutenmarketing.com/productsearch/1.0?keyword=beverage &sort=retailprice&sorttype=asc&sort=productname&sorttype=asc

最具灵活性的构建方式如下所示:

v.Add("keyword", "beverage")
v.Add("sort", "retailprice")
v.Add("sorttype", "asc")
v.Add("productname", "productname")
v.Add("sorttype", "asc")

        url := "https://api.rakutenmarketing.com/productsearch/1.0?" + v.Encode()
	fmt.Println(url)

	client := &http.Client{}
	req, err := http.NewRequest("GET", "https://api.rakutenmarketing.com/token", r)
	req.Header.Add("Authorization", `"Basic djl6eUp5YUNZX2tZODdOaFRhSmVOZnFqME93YTpGZzJxWHRhNTlvXzUyZ0VNNDFiVThaZjhQa3Nh"`)

	resp, err := client.Do(req)
	if err != nil {
		fmt.Print(err.Error())
		os.Exit(1)
	}

	responseData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(responseData))

通过阅读您提供的两份PDF文档,我认为以下代码应该可行。但我无法进行测试。结果应该是XML格式。您或许可以将下面的 req.Header.Add("Accept", "application/xml") 改为 req.Header.Add("Accept", "application/json") 来获取JSON格式的数据。祝您好运。

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
)

type bearerToken struct {
	TokenType    string `json:"token_type"`
	ExpiresIn    int    `json:"expires_in"`
	RefreshToken string `json:"refresh_token"`
	AccessToken  string `json:"access_token"`
}

func main() {
	// 这是步骤 #4(第5页)
	v := url.Values{}
	v.Add("grant_type", "password")
	v.Add("username", "jj")
	v.Add("password", "pp1234")
	v.Add("scope", "342511")
	r := bytes.NewReader([]byte(v.Encode()))

	client := &http.Client{}
	req, err := http.NewRequest("POST", "https://api.rakutenmarketing.com/token", r)
	req.Header.Add("Authorization", `"Basic djl6eUp5YUNZX2tZODdOaFRhSmVOZnFqME93YTpGZzJxWHRhNTlvXzUyZ0VNNDFiVThaZjhQa3Nh"`)
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	resp, err := client.Do(req)
	if err != nil {
		fmt.Print(err.Error())
		os.Exit(1)
	}

	// 应该会返回一个承载令牌
	var bt bearerToken
	err = json.NewDecoder(resp.Body).Decode(&bt)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(bt)

	// 我认为最重要的是access_token,它用于所有其他搜索

	/* 这是一个示例请求,用于获取与宠物类别中狗项圈相关的第一页(页码=1)20个结果(最大=20),
	英语语言,来自ID为2557的广告商(mid=2557):

	https://api.rakutenmarketing.com/productsearch/1.0?keyword="Dog collar"
	&cat="Pets"&language=en_US&max=20&pagenumber=1&mid=2557*/

	v = url.Values{}
	v.Add("keyword", "\"Dog Collar\"")
	v.Add("cat", "\"Pets\"")
	v.Add("language", "en_US")
	v.Add("max", "20")
	v.Add("pagenumber", "1")
	v.Add("mid", "2557")

	url := "https://api.rakutenmarketing.com/productsearch/1.0?" + v.Encode()
	client = &http.Client{}
	req, err = http.NewRequest("GET", url, nil)
	req.Header.Add("Authorization", "Bearer "+bt.AccessToken)
	req.Header.Add("Accept", "application/xml")
	resp, err = client.Do(req)
	if err != nil {
		fmt.Print(err.Error())
		os.Exit(1)
	}

	responseData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(string(responseData))

}

在您的代码中存在几个关键问题导致API调用失败。以下是需要修正的部分:

主要问题分析

  1. 使用了错误的HTTP方法 - 文档要求使用POST,但您的代码使用了GET
  2. 授权头格式不正确 - 包含了多余的引号
  3. 请求体未正确设置 - 创建了payload但没有发送
  4. 使用了错误的响应类型 - 响应结构体与API返回格式不匹配

修正后的代码

package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
)

type TokenResponse struct {
	AccessToken string `json:"access_token"`
	TokenType   string `json:"token_type"`
	ExpiresIn   int    `json:"expires_in"`
	Scope       string `json:"scope"`
}

type ErrorResponse struct {
	Fault struct {
		Code        string `json:"code"`
		Type        string `json:"type"`
		Message     string `json:"message"`
		Description string `json:"description"`
	} `json:"fault"`
}

func main() {
	client := &http.Client{}
	
	// 创建请求体数据
	payload := map[string]interface{}{
		"grant_type": "password",
		"username":   "jj",
		"password":   "pp1234",
		"scope":      342511,
	}
	
	jsonData, err := json.Marshal(payload)
	if err != nil {
		log.Fatal("JSON序列化错误:", err)
	}

	// 创建POST请求
	req, err := http.NewRequest("POST", "https://api.rakutenmarketing.com/token", bytes.NewBuffer(jsonData))
	if err != nil {
		log.Fatal("创建请求失败:", err)
	}

	// 设置正确的头部
	req.Header.Add("Authorization", "Basic djl6eUp5YUNZX2tZODdOaFRhSmVOZnFqME93YTpGZzJxWHRhNTlvXzUyZ0VNNDFiVThaZjhQa3Nh")
	req.Header.Add("Content-Type", "application/json")

	// 发送请求
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal("请求失败:", err)
	}
	defer resp.Body.Close()

	// 读取响应
	responseData, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal("读取响应失败:", err)
	}

	fmt.Printf("状态码: %d\n", resp.StatusCode)
	fmt.Printf("响应体: %s\n", string(responseData))

	// 根据状态码处理响应
	if resp.StatusCode == 200 {
		var tokenResp TokenResponse
		if err := json.Unmarshal(responseData, &tokenResp); err != nil {
			log.Fatal("解析token响应失败:", err)
		}
		fmt.Printf("访问令牌: %s\n", tokenResp.AccessToken)
	} else {
		var errorResp ErrorResponse
		if err := json.Unmarshal(responseData, &errorResp); err != nil {
			fmt.Printf("原始错误响应: %s\n", string(responseData))
		} else {
			fmt.Printf("错误代码: %s, 描述: %s\n", errorResp.Fault.Code, errorResp.Fault.Description)
		}
	}
}

关键修正点

  1. 使用POST方法http.NewRequest("POST", ...)
  2. 移除授权头中的多余引号"Basic djl6eUp5YUNZX2tZODdOaFRhSmVOZnFqME93YTpGZzJxWHRhNTlvXzUyZ0VNNDFiVThaZjhQa3Nh"
  3. 添加Content-Type头application/json
  4. 正确发送请求体:使用bytes.NewBuffer(jsonData)
  5. 添加适当的错误处理结构体

这个修正版本应该能够正确处理乐天API的认证请求。如果仍然遇到问题,请检查凭据是否正确以及API文档是否有其他要求。

回到顶部