基於有道API的命令行詞典(golang版)

Godict

近期一直再使用golang語言開發一些工具,相關的後端技術鏈(golang+orm+postgresql+gin+jwt+logrus)和對應前端的技術鏈(vue+iview+axios+vue-router)基本已經打通了,項目地址。可是想到除了這一套先後端的東西外,命令行的一些操做也是不可避免的。所以就找到了cobra這個應用普遍的第三方命令行庫,並借這個小項目練一下手。html

功能

golang自然的帶有網絡操做的優點,因此直接借用現有的第三方api服務來作一個實用的小工具。首先想到的就是詞典翻譯,由於這個工具我以前在學習python時就作過一個。前端

python版本有道詞典vue

既然須要作一個golang版本的有道詞典(前期只考慮命令行),那麼第一步就須要有翻譯接口。python

接口獲取有兩種途徑:ios

  1. 模擬網頁請求,而後解析html文本抓取其中的有效結果數據。這就是所謂的爬蟲了。
  2. 使用官方指定的api調用接口直接獲取數據。

第一種方法方法簡單粗暴,沒啥限制,可是因爲時爬蟲解析整個網頁,若是網頁結構變化了,就容易失效,並且效率也相對較低,畢竟要從一大堆數據中找出一點點有用的東西出來。git

第二種方法是官方提供的接口,因此基本是長期有效的,相對很穩定,返回的數據就是json數據,比較簡潔,沒有多餘的無用內容,方便解析。可是天天的訪問次數有必定限制。不過我的使用也夠用了。github

綜上所述,咱們選擇第二種方式來實現。golang

因此第一個須要使用的庫就是golang官方的net/http庫了。web

有道智雲API

網易提供了現有的api,這個api須要先註冊,而後獲取一個應用的key,同時會生成一個應用的密鑰,此處我把這兩個東西用appKey和appSecret來表示。至於怎麼申請,官方流程會說的很詳細算法

有道智雲應用ID和應用密鑰

API使用方式

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);

好了,到了這一步基本的一些操做信息就都有了

咱們來逐個分析一下參數:

q

就是須要翻譯的文本

from to

就是從什麼語言翻譯成什麼語言,對應語言的格式官方文檔有詳細列表。

appKey

這個就是上面提到的在有道智雲申請的弄個東西了。

salt

是一個uuid,因此咱們須要一個用來生成uuid的庫,golang有第三方uuid庫。

sign

這個就是最關鍵的東西,前面,這個須要根據前面的這些信息來計算出來,計算公式上面提到了。

signType

這個固定位v3就行

準備數據

經過上面的分析,能夠知道咱們須要準備一下數據:

待翻譯的單詞(word),uuid,源語言(fromLan),目標語言(toLan),appKey,appSecret,sign,signType

咱們先考慮一下整個app的工做流程:

鑑於appKey和appSecret是比較私密的東西,因此應該放到配置文件中來讓用戶配置本身對應的appKey和appSecret,而不該該把這兩部分在程序中寫死。因此咱們須要加載一個配置文件,暫定爲應用同級目錄下的config.json。

帶翻譯的單詞、源語言和目標語言這個應該是由用戶來輸入的,因此須要有一個命令行傳參,咱們借用cobra。

uuid須要在應用程序內實時生成

sign須要根據已知變量來計算,signType固定值


cobra

cobra是一個構建命令行工具的庫,咱們先大體描述一下咱們須要的命令結構,首先word是必須的,還要附加兩個標誌(flag):from和to。

因此大概就是這個樣子:

$ ./appname word --from en --to zh-CSH

或者簡寫成

$ ./appname word -f en -t zh-CSH

cobra中的命令組織方式是一個樹狀的方式,首先有一個根命令,根命令中添加若干個子命令,而後每一個子命令又能夠添加本身的子命令。

所處cobra中,最基本的單元就是命令(cobra.Command),命令之間能夠添加父子關係,最後組織成一個命令樹。

每一個命令有基本的5個成員:

  • Use 用來描述命令的使用方式
  • Short 命令的簡短幫助信息
  • Long 命令完整的幫助信息
  • Args 命令的參數約束
  • Run 命令匹配成功後執行的函數體

很顯然,咱們這個命令工具暫時用不到子命令,因此咱們直接使用一個根命令便可。

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

加載config.json配置

因爲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

生成UUID

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

計算sign

經過上面的流程,咱們已經能夠獲取到一個查詢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的POST請求

有道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結果和有道智雲的文檔說明。

返回的結果是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>

項目代碼倉庫

相關文章
相關標籤/搜索