我与 Simona Cotin、John Papa、Jessie Frazelle、Burke Holland 和 Paige Bailey 等优秀的人才在一个大型团队中工作。我们经常交流,因为这是开发者倡导者的工作内容之一,而且我们也经常被问到将在哪里发言。在大多数情况下,我们每个人都管理自己的网站,在网站上列出所有这些演讲,但这对于想要探索的人来说并不是一个很好的体验,所以我做了一个演示,可以轻松地查看谁在哪个会议上发言,时间是什么,以及所有这些信息的链接。为了好玩,我使用了 three.js,这样你就可以快速可视化我们都访问了多少地方。

你可以查看 此处的实时演示,或者在 GitHub 上 探索代码。
在本教程中,我将介绍如何通过使用无服务器函数从 Google 获取我们所有演讲地点的地理位置数据来设置地球仪。我还将介绍如何使用 Vuex(基本上是 Vue 版本的 Redux)来存储所有这些数据并将其输出到表格和地球仪,以及如何在 Vue 中使用计算属性来使对该表格的排序超级高效和流畅。
文章系列
- 使用无服务器函数自动更新 GitHub 文件(您就在这里!)
- 过滤和使用数据
无服务器函数
到底是怎么回事?
最近我在推特上说,“无服务器是一件非常有趣的事情,但标题最吸引人。”我在这里要坚持这一点,并说,每个人都会告诉你的第一件事是,无服务器是一个错误的名称,因为你实际上仍在使用服务器。这是真的。那么为什么称之为无服务器呢?无服务器的承诺是减少设置和维护服务器的时间。你基本上是让服务为你处理维护和扩展,并将你的需求缩减为这样声明的函数:当收到此请求时,运行此代码。出于这个原因,有时人们将它们称为函数即服务或 FaaS。
这有用吗?当然!我喜欢在不需要的时候不必照看服务器,而且付款也会自动扩展,这意味着你不会为任何你不使用的东西付费。
FaaS 是否适合始终使用?嗯,不完全是。如果你想管理小的执行,它非常有用。无服务器函数可以检索数据,可以发送电子邮件通知,甚至可以做一些事情,例如动态裁剪图像。但是对于任何可能占用资源或大量计算的进程,能够像平常一样与服务器通信实际上可能更有效。
我们的演示在这里是一个很好的例子,说明我们想要使用无服务器的地方。我们主要只是维护和更新一个 JSON 文件。我们将拥有所有初始的演讲者数据,并且需要从 Google 获取地理位置数据来创建我们的地球仪。我们还可以让它通过 GitHub 提交来触发。让我们深入了解。
创建无服务器函数
我们将从一个大的 JSON 文件开始,该文件是我从同事演讲活动的电子表格中输出的。该文件包含我创建表格所需的一切,但为了创建地球仪,我将使用 来自 Google 数据艺术的这个 webgl-globe,我将对其进行修改。你可以在自述文件中看到,我最终将格式化我的数据以提取年份,但我还需要我们访问的每个地点的纬度和经度
var data = [
[
'seriesA', [ latitude, longitude, magnitude, latitude, longitude, magnitude, ... ]
],
[
'seriesB', [ latitude, longitude, magnitude, latitude, longitude, magnitude, ... ]
]
];
最终,我还要减少每年重复出现的实例以使数量级变得更合理,但我们将在这个系列的第二部分中处理对数据的修改,并在 Vue 中完成。
要开始,如果你还没有,请创建一个 免费的 Azure 试用帐户。然后转到门户网站:preview.portal.azure.com
在里面,你会看到一个侧边栏,其中包含许多选项。顶部将显示新建。单击它。

接下来,我们将从列表中选择函数应用,并填写我们函数的新名称。这将为我们提供一些选项。你可以看到它将自动获取我们的资源组、订阅并创建一个存储帐户。它还将使用资源组中的位置数据,因此,令人高兴的是,它很容易填充,如以下 GIF 所示。

