用 Golang封装你的API
wptr33 2025-05-21 16:54 4 浏览
每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。
本文探讨了在 用 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 的一部分,但尚未在此包装器中实现。
相关推荐
- 数据库基础:mysql主从集群搭建
-
文章首发于微信公众号:java架构师进阶之路前言:Mysql数据库没有增量备份的机制,当数据量太大的时候备份是一个很大的问题。还好mysql数据库提供了一种主从备份的机制,其实就是把主数据库的所有的...
- Mysql-cluster搭建
-
前期准备准备五台虚拟机:ip地址分别为:192.168.1.211管理节点192.168.1.64SQL节点192.168.1.65SQL节点192.168.1.70数据节点192.168.1...
- mysql 主从数据库搭建
-
一、创建目录在dev/htb下面创建文件夹master01htb]#mkdirmysql/master01-p2)进入master01...
- 从零搭建高可用的 MySQL 主从复制架构(基于 Linux 实战指南)
-
背景在生产环境中,单点MySQL数据库容易成为性能瓶颈或单点故障源。搭建MySQL主从复制架构,可以实现读写分离、高可用,提升系统的整体稳定性与扩展性。...
- 「MySQL 8」MySQL 5.7都即将停只维护了,是时候学习一波MySQL 8了
-
MySQL8新特性选择MySQL8的背景:MySQL5.6已经停止版本更新了,对于MySQL5.7版本,其将于2023年10月31日停止支持。后续官方将不再进行后续的代码维护。另外,...
- Mysql启动选项和配置文件
-
Mysql启动选项和配置文件Mysql启动方式下面的启动命令都需要依赖在Linux环境下配置的Mysql环境变量...
- centos安装mysql操作手册
-
1.下载Mysql首先去Mysql官网下载安装包,网址https://dev.mysql.com/downloads/mysql/推荐大家下载Linux通用版本的,便于管理安装位置,也方便一台服务器...
- MySQL安装
-
MySQL的安装过程因操作系统的不同而有所差异。以下是在几种常见操作系统上安装MySQL的基本步骤:Windows下载MySQL:访问MySQL官方网站下载页面:MySQLDownloads...
- MySQL数据库安装教程
-
前言今天就带各位小伙伴学习数据库技术。数据库技术是Java开发中必不可少的一部分知识内容。也是非常重要的技术。...
- MySQL学到什么程度?才有可以在简历上写精通
-
前言如今互联网行业用的最多就是MySQL,然而对于高级Web面试者,尤其对于寻找30k下工作的求职者,很多MySQL相关知识点基本都会涉及,如果面试中,你的相关知识答的模糊和不切要点,基...
- 一起免费考 MySQL OCP 认证啦
-
前言:在1995年,首个MySQL版本发布,为庆祝MySQL诞辰30周年,OracleUniversity在限定期间内推出了多个MySQL的免费培训课程与认证,其中也包括My...
- 教程2 | 制作用户管理系统
-
一、项目简介用户管理系统是一个基于C/S模式的小型管理系统,使用了GUI技术来实现管理系统的页面效果,该管理系统可以对用户的信息,比如姓名、年龄、密码和地址等进行增删改查操作。用户管理系统通过JDBC...
- 红帽Linux中安装mysql8详细步骤
-
注意:我写的解压路径和截图路径不一致,仅供参考先前往官网下载mysql8下载地址:https://dev.mysql.com/downloads/选择指定版本和系统下载命令...
- MySQL主从配置
-
主从原理MySQL主从又叫做Replication、AB复制。简单讲就是A和B两台机器做主从后,在A上写数据,另外一台B也会跟着写数据,两者数据实时同步的。...
- mysql的主从搭建以及实现主从切换方法
-
主从搭建的方法:a.准备两台服务器...
- 一周热门
-
-
C# 13 和 .NET 9 全知道 :13 使用 ASP.NET Core 构建网站 (1)
-
因果推断Matching方式实现代码 因果推断模型
-
git pull命令使用实例 git pull--rebase
-
面试官:git pull是哪两个指令的组合?
-
git 执行pull错误如何撤销 git pull fail
-
git fetch 和git pull 的异同 git中fetch和pull的区别
-
git pull 和git fetch 命令分别有什么作用?二者有什么区别?
-
git pull 之后本地代码被覆盖 解决方案
-
还可以这样玩?Git基本原理及各种骚操作,涨知识了
-
git命令之pull git.pull
-
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)