实现效果
输入中文,同时被翻译为英文、法文和日文
实现过程
1. 找到并调用api
通过开发者工具,网络面板发现,翻译时调用的API是:https://api.interpreter.caiyunai.com/v1/translator
通过查找官方文档可得到api的用法:
- 请求URL:
https://api.interpreter.caiyunai.com/v1/translator - 请求头:
content-type:application/jsonx-authorization:token token内容
- 请求体:
source:待翻译的文本trans_type:翻译类型
token可以用文档中的提供的测试token:3975l6lr5pcbvidl6jl2
本文用到的翻译类型有:
zh2en: 中文翻译为英文zh2fr: 中文翻译为法文zn2ja: 中文翻译为日文
在Postman中尝试调用API,成功:
可以看到,返回体中的target字段就是我们需要的翻译结果。
2. 编写程序
把请求体和响应体JSON转为结构体
// TranslateReqBody 请求体结构
type TranslateReqBody struct {
Source string `json:"source"`
TransType string `json:"trans_type"`
}
// TranslateRespData 响应体结构
type TranslateRespData struct {
Rc int `json:"rc"`
Target string `json:"target"`
Confidence float64 `json:"confidence"`
SrcTgt struct {
} `json:"src_tgt"`
Isdict int `json:"isdict"`
TransType string `json:"trans_type"`
}
编写函数,调用API
func Translate(sentence string) ([]string, error) {
// 序列化请求体结构
zh2enReq := TransReqBody{sentence, "zh2en"}
enBuf, err := json.Marshal(zh2enReq)
if err != nil {
return nil, fmt.Errorf("zh2en buf marshal failed: %w\n", err)
}
zh2frReq := TransReqBody{sentence, "zh2fr"}
frBuf, err := json.Marshal(zh2frReq)
if err != nil {
return nil, fmt.Errorf("zh2fr buf marshal failed: %w\n", err)
}
zh2jaReq := TransReqBody{sentence, "zh2ja"}
jaBuf, err := json.Marshal(zh2jaReq)
if err != nil {
return nil, fmt.Errorf("zh2ja buf marshal failed: %w\n", err)
}
// 把三个buf放在切片中,循环遍历构造请求体
buffer := [][]byte{enBuf, frBuf, jaBuf}
const URL string = "https://api.interpreter.caiyunai.com/v1/translator"
req, err := http.NewRequest("POST", URL, nil)
if err != nil {
return nil, fmt.Errorf("zh2en request failed: %w\n", err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/json;charset=utf-8")
req.Header.Set("X-Authorization", "token 3975l6lr5pcbvidl6jl2")
// 构造客户端
client := &http.Client{Timeout: time.Second * 10}
// 发送请求的匿名函数
sendReq := func(buf []byte) (*http.Response, error) {
// 把buf转换成io.Reader类型,并设为请求体
req.Body = io.NopCloser(bytes.NewReader(buf))
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
fmt.Println("body close failed:", err)
}
}(req.Body)
// 发送请求
resp, err := client.Do(req)
if err != nil {
return nil, err
}
return resp, nil
}
// 按照英文,法文,日文的顺序存放翻译结果
transResult := make([]string, 3)
// 循环遍历发送请求
for i, buf := range buffer {
// 读取响应体
resp, err := sendReq(buf)
if err != nil {
return nil, fmt.Errorf("send request failed: %w\n", err)
}
respBodyText, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read resp body failed: %w\n", err)
}
//检查返回状态码是否正常
if resp.StatusCode != http.StatusOK {
return nil, errors.New(resp.Status + "\t" + string(respBodyText))
}
// 解析出翻译结果
var transResp TransRespData
err = json.Unmarshal(respBodyText, &transResp)
if err != nil {
return nil, fmt.Errorf("unmarshal resp body failed: %w\n", err)
}
// 把翻译结果存入切片中
transResult[i] = transResp.Target
// 关闭响应体
err = resp.Body.Close()
if err != nil {
return nil, err
}
}
return transResult, nil
}
Tip
在直接循环中调用defer可能造成内存泄漏。因为defer是在函数返回时调用的,而不是循环结束时。正确的姿势应该是:把defer相关的逻辑单元定义为单独的函数,在循环中调用这个函数。
这就是为什么笔者要在Translate函数的31~46行编写sendReq匿名函数。
在main函数中调用
func main() {
var transText string
fmt.Print("输入要翻译的中文: ")
_, _ = fmt.Scanln(&transText)
transResult, err := practice.Translate(transText)
if err != nil {
fmt.Println(err)
return
}
for _, transSentence := range transResult {
fmt.Println(transSentence)
}
}
运行结果
3. 并行优化
要点
- 用长为3的字符串数组存放三种翻译类型
- 遍历翻译类型数组,以索引为参数创建匿名函数,并交给
goroutine运行 - 用
sync.WaitGroup等待三条goroutine结束 - 用管道来传输错误信息
优化后的代码
func Translate(sentence string) ([]string, error) {
transTypes := [3]string{"zh2en", "zh2fr", "zh2ja"}
transResult := make([]string, 3)
const URL string = "https://api.interpreter.caiyunai.com/v1/translator"
client := &http.Client{Timeout: time.Second * 10}
var wg sync.WaitGroup // 用于等待所有goroutine完成
errCh := make(chan error, len(transTypes)) // 用于接收错误信息
for i, transType := range transTypes {
wg.Add(1)
go func(i int, transType string) {
defer wg.Done()
// 序列化请求体结构
transReq := TransReqBody{sentence, transType}
buf, err := json.Marshal(transReq)
if err != nil {
errCh <- fmt.Errorf("%v buf marshal failed: %w\n", transType, err)
return
}
// 创建请求体
reqData := bytes.NewReader(buf)
req, err := http.NewRequest("POST", URL, reqData)
if err != nil {
errCh <- fmt.Errorf("%v request failed: %w\n", transType, err)
}
// 设置请求头
req.Header.Set("Content-Type", "application/json;charset=utf-8")
req.Header.Set("X-Authorization", "token 3975l6lr5pcbvidl6jl2")
// 发送请求
resp, err := client.Do(req)
if err != nil {
errCh <- fmt.Errorf("%v request failed: %w\n", transType, err)
}
// 读取响应体
respBodyText, err := io.ReadAll(resp.Body)
if err != nil {
errCh <- fmt.Errorf("%v read resp body failed: %w\n", transType, err)
}
defer func(Body io.ReadCloser) {
err := Body.Close()
if err != nil {
fmt.Println("body close failed:", err)
}
}(resp.Body)
// 检查返回状态码是否正常
if resp.StatusCode != http.StatusOK {
errCh <- errors.New(resp.Status + "\t" + string(respBodyText))
}
// 解析出翻译结果
var transResp TransRespData
err = json.Unmarshal(respBodyText, &transResp)
if err != nil {
errCh <- fmt.Errorf("%v unmarshal resp body failed: %w\n", transType, err)
}
// 把翻译结果存入切片中
transResult[i] = transType + "\t" + transResp.Target
}(i, transType)
}
// 等待所有goroutine完成
wg.Wait()
// 收集错误信息
close(errCh)
var allErrors []error
for err := range errCh {
allErrors = append(allErrors, err)
}
if len(allErrors) != 0 {
return nil, fmt.Errorf("%+v\n", allErrors)
}
return transResult, nil
}