基于有道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>

项目代码仓库

相关文章
相关标签/搜索