青训营GO语言工程实践课后作业:并行翻译引擎 | 豆包MarsCode AI刷题

154 阅读4分钟

实现效果

输入中文,同时被翻译为英文、法文和日文

iShot_2024-11-03_14.10.54.png

实现过程

1. 找到并调用api

通过开发者工具,网络面板发现,翻译时调用的API是:https://api.interpreter.caiyunai.com/v1/translator

image.png

通过查找官方文档可得到api的用法:

  • 请求URL:https://api.interpreter.caiyunai.com/v1/translator
  • 请求头:
    • content-type: application/json
    • x-authorization: token token内容
  • 请求体:
    • source: 待翻译的文本
    • trans_type: 翻译类型

token可以用文档中的提供的测试token:3975l6lr5pcbvidl6jl2

本文用到的翻译类型有:

  • zh2en: 中文翻译为英文
  • zh2fr: 中文翻译为法文
  • zn2ja: 中文翻译为日文

在Postman中尝试调用API,成功:

image.png

可以看到,返回体中的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)
    }
}

运行结果

image.png

3. 并行优化

要点

  1. 用长为3的字符串数组存放三种翻译类型
  2. 遍历翻译类型数组,以索引为参数创建匿名函数,并交给goroutine运行
  3. sync.WaitGroup等待三条goroutine结束
  4. 用管道来传输错误信息

优化后的代码

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
}

运行结果

image.png