Go语言基础实战词典项目| 青训营笔记

90 阅读6分钟

Go语言基础实战项目——词典

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

简介

需求:输入一个单词,返回单词的中文翻译和音标信息。

原理:网页上的翻译功能是通过提交表单发送HTTP请求到服务器,然后服务器向浏览器返回对应的信息,要实现需求的功能,主要可以分为以下步骤:

  1. 获取请求API,在Go中构建并发送HTTP请求
  2. 封装结构体,通过响应的JSON信息构建对应的结构体
  3. 将所需内容进行打印。

程序运行流程如下:

  1. 用户输入单词
  2. 将单词封装为HTTP请求
  3. 发出请求
  4. 接受响应
  5. 解析响应体的内容
  6. 输出到控制台

用到的网站:

JSON转Golang Struct - 在线工具 - OKTools

Convert curl commands to Go (curlconverter.com)

彩云小译 - 在线翻译 (caiyunapp.com)

获取API

进入彩云小译 - 在线翻译 (caiyunapp.com),按F12进入

image-20230118213142603

随便进行一次查询,找到Netword栏中请求方式为POST的请求,并查看一下Response,确定是否为翻译信息的请求。

在查询前可以点击左上角的Clear,清除一下无关信息的干扰,图中是Edge浏览器的布局

image-20230118213828977

response中的内容:

{"rc":0,"wiki":{"known_in_laguages":35,"description":{"source":"song by Adele","target":null},"id":"Q21172725","item":{"source":"Hello","target":"\u4f60\u8fd8\u597d\u5417"},"image_url":"http://www.caiyunapp.com/imgs/link_default_img.png","is_subject":"true","sitelink":"https://www.caiyunapp.com/read_mode/?id=638c1dc20cdda67eb511c5c8"},"dictionary":{"prons":{"en-us":"[h\u0259\u02c8lo]","en":"[\u02c8he\u02c8l\u0259u]"},"explanations":["int.\u5582;\u54c8\u7f57","n.\u5f15\u4eba\u6ce8\u610f\u7684\u547c\u58f0","v.\u5411\u4eba\u547c(\u5582)"],"synonym":["greetings","salutations"],"antonym":[],"wqx_example":[["say hello to","\u5411\u67d0\u4eba\u95ee\u5019,\u548c\u67d0\u4eba\u6253\u62db\u547c"],["Say hello to him for me . ","\u4ee3\u6211\u95ee\u5019\u4ed6\u3002"]],"entry":"hello","type":"word","related":[],"source":"wenquxing"}}

确认是该请求之后,通过右击该请求,将其cURL进行复制:

如果有cmd和bash两个版本的,要复制bash版本的,cmd版本的版本的解析出来得到的结果不符合预期。

image-20230118214253622

进入Convert curl commands to Go (curlconverter.com),将复制好的cURL进行粘贴,得到的代码实现的功能是,封装请求发送HTTP,并将响应的内容打印到控制台。

image-20230118214502786

我们在idea中验证一下代码,测试没问题之后进行下一步。

package main
​
import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "strings"
)func main() {
    client := &http.Client{}
    var data = strings.NewReader(`{"trans_type":"en2zh","source":"Hello"}`)
    req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
    if err != nil {
        log.Fatal(err)
    }
    req.Header.Set("authority", "api.interpreter.caiyunai.com")
    req.Header.Set("accept", "application/json, text/plain, */*")
    req.Header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
    req.Header.Set("app-name", "xy")
    req.Header.Set("content-type", "application/json;charset=UTF-8")
    req.Header.Set("device-id", "")
    req.Header.Set("origin", "https://fanyi.caiyunapp.com")
    req.Header.Set("os-type", "web")
    req.Header.Set("os-version", "")
    req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
    req.Header.Set("sec-ch-ua", `"Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"`)
    req.Header.Set("sec-ch-ua-mobile", "?0")
    req.Header.Set("sec-ch-ua-platform", `"Windows"`)
    req.Header.Set("sec-fetch-dest", "empty")
    req.Header.Set("sec-fetch-mode", "cors")
    req.Header.Set("sec-fetch-site", "cross-site")
    req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.55")
    req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
    resp, err := client.Do(req)
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()
    bodyText, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s\n", bodyText)
}

