用 Golang封装你的API(golang封装dll)
liuian 2025-05-09 20:03 31 浏览
每日分享最新,最流行的软件开发知识与最新行业趋势,希望大家能够一键三连,多多支持,跪求关注,点赞,留言。
本文探讨了在 用 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 的一部分,但尚未在此包装器中实现。
相关推荐
- python入门到脱坑函数—定义函数_如何定义函数python
-
Python函数定义:从入门到精通一、函数的基本概念函数是组织好的、可重复使用的代码块,用于执行特定任务。在Python中,函数可以提高代码的模块性和重复利用率。二、定义函数的基本语法def函数名(...
- javascript函数的call、apply和bind的原理及作用详解
-
javascript函数的call、apply和bind本质是用来实现继承的,专业点说法就是改变函数体内部this的指向,当一个对象没有某个功能时,就可以用这3个来从有相关功能的对象里借用过来...
- JS中 call()、apply()、bind() 的用法
-
其实是一个很简单的东西,认真看十分钟就从一脸懵B到完全理解!先看明白下面:例1obj.objAge;//17obj.myFun()//小张年龄undefined例2shows(...
- Pandas每日函数学习之apply函数_apply函数python
-
apply函数是Pandas中的一个非常强大的工具,它允许你对DataFrame或Series中的数据应用一个函数,可以是自定义的函数,也可以是内置的函数。apply可以作用于DataF...
- Win10搜索不习惯 换个设定就好了_window10搜索用不了怎么办
-
Windows10的搜索功能是真的方便,这点用惯了Windows10的小伙伴应该都知道,不过它有个小问题,就是Windows10虽然会自动联网搜索,但默认使用微软自家的Bing搜索引擎和Edge...
- 面试秘籍:call、bind、apply的区别,面试官为什么总爱问这三位?
-
引言你有没有发现,每次JavaScript面试,面试官总爱问你call、bind和apply的区别?好像这三个方法成了通关密码,掌握了它们,就能顺利过关。其实不难理解,面试官问这些问题,不...
- 记住这8招,帮你掌握“追拍“摄影技法—摄影早自习第422日
-
杨海英同学提问:请问叶梓老师,我练习追拍时,总也不能把运动的人物拍清晰,速度一般掌握在1/40-1/60,请问您如何把追拍拍的清晰?这跟不同的运动形式有关系吗?请您给讲讲要点,谢谢您!摄影:Damia...
- [Sony] 有点残酷的测试A7RII PK FS7
-
都是好机!手中利器!主要是最近天天研究fs5,想知道fs5与a7rii后期匹配问题,苦等朋友的fs5月底到货,于是先拿手里现有的fs7小测一下,十九八九也能看到fs5的影子,另外也了解一下fs5k标配...
- AndroidStudio_Android使用OkHttp发起Http请求
-
这个okHttp的使用,其实网络上有很多的案例的,但是,如果以前没用过,copy别人的直接用的话,可以发现要么导包导不进来,要么,人家给的代码也不完整,这里自己整理一下.1.引入OkHttp的jar...
- ESL-通过事件控制FreeSWITCH_es事务控制
-
通过事件提供的最底层控制机制,允许我们有效地利用工具箱,适时选择使用其中的单个工具。FreeSWITCH是一个核心交换与混合矩阵,它周围有几十个模块提供各种功能特性。我们完全控制了所有的即时信息,这些...
- 【调试】perf和火焰图_perf生成火焰图
-
简介perf是linux上的性能分析工具,perf可以对event进行统计得到event的发生次数,或者对event进行采样,得到每次event发生时的相关数据(cpu、进程id、运行栈等),利用这些...
- 文本检索控件也玩安卓?dtSearch Engine发布Android测试版
-
dtSearchEngineforLinux(原生64-bit/32-bitC++和JavaAPIs)和dtSearchEngineforWin&.NET(原生64-bi...
- 网站后台莫名增加N个管理员,记一次SQL注入攻击
-
网站没流量,但却经常被SQL注入光顾。最近,网站真的很奇怪,网站后台不光莫名多了很多“管理员”,所有的Wordpres插件还会被自动暂停,导致一些插件支持的页面,如WooCommerce无法正常访问、...
- 多元回归树分析Multivariate Regression Trees,MRT
-
多元回归树(MultivariateRegressionTrees,MRT)是单元回归树的拓展,是一种对一系列连续型变量递归划分成多个类群的聚类方法,是在决策树(decision-trees)基础...
- JMETER性能测试_JMETER性能测试指标
-
jmeter为性能测试提供了一下特色:jmeter可以对测试静态资源(例如js、html等)以及动态资源(例如php、jsp、ajax等等)进行性能测试jmeter可以挖掘出系统最大能处...
- 一周热门
-
-
【验证码逆向专栏】vaptcha 手势验证码逆向分析
-
Python实现人事自动打卡,再也不会被批评
-
Psutil + Flask + Pyecharts + Bootstrap 开发动态可视化系统监控
-
一个解决支持HTML/CSS/JS网页转PDF(高质量)的终极解决方案
-
再见Swagger UI 国人开源了一款超好用的 API 文档生成框架,真香
-
网页转成pdf文件的经验分享 网页转成pdf文件的经验分享怎么弄
-
C++ std::vector 简介
-
飞牛OS入门安装遇到问题,如何解决?
-
系统C盘清理:微信PC端文件清理,扩大C盘可用空间步骤
-
10款高性能NAS丨双十一必看,轻松搞定虚拟机、Docker、软路由
-
- 最近发表
-
- python入门到脱坑函数—定义函数_如何定义函数python
- javascript函数的call、apply和bind的原理及作用详解
- JS中 call()、apply()、bind() 的用法
- Pandas每日函数学习之apply函数_apply函数python
- Win10搜索不习惯 换个设定就好了_window10搜索用不了怎么办
- 面试秘籍:call、bind、apply的区别,面试官为什么总爱问这三位?
- 记住这8招,帮你掌握“追拍“摄影技法—摄影早自习第422日
- [Sony] 有点残酷的测试A7RII PK FS7
- AndroidStudio_Android使用OkHttp发起Http请求
- ESL-通过事件控制FreeSWITCH_es事务控制
- 标签列表
-
- python判断字典是否为空 (50)
- crontab每周一执行 (48)
- aes和des区别 (43)
- bash脚本和shell脚本的区别 (35)
- canvas库 (33)
- dataframe筛选满足条件的行 (35)
- gitlab日志 (33)
- lua xpcall (36)
- blob转json (33)
- python判断是否在列表中 (34)
- python html转pdf (36)
- 安装指定版本npm (37)
- idea搜索jar包内容 (33)
- css鼠标悬停出现隐藏的文字 (34)
- linux nacos启动命令 (33)
- gitlab 日志 (36)
- adb pull (37)
- python判断元素在不在列表里 (34)
- python 字典删除元素 (34)
- vscode切换git分支 (35)
- python bytes转16进制 (35)
- grep前后几行 (34)
- hashmap转list (35)
- c++ 字符串查找 (35)
- mysql刷新权限 (34)