开发一个网址导航来实践腾讯云无服务器函数的功能,因为我有一丢丢的收集癖,一直就想有一个自己的导航,虽然现在各种导航网址遍地开花,但是这并不妨碍我再造一个轮子,可能我的轮子还是平行四边形的呢。
什么是 SCF ?我们能利用 SCF 做什么?serverless 要怎么用?优势是什么? 不足点又是什么?对于想尝鲜无服务器函数功能的小伙伴前期要有一定的知识储备,没有银弹,合适的才是最好的。
初期看文档,看看腾讯云无服务器函数支持哪些语言和响应的版本,函数和函数能否相互调用(可以调用)等,了解腾讯云给出的功能范围,确定我能通过这些功能做什么事。别忘了还有计费模式,你得知道是怎么扣费的!
对于 serverless 的应用场景目前只要是在以下几个方面:
同时推荐具备无状态的特性,对于网址导航这个功能,页面偏静态,接口调用少,我需要
基本功能包含增删改查,除了查询,其他的功能我也不需要权限,管理员自己就能 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 上作为静态资源,这里我并没有使用腾讯云的服务,因为某🐂的 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,无服务器函数在开发过程中,只需要聚焦业务功能的实现,同时,本地可以近乎一键部署到线上,效率非常高,运维部署层面上可以节省很多时间和精力。