运行程序,如果能返回上面response里面的内容,可以进行下一步。

自定义请求

再看一下上面生成代码,其中的请求部分:

//...
var data = strings.NewReader(`{"trans_type":"en2zh","source":"Hello"}`)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
// ...

可以看出data就是我们可以自定义的表单内容,trans_type代表翻译是从英文到中文,source就是要翻译的内容,其形式是json字符串的形式,所以我们可以构建一个和其key相同的结构体用于构造请求:

type DicReq struct {
    TransType string `json:"trans_type"`
    Source    string `json:"source"`
}

对代码进行修改,构造完结构体后要转换成json的格式,修改后的代码如下:

func main() {
    client := &http.Client{}
    //var data = strings.NewReader(`{"trans_type":"en2zh","source":"Good"}`)
    dataStruct := DicReq{TransType: "en2zh", Source: "Hello"}    //构建结构体
    marshal, err := json.Marshal(dataStruct)   //转换为json格式
    if err != nil {
        log.Fatal(err)
    }
    data := bytes.NewReader(marshal)   
    req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
    if err != nil {
        log.Fatal(err)
    }
    //...
    fmt.Printf("%s\n", bodyText)
}
​

用单词Good进行测试,得到结果,虽然汉字没解析出来,但从个中单词可以看出结果是否符合预期,如果没问题,接着就要解析服务器返回的响应内容。

解析响应

服务器返回的响应内容也是JSON的格式,我们在上一步将结构体转换成了对应的JSON字符串,那么反过来我们也可以将JSON字符串转换成结构体,但是我们需要知道JSON字符串对应的结构,这时候如果要手动构建比较麻烦,所以我们要用到oktools.net/json2go这个网站,解析JSON字符串成Go语言中的结构体。

将响应的JSON字符串粘贴进去,选择嵌套,复制得到的结构体到代码中。

image-20230118235901525

通过json.Unmarshal()可以将JSON字符串解析成对应的结构体,修改代码打印响应的部分:

type DicResp struct {
    Rc   int `json:"rc"`
    Wiki struct {
        KnownInLaguages int `json:"known_in_laguages"`
        Description     struct {
            Source string      `json:"source"`
            Target interface{} `json:"target"`
        } `json:"description"`
        ID   string `json:"id"`
        Item struct {
            Source string `json:"source"`
            Target string `json:"target"`
        } `json:"item"`
        ImageURL  string `json:"image_url"`
        IsSubject string `json:"is_subject"`
        Sitelink  string `json:"sitelink"`
    } `json:"wiki"`
    Dictionary struct {
        Prons struct {
            EnUs string `json:"en-us"`
            En   string `json:"en"`
        } `json:"prons"`
        Explanations []string      `json:"explanations"`
        Synonym      []string      `json:"synonym"`
        Antonym      []interface{} `json:"antonym"`
        WqxExample   [][]string    `json:"wqx_example"`
        Entry        string        `json:"entry"`
        Type         string        `json:"type"`
        Related      []interface{} `json:"related"`
        Source       string        `json:"source"`
    } `json:"dictionary"`
}
func main() {
    //...
    //fmt.Printf("%s\n", bodyText)
    var dicresp DicResp
    err = json.Unmarshal(bodyText, &dicresp)
    fmt.Println(dicresp.Dictionary.Entry)   //打印需要翻译的单词
    fmt.Println("UK:", dicresp.Dictionary.Prons.En, "US:", dicresp.Dictionary.Prons.EnUs)//打印音标
    for _, expla := range dicresp.Dictionary.Explanations {
        fmt.Println(expla)   //打印翻译
    }
}

