前言:
今天咱们对“封装api给其他人调用命令”大体比较关心,兄弟们都想要学习一些“封装api给其他人调用命令”的相关内容。那么小编同时在网摘上收集了一些关于“封装api给其他人调用命令””的相关内容,希望小伙伴们能喜欢,朋友们一起来学习一下吧!每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。
@头条创作挑战赛
本文探讨了在 用 Golang封装你的API的过程以及几个不同的编程步骤。
我做了一个非常有限的时间来证明如何为客户正在开发的开放 API 编写命令行包装器。
目标 REST API 是jquants-api,如前 一篇文章中所述。
我选择在 Golang 中实现封装,事实证明这非常快速且令人愉快。该任务最终在一个短暂的晚上完成,生成的具有核心功能的 Golang 的封装已上传到GitHub 上。
这是关于编写 API 的过程和几个不同的编程步骤的简短故事。
目标
首先,让我们列出我们必须处理的编程任务:
创建一个测试和支持代码,检查我们可以将用户名和密码保存在与 jquants-api-jvm 格式兼容的 edn 文件中
编写另一个测试和支持代码来检索刷新令牌
编写另一个测试和支持代码来检索 ID 令牌
使用 ID 令牌编写另一个测试和支持代码以检索每日值
将我们的包装器发布到 GitHub
在另一个程序中使用我们的 Go 库
首先编写测试用例,准备并保存登录结构以访问 API
我们总是谈论使用 TDD 编写代码——现在是时候这样做了。检查我们是否有代码可以输入用户名和密码并将其保存在与 jquants-api-jvm 格式兼容的 edn 文件中。
在 helper_test.go 文件中,让我们为PrepareLogin函数编写框架测试。
package jquants_api_go
import (
"fmt"
"os"
"testing"
)
func TestPrepareLogin(t *testing.T) {
PrepareLogin(os.Getenv("USERNAME"), os.Getenv("PASSWORD"))
}
在这里,我们从环境中获取 USERNAME 和 PASSWORD,使用os.GetEnv.
我们将准备函数写在一个helper.go文件中。它会:
获取用户名和密码作为参数
实例化一个登录结构
将其编组为 EDN 文件内容
func PrepareLogin(username string, password string) {
var user = Login{username, password}
encoded, _ := edn.Marshal(&user)
writeConfigFile("login.edn", encoded)
}
我们的 Login 结构首先将是:
type Login struct {
UserName string `edn:"mailaddress"`
Password string `edn:"password"`
}
调用edn.Marshal将创建一个 byte[] 数组内容,我们可以将其写入文件,因此writeConfigFile只需os.WriteFile使用从 EDN 编组返回的数组进行调用。
func writeConfigFile(file string, content []byte) {
os.WriteFile(getConfigFile(file), content, 0664)
}
为了能够使用 EDN 库,我们需要将其添加到go.mod文件中:
require olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3
在运行测试之前,一定要输入你的 jquants API 的凭证:
export USERNAME="youremail@you.com"
export PASSWORD="yourpassword"
在这个阶段,你应该可以go test在项目文件夹中运行,并看到以下输出:
PASS
ok github.com/hellonico/jquants-api-go 1.012s
您还应该看到login.edn文件的内容已正确填充:
cat ~/.config/jquants/login.edn
{:mailaddress "youremail@you.com" :password "yourpassword"}
使用登录向 jQuants API 发送 HTTP 请求并检索 RefreshToken
要测试的第二个函数是TestRefreshToken,它使用用户名和密码发送 HTTP 发布请求,并检索刷新令牌作为 API 调用的答案。我们helper_test.go使用新的测试用例更新文件:
func TestRefreshToken(t *testing.T) {
token, _ := GetRefreshToken()
fmt.Printf("%s\n", token)
}
该GetRefreshToken函数将:
加载先前存储在文件中的用户并将其准备为 JSON 数据
使用 URL 和 JSON 格式的用户作为正文内容准备 HTTP 请求
发送 HTTP 请求
API 将返回将存储在 RefreshToken 结构中的数据
让我们将该刷新令牌存储为 EDN 文件
支持GetUser现在将加载在之前的步骤中写入的文件内容。我们已经有了Login结构,然后将只使用edn.Unmarshall() 文件中的内容。
func GetUser() Login {
s, _ := os.ReadFile(getConfigFile("login.edn"))
var user Login
edn.Unmarshal(s, &user)
return user
}
请注意,虽然我们希望将 Login 结构读/写到 EDN 格式的文件中,但我们还希望在发送 HTTP 请求时将结构编组为 JSON。
所以我们的 Login 结构上的元数据需要稍微更新一下:
type Login struct {
UserName string `edn:"mailaddress" json:"mailaddress"`
Password string `edn:"password" json:"password"`
}
我们还需要一个新结构来读取 API 返回的令牌,并且我们还希望将其存储为 EDN,就像我们为Login结构所做的那样:
type RefreshToken struct {
RefreshToken string `edn:"refreshToken" json:"refreshToken"`
}
现在,我们拥有了编写GetRefreshToken函数的所有内容:
func GetRefreshToken() (RefreshToken, error) {
// load user stored in file previously and prepare it as json data
var user = GetUser()
data, err := json.Marshal(user)
// prepare the http request, with the url, and the json formatted user as body content
url := fmt.Sprintf("%s/token/auth_user", BASE_URL)
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(data))
// send the request
client := http.Client{}
res, err := client.Do(req)
// the API will returns data that will store in a RefreshToken struct
var rt RefreshToken
json.NewDecoder(res.Body).Decode(&rt)
// and let's store that refresh token as an EDN file
encoded, err := edn.Marshal(&rt)
writeConfigFile(REFRESH_TOKEN_FILE, encoded)
return rt, err
}
运行go test有点冗长,因为我们将 refreshToken 打印到标准输出,但测试应该通过了!
{eyJjdHkiOiJKV1QiLC...}
PASS
ok github.com/hellonico/jquants-api-go 3.231s
获取 ID 令牌
从刷新令牌中,您可以检索 IdToken,它是用于向 jquants API 发送请求的令牌。这与 .it 具有几乎相同的流程GetRefreshToken,为了支持它,我们主要引入了一个新结构IdToken,其中包含必要的元数据来编组到 edn/json 或从 edn/json 编组。
type IdToken struct {
IdToken string `edn:"idToken" json:"idToken"`
}
这次剩下的代码是:
func GetIdToken() (IdToken, error) {
var token = ReadRefreshToken()
url := fmt.Sprintf("%s/token/auth_refresh?refreshtoken=%s", BASE_URL, token.RefreshToken)
req, err := http.NewRequest(http.MethodPost, url, nil)
client := http.Client{}
res, err := client.Do(req)
var rt IdToken
json.NewDecoder(res.Body).Decode(&rt)
encoded, err := edn.Marshal(&rt)
writeConfigFile(ID_TOKEN_FILE, encoded)
return rt, err
}
获取每日行情
我们来到了包装代码的核心,在这里我们使用 IdToken,并通过 HTTP GET 请求从 jquants HTTP API 中请求每日报价。
检索每日报价的代码流程是:
和以前一样,从 EDN 文件中读取 ID 令牌
使用参数 code 和 dates 参数准备目标 URL
使用 idToken 作为 HTTP 标头发送 HTTP 请求
将结果解析为每日报价结构,它是报价结构的切片
测试用例只是检查返回的非空值并暂时打印引号。
func TestDaily(t *testing.T) {
var quotes = Daily("86970", "", "20220929", "20221003")
if quotes.DailyQuotes == nil {
t.Failed()
}
for _, quote := range quotes.DailyQuotes {
fmt.Printf("%s,%f\n", quote.Date, quote.Close)
}
}
的支持代码func Daily如下所示:
func Daily(code string, date string, from string, to string) DailyQuotes {
// read id token
idtoken := ReadIdToken()
// prepare url with parameters
baseUrl := fmt.Sprintf("%s/prices/daily_quotes?code=%s", BASE_URL, code)
var url string
if from != "" && to != "" {
url = fmt.Sprintf("%s&from=%s&to=%s", baseUrl, from, to)
} else {
url = fmt.Sprintf("%s&date=%s", baseUrl, date)
}
// send the HTTP request using the idToken
res := sendRequest(url, idtoken.IdToken)
// parse the result as daily quotes
var quotes DailyQuotes
err_ := json.NewDecoder(res.Body).Decode("es)
Check(err_)
return quotes
}
现在我们需要填写一些空白:
sendRequest 需要更多细节
DailyQuotes的解析其实并没有那么简单
所以,首先让我们把 sendRequest 函数排除在外。它使用 设置标题http.Header,并注意您可以在此处添加任意数量的标题。然后它发送 HTTP GET 请求并按原样返回响应。
func sendRequest(url string, idToken string) *http.Response {
req, _ := http.NewRequest(http.MethodGet, url, nil)
req.Header = http.Header{
"Authorization": {"Bearer " + idToken},
}
client := http.Client{}
res, _ := client.Do(req)
return res
}
现在来解析每日报价。如果您使用 Goland 作为您的编辑器,您会注意到,如果您将 JSON 内容复制粘贴到您的 Go 文件中,编辑器将要求直接将 JSON 转换为 Go 代码!
挺整洁的。
type Quote struct {
Code string `json:"Code"`
Close float64 `json:"Close"`
Date JSONTime `json:"Date"`
AdjustmentHigh float64 `json:"AdjustmentHigh"`
Volume float64 `json:"Volume"`
TurnoverValue float64 `json:"TurnoverValue"`
AdjustmentClose float64 `json:"AdjustmentClose"`
AdjustmentLow float64 `json:"AdjustmentLow"`
Low float64 `json:"Low"`
High float64 `json:"High"`
Open float64 `json:"Open"`
AdjustmentOpen float64 `json:"AdjustmentOpen"`
AdjustmentFactor float64 `json:"AdjustmentFactor"`
AdjustmentVolume float64 `json:"AdjustmentVolume"`
}
type DailyQuotes struct {
DailyQuotes []Quote `json:"daily_quotes"`
}
虽然默认值非常好,但我们需要做更多的调整以正确解组日期。以下内容来自以下关于如何编组/解组 JSON 日期的帖子。
JSONTime 类型会将其内部日期存储为 64 位整数,我们将函数添加到 JSONTime 以编组/解组 JSONTime。如图所示,来自 JSON 内容的时间值可以是字符串或整数。
type JSONTime int64
// String converts the unix timestamp into a string
func (t JSONTime) String() string {
tm := t.Time()
return fmt.Sprintf("\"%s\"", tm.Format("2006-01-02"))
}
// Time returns a `time.Time` representation of this value.
func (t JSONTime) Time() time.Time {
return time.Unix(int64(t), 0)
}
// UnmarshalJSON will unmarshal both string and int JSON values
func (t *JSONTime) UnmarshalJSON(buf []byte) error {
s := bytes.Trim(buf, `"`)
aa, _ := time.Parse("20060102", string(s))
*t = JSONTime(aa.Unix())
return nil
}
最初编写的测试用例现在应该通过go test.
"2022-09-29",1952.000000
"2022-09-30",1952.500000
"2022-10-03",1946.000000
PASS
ok github.com/hellonico/jquants-api-go 1.883s
我们的助手现在已经准备好了,我们可以向它添加一些 CI。
CircleCI 配置
配置是字符到字符的,接近于使用 Golang 进行测试的官方 CircleCI 文档。
我们只需将 Docker 映像更新为1.17.
version: 2.1
jobs:
build:
working_directory: ~/repo
docker:
- image: cimg/go:1.17.9
steps:
- checkout
- restore_cache:
keys:
- go-mod-v4-{{ checksum "go.sum" }}
- run:
name: Install Dependencies
command: go get ./...
- save_cache:
key: go-mod-v4-{{ checksum "go.sum" }}
paths:
- "/go/pkg/mod"
- run: go test -v
现在我们准备在 CircleCI 上设置项目:
我们的 helper_test.go 中所需的参数 USERNAME 和 PASSWORD 可以直接从 CircleCI 项目的环境变量设置中设置:
主分支上的任何提交都会触发 CircleCI 构建(当然,您也可以手动触发它),如果一切顺利,您应该会看到成功的步骤:
我们的包装是经过良好测试的。让我们开始发布它。
在 GitHub 上发布库
提供我们的 go.mod 文件具有以下内容:
module github.com/hellonico/jquants-api-go
go 1.17
require olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3
发布代码的最佳方式是使用 git 标签。因此,让我们创建一个 git 标签并将其推送到 GitHub:
git tag v0.6.0
git push --tags
现在,一个单独的项目可以通过在他们的go.mod.
require github.com/hellonico/jquants-api-go v0.6.0
从外部程序使用库
我们的简单程序将使用标志模块解析参数,然后调用不同的函数,就像在我们的包装器的测试用例中所做的那样。
package main
import (
"flag"
"fmt"
jquants "github.com/hellonico/jquants-api-go"
)
func main() {
code := flag.String("code", "86970", "Company Code")
date := flag.String("date", "20220930", "Date of the quote")
from := flag.String("from", "", "Start Date for date range")
to := flag.String("to", "", "End Date for date range")
refreshToken := flag.Bool("refresh", false, "refresh RefreshToken")
refreshId := flag.Bool("id", false, "refresh IdToken")
flag.Parse()
if *refreshToken {
jquants.GetRefreshToken()
}
if *refreshId {
jquants.GetIdToken()
}
var quotes = jquants.Daily(*code, *date, *from, *to)
fmt.Printf("[%d] Daily Quotes for %s \n", len(quotes.DailyQuotes), *code)
for _, quote := range quotes.DailyQuotes {
fmt.Printf("%s,%f\n", quote.Date, quote.Close)
}
}
我们可以使用go build.
go build
并在此处使用所需参数运行它:
刷新 ID 令牌
刷新刷新令牌
在 20221005 和 20221010 之间获取代码为 86970 的实体的每日值
./jquants-example --id --refresh --from=20221005 --to=20221010 --code=86970
Code: 86970 and Date: 20220930 [From: 20221005 To: 20221010]
[3] Daily Quotes for 86970
"2022-10-05",2016.500000
"2022-10-06",2029.000000
"2022-10-07",1992.500000
不错的作品。我们将把它留给用户编写其余的statements,listedInfo它们是 JQuants API 的一部分,但尚未在此包装器中实现。
标签: #封装api给其他人调用命令 #封装成api