Go 教程:使用 Go 标准库来构建一个 CLI App
您或许已经围绕 Go 语法进行了一次又一次的练习, 但是除非您自己构建了一个应用程序, 不然的话是体会不到用 Go 编写应用程序的真实触感的.
在这篇博文中, 我们将用 Go 构建一个 CLI 应用程序, 我们暂且把它叫做 go-grab-xkcd. 该应用程序从 XKCD 拉取漫画并通过命令行参数为您提供各种操作选项.
我们将仅使用 Go 标准库构建整个应用程序而不使用外部依赖.
这个应用程序的想法或许看起来有点秀逗, 但目的是实践用 Go 编写生产级别的 (某种) 代码而不是想被 Google 收购.
最后还有一个额外奖励
注意: 本文假设读者已熟悉 Go 语法和术语, 并处于初学者和中级之间.
让我们先来运行一下这个应用程序, 然后再进行操作
$ go-grab-xkcd --help
Usage of go-grab-xkcd:
-n int
Comic number to fetch (default latest)
-o string
Print output in format: text/json (default "text")
-s Save image to current directory
-t int
Client timeout in seconds (default 30)
$ go-grab-xkcd -n 323
Title: Ballmer Peak
Comic No: 323
Date: 1-10-2007
Description: Apple uses automated schnapps IVs.
Image: https://imgs.xkcd.com/comics/ballmer_peak.png
$ go-grab-xkcd -n 323 -o json
{
"title": "Ballmer Peak",
"number": 323,
"date": "1-10-2007",
"description": "Apple uses automated schnapps IVs.",
"image": "https://imgs.xkcd.com/comics/ballmer_peak.png"
}
你可以通过下载并运行计算机上的应用程序来尝试其他选项.
本教程结束后你将熟悉以下主题内容:
- 接收命令行参数
- JSON 和 Go 结构体之间的相互转换
- 进行 API 调用
- 创建文件 (从网络上下载并保存)
- 字符串操作
以下是项目结构
$ tree go-grab-xkcd
go-grab-xkcd
├── client
│ └── xkcd.go
└── model
└── comic.go
├── main.go
└── go.mod
go.mod
- Go Modules file used in Go for package managementmain.go
- Main entrypoint of the applicationcomic.go
- Go representation of the data as astruct
and operations on itxkcd.go
- xkcd client for making HTTP calls to the API, parsing response and saving to disk
1: 初始化项目
创建一个 go.mod
文件-
$ go mod init
这将会有助于你进行包管理 (思考JS中的package.json文件)。
2: xkcd API
xkcd是令人称赞的,您不需要任何注册或访问密钥就可以使用它们的API。 打开xkcd API “文档”您将会发现有两个端点-
http://xkcd.com/info.0.json
- 获取最新漫画http://xkcd.com/614/info.0.json
-通过漫画编号获取指定的漫画
以下是这些端点的JSON响应-
{
"num": 2311,
"month": "5",
"day": "25",
"year": "2020",
"title": "Confidence Interval",
"alt": "The worst part is that's the millisigma interval.",
"img": "https://imgs.xkcd.com/comics/confidence_interval.png",
"safe_title": "Confidence Interval",
"link": "",
"news": "",
"transcript": ""
}
文章相关 xkcd
2: 为漫画创建模型
根据上述 JSON 响应, 我们在 model
包中的 comic.go
中创建一个叫做 ComicResponse
的 struct
type ComicResponse struct {
Month string `json:"month"`
Num int `json:"num"`
Link string `json:"link"`
Year string `json:"year"`
News string `json:"news"`
SafeTitle string `json:"safe_title"`
Transcript string `json:"transcript"`
Alt string `json:"alt"`
Img string `json:"img"`
Title string `json:"title"`
Day string `json:"day"`
}
您可以使用 JSON-to-Go 工具从 JSON 自动生成结构体.
顺便创建另一个结构体, 该结构体将用于从我们的应用程序输出数据.
type Comic struct {
Title string `json:"title"`
Number int `json:"number"`
Date string `json:"date"`
Description string `json:"description"`
Image string `json:"image"`
}
将以下两种方法添加到 ComicResponse
结构体
// FormattedDate 函数将格式化日期元素为一个字符串
func (cr ComicResponse) FormattedDate() string {
return fmt.Sprintf("%s-%s-%s", cr.Day, cr.Month, cr.Year)
}
// Comic 函数将从 API 接收到的 ComicResponse 转换为应用程序的输出格式, Comic 结构体
func (cr ComicResponse) Comic() Comic {
return Comic{
Title: cr.Title,
Number: cr.Num,
Date: cr.FormattedDate(),
Description: cr.Alt,
Image: cr.Img,
}
}
然后将以下两种方法添加到 Comic
结构体
// PrettyString 函数创建一个漂亮的 Comic 字符串并用于输出
func (c Comic) PrettyString() string {
p := fmt.Sprintf(
"Title: %s\nComic No: %d\nDate: %s\nDescription: %s\nImage: %s\n",
c.Title, c.Number, c.Date, c.Description, c.Image)
return p
}
// JSON 函数将 Comic 结构体转换为 JSON, 我们将使用 JSON 字符串作为输出内容
func (c Comic) JSON() string {
cJSON, err := json.Marshal(c)
if err != nil {
return ""
}
return string(cJSON)
}
3: 设置 xkcd 客户端发起请求, 解析响应并保存到磁盘
在 client
包中创建 xkcd.go
文件.
首先定义一个叫做 ComicNumber
自定义类型, 数据类型为 int
type ComicNumber int
定义常量
const (
// xkcd 的 BaseURL
BaseURL string = "https://xkcd.com"
// DefaultClientTimeout 是取消请求之前要等待的时间
DefaultClientTimeout time.Duration = 30 * time.Second
// 根据 xkcd API, LatestComic 是最新的漫画编号
LatestComic ComicNumber = 0
)
创建一个结构体 XKCDClient
, 它将用于向 API 发出请求.
// XKCDClient 是 XKCD 的客户端结构体
type XKCDClient struct {
client *http.Client
baseURL string
}
// NewXKCDClient 创建一个新的 XKCDClient
func NewXKCDClient() *XKCDClient {
return &XKCDClient{
client: &http.Client{
Timeout: DefaultClientTimeout,
},
baseURL: BaseURL,
}
}
添加以下 4 种方法到 XKCDClient
-
SetTimeout()
// SetTimeout 重写了默认的 ClientTimeout func (hc *XKCDClient) SetTimeout(d time.Duration) { hc.client.Timeout = d }
-
Fetch()
// Fetch 根据提供的漫画编号检索漫画 func (hc *XKCDClient) Fetch(n ComicNumber, save bool) (model.Comic, error) { resp, err := hc.client.Get(hc.buildURL(n)) if err != nil { return model.Comic{}, err } defer resp.Body.Close() var comicResp model.ComicResponse if err := json.NewDecoder(resp.Body).Decode(&comicResp); err != nil { return model.Comic{}, err } if save { if err := hc.SaveToDisk(comicResp.Img, "."); err != nil { fmt.Println("Failed to save image!") } } return comicResp.Comic(), nil }
-
SaveToDisk()
// SaveToDisk 下载并保存漫画到本地磁盘 func (hc *XKCDClient) SaveToDisk(url, savePath string) error { resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close() absSavePath, _ := filepath.Abs(savePath) filePath := fmt.Sprintf("%s/%s", absSavePath, path.Base(url)) file, err := os.Create(filePath) if err != nil { return err } defer file.Close() _, err = io.Copy(file, resp.Body) if err != nil { return err } return nil }
-
buildURL()
func (hc *XKCDClient) buildURL(n ComicNumber) string { var finalURL string if n == LatestComic { finalURL = fmt.Sprintf("%s/info.0.json", hc.baseURL) } else { finalURL = fmt.Sprintf("%s/%d/info.0.json", hc.baseURL, n) } return finalURL }
4: 连接所有
在 main()
函数内部我们链接了所有内容
- 读取命令行参数
- 实例化
XKCDClient
- 使用
XKCDClient
从 API 拉取数据 - 输出
读取命令行参数
comicNo := flag.Int(
"n", int(client.LatestComic), "Comic number to fetch (default latest)",
)
clientTimeout := flag.Int64(
"t", int64(client.DefaultClientTimeout.Seconds()), "Client timeout in seconds",
)
saveImage := flag.Bool(
"s", false, "Save image to current directory",
)
outputType := flag.String(
"o", "text", "Print output in format: text/json",
)
flag.Parse()
实例化 XKCDClient
xkcdClient := client.NewXKCDClient()
xkcdClient.SetTimeout(time.Duration(*clientTimeout) * time.Second)
使用 XKCDClient
从 API 拉取数据
comic, err := xkcdClient.Fetch(client.ComicNumber(*comicNo), *saveImage)
if err != nil {
log.Println(err)
}
输出
if *outputType == "json" {
fmt.Println(comic.JSON())
} else {
fmt.Println(comic.PrettyString())
}
程序运行如下
$ go run main.go -n 323 -o json
或者将其构建为你的笔记本电脑的二进制可执行文件并运行它
$ go build .
$ ./go-grab-xkcd -n 323 -s -o json
可以在这个 Github 仓库找到完整的源代码 - go-grab-xkcd
额外奖励
通过使用这个简单的 shell 魔术工具可以依次下载多个漫画
$ for i in {1..10}; do ./go-grab-xkcd -n $i -s; done;
上面的 shell 代码简单地在 for
循环中调用 go-grab-xkcd
命令, 由于 xkcd 使用序列整数, 因此将 i
值替换为漫画编号作为漫画编号/ID.
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: