近期一直再使用golang語言開發一些工具,相關的後端技術鏈(golang+orm+postgresql+gin+jwt+logrus)和對應前端的技術鏈(vue+iview+axios+vue-router)基本已經打通了,項目地址。可是想到除了這一套先後端的東西外,命令行的一些操做也是不可避免的。所以就找到了cobra這個應用普遍的第三方命令行庫,並借這個小項目練一下手。html
golang自然的帶有網絡操做的優點,因此直接借用現有的第三方api服務來作一個實用的小工具。首先想到的就是詞典翻譯,由於這個工具我以前在學習python時就作過一個。前端
python版本有道詞典vue
既然須要作一個golang版本的有道詞典(前期只考慮命令行),那麼第一步就須要有翻譯接口。python
接口獲取有兩種途徑:ios
第一種方法方法簡單粗暴,沒啥限制,可是因爲時爬蟲解析整個網頁,若是網頁結構變化了,就容易失效,並且效率也相對較低,畢竟要從一大堆數據中找出一點點有用的東西出來。git
第二種方法是官方提供的接口,因此基本是長期有效的,相對很穩定,返回的數據就是json數據,比較簡潔,沒有多餘的無用內容,方便解析。可是天天的訪問次數有必定限制。不過我的使用也夠用了。github
綜上所述,咱們選擇第二種方式來實現。golang
因此第一個須要使用的庫就是golang官方的net/http庫了。web
網易提供了現有的api,這個api須要先註冊,而後獲取一個應用的key,同時會生成一個應用的密鑰,此處我把這兩個東西用appKey和appSecret來表示。至於怎麼申請,官方流程會說的很詳細算法
api的使用方式須要參考有道智雲的官方文檔
有道智雲官方文檔 )
從文檔上咱們獲取到一下信息:
文本翻譯接口地址: https://openapi.youdao.com/api
協議:
規則 | 描述 |
---|---|
傳輸方式 | HTTPS |
請求方式 | GET/POST |
字符編碼 | 統一使用UTF-8 編碼 |
請求格式 | 表單 |
響應格式 | JSON |
表單中的參數:
字段名 | 類型 | 含義 | 必填 | 備註 |
---|---|---|---|---|
q | text | 待翻譯文本 | True | 必須是UTF-8編碼 |
from | text | 源語言 | True | 參考下方 支持語言 (可設置爲auto) |
to | text | 目標語言 | True | 參考下方 支持語言 (可設置爲auto) |
appKey | text | 應用ID | True | 可在 應用管理 查看 |
salt | text | UUID | True | UUID |
sign | text | 簽名 | True | sha256(應用ID+input+salt+curtime+應用密鑰) |
signType | text | 簽名類型 | True | v3 |
curtime | text | 當前UTC時間戳(秒) | true | TimeStamp |
ext | text | 翻譯結果音頻格式,支持mp3 | false | mp3 |
voice | text | 翻譯結果發音選擇 | false | 0爲女聲,1爲男聲。默認爲女聲 |
簽名生成方法以下:
signType=v3;
sign=sha256(應用ID
+input
+salt
+curtime
+應用密鑰
);
其中,input的計算方式爲:input
=q前10個字符
+q長度
+q後10個字符
(當q長度大於20)或input
=q字符串
(當q長度小於等於20);
好了,到了這一步基本的一些操做信息就都有了
咱們來逐個分析一下參數:
就是須要翻譯的文本
就是從什麼語言翻譯成什麼語言,對應語言的格式官方文檔有詳細列表。
這個就是上面提到的在有道智雲申請的弄個東西了。
是一個uuid,因此咱們須要一個用來生成uuid的庫,golang有第三方uuid庫。
這個就是最關鍵的東西,前面,這個須要根據前面的這些信息來計算出來,計算公式上面提到了。
這個固定位v3就行
經過上面的分析,能夠知道咱們須要準備一下數據:
待翻譯的單詞(word),uuid,源語言(fromLan),目標語言(toLan),appKey,appSecret,sign,signType
咱們先考慮一下整個app的工做流程:
鑑於appKey和appSecret是比較私密的東西,因此應該放到配置文件中來讓用戶配置本身對應的appKey和appSecret,而不該該把這兩部分在程序中寫死。因此咱們須要加載一個配置文件,暫定爲應用同級目錄下的config.json。
帶翻譯的單詞、源語言和目標語言這個應該是由用戶來輸入的,因此須要有一個命令行傳參,咱們借用cobra。
uuid須要在應用程序內實時生成
sign須要根據已知變量來計算,signType固定值
cobra是一個構建命令行工具的庫,咱們先大體描述一下咱們須要的命令結構,首先word是必須的,還要附加兩個標誌(flag):from和to。
因此大概就是這個樣子:
$ ./appname word --from en --to zh-CSH
或者簡寫成
$ ./appname word -f en -t zh-CSH
cobra中的命令組織方式是一個樹狀的方式,首先有一個根命令,根命令中添加若干個子命令,而後每一個子命令又能夠添加本身的子命令。
所處cobra中,最基本的單元就是命令(cobra.Command),命令之間能夠添加父子關係,最後組織成一個命令樹。
每一個命令有基本的5個成員:
很顯然,咱們這個命令工具暫時用不到子命令,因此咱們直接使用一個根命令便可。
var rootCmd = &cobra.Command{Use: "app {word}", Short: "translate words", Long: `translate words to other language by cmdline`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { //do something fmt.Println("Arg:", strings.Join(args, " ")) fmt.Println("translate:", "from", fromLan, "to", toLan) }}
根命令按照上面的定義便可。
還有兩個flag須要添加到rootCmd上面,這兩個選項是以kv鍵值對形式存在的,能夠省略,因此須要提供一個默認值。根據有道智雲的文檔能夠看到,翻譯語言能夠自動識別,因此咱們只須要默認設置成auto便可
rootCmd.Flags().StringVarP(&fromLan, "from", "f", "auto", "translate from this language") rootCmd.Flags().StringVarP(&toLan, "to", "t", "auto", "translate to this language")
此處咱們須要定義兩個字符串變量來接收這兩個flag的值
var fromLan string var toLan string
而後只須要將這個命令運行起來便可
rootCmd.Execute()
完整代碼:
package main import ( "fmt" "strings" "github.com/spf13/cobra" ) var fromLan string var toLan string func main() { var rootCmd = &cobra.Command{Use: "app {word}", Short: "translate words", Long: `translate words to other language by cmdline`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { fmt.Println("Arg:", strings.Join(args, " ")) fmt.Println("translate:", "from", fromLan, "to", toLan) }} rootCmd.Flags().StringVarP(&fromLan, "from", "f", "auto", "translate from this language") rootCmd.Flags().StringVarP(&toLan, "to", "t", "auto", "translate to this language") rootCmd.Execute() }
PS E:\code\code_go\Godict\test> go build PS E:\code\code_go\Godict\test> ./test nice --from en --to zh-CSH Arg: nice translate: from en to zh-CSH
因爲golang自帶有json編解碼庫,因此咱們使用json格式的配置文件。
如前面所述,配置文件須要加載appKey和appSecret兩個參數,所以定義以下:
{ "appKey":"your app key", "appSecret":"your app secret code" }
json在golang中,使用tag來指定json與結構體的映射
type Config struct { AppKey string `json:"appKey"` AppSecret string `json:"appSecret"` }
json是一個文本文件,因此咱們首先須要把文件中的內容讀取出來
fileobj, err := os.Open(str) if err != nil { return err } defer fileobj.Close() var fileContext []byte fileContext, err = ioutil.ReadAll(fileobj)
而後將讀取出來的內容使用json.Unmarshal函數解析
json.Unmarshal(fileContext, cfg)
咱們將此部分代碼定義成一個函數方便調用:
func InitConfig(str string, cfg *Config) error { fileobj, err := os.Open(str) if err != nil { return err } defer fileobj.Close() var fileContext []byte fileContext, err = ioutil.ReadAll(fileobj) json.Unmarshal(fileContext, cfg) return nil }
此處函數須要傳入config.json文件的路徑和解析成功後保存數據的Config變量指針。咱們此處規定加載應用同級目錄下的config.json。因此咱們須要能獲取應用程序的絕對路徑,此處使用絕對路徑是爲了保證config.json必定能獲取到。
絕對路徑可使用一下方式獲取:
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
整理成一個函數方便調用:
func GetCurrentDirectory() string { dir, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { log.Fatal(err) return "" } return strings.Replace(dir, "\\", "/", -1) //將\替換成/ }
上述完整代碼和測試:
package main import ( "encoding/json" "fmt" "io/ioutil" "log" "os" "path/filepath" "strings" "github.com/spf13/cobra" ) type Config struct { AppKey string `json:"appKey"` AppSecret string `json:"appSecret"` } var config Config var fromLan string var toLan string func main() { var rootCmd = &cobra.Command{Use: "app {word}", Short: "translate words", Long: `translate words to other language by cmdline`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { fmt.Println("Arg:", strings.Join(args, " ")) fmt.Println("translate:", "from", fromLan, "to", toLan) var curpath string = GetCurrentDirectory() err := InitConfig(curpath+"/config.json", &config) if err != nil { fmt.Println("config.json is open error.") return } fmt.Println("appKey:", config.AppKey) fmt.Println("appSecret:", config.AppSecret) }} rootCmd.Flags().StringVarP(&fromLan, "from", "f", "auto", "translate from this language") rootCmd.Flags().StringVarP(&toLan, "to", "t", "auto", "translate to this language") rootCmd.Execute() } func InitConfig(str string, cfg *Config) error { fileobj, err := os.Open(str) if err != nil { return err } defer fileobj.Close() var fileContext []byte fileContext, err = ioutil.ReadAll(fileobj) json.Unmarshal(fileContext, cfg) return nil } func GetCurrentDirectory() string { dir, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { log.Fatal(err) return "" } return strings.Replace(dir, "\\", "/", -1) //將\替換成/ }
PS E:\code\code_go\Godict\test> go build PS E:\code\code_go\Godict\test> ./test nice --from en --to zh-CSH Arg: nice translate: from en to zh-CSH appKey: your app key appSecret: your app secret code
golang有現成的第三方uuid庫,使用比較簡單
import uuid "github.com/satori/go.uuid"
生成uuid
u1 := uuid.NewV4() u1str := u1.String()
因爲比較簡單,此處就不放完整的測試代碼了。
PS E:\code\code_go\Godict\test> go build PS E:\code\code_go\Godict\test> ./test nice --from en --to zh-CSH Arg: nice translate: from en to zh-CSH appKey: your app key appSecret: your app secret code uuid: 3ad0c54a-24a6-476b-9b8c-730656b5b759
經過上面的流程,咱們已經能夠獲取到一個查詢api須要的大部分參數了,除了sign。因此這一步就是來計算sign,sign就是將前面獲取到的值,經過必定規律組合,而後使用指定算法計算出來。
簽名生成方法以下:
signType=v3;
sign=sha256(應用ID
+input
+salt
+curtime
+應用密鑰
);
其中,input的計算方式爲:input
=q前10個字符
+q長度
+q後10個字符
(當q長度大於20)或input
=q字符串
(當q長度小於等於20);
能夠看到sign的計算中須要:應用ID(appKey),input,salt(uuid),curtime,應用密鑰(appSecret)。
這些變量中,只有curtime這個須要尚未獲取。
curtime就是當前時間的秒時間戳,利用golang的time庫很容易獲取:
stamp := time.Now().Unix()
可是這個地方獲取的是一個int64的整型數值,咱們須要轉換爲字符換。能夠利用strconv.FormatInt來轉換成字符串。爲何不用os.Itoa?由於os.Itoa的的入參類型爲int,而strconv.FormatInt的入參類型爲int64,爲了確保變量精度一直,因此直接用strconv.FormatInt。
strconv.FormatInt(stamp, 10)
能夠注意到對於input的處理,input能夠說就是精簡版的q,精簡規則上面有說明:
input的計算方式爲:input
=q前10個字符
+ q長度
+ q後10個字符
(當q長度大於20)或 input
=q字符串
(當q長度小於等於20);
咱們把這個地方提煉成一個函數方便調用:
func truncate(q string) string { res := make([]byte, 10) qlen := len([]rune(q)) if qlen <= 20 { return q } else { temp := []byte(q) copy(res, temp[:10]) lenstr := strconv.Itoa(qlen) res = append(res, lenstr...) res = append(res, temp[qlen-10:qlen]...) return string(res) } }
至此,全部用來計算sign的變量都準備好了,咱們只須要將這些資源的字符串形式拼接起來,而後使用sha256計算便可。sha256的計算直接調用自帶的庫中的sig := sha256.Sum256。
u1 := uuid.NewV4() input := truncate(words) stamp := time.Now().Unix() instr := config.AppKey + input + u1.String() + strconv.FormatInt(stamp, 10) + config.AppSecret sig := sha256.Sum256([]byte(instr))
咱們成功計算出來sign,可是這是計算出來的結果仍是16進制的,而咱們實際需求的是字符串格式的,即須要將hex轉換成對應的16進制字符串,好比將{0x11,0x56,0xA3}轉換成「1156A3」這樣的。
咱們上面有說起到strconv.FormatInt能夠將數字轉換成字符串,並且能夠指定轉換的進制。那麼咱們只須要將sig這個16進制切片的每一個元素轉換成對應的字符串,而後拼接起來便可。可是須要注意的是,像0x05這樣的用strconv.FormatInt轉換出來的字符串會只有一個字符長度,畢竟0x05實際就是0x5。這就不太符合咱們的需求了,可是問題不大,人爲的判斷處理一下便可。此處咱們仍然將這個轉換寫成一個函數:
func HexBuffToString(buff []byte) string { var ret string for _, value := range buff { str := strconv.FormatUint(uint64(value), 16) if len([]rune(str)) == 1 { ret = ret + "0" + str } else { ret = ret + str } } return ret }
爲了方便測試,咱們對appKey和appSecret的值作一下設定:
{ "appKey":"appKey", "appSecret":"appSecret" }
完整的測試代碼:
package main import ( "crypto/sha256" "encoding/json" "fmt" "io/ioutil" "log" "os" "path/filepath" "strconv" "strings" "time" uuid "github.com/satori/go.uuid" "github.com/spf13/cobra" ) type Config struct { AppKey string `json:"appKey"` AppSecret string `json:"appSecret"` } var config Config var fromLan string var toLan string func main() { var rootCmd = &cobra.Command{Use: "app {word}", Short: "translate words", Long: `translate words to other language by cmdline`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { words := strings.Join(args, " ") fmt.Println("Arg:", words) fmt.Println("translate:", "from", fromLan, "to", toLan) var curpath string = GetCurrentDirectory() err := InitConfig(curpath+"/config.json", &config) if err != nil { fmt.Println("config.json is open error.") return } fmt.Println("appKey:", config.AppKey) fmt.Println("appSecret:", config.AppSecret) u1 := uuid.NewV4() fmt.Println("uuid:", u1.String()) input := truncate(words) stamp := time.Now().Unix() instr := config.AppKey + input + u1.String() + strconv.FormatInt(stamp, 10) + config.AppSecret sig := sha256.Sum256([]byte(instr)) var sigstr string = HexBuffToString(sig[:]) fmt.Println("sign:", sigstr) }} rootCmd.Flags().StringVarP(&fromLan, "from", "f", "auto", "translate from this language") rootCmd.Flags().StringVarP(&toLan, "to", "t", "auto", "translate to this language") rootCmd.Execute() } func InitConfig(str string, cfg *Config) error { fileobj, err := os.Open(str) if err != nil { return err } defer fileobj.Close() var fileContext []byte fileContext, err = ioutil.ReadAll(fileobj) json.Unmarshal(fileContext, cfg) return nil } func GetCurrentDirectory() string { dir, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { log.Fatal(err) return "" } return strings.Replace(dir, "\\", "/", -1) //將\替換成/ } func truncate(q string) string { res := make([]byte, 10) qlen := len([]rune(q)) if qlen <= 20 { return q } else { temp := []byte(q) copy(res, temp[:10]) lenstr := strconv.Itoa(qlen) res = append(res, lenstr...) res = append(res, temp[qlen-10:qlen]...) return string(res) } } func HexBuffToString(buff []byte) string { var ret string for _, value := range buff { str := strconv.FormatUint(uint64(value), 16) if len([]rune(str)) == 1 { ret = ret + "0" + str } else { ret = ret + str } } return ret }
測試:
PS E:\code\code_go\Godict\test> go build PS E:\code\code_go\Godict\test> ./test nice --from en --to zh-CSH Arg: nice translate: from en to zh-CSH appKey: appKey appSecret: appSecret uuid: f938ba34-3b59-427d-ac36-779d935a0896 sign: 2f94f435042839a6c5fcb82578c31ff6390f7efeee160365e1be420b23585ee3
有道api支持get和post,咱們此處使用post方式,post的全部參數前面都已經可以獲取了。
golang發起post請求須要引入net/http庫
"net/http"
由於帶有參數,因此是以表單的形式發起請求的,直接使用http.PostForm。該函數須要傳入一個url.Values,實際就是一個map。
咱們使用前面準備好的數據來構造這個map:
data := make(url.Values, 0) data["q"] = []string{words} data["from"] = []string{from} data["to"] = []string{to} data["appKey"] = []string{config.AppKey} data["salt"] = []string{u1.String()} data["sign"] = []string{sigstr} data["signType"] = []string{signType} data["curtime"] = []string{strconv.FormatInt(stamp, 10)}
使用http.PostForm發起請求,響應的結果會保存在http.Response中
var resp *http.Response resp, err = http.PostForm("https://openapi.youdao.com/api",data) if err != nil { fmt.Println(err) } defer resp.Body.Close()
提取body中的json數據
body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error }
注意:此時的appKey和appSecret必需要是有效的值,不然沒法獲得想要的結果
PS E:\code\code_go\Godict\test> .\test.exe nice -f en -t zh-CSH Arg: nice translate: from en to zh-CSH uuid: f0960478-26f4-47a1-a3d6-fe8f5b28afa6 resp body: {"tSpeakUrl":"http://openapi.youdao.com/ttsapi?q=%E4%B8%8D%E9%94%99%E7%9A%84&langType=zh-CHS&sign=3915602C29F3B9B9B2A521ABABB13D20&salt=1576996331194&voice=4&format=mp3&appKey=4a582e1425d5810e","returnPhrase":["nice"],"web":[{"value":["尼斯","研究所","美好的","英 國國家衛生與臨牀優化研究所"],"key":"Nice"},{"value":["好人文化","好好先生","老好人"],"key":"nice guy"},{"value":["奈伊茜","奈思河","以市 場爲導向","從顧客需求的角度着手"],"key":"NICE CLAUP"}],"query":"nice","translation":["不錯的"],"errorCode":"0","dict":{"url":"yddict://m.youdao.com/dict?le=eng&q=nice"},"webdict":{"url":"http://m.youdao.com/dict?le=eng&q=nice"},"basic":{"us-phonetic":"naɪs","phonetic":"naɪs","uk-phonetic":"naɪs","wfs":[{"wf":{"name":"比較級","value":"nicer"}},{"wf":{"name":"最高級","value":"nicest"}}],"uk-speech":"http://openapi.youdao.com/ttsapi?q=nice&langType=en&sign=151BAD30E03C856BD7154428FA13C367&salt=1576996331194&voice=5&format=mp3&appKey=xxxxxxxxxxx","explains":["adj. 精密的;美好的;細微的;和善的","n. (Nice)人名;(英)尼斯"],"us-speech":"http://openapi.youdao.com/ttsapi?q=nice&langType=en&sign=151BAD30E03C856BD7154428FA13C367&salt=1576996331194&voice=6&format=mp3&appKey=xxxxxxxxxxxx"},"l":"en2zh-CHS","speakUrl":"http://openapi.youdao.com/ttsapi?q=nice&langType=en&sign=151BAD30E03C856BD7154428FA13C367&salt=1576996331194&voice=4&format=mp3&appKey=xxxxxxxxxxxxxx"}
數據解析咱們能夠參考實際返回的json結果和有道智雲的文檔說明。
返回的結果是json格式,包含字段與FROM和TO的值有關,具體說明以下:
字段名 | 類型 | 含義 | 備註 |
---|---|---|---|
errorCode | text | 錯誤返回碼 | 必定存在 |
query | text | 源語言 | 查詢正確時,必定存在 |
translation | Array | 翻譯結果 | 查詢正確時,必定存在 |
basic | text | 詞義 | 基本詞典,查詞時纔有 |
web | Array | 詞義 | 網絡釋義,該結果不必定存在 |
l | text | 源語言和目標語言 | 必定存在 |
dict | text | 詞典deeplink | 查詢語種爲支持語言時,存在 |
webdict | text | webdeeplink | 查詢語種爲支持語言時,存在 |
tSpeakUrl | text | 翻譯結果發音地址 | 翻譯成功必定存在,須要應用綁定語音合成實例才能正常播放 不然返回110錯誤碼 |
speakUrl | text | 源語言發音地址 | 翻譯成功必定存在,須要應用綁定語音合成實例才能正常播放 不然返回110錯誤碼 |
returnPhrase | Array | 單詞校驗後的結果 | 主要校驗字母大小寫、單詞前含符號、中文簡繁體 |
注:
a. 中文查詞的basic字段只包含explains字段。
b. 英文查詞的basic字段中又包含如下字段。
字段 | 含義 |
---|---|
us-phonetic | 美式音標,英文查詞成功,必定存在 |
phonetic | 默認音標,默認是英式音標,英文查詞成功,必定存在 |
uk-phonetic | 英式音標,英文查詞成功,必定存在 |
uk-speech | 英式發音,英文查詞成功,必定存在 |
us-speech | 美式發音,英文查詞成功,必定存在 |
explains | 基本釋義 |
經過對比,咱們發現實際的結果和文檔上大致一致,可是dict和webdict這兩個字段略有差別,這兩個實際返回的是一個對象,而不是文檔上的text。
咱們如今來定義這個json對應的數據結構
type DictResp struct { ErrorCode string `json:"errorCode"` Query string `json:"query"` Translation []string `json:"translation"` Basic DictBasic `json:"basic"` Web []DictWeb `json:"web,omitempty"` Lang string `json:"l"` Dict map[string]interface{} `json:"dict,omitempty"` Webdict map[string]interface{} `json:"webdict,omitempty"` TSpeakUrl string `json:"tSpeakUrl,omitempty"` SpeakUrl string `json:"speakUrl,omitempty"` ReturnPhrase []string `json:"returnPhrase,omitempty"` }
ErrorCode、Query、Lang、TSpeakUrl和SpeakUrl是字符串。
Translation和ReturnPhrase是一個字符串數組。
Basic是一個對象,咱們直接在裏面嵌套一個對應的結構體就好了。
Web是一個對象數組,因此要嵌套一個結構體數組。
Dict和Webdict也是對象,可是它們內部的東西對咱們沒什麼用,咱們不須要關心,因此直接定義成map[string]interface{}便可
DictBasic的結構以下:
type DictBasic struct { UsPhonetic string `json:"us-phonetic"` Phonetic string `json:"phonetic"` UkPhonetic string `json:"uk-phonetic"` UkSpeech string `json:"uk-speech"` UsSpeech string `json:"us-speech"` Explains []string `json:"explains"` }
這裏麪包含着音標和一些拓展的翻譯。
DictWeb的數據結構以下:
type DictWeb struct { Key string `json:"key"` Value []string `json:"value"` }
這裏面主要包含的是網絡翻譯。
以後只須要調用json庫的解析函數就好了
var jsonObj DictResp json.Unmarshal(body, &jsonObj)
正確獲取了咱們想要的結果後,咱們只須要按照咱們但願的格式顯示出來便可,下面提供一個格式化顯示函數,以供參考:
func show(resp *DictResp, w io.Writer) { if resp.ErrorCode != "0" { fmt.Fprintln(w, "請輸入正確的數據") } fmt.Fprintln(w, "@", resp.Query) if resp.Basic.UkPhonetic != "" { fmt.Fprintln(w, "英:", "[", resp.Basic.UkPhonetic, "]") } if resp.Basic.UsPhonetic != "" { fmt.Fprintln(w, "美:", "[", resp.Basic.UsPhonetic, "]") } fmt.Fprintln(w, "[翻譯]") for key, item := range resp.Translation { fmt.Fprintln(w, "\t", key+1, ".", item) } fmt.Fprintln(w, "[延伸]") for key, item := range resp.Basic.Explains { fmt.Fprintln(w, "\t", key+1, ".", item) } fmt.Fprintln(w, "[網絡]") for key, item := range resp.Web { fmt.Fprintln(w, "\t", key+1, ".", item.Key) fmt.Fprint(w, "\t翻譯:") for _, val := range item.Value { fmt.Fprint(w, val, ",") } fmt.Fprint(w, "\n") } }
若是像直接在控制檯顯示,能夠這樣調用:
show(&jsonObj, os.Stdout)
那麼最後完整的代碼就是這樣的:
package main import ( "crypto/sha256" "encoding/json" "fmt" "io" "io/ioutil" "log" "net/http" "net/url" "os" "path/filepath" "strconv" "strings" "time" uuid "github.com/satori/go.uuid" "github.com/spf13/cobra" ) type Config struct { AppKey string `json:"appKey"` AppSecret string `json:"appSecret"` } type DictWeb struct { Key string `json:"key"` Value []string `json:"value"` } type DictBasic struct { UsPhonetic string `json:"us-phonetic"` Phonetic string `json:"phonetic"` UkPhonetic string `json:"uk-phonetic"` UkSpeech string `json:"uk-speech"` UsSpeech string `json:"us-speech"` Explains []string `json:"explains"` } type DictResp struct { ErrorCode string `json:"errorCode"` Query string `json:"query"` Translation []string `json:"translation"` Basic DictBasic `json:"basic"` Web []DictWeb `json:"web,omitempty"` Lang string `json:"l"` Dict map[string]interface{} `json:"dict,omitempty"` Webdict map[string]interface{} `json:"webdict,omitempty"` TSpeakUrl string `json:"tSpeakUrl,omitempty"` SpeakUrl string `json:"speakUrl,omitempty"` ReturnPhrase []string `json:"returnPhrase,omitempty"` } var config Config var fromLan string var toLan string func main() { var rootCmd = &cobra.Command{Use: "app {word}", Short: "translate words", Long: `translate words to other language by cmdline`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { words := strings.Join(args, " ") var curpath string = GetCurrentDirectory() err := InitConfig(curpath+"/config.json", &config) if err != nil { fmt.Println("config.json is open error.") return } u1 := uuid.NewV4() input := truncate(words) stamp := time.Now().Unix() instr := config.AppKey + input + u1.String() + strconv.FormatInt(stamp, 10) + config.AppSecret sig := sha256.Sum256([]byte(instr)) var sigstr string = HexBuffToString(sig[:]) data := make(url.Values, 0) data["q"] = []string{words} data["from"] = []string{fromLan} data["to"] = []string{toLan} data["appKey"] = []string{config.AppKey} data["salt"] = []string{u1.String()} data["sign"] = []string{sigstr} data["signType"] = []string{"v3"} data["curtime"] = []string{strconv.FormatInt(stamp, 10)} var resp *http.Response resp, err = http.PostForm("https://openapi.youdao.com/api", data) if err != nil { fmt.Println(err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { // handle error } var jsonObj DictResp json.Unmarshal(body, &jsonObj) show(&jsonObj, os.Stdout) }} rootCmd.Flags().StringVarP(&fromLan, "from", "f", "auto", "translate from this language") rootCmd.Flags().StringVarP(&toLan, "to", "t", "auto", "translate to this language") rootCmd.Execute() } func InitConfig(str string, cfg *Config) error { fileobj, err := os.Open(str) if err != nil { return err } defer fileobj.Close() var fileContext []byte fileContext, err = ioutil.ReadAll(fileobj) json.Unmarshal(fileContext, cfg) return nil } func GetCurrentDirectory() string { dir, err := filepath.Abs(filepath.Dir(os.Args[0])) if err != nil { log.Fatal(err) return "" } return strings.Replace(dir, "\\", "/", -1) //將\替換成/ } func truncate(q string) string { res := make([]byte, 10) qlen := len([]rune(q)) if qlen <= 20 { return q } else { temp := []byte(q) copy(res, temp[:10]) lenstr := strconv.Itoa(qlen) res = append(res, lenstr...) res = append(res, temp[qlen-10:qlen]...) return string(res) } } func HexBuffToString(buff []byte) string { var ret string for _, value := range buff { str := strconv.FormatUint(uint64(value), 16) if len([]rune(str)) == 1 { ret = ret + "0" + str } else { ret = ret + str } } return ret } func show(resp *DictResp, w io.Writer) { if resp.ErrorCode != "0" { fmt.Fprintln(w, "請輸入正確的數據") } fmt.Fprintln(w, "@", resp.Query) if resp.Basic.UkPhonetic != "" { fmt.Fprintln(w, "英:", "[", resp.Basic.UkPhonetic, "]") } if resp.Basic.UsPhonetic != "" { fmt.Fprintln(w, "美:", "[", resp.Basic.UsPhonetic, "]") } fmt.Fprintln(w, "[翻譯]") for key, item := range resp.Translation { fmt.Fprintln(w, "\t", key+1, ".", item) } fmt.Fprintln(w, "[延伸]") for key, item := range resp.Basic.Explains { fmt.Fprintln(w, "\t", key+1, ".", item) } fmt.Fprintln(w, "[網絡]") for key, item := range resp.Web { fmt.Fprintln(w, "\t", key+1, ".", item.Key) fmt.Fprint(w, "\t翻譯:") for _, val := range item.Value { fmt.Fprint(w, val, ",") } fmt.Fprint(w, "\n") } }
效果:
PS E:\code\code_go\Godict\test> go build PS E:\code\code_go\Godict\test> .\test.exe 手機 -f zh-CSH -t en @ 手機 [翻譯] 1 . Mobile phone [延伸] 1 . mobile phone 2 . cellphone [網絡] 1 . 手機 翻譯:mobile phone,Iphone,handset, 2 . 手機電視 翻譯:CMMB,DVB-H,mobile tv,Dopool, 3 . 翻蓋手機 翻譯:flip,clamshell phone,OPPO,flip cell phone, PS E:\code\code_go\Godict\test>
固然-f 和-t也能夠省略:
PS E:\code\code_go\Godict\test> .\test.exe work @ work 英: [ wɜːk ] 美: [ wɜːrk ] [翻譯] 1 . 工做 [延伸] 1 . n. 工做;功;產品;操做;職業;行爲;事業;工廠;著做;文學、音樂或藝術做品 2 . vt. 使工做;操做;經營;使緩慢前進 3 . vi. 工做;運做;起做用 4 . n. (Work)(英、埃塞、丹、冰、美)沃克(人名) [網絡] 1 . Work 翻譯:做品,起做用,工件,運轉, 2 . at work 翻譯:在工做,忙於,上班,在上班, 3 . work function 翻譯:功函數,逸出功,自由能,功函數, PS E:\code\code_go\Godict\test>