16

Serverless 实践篇 - 如何基于腾讯云快速开发免费的翻译工具

 4 years ago
source link: https://yugasun.com/post/serverless-practice-dict.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

作为一名程序员,日常工作和学习中,我们会接触到各种英文文档和代码,因此英文基础是不可或缺的。但是我们脑海中的英文词汇是有限的,总会碰到一些不认识的单词,因此一个好的翻译软件就显得尤为重要。由于每次点开翻译软件,然后再输入陌生单词,获得答案的操作,总觉得太繁琐,而且大多数时候我们只需要一个简单的翻译就行,并不需要翻译软件列出的一大堆翻译解释。因此,开发一款简单的翻译工具的念头应运而生。

思考

要开发一款翻译工具,第一反应就是想到使用现成的翻译接口,只需要我本地写几行脚本调用,可处理下数据就行了,同时我又想把它做成一个简单的服务,可以通过简单的 query api 的方式,就可以获得翻译,ok,现在需求很明确了:

0. 一个免费的翻译接口
1. 我需要一个简单的翻译服务,它的使用频率很低
2. 同时这个服务最好在我只有翻译需求时才运行
3. 我是个穷B,我不想花钱

写下第三条时,我默默地擦拭掉了眼角的泪水,可能年纪大了,容易进沙子……

准备

不知道为什么,我的脑海中第一个闪现的就是 云函数 ,因为他满足了上面提到的所有需求,关键是她免费,免费,免费……. 重要的是事情说三遍。当然免费是有限度的,对于这种小工具来说,已经够了。作为一名合格的撸羊毛党,对于免费的服务发现和洞察能力,是一门基本修养。

于是三下五除二就注册了一个腾讯云账号,顺势就开通了云函数服务。

接下来就是翻译接口了,在腾讯云平台搜了下,正好有腾讯云机器翻译的文本翻译接口正好可以满足需求,重点是它 每月有5百万字符 的免费额度,简直是我等屌丝的福音……

还有一个很重要的步骤就是 创建API密钥 ,因为之后无论是 请求云API请求 还是 scf 命令行工具部署 都需要 API密钥 ,只需要到腾讯云控制台 创建API密钥 就行。

鉴权开发

接下来就是正式开发了。腾讯云机器翻译的接口鉴权有两种签名算法:一种是简单的 HMAC-SHA1/SHA256 算法,另一种则是相对复杂的 TC3-HMAC-SHA256 算法。官方给出的解释是:

TC3-HMAC-SHA256 签名方法相比以前的 HmacSHA1 和 HmacSHA256 签名方法,功能上覆盖了以前的签名方法,而且更安全,支持更大的请求,支持 json 格式,性能有一定提升,建议使用该签名方法计算签名。

考虑到以后的扩展性(作为一名喜欢装x的程序员),毅然选择了第二种鉴权算法。可是官方文档并未给出 Javascript 的实现版本,于是自己花时间用 Typescript 手写了这个签名算法,整体还是没有什么难度的,只需要按照 官方文档 ,一步一步实现就好,这里奉上 源代码

注意:因为 GET 和 POST 方式的参数是有区别的,目前只支持 POST 方式。

搞定了最复杂的签名算法,接下来就是 云函数 的开发了。

云函数创建

创建一个 云函数 ,参考官方文档 使用控制台创建函数 ,模板选择 Nodejs8.9 运行环境,创建成功后,在线编辑函数代码就行。当然你也可以本地创建,参考这个代码模板 https://github.com/yugasun/tencent-serverless-demo/tree/master/dict , 然后根据个人需求修改 template.yaml 文件就行。

如果你是本地开发的函数,需要部署到线上,就需要用到 SCF命令行工具 了,更具文档安装下就行。(当然云函数的部署,还有其他很多种,待大家自己去探索了)

业务开发

接下来就是编写业务逻辑了,本来代码中应该包含了鉴权和标准云API请求的代码,但是考虑到以后可能还会再次使用,于是将腾讯云相关的 API 请求代码封装成了 tss-capi 模块。然后重构后的函数代码就变得简洁很多:

const Dotenv = require('dotenv');
const { Capi } = require('tss-capi');
const path = require('path');

function scfReturn(err, data) {
  return {
    isBase64Encoded: false,
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: { error: err, data: data },
  };
}

