龙空技术网

用 Golang封装你的API

科技狠活与软件技术 4081

前言:

今天咱们对“封装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