您或許已經(jīng)圍繞 Go 語法進行了一次又一次的練習(xí),但是除非您自己構(gòu)建了一個應(yīng)用程序,不然的話是體會不到用 Go 編寫應(yīng)用程序的真實觸感的.
在這篇博文中,我們將用 Go 構(gòu)建一個 CLI 應(yīng)用程序,我們暫且把它叫做 go-grab-xkcd. 該應(yīng)用程序從 XKCD 拉取漫畫并通過命令行參數(shù)為您提供各種操作選項.
我們將僅使用 Go 標準庫構(gòu)建整個應(yīng)用程序而不使用外部依賴.
這個應(yīng)用程序的想法或許看起來有點秀逗,但目的是實踐用 Go 編寫生產(chǎn)級別的 (某種) 代碼而不是想被 Google 收購.
最后還有一個額外獎勵
注意:本文假設(shè)讀者已熟悉 Go 語法和術(shù)語,并處于初學(xué)者和中級之間.
讓我們先來運行一下這個應(yīng)用程序,然后再進行操作
$ 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" }
你可以通過下載并運行計算機上的應(yīng)用程序來嘗試其他選項.
本教程結(jié)束后你將熟悉以下主題內(nèi)容:
接收命令行參數(shù)
JSON 和 Go 結(jié)構(gòu)體之間的相互轉(zhuǎn)換
進行 API 調(diào)用
創(chuàng)建文件 (從網(wǎng)絡(luò)上下載并保存)
字符串操作
以下是項目結(jié)構(gòu)
$ 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 management main.go - Main entrypoint of the application comic.go - Go representation of the data as a struct and operations on it xkcd.go - xkcd client for making HTTP calls to the API, parsing response and saving to disk
1: 初始化項目
創(chuàng)建一個 go.mod 文件 –
$ go mod init
這將會有助于你進行包管理 (思考 JS 中的 package.json 文件)。
2: xkcd API
xkcd 是令人稱贊的,您不需要任何注冊或訪問密鑰就可以使用它們的 API。 打開 xkcd API “文檔” 您將會發(fā)現(xiàn)有兩個端點 –
http://xkcd.com/info.0.json – 獲取最新漫畫
http://xkcd.com/614/info.0.json – 通過漫畫編號獲取指定的漫畫
以下是這些端點的 JSON 響應(yīng) –
{ "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": "" }
文章相關(guān) xkcd
2: 為漫畫創(chuàng)建模型
根據(jù)上述 JSON 響應(yīng),我們在 model 包中的 comic.go 中創(chuàng)建一個叫做 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 自動生成結(jié)構(gòu)體.
順便創(chuàng)建另一個結(jié)構(gòu)體,該結(jié)構(gòu)體將用于從我們的應(yīng)用程序輸出數(shù)據(jù).
type Comic struct { Title string `json:"title"` Number int `json:"number"` Date string `json:"date"` Description string `json:"description"` Image string `json:"image"` }
將以下兩種方法添加到 ComicResponse 結(jié)構(gòu)體
// FormattedDate 函數(shù)將格式化日期元素為一個字符串 func (cr ComicResponse) FormattedDate() string { return fmt.Sprintf("%s-%s-%s", cr.Day, cr.Month, cr.Year) } // Comic 函數(shù)將從 API 接收到的 ComicResponse 轉(zhuǎn)換為應(yīng)用程序的輸出格式, Comic 結(jié)構(gòu)體 func (cr ComicResponse) Comic() Comic { return Comic{ Title: cr.Title, Number: cr.Num, Date: cr.FormattedDate(), Description: cr.Alt, Image: cr.Img, } }
然后將以下兩種方法添加到 Comic 結(jié)構(gòu)體
// PrettyString 函數(shù)創(chuàng)建一個漂亮的 Comic 字符串并用于輸出 func (c Comic) PrettyString() string { p := fmt.Sprintf( "Title: %snComic No: %dnDate: %snDescription: %snImage: %sn", c.Title, c.Number, c.Date, c.Description, c.Image) return p } // JSON 函數(shù)將 Comic 結(jié)構(gòu)體轉(zhuǎn)換為 JSON, 我們將使用 JSON 字符串作為輸出內(nèi)容 func (c Comic) JSON() string { cJSON, err := json.Marshal(c) if err != nil { return "" } return string(cJSON) }
3: 設(shè)置 xkcd 客戶端發(fā)起請求,解析響應(yīng)并保存到磁盤
在 client 包中創(chuàng)建 xkcd.go 文件.
首先定義一個叫做 ComicNumber 自定義類型,數(shù)據(jù)類型為 int
type ComicNumber int
定義常量
const ( // xkcd 的 BaseURL BaseURL string = "https://xkcd.com" // DefaultClientTimeout 是取消請求之前要等待的時間 DefaultClientTimeout time.Duration = 30 * time.Second // 根據(jù) xkcd API, LatestComic 是最新的漫畫編號 LatestComic ComicNumber = 0 )
創(chuàng)建一個結(jié)構(gòu)體 XKCDClient, 它將用于向 API 發(fā)出請求.
// XKCDClient 是 XKCD 的客戶端結(jié)構(gòu)體 type XKCDClient struct { client *http.Client baseURL string } // NewXKCDClient 創(chuàng)建一個新的 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 根據(jù)提供的漫畫編號檢索漫畫 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() 函數(shù)內(nèi)部我們鏈接了所有內(nèi)容
讀取命令行參數(shù)
實例化 XKCDClient
使用 XKCDClient 從 API 拉取數(shù)據(jù)
輸出
讀取命令行參數(shù)
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 拉取數(shù)據(jù)
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
或者將其構(gòu)建為你的筆記本電腦的二進制可執(zhí)行文件并運行它
$ go build . $ ./go-grab-xkcd -n 323 -s -o json
可以在這個 Github 倉庫找到完整的源代碼 – go-grab-xkcd
額外獎勵
通過使用這個簡單的 shell 魔術(shù)工具可以依次下載多個漫畫
$ for i in {1..10}; do ./go-grab-xkcd -n $i -s; done;
上面的 shell 代碼簡單地在 for 循環(huán)中調(diào)用 go-grab-xkcd 命令,由于 xkcd 使用序列整數(shù),因此將 i 值替換為漫畫編號作為漫畫編號 / ID.
推薦教程:《PHP》