默认值可能非常适合你的需求。如上面的 GIF 所示,它将自动填充大多数字段,这些字段仅来自应用程序名称。你可能需要根据大多数流量来自哪里或从一个中点(例如,如果你在旧金山和纽约都有很多流量),最好选择美国中部的一个位置来更改你的位置。
托管计划可以是消耗型(默认值)或应用服务计划。我选择消耗型,因为资源会动态添加或删除,这是整个无服务器概念的魔力。如果你想获得更高层次的控制或细节,你可能想要应用服务计划,但请记住,这意味着你将手动扩展和添加资源,因此你需要做更多的工作。

你将被带到一个屏幕,其中显示有关你的函数的很多信息。检查是否一切正常,然后单击侧边栏上的函数加号。

从那里你可以选择一个模板,我们将向下翻页并从给定的选项中选择 GitHub Webhook – JavaScript。

选择此选项将把你带到一个页面,其中包含一个`index.js`文件。如果你愿意,你可以输入代码,但他们提供了一些默认代码来运行初始测试,以查看一切是否正常。在我们创建函数之前,让我们先测试一下,以确保一切看起来都正常。

我们将点击顶部的保存和运行按钮,以下是我们的结果。你可以看到输出给了我们一个评论,我们在绿色中获得了 200 OK 的状态,并且获得了一些日志来验证我们的 GitHub webhook 成功触发。

非常好!现在是最有趣的部分:让我们编写自己的函数。
编写我们的第一个无服务器函数
在我们的例子中,我们拥有所有演讲的地理位置数据,这是我们需要创建表格的,但为了创建我们地球仪的 JSON 文件,我们需要更多的数据:我们需要所有演讲活动的纬度和经度。JSON 文件将由我们的 Vuex 中心存储读取,我们可以将需要读取的部分传递给每个组件。
我用于无服务器函数的文件存储在我的 GitHub 存储库中,你可以在这里浏览整个文件,但让我们也一起看一下
我要提到的第一件事是,我已经用配置选项填充了这些变量,用于本教程的目的,因为我不想把所有私人信息都告诉你。我的意思是,这很好,我们是朋友,但我们刚认识。
// GitHub configuration is read from process.env
let GH_USER = process.env.GH_USER;
let GH_KEY = process.env.GH_KEY;
let GH_REPO = process.env.GH_REPO;
let GH_FILE = process.env.GH_FILE;
在现实世界中,我可以直接插入数据
// GitHub configuration is read from process.env
let GH_USER = sdras;
…等等。为了使用这些环境变量(如果你也希望存储它们并保持它们私密),你可以像上面一样使用它们,然后转到仪表板中的你的函数。在那里你会看到一个名为已配置功能的区域。单击应用程序设置,你将被带到一个页面,其中有一个表格,你可以在其中输入这些信息。
使用我们的数据集
首先,我们将从 GitHub 检索原始 JSON 文件并对其进行解码/解析。我们将使用一种方法,该方法从 GitHub 响应中获取文件并对其进行 Base64 编码(此处提供更多信息)。
module.exports = function(context, data) {
// Make the context available globally
gContext = context;
getGithubJson(githubFilename(), (data, err) => {
if (!err) {
// No error; base64 decode and JSON parse the data from the Github response
let content = JSON.parse(
new Buffer(data.content, 'base64').toString('ascii')
);
接下来,我们将获取原始数据中每个项目的地理信息,如果一切顺利,我们将将其推送到 GitHub,否则,将会出错。我们将有两个错误:一个用于一般错误,另一个用于我们收到正确响应但存在地理错误的情况,这样我们就可以区分它们。你会注意到我们使用的是 `gContext.log` 将输出发送到我们的门户控制台。
getGeo(makeIterator(content), (updatedContent, err) => {
if (!err) {
// we need to base64 encode the JSON to embed it into the PUT (dear god, why)
let updatedContentB64 = new Buffer(
JSON.stringify(updatedContent, null, 2)
).toString('base64');
let pushData = {
path: GH_FILE,
message: 'Looked up locations, beep boop.',
content: updatedContentB64,
sha: data.sha
};
putGithubJson(githubFilename(), pushData, err => {
context.log('All done!');
context.done();
});
} else {
gContext.log('All done with get Geo error: ' + err);
context.done();
}
});
} else {
gContext.log('All done with error: ' + err);
context.done();
}
});
};
太好了!现在,给定一个条目数组(包装在迭代器中),我们将遍历每个条目并使用 Google 地图 API 填充纬度和经度。请注意,我们还会缓存位置,以尝试节省一些 API 调用。
function getGeo(itr, cb) {
let curr = itr.next();
if (curr.done) {
// All done processing- pass the (now-populated) entries to the next callback
cb(curr.data);
return;
}
let location = curr.value.Location;
现在让我们检查缓存,看看我们是否已经查找过这个位置
if (location in GEO_CACHE) {
gContext.log(
'Cached ' +
location +
' -> ' +
GEO_CACHE[location].lat +
' ' +
GEO_CACHE[location].long
);
curr.value.Latitude = GEO_CACHE[location].lat;
curr.value.Longitude = GEO_CACHE[location].long;
getGeo(itr, cb);
return;
}
然后,如果没有在缓存中找到任何内容,我们将进行查找并缓存结果,或者让我们知道我们没有找到任何内容
getGoogleJson(location, (data, err) => {
if (err) {
gContext.log('Error on ' + location + ' :' + err);
} else {
if (data.results.length > 0) {
let info = {
lat: data.results[0].geometry.location.lat,
long: data.results[0].geometry.location.lng
};
GEO_CACHE[location] = info;
curr.value.Latitude = info.lat;
curr.value.Longitude = info.long;
gContext.log(location + ' -> ' + info.lat + ' ' + info.long);
} else {
gContext.log(
"Didn't find anything for " + location + ' ::' + JSON.stringify(data)
);
}
}
setTimeout(() => getGeo(itr, cb), 1000);
});
}
我们在过程中使用了一些辅助函数,这些函数有助于获取 Google JSON,以及获取和放入 GitHub JSON。
现在,如果我们在门户中运行此函数,我们将看到我们的输出

