API翻译请求 | 豆包MarsCode AI刷题

2 阅读4分钟

序言

这是一篇初学者的实践记录,问题详见“Go语言的实战案例”的实践内容。 本次任务要求如下:

1.在给出的彩云小译之外,添加对另外一种翻译引擎的支持

2.在1的条件下,并行请求两个引擎的翻译结果

本文实现了Go语言请求API翻译并返回结果的过程,并通过协程并行请求两个翻译结果。

有道翻译实现

访问新手指南 获得一个翻译API。翻译API包含两部分:应用ID以及应用密钥。 阅读有道翻译API 文档,得知我们需要构造的请求如下:

字段名类型含义必填备注
qtext待翻译文本True必须是UTF-8编码
fromtext源语言True参考下方支持语言(可设置为auto)
totext目标语言True参考下方支持语言
appKeytext应用IDTrue可在应用管理查看
salttext随机字符串Trueuuid(可使用uuid生成)
signtext签名Truesha256(应用ID+input+salt+curtime+应用密钥)
signTypetext签名类型Truev3
curtimetext当前UTC时间戳TrueTimeStamp
exttext翻译结果音频格式False支持mp3

作为一个简单的文本翻译,我们不关心非必填项,因此,构造如下结构体:

type YoudaoTranslateRequest struct {
	Q        string `json:"q"`
	From     string `json:"from"`
	To       string `json:"to"`
	AppKey   string `json:"appKey"`
	Salt     string `json:"salt"`
	Sign     string `json:"sign"`
	SignType string `json:"signType"`
	Curtime  string `json:"curtime"`
}

同样地,我们阅读返回结果如下:

字段名类型含义备注
errorCodetext错误返回码一定存在
querytext源语言查询正确时,一定存在
translationArray翻译结果查询正确时,一定存在
ltext源语言和目标语言一定存在
dicttext词典deeplink查询语种为支持语言时,存在
webdicttextwebdeeplink查询语种为支持语言时,存在
tSpeakUrltext翻译结果发音地址翻译成功一定存在,需要应用绑定语音合成服务才能正常播放,否则返回110错误码
speakUrltext源语言发音地址翻译成功一定存在,需要应用绑定语音合成服务才能正常播放,否则返回110错误码

务必注意一定要阅读文档中示例给出的对“good”的翻译请求结果。我们在其中注意到dict和webdict实际上是一个结构体而非text。编写代码时特别注意,否则会报告json: cannot unmarshal object into Go struct field这个错误。

// 定义响应结构体
type YoudaoTranslateResponse struct {
	ErrorCode   string   `json:"errorCode"`
	Query       string   `json:"query"`
	Translation []string `json:"translation"`
	L           string   `json:"l,omitempty"`
	Dict        *struct {
		URL string `json:"url"`
	} `json:"dict,omitempty"`
	Webdict     *struct {
		URL string `json:"url"`
	} `json:"webdict,omitempty"`
	TSpeakUrl   *string  `json:"tSpeakUrl,omitempty"`
	SpeakUrl    *string  `json:"speakUrl,omitempty"`
}

现在,我们开始编写 youdaoTranslate翻译函数。按照文档要求,首先传入我们的API,然后构造请求结构体中的所有内容:

首先准备应用ID和密钥,加的盐(salt)是随机字符串,官方推荐uuid实现。curtime则请求一个当前时间戳,防止重放攻击

func youdaoTranslate(text, from, to string) (string, error) {
	const appKey = "xxx"       
	const appSecret = "xxx"
	salt := uuid.New().String() 
	curtime := strconv.FormatInt(time.Now().Unix(), 10) 

随后我们计算输入字符串的 input,文档已经给出计算方法,直接实现即可

	var input string
	if len(text) > 20 {
		input = text[:10] + strconv.Itoa(len(text)) + text[len(text)-10:]
	} else {
		input = text
	}

最后计算签名 sign,同样参考文档计算方法:


   signStr := appKey + input + salt + curtime + appSecret
   hash := sha256.New()
   hash.Write([]byte(signStr))
   sign := hex.EncodeToString(hash.Sum(nil))

构造请求结构体并编码URL:

  
  reqData := YoudaoTranslateRequest{
  //......
  }
  params := url.Values{}
  params.Set("q", reqData.Q)
  //......

随后,发送请求,接受请求,转换为json格式,检查错误码。

// 发送 POST 请求
	resp, err := http.PostForm("https://openapi.youdao.com/api", params)
	if err != nil {
		return "", fmt.Errorf("Send Request Error: %v", err)
	}
	defer resp.Body.Close()

	// 读取响应体
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("Read Response Error: %v", err)
	}

	// 解析 JSON 响应到响应结构体
	var result YoudaoTranslateResponse
	if err := json.Unmarshal(body, &result); err != nil {
		return "", fmt.Errorf("Parse JSON Response Error: %v", err)
	}
        
        // 检查错误码并返回结果
	if result.ErrorCode != "0" {
		return "", fmt.Errorf("API Error: %s", result.ErrorCode)
	}
	if len(result.Translation) == 0 {
		return "", fmt.Errorf("No Translation Found: %s", text)
	}
	return result.Translation[0], nil

错误捕捉:我们在不同的阶段定义不同的错误,如Send Request Error等,这部分手动给出的错误标识了错误发生在哪个阶段,随后跟随其他来源(如API)给出的错误。

并行请求两个来源的翻译

这部分就是对协程的应用。我们首先修改query()函数,让它返回结果字符串而不是直接打印:

var result strings.Builder

	// 单词和发音
result.WriteString(fmt.Sprintf("%s\nUK: %s\nUS: %s\n", word, dictResponse.Dictionary.Prons.En, dictResponse.Dictionary.Prons.EnUs))
	
	// 释义
for _, item := range dictResponse.Dictionary.Explanations {
		result.WriteString(fmt.Sprintf("%s\n", item))
	}
        
return result.String(),nil

由于需要多次拼接字符串,因此,使用strings.Builder更加高效。因为我们在这里已经知道要多次拼接字符串,而直接使用+会带来多次拷贝开销。

随后,在一个新的函数中启动两个协程,并行执行 query() 和 youdaoTranslate() 函数。

至此,任务完成。