这段笔记展示了如何在Go语言中开发一个简易词典程序,并通过逐版本演进来增强程序的功能性和用户交互性。
-
V1特点:使用标准输入输出和随机数来实现猜数字游戏的基本框架。
-
V2特点:通过HTTP请求到在线API,实现词典查询功能,使用JSON进行数据序列化和请求设置。
-
V3特点:引入复杂的JSON解析,通过定义详尽的Go结构体来处理API响应,优化数据处理和展示。
-
V4特点:整合命令行用户输入,动态查询单词,展示查询结果的详细信息如发音和释义。
每个版本的改进都使得程序不仅提供了基本的查询功能,还增强了与外部API的交互能力,改善了数据处理和用户界面的互动性。最终版本提供实用的查询功能、结构化输出和优化的用户体验。请根据目录快速检索和入门。
Go语言的实战案例
01-simpledict V1
package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"strconv"
"strings"
"time"
)
func main() {
maxNum := 100
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
// fmt.Println("The secret number is ", secretNumber)
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
for {
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
continue
}
input = strings.Trim(input, "\r\n")
guess, err := strconv.Atoi(input)
if err != nil {
fmt.Println("Invalid input. Please enter an integer value")
continue
}
fmt.Println("You guess is", guess)
if guess > secretNumber {
fmt.Println("Your guess is bigger than the secret number. Please try again")
} else if guess < secretNumber {
fmt.Println("Your guess is smaller than the secret number. Please try again")
} else {
fmt.Println("Correct, you Legend!")
break
}
}
}
SimpleDict V1 核心知识点:基础输入输出和随机数交互模板
核心知识点:
-
基础交互模板的重用:
- 这个版本的代码实际上是基于之前“猜数字游戏”的代码模板,但它即将被改造成“简单词典”工具。这表明很多基础的交互逻辑(如用户输入、数据验证、输出反馈)可以重用于不同的应用场景,比如游戏或工具。
-
用户输入的读取和处理:
- 使用
bufio.NewReader(os.Stdin)
读取用户输入,利用reader.ReadString('\n')
接受用户的输入内容。 strings.Trim(input, "\r\n")
用于去除输入中的换行符,以便对输入进行后续的处理。
- 使用
-
数据校验和反馈:
- 尝试将用户输入转换为整数,借助
strconv.Atoi(input)
实现,如果无法转换为整数,则提示用户输入有效的整数值。尽管这个逻辑在猜数字游戏中很合理,但在词典程序中可能需要处理字符串,而非只接受数字。
- 尝试将用户输入转换为整数,借助
-
随机数的生成与逻辑实现:
- 目前代码仍然保留了生成随机数的逻辑(
rand.Intn(maxNum)
),但是这种逻辑在词典程序中并不适用。后续版本中应移除这个随机数逻辑,代之以词典相关的功能,例如查询词汇定义等。
- 目前代码仍然保留了生成随机数的逻辑(
应用场景:
- 基础输入输出逻辑的模板:本代码展示了如何建立与用户的基本交互,这种交互模板可用于游戏、工具、配置程序等需要用户输入的应用场景。
- 错误处理:在用户输入无效数据时,通过
continue
让用户重新输入,这是处理用户输入的一种友好方式,适合用于需要用户多次尝试的情景。
改进方向:
- 去掉与猜数字无关的逻辑:未来版本中应移除不相关的随机数生成和猜测逻辑,改为处理与词典相关的功能,例如接受用户输入查询词汇含义。
- 引入词典数据:下一步需要引入实际的词典数据(例如一个包含词汇和解释的映射),使得用户可以输入单词并获得其解释。
- 实现简单的查询功能:将用户输入与词典数据匹配,如果存在该单词则返回其定义,如果不存在则提示用户找不到该单词。
未来增强:
- 词典功能:后续版本应将“猜数字”的逻辑转换为“单词查询”的逻辑。可以通过引入词典数据库或使用简单的哈希表来实现查询。
- 多次查询的交互:为了让用户能够方便地查询多个单词,可以使用循环,让用户在单次查询后可以继续输入其他单词,直到他们选择退出。
02-simpledict V2
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
func main() {
client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: "good"}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Connection", "keep-alive")
req.Header.Set("DNT", "1")
req.Header.Set("os-version", "")
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
req.Header.Set("app-name", "xy")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("device-id", "")
req.Header.Set("os-type", "web")
req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
req.Header.Set("Sec-Fetch-Site", "cross-site")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
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)
}
SimpleDict V2 核心知识点:HTTP请求与API调用实现在线词典功能
核心知识点:
-
HTTP客户端和请求构造:
- 本版本通过
net/http
包实现了对外部API接口的调用,使得应用可以利用在线词典服务。使用http.Client{}
创建客户端,能够发送请求和接收响应。 - 通过
http.NewRequest("POST", url, data)
创建了一个 HTTP POST 请求,将数据发送到指定的 API 地址。
- 本版本通过
-
结构体的定义与JSON编码:
- 定义了
DictRequest
结构体,用于表示请求体的数据格式,包括翻译类型(TransType
)、待翻译的源文本(Source
)和用户ID(UserID
)。 - 使用
json.Marshal(request)
将DictRequest
对象序列化为 JSON 格式,便于作为请求体发送至服务器。这种方式确保了数据在发送前可以被正确编码。
- 定义了
-
HTTP请求头的设置:
- 使用
req.Header.Set()
设置多个 HTTP 请求头,模拟浏览器请求,包括User-Agent
、Content-Type
、Accept
等。 - 请求头信息在调用外部 API 时非常重要,特别是在模拟浏览器访问或需要身份验证时,可以使请求看起来更像一个合法客户端,从而避免被拒绝访问。
- 使用
-
API响应的处理:
- 使用
client.Do(req)
发送请求,并通过resp.Body
获取服务器响应。利用ioutil.ReadAll(resp.Body)
读取响应体,将其转换为字节数组并输出为字符串格式。 defer resp.Body.Close()
确保响应体在函数结束时被正确关闭,避免资源泄露。
- 使用
应用场景:
- 在线词典查询:通过调用在线 API 服务,实现了从英文到中文的翻译功能。适用于需要快速构建一个查询单词或翻译词汇的工具的场景。
- HTTP API 调用:通过构造 HTTP 请求、设置请求头和处理响应,实现对外部服务的调用,适用于需要集成第三方服务的应用,如天气查询、股票数据获取等。
- API与JSON:通过 JSON 格式发送请求体和解析响应,适合在现代 Web 应用开发中使用的常见数据格式,方便与外部系统进行交互。
改进方向:
-
用户交互:
- 当前版本中的待翻译词汇是硬编码的字符串
"good"
。可以改进为让用户输入一个词汇,程序再进行翻译查询,使其更具交互性和实用性。
- 当前版本中的待翻译词汇是硬编码的字符串
-
响应解析:
- 当前程序只是简单地输出响应体,没有进行进一步的解析。可以改进为将响应 JSON 解析为结构体,并输出更加清晰的信息(例如输出翻译结果)。
-
API Key与安全性:
- 代码中包含了硬编码的授权令牌(
X-Authorization
),这种方式不安全,尤其在公开代码时容易泄露敏感信息。可以通过环境变量或配置文件来存储这些敏感数据,提高安全性。
- 代码中包含了硬编码的授权令牌(
-
错误处理与改进:
- 当前代码中,任何错误都会调用
log.Fatal()
终止程序。这种错误处理方式不太友好,可以改进为友好的提示信息,让用户了解出错原因而不是直接退出程序。
- 当前代码中,任何错误都会调用
游戏体验和使用体验的提升:
- 更好的人机交互:通过让用户输入单词并返回翻译结果,程序的互动性会更高,更像是一个实用的词典工具。
- 解析结果展示:响应内容通过解析可以更加直观地呈现给用户,而不仅仅是打印整个 JSON 响应,提供更好的使用体验。
03-simpledict V3
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
type DictResponse 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 []string `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() {
client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: "good"}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Connection", "keep-alive")
req.Header.Set("DNT", "1")
req.Header.Set("os-version", "")
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
req.Header.Set("app-name", "xy")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("device-id", "")
req.Header.Set("os-type", "web")
req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
req.Header.Set("Sec-Fetch-Site", "cross-site")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
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 dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%#v\n", dictResponse)
}
SimpleDict V3 核心知识点:API响应解析与结构化输出
核心知识点:
-
定义响应结构体 (
DictResponse
):- 本版本增加了对 API 响应的结构体定义 (
DictResponse
),用于将 JSON 响应转换为 Go 语言的结构体。这使得程序可以更容易地访问和处理 API 返回的数据。 - 结构体
DictResponse
中包含了嵌套的字段(例如Dictionary
和Wiki
),用于准确映射 API 响应中的各个部分。通过这种方式,可以避免直接操作 JSON 数据的复杂性。
- 本版本增加了对 API 响应的结构体定义 (
-
HTTP请求和响应处理:
- 与前一版本类似,通过
http.Client
和http.NewRequest
创建 HTTP POST 请求,向在线词典服务发送翻译请求。 - 利用
json.Marshal()
将DictRequest
序列化为 JSON 数据,进行 POST 请求的发送。 - 使用
defer resp.Body.Close()
确保响应体在使用完成后被关闭,避免内存泄露。
- 与前一版本类似,通过
-
JSON解析 (
json.Unmarshal()
):- 通过
json.Unmarshal(bodyText, &dictResponse)
将 JSON 格式的响应数据解析为DictResponse
结构体。这样就能方便地通过结构体字段访问具体的数据。 - 这一步将原本不太直观的 JSON 数据转换为易于理解和操作的 Go 结构体,便于后续处理和展示。
- 通过
-
改进的数据展示:
- 输出使用
fmt.Printf("%#v\n", dictResponse)
来展示结构体的完整内容。这种展示方式便于调试,能够看到所有解析后的字段和它们的值。
- 输出使用
应用场景:
- API响应的解析与操作:通过定义结构体来解析复杂的 JSON 响应,适用于需要从 API 返回的数据中提取详细信息的场景,例如获取天气数据、汇率、词典查询等。
- 复杂数据处理:通过嵌套结构体映射复杂的 JSON 结构,有助于简化处理逻辑,尤其是在对多层嵌套数据的操作中,能够显著提高代码可读性和易维护性。
改进方向:
-
优化输出展示:
- 当前的输出只是将结构体的内容打印出来,略显杂乱。可以改进为根据需要打印结构体中的特定字段,例如词的定义、同义词、反义词等,让输出更为简洁和有意义。
-
用户交互增强:
- 目前请求的源词汇(
Source
)仍然是硬编码的。可以通过让用户输入要查询的词汇,增强工具的实际使用价值。
- 目前请求的源词汇(
-
错误处理的改进:
- JSON解析错误直接终止程序不太友好,可以改进为用户友好的错误提示信息,甚至可以通过重新输入来进行新的尝试。
-
多次查询和自动化:
- 当前程序每次只能查询一个词汇,运行一次即结束。可以引入一个循环,让用户能够多次输入不同的词汇,连续进行查询,直到用户选择退出。
用户体验的提升:
- 更加结构化和清晰的输出:通过将 API 响应解析为结构体,可以更直观地展示信息,例如输出单词的发音、释义、例句等,用户体验会更好。
- 更高的交互性:未来版本可以通过引入用户输入来支持不同的词汇查询,使得工具从单一用途变得更为灵活和实用,这会让用户更加愿意使用和探索这个小工具。
04-simpledict V4
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
type DictResponse 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 []string `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 query(word string) {
client := &http.Client{}
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
req.Header.Set("Connection", "keep-alive")
req.Header.Set("DNT", "1")
req.Header.Set("os-version", "")
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36")
req.Header.Set("app-name", "xy")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("device-id", "")
req.Header.Set("os-type", "web")
req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
req.Header.Set("Sec-Fetch-Site", "cross-site")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9")
req.Header.Set("Cookie", "_ym_uid=16456948721020430059; _ym_d=1645694872")
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)
}
if resp.StatusCode != 200 {
log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText))
}
var dictResponse DictResponse
err = json.Unmarshal(bodyText, &dictResponse)
if err != nil {
log.Fatal(err)
}
fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs)
for _, item := range dictResponse.Dictionary.Explanations {
fmt.Println(item)
}
}
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage: simpleDict WORD
example: simpleDict hello
`)
os.Exit(1)
}
word := os.Args[1]
query(word)
}
SimpleDict V4 核心知识点:用户输入和查询功能的整合
核心知识点:
-
用户输入作为查询词汇:
- 在本版本中,用户可以通过命令行参数输入一个单词来进行查询,增加了程序的交互性和灵活性。程序的启动命令格式为:
simpleDict WORD
。 - 通过
os.Args[1]
获取命令行传入的单词,这样用户可以动态输入不同的词汇,而不是依赖硬编码的词汇。
- 在本版本中,用户可以通过命令行参数输入一个单词来进行查询,增加了程序的交互性和灵活性。程序的启动命令格式为:
-
封装查询功能:
- 将词典查询逻辑封装为
query()
函数,参数为用户输入的单词。这种封装使代码结构更加清晰,增加了代码的重用性和维护性。 query(word)
函数负责向 API 发送请求,处理响应,并将结果打印出来。
- 将词典查询逻辑封装为
-
HTTP 请求的改进:
- 和前一版本相似,构造了 HTTP POST 请求,但增加了响应状态码的检查。
- 检查
resp.StatusCode
确保 HTTP 请求的状态是200
,如果不是,则打印错误消息并终止程序。这种改进有助于更好地处理服务端响应中的异常情况。
-
解析并输出关键信息:
- 通过结构体
DictResponse
将返回的 JSON 数据解析后,程序能够输出详细的词典信息。 - 程序打印单词的英式发音和美式发音 (
UK
和US
),以及翻译解释部分(Explanations
)。这样用户可以直观地看到查询单词的读音和含义。
- 通过结构体
应用场景:
- 命令行工具:本版本实现了一个命令行词典工具的基本功能,适用于需要快速查询单词释义的场景,例如开发者在写代码时查单词,或者需要了解某个英文单词的释义。
- 封装和可重用性:通过将查询功能封装为
query()
函数,可以很容易将该功能嵌入其他应用中,或者在后续版本中进行功能拓展和维护。
改进方向:
-
更友好的错误处理:
- 目前程序中,当遇到网络错误或解析错误时会直接终止程序并打印日志 (
log.Fatal
)。可以改进为向用户输出友好的错误提示,并允许用户重新输入单词继续查询。
- 目前程序中,当遇到网络错误或解析错误时会直接终止程序并打印日志 (
-
多单词查询支持:
- 当前版本一次只能查询一个单词,可以通过循环或增加对多单词输入的支持,让用户在一次执行中查询多个单词,以提升用户体验。
-
改进输出格式:
- 增加对单词含义的分组展示,让用户可以更方便地阅读解释部分,例如分为“动词”、“名词”等部分,而不是简单地列出所有释义。
- 增加对其他信息的展示,例如词的例句(
wqx_example
)、同义词(synonym
)等,以帮助用户更全面地理解词汇。
-
授权和安全性:
- 将 API 的授权令牌 (
X-Authorization
) 移动到配置文件或环境变量中,这样在代码发布时不会泄露敏感信息。可以通过os.Getenv()
来读取环境变量中的授权信息。
- 将 API 的授权令牌 (
用户体验的提升:
- 动态查询:用户可以通过命令行输入不同的单词进行查询,而不是硬编码查询词汇,增加了程序的实用性和交互性。
- 更加详细的输出:本版本中增加了对单词的英美发音的显示,以及释义的展示,使用户对单词的了解更加全面和直观。结合进一步的输出改进和友好的错误处理,可以让用户更加轻松地使用这个工具。