腾讯云无服务器函数开发网址导航

概述

开发一个网址导航来实践腾讯云无服务器函数的功能,因为我有一丢丢的收集癖,一直就想有一个自己的导航,虽然现在各种导航网址遍地开花,但是这并不妨碍我再造一个轮子,可能我的轮子还是平行四边形的呢。

前期准备

什么是 SCF ?我们能利用 SCF 做什么?serverless 要怎么用?优势是什么? 不足点又是什么?对于想尝鲜无服务器函数功能的小伙伴前期要有一定的知识储备,没有银弹,合适的才是最好的。

文档指南

初期看文档,看看腾讯云无服务器函数支持哪些语言和响应的版本,函数和函数能否相互调用(可以调用)等,了解腾讯云给出的功能范围,确定我能通过这些功能做什么事。别忘了还有计费模式,你得知道是怎么扣费的!

思考🤔

对于 serverless 的应用场景目前只要是在以下几个方面:

  1. 事件请求场景
  2. 流量突发场景
  3. 处理大数据场景

同时推荐具备无状态的特性,对于网址导航这个功能,页面偏静态,接口调用少,我需要

  1. 一个定时服务来下载对应网址的 favicon 文件
  2. 收集用户的投稿链接
  3. 页面加载时获取内容

基本功能包含增删改查,除了查询,其他的功能我也不需要权限,管理员自己就能 cover 掉,不和其他业务强绑定关联,模块自身独立,除非后续迭代,当前的应用场景是匹配 serverless 能力的。

整体架构

其实可以做的功能还有很多,但不一定是刚需,目前只要满足最基本的使用即可。

定时任务的频率

现实场景中,Favicon 的变动频度是很小的,所以只需要一个定时任务就可以实现。由于函数的执行时间也是有限制的,理想情况下一次图片抓取主要取决于「网站能否被打开」,因为部分网站可能会被“特殊照顾”,只考虑国内的一些网站的话,执行时间控制在 5 秒以内还是绰绰有余的。但你还是无法保证每次从数据库中取出的这些链接都能被执行完,实际情况也确实是不可能完成,所以我们需要在定时的基础上,人为的做一个控制:判断上一次修改时间距离当前时间是否大于N(单位可以是天,也可以是小时,分钟),这样函数被定时启动的时候只下载之前没有被 Download 的网页图标。

关键代码:

exports.main_handler = async (event, context, callback) => {
    const lists = await sqlAction.query('select * from tableName where deleted=0');
    const during =  60 * 60 * 24 * 5 * 1000; // 5 天
    for (const item of lists) {
        const past = new Date(item.update_time).getTime();
        const now = new Date().getTime();
        if (now - past >= during || isNaN(past)) {
            const fileName = await download(item.link, item.name);
            if (fileName) {
                await sqlAction.query(`update tableName set favicon='${fileName}', update_time='${helper.formatDate(new Date().getTime())}' where id=${item.id}`);
            }
        }
    }
    return 'success';
};

COS 存储

定时任务抓取的小图标需要保存到 cos 上作为静态资源,这里我并没有使用腾讯云的服务,因为某🐂的 http 访问有免费额度,所以我使用 ta 来上传抓取的资源,因为是定时抓取,所以需要对文件可覆盖,不能使用随机ID命名的方式,否则资源会越来越多,后期还要做清理。使用其他云存储的也是同理。

上传代码:

const mac = new qiniu.auth.digest.Mac(qiniu.ak, qiniu.sk);
const config = new qiniu.conf.Config();
// 空间对应的机房
config.zone = qiniu.zone.Zone_z1;
// 是否使用https域名
config.useHttpsDomain = true;
// 上传是否使用cdn加速
config.useCdnDomain = true;

// 这种配置方式可以相同文件名覆盖
const options = {
    scope: 'bucketName:' + name,
};

const putPolicy = new qiniu.rs.PutPolicy(options);
const uploadToken = putPolicy.uploadToken(mac);

const formUploader = new qiniu.form_up.FormUploader(_config);
const putExtra = new qiniu.form_up.PutExtra();
const path = process.cwd() + '/icons/' + name;
// 文件上传
formUploader.putFile(uploadToken, name, path, putExtra, function (respErr, respBody, respInfo) {
    if (respErr) {
        reject(respErr);
        throw respErr;
    }
    if (respInfo.statusCode == 200) {
        resolve(respBody)
    } else {
        reject(respBody)
    }
});

静态页面

对于单页应用是可以放在任意的静态服务器上,或者 CDN 上,但是对于有 SEO 要求的网页来说,服务端渲染是少不了的,这里使用了相对简单的方式。函数调数据库读取内容,NodeJs 使用 ejs 引擎来渲染。

渲染代码:

let html = fs.readFileSync(path.resolve(__dirname, './index.html'), {
    encoding: 'utf-8'
});
html = Ejs.render(html.toString(), {
    title: 'Nox导航 - 互联网从这开始',
    list: result
});

return {
    isBase64Encoded: false,
    statusCode: 200,
    headers: { 'Content-Type': 'text/html' },
    body: html
}

反向代理

由于部署好的页面只有一个访问路径,如果使用二维码分享的话就无所谓了,但是要对外服务的话这个路径暴露出去就不太合适了,需要用 Nginx 做一次代理。

代理设置:

location /daohang {
    proxy_redirect off;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_redirect https://service-8nvzq5f1-1251487239.ap-shanghai.apigateway.myqcloud.com/release/navigation-index  /;
    proxy_set_header Accept-Encoding "";
    proxy_set_header User-Agent $http_user_agent;
    proxy_set_header Accept-Language "zh-CN";
    proxy_connect_timeout      240;
    proxy_send_timeout         240;
    proxy_read_timeout         240;
    # note, there is not SSL here! plain HTTP is used
    proxy_pass https://service-8nvzq5f1-1251487239.ap-shanghai.apigateway.myqcloud.com/release/navigation-index;
    sub_filter_once off;
}

最后我们可以使用 https://www.noxxxx.com/daohang 来访问了!

注意点

1. 部署 html 页面,需要开启集成响应功能,否则返回的内容不能被识别为 html。

2. Mysql 调用后需要 dstory 掉,否则整个函数运行会超时。

3. 本地调试运行函数后报异常,抛出的错误信息没有具体的代码行数,通常需要优先检查自己的代码逻辑是不是有问题,比如说取对象属性名时,对象为空的情况,也会有类似下面的这种错误,单看报错日志看不出什么…

总结

通过腾讯云的 scf 插件结合 visual studio code,无服务器函数在开发过程中,只需要聚焦业务功能的实现,同时,本地可以近乎一键部署到线上,效率非常高,运维部署层面上可以节省很多时间和精力。