它起作用了!我们的无服务器函数使用所有新数据更新了我们的 JSON 文件。我真的很喜欢我可以使用后端服务,而无需离开我熟悉的 JavaScript。我们只需要 git pull 就可以使用此文件作为我们 Vuex 中央存储中的状态。这将允许我们填充表格,我们将在本系列的下一部分中解决这个问题,我们还将使用它来更新我们的地球仪。如果你想自己尝试无服务器函数并查看其运行情况,你可以使用 免费试用帐户 创建一个。
文章系列
- 使用无服务器函数自动更新 GitHub 文件(您就在这里!)
- 过滤和使用数据
这太棒了!莎拉,你做得太棒了!
看起来很棒,但除了活动位置之外,地球仪到底显示了什么?别针的高度和颜色代表什么?
嗨,Lukasz,
别针的高度是我们访问该地点的频率,因此,如果我们只访问过某个地点一次,别针就会很小,如果我们经常访问,比如奥兰多,别针就会非常大。第二篇文章详细介绍了它是如何实现的。颜色按与别针相同的方式进行缩放 - 更小:更黄;更大:更洋红/紫色。
谢谢,
莎拉
谢谢,莎拉!现在一切都清楚了 :)
你能详细谈谈无服务器函数在你的设置中是如何触发的吗?你将它设置为一个 GitHub web 挂钩,但如果你在存储库中实际设置了一个 web 挂钩,而存储库中包含发言人 JSON,它不会导致无限循环,因为它正在将更新推送到同一个存储库?
难以置信,这是手工编码的。难以置信。这种数学让我头晕。出色的工作!
https://github.com/sdras/cda-locale/blob/master/mixins/createGlobe.js
你选择递归调用 `getGeo(itr, cb)` 并在 `setTimeout(getGeo(...), 1000);` 中调用它,而不是迭代地调用它,是否有特殊原因?Google API 上是否有任何类型的节流机制,要求你每秒只能进行一次 API 调用?