结尾

到此需求的功能已经完成大部分了,可以像老师一样在命令行调用,加上单词的参数来进行使用,我这里是加了个循环来读取用户输入的字符串进行翻译,所有程序如下:

package main
​
import (
    "bufio"
    "bytes"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "os"
    "strings"
)
​
type DicResp struct {
    Rc   int `json:"rc"`
    Wiki struct {
        KnownInLaguages int `json:"known_in_laguages"`
        Description     struct {
            Source string      `json:"source"`
            Target interface{} `json:"target"`
        } `json:"description"`
        ID   string `json:"id"`
        Item struct {
            Source string `json:"source"`
            Target string `json:"target"`
        } `json:"item"`
        ImageURL  string `json:"image_url"`
        IsSubject string `json:"is_subject"`
        Sitelink  string `json:"sitelink"`
    } `json:"wiki"`
    Dictionary struct {
        Prons struct {
            EnUs string `json:"en-us"`
            En   string `json:"en"`
        } `json:"prons"`
        Explanations []string      `json:"explanations"`
        Synonym      []string      `json:"synonym"`
        Antonym      []interface{} `json:"antonym"`
        WqxExample   [][]string    `json:"wqx_example"`
        Entry        string        `json:"entry"`
        Type         string        `json:"type"`
        Related      []interface{} `json:"related"`
        Source       string        `json:"source"`
    } `json:"dictionary"`
}
type DicReq struct {
    TransType string `json:"trans_type"`
    Source    string `json:"source"`
}
​
func main() {
    reader := bufio.NewReader(os.Stdin)
    for {
        client := &http.Client{}
        s, err := reader.ReadString('\n')
        if err != nil {
            fmt.Println("input exception!")
        }
        dataStruct := DicReq{TransType: "en2zh", Source: strings.TrimSuffix(s, "\r\n")}  //去一下换行符
        data, err := json.Marshal(dataStruct)
        fmt.Println(string(marshal))
        if err != nil {
            log.Fatal(err)
        }
        data := bytes.NewReader(marshal)
        req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
        if err != nil {
            log.Fatal(err)
        }
        req.Header.Set("authority", "api.interpreter.caiyunai.com")
        req.Header.Set("accept", "application/json, text/plain, */*")
        req.Header.Set("accept-language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
        req.Header.Set("app-name", "xy")
        req.Header.Set("content-type", "application/json;charset=UTF-8")
        req.Header.Set("device-id", "")
        req.Header.Set("origin", "https://fanyi.caiyunapp.com")
        req.Header.Set("os-type", "web")
        req.Header.Set("os-version", "")
        req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
        req.Header.Set("sec-ch-ua", `"Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"`)
        req.Header.Set("sec-ch-ua-mobile", "?0")
        req.Header.Set("sec-ch-ua-platform", `"Windows"`)
        req.Header.Set("sec-fetch-dest", "empty")
        req.Header.Set("sec-fetch-mode", "cors")
        req.Header.Set("sec-fetch-site", "cross-site")
        req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.52")
        req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
        resp, err := client.Do(req)
        if err != nil {
            log.Fatal(err)
        }
        defer resp.Body.Close()
        bodyText, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            log.Fatal(err)
        }
        var dicresp DicResp
        err = json.Unmarshal(bodyText, &dicresp)
        fmt.Println(dicresp.Dictionary.Entry)
        fmt.Println("UK:", dicresp.Dictionary.Prons.En, "US:", dicresp.Dictionary.Prons.EnUs)
        for _, expla := range dicresp.Dictionary.Explanations {
            fmt.Println(expla)
        }
    }
}

总结

这个小项目作为我Go语言的入门非常合适,有一定的趣味性同时也学到了很多东西,也是知道那些聚合翻译的实现方式了,有能力的朋友可以试试其他的翻译引擎,也许会比这个有挑战性(可能会有一些加密解密的工作)。