exports.main_handler = async (event, context, callback) => {
  const query = event.queryString || {};
  const sourceText = query.q;
  if (!sourceText) {
    return scfReturn(new Error('Please set word you want to translate.'), null);
  }
  try {
    const envPath = path.join(__dirname, '.env');
    const { parsed } = Dotenv.config({
      path: envPath,
    });

    const client = new Capi({
      Region: 'ap-guangzhou',
      SecretId: parsed.TENCENT_SECRET_ID,
      SecretKey: parsed.TENCENT_SECRET_KEY,
      ServiceType: 'tmt',
      host: 'tmt.tencentcloudapi.com',
    });

    const res = await client.request(
      {
        Action: 'TextTranslate',
        Version: '2018-03-21',
        SourceText: sourceText,
        Source: 'auto',
        Target: 'zh',
        ProjectId: 0,
      },
      {
        host: 'tmt.tencentcloudapi.com',
      },
    );

    const translateText = res.Response && res.Response.TargetText;
    return scfReturn(null, translateText);
  } catch (e) {
    return scfReturn(e, null);
  }
};

注意:函数使用了 dotenv 来配置上文提到的 API 密钥 ,开发中你需要将含有 TENCENT_SECRET_IDTENCENT_SECRET_KEY.env 文件放到项目根目录。

细心的读者可能还会发现,这里的函数返回都是通过 scfReturn 规范化的,这是为什么呢?

这里踩了一个坑,正常情况下,云函数执行结果是可以返回任何结果的,但是由于这里本人在创建 API 网关触发器 时,点击启用了 集成响应 ,但是当时并没有注意这个功能,就没有理会她,导致接口请求一直报错,也很莫名其妙。通过搜索,发现官方对于 集成响应 的说明:

集成响应,是指 API 网关会将云函数的返回内容进行解析,并根据解析内容构造 HTTP 响应。通过使用集成响应,可以通过代码自主控制响应的状态码、headers、body 内容,可以实现非 JSON 格式的内容响应,例如响应 XML、HTML、甚至 JS 内容。在使用集成响应时,需要按照 API 网关触发器的集成响应返回数据结构,才可以被 API 网关成功解析,否则会出现 {“errno”:403,”error”:”Invalid scf response format. please check your scf response format.”} 错误信息。

找到了接口报错的原因了,于是便写了 scfReturn 函数,来规范所有接口返回。

函数部署

借助 SCF命令行工具 ,云函数的部署变得相当简单。你只需要在项目根目录下执行 scf deploy 命令,接下来所有的一切事情,命令行就会自动帮你搞定。当然如果需要自动创建 API 网关触发器 ,还需要在 template.yaml 文件中进行配置,如下:

Resources:
  default:
    Type: TencentCloud::Serverless::Namespace
    dict:
      Type: TencentCloud::Serverless::Function
      Properties:
        CodeUri: ./
        Description: Tencent Machine Translator
        Environment:
          Variables: {}
        Handler: index.main_handler
        Role: QCS_SCFExcuteRole
        MemorySize: 128
        Runtime: Nodejs8.9
        Timeout: 3
        VpcConfig:
          SubnetId: ''
          VpcId: ''
        Events:
          dict:
            Type: APIGW
            Properties:
              StageName: release
              ServiceId: service-7kqwzu92
              HttpMethod: ANY

注意: 配置中的 service-7kqwzu92 是我在 API网关 创建的服务,需要修改成私人配置。

第一次部署成功后,如果再次运行 scf deploy 会提示 default dict: The function already exists. 函数已经存在的错误,这里就需要进行强制覆盖,将部署命令修改为 scf deploy -f 就好。

最后

终于一个简单免费的云词典开发好了,浏览器访问: http://service-7kqwzu92-1251556596.gz.apigw.tencentcs.com/test/dictt?q=hello ,成功输出翻译结果:

{
  "isBase64Encoded": false,
  "statusCode": 200,
  "headers": {
    "Content-Type": "application/json"
  },
  "body": {
  	"error": null,
		"data": "你好"
  }
}

当然,这还并不能满足我作为一名 懒惰 程序员的需求,因为我现在翻译,还需打开浏览器,然后输我要翻译的字符串才行,于是我又在本地写了一个简单的脚本,通过执行终端命令就可以了,最后的运行效果是:

$ dict billionaire
亿万富翁

不知道为啥,看到 亿万富翁 的翻译输出到命令行时,一粒沙子又莫名的飞入了我的眼中:

金钱最大不是只有 100块 吗?尽然还有 亿万 这个单位……


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK