目录
介绍
这个项目对我来说有点锻炼。我最近开始在AWS中学习初学者的东西,所以我想通过创建一个简单的API来测试Lambda函数。本文介绍了如何使用AWS Lambda(一个使用AWS Amplify的简单网站)创建API函数,并使用AWS API Gateway连接这两者。
lambda函数本身是一个用Node.js编写的cron表达式解析器。它将Cron表达式作为参数,并在该cron表达式下一次触发事件时计算并返回。
解释代码
通过API事件接收的表达式首先针对两个表达式进行测试,如果匹配,则将其拆分为要解析的数组。
var matches_POSIX = regex_POSIX.exec(testexp);
var matches_EXT = regex_EXT.exec(testexp);
var arr = [];
if(matches_POSIX === null && matches_EXT === null) {
console.log("Expression does not match!!!");
return null;
}
else if(matches_POSIX !== null) {
console.log("Expression is POSIX");
arr.push("*");
matches_POSIX.forEach((m, i) => i != 0 ? arr.push(m) : null);
arr.push("*");
if(arr[3] != "?" && arr[5] == "*") arr[5] = "?";
if(arr[5] != "?" && arr[3] == "*") arr[3] = "?";
}
else {
console.log("Expression is EXTENDED");
matches_EXT.forEach((m, i) => i != 0 ? arr.push(m) : null);
}
主要功能是getNextTime。它将遍历数组的所有元素并使用适当的参数调用resolve函数。它还将测试已解决的错误值,这基本上是返回数组的第一个元素中的值-1。
解析顺序为:年-月-星期-日-时-分-秒。这种倒推方法的原因是因为,例如,要知道一个月有多少天,我们需要知道它是哪一年,要知道某个日期的哪个工作日,我们需要知道月份和年份等。
稍后我们在确定下一个触发cron事件的时间时也会使用这种方法,因为我们希望通过获取最接近的未来日期来缩小时间窗口;这将在后面的文本中更彻底地解释。
解析函数
根据正在求值的cron表达式的哪一部分(通过第二个参数传递),这个函数被分成几个部分;参数值的含义在上面的注释中解释了:
switch(i) {
// year
case 6:
if(m === "*") return Array.apply(null, Array(10)).map(function(e, i)
{ return ts.getFullYear() + i; }); // current and next 9 years
if(!m.includes("-") && !m.includes(","))
return [parseInt(m, 10)]; // single value
return resolveList(m, i); // list of years
// month
case 4:
if(m === "*") return Array.apply(null,
Array(12)).map(function(e, i) { return i; }); // return all months
if(!m.includes("-") && !m.includes(",") && !isNaN(parseInt(m, 10)))
return [parseInt(m, 10)-1]; // single value numeric
if(isNaN(parseInt(m, 10)) && month2num(m) != -1)
return [month2num(m)-1]; // single value string
return resolveList(m, i).map(function(e,i)
{ return (e > 0 ? e-1 : e) }); // list of months
// day of month
case 3:
if(m === "?") return undefined; // empty - use dw instead
if(m === "*") return Array.apply(null, Array
(new Date(yyyy, mm+1, 0).getDate())).map(function(e, i)
{ return i + 1; }); // return all days
if(m.replace(/L|W/g,"").length != m.length)
return null; // last (week)day of month - skip,
// resolve outside this function
if(!m.includes("-") && !m.includes(","))
return [parseInt(m, 10)]; // single value
return resolveList(m, i); // list of days
// day of week
case 5:
if(m.replace(/L|#/g,"").length != m.length)
return null; // just ignore special cases,
// to be resolved outside this function
if(m === "?") return undefined; // empty - use dd instead
if(m === "*") return Array.apply(null, Array(7)).map
(function(e, i) { return i; }); // return all days
if(!m.includes("-") && !m.includes(","))
return [parseInt(m, 10)]; // single value numeric
if(isNaN(parseInt(m, 10)) && day2num(m) != -1)
return [day2num(m)]; // single value string
return resolveList(m, i); // list of days
// hour
case 2:
if(m === "*") return Array.apply(null,
Array(24)).map(function(e, i) { return i; }); // return all hours
if(!m.includes("-") && !m.includes(",") && !m.includes("/"))
return [parseInt(m, 10)]; // single value
return resolveList(m, i); // list of hours
// min / sec
case 1: case 0:
if(m === "*") return Array.apply(null, Array(60)).map(function(e, i)
{ return i; }); // return all min/sec
if(!m.includes("-") && !m.includes(",") && !m.includes("/"))
return [parseInt(m, 10 )]; // single value
return resolveList(m, i); // list of min/sec
}
该resolve函数尝试为cron的每个部分返回一个包含所有可能值的数组(年份除外;因为年份是无限的,该函数最多将返回未来10年)。
如果提供了星号(*),则表示所有的值都是可能的,因此首先使用Array.apply函数创建一个数组,以null元素为初始值,然后使用map函数设置元素,例如12个月:
if(m === "*") return Array.apply(null, Array(12)).map(function(e, i)
{ return i; }); // return all months
可能的结果要么是所有值(*),只有一个值,要么是用逗号分隔的列表或范围表示的值列表——这在名为resolveList的单独函数中解析:
function resolveList(m, i) {
var retValue = [];
var msplit;
var k;
var limit;
if(m.includes("-")) { // all in between
msplit = m.split("-").map(function(e) {
if(i == 4) e = month2num(e);
if(i == 5) e = day2num(e);
return parseInt(e, 10);
});
if (msplit[0] < msplit[1]) {
for(k = msplit[0]; k <= msplit[1]; k++) retValue.push(k);
}
else {
console.log("error: illegal expression " + m);
retValue.push(-1);
}
return retValue;
}
else if(m.includes(",")) { // all listed
m.split(",").map(function(e) { // convert to int
return parseInt(e, 10);
}).forEach(k => { // remove duplicates
//console.log("currentValue=" + k + " ; retValue=" + retValue.toString());
if(!retValue.includes(k)) retValue.push(k);
});
return retValue.sort(); // sort
//m.split(",").forEach(k => retValue.push(parseInt(k)));
}
else if(m.includes("/")) {
msplit = m.split("/");
if(msplit[0] == "*") msplit[0] = "0";
msplit = msplit.map(function(e) {
return parseInt(e, 10);
});
if(i <= 1) limit = 60; // seconds/minutes
if(i == 2) limit = 12; // seconds/minutes
for(k = msplit[0] + msplit[1]; k <= limit;
k = k + msplit[1]) retValue.push(k == limit ? 0 : k);
}
return retValue;
}
特别案例
所有特殊值,例如L、W或_#_都在resolve函数之外解析,因为它们只有在我们已经缩小时间范围后才能解析。它们在函数specialDay和specialDate中解析,直接从主函数调用:
function specialDate(exp, mm, yyyy) {
if(exp == "L")
return new Date(yyyy, mm + 1, 0).getDate();
if(exp == "LW")
for(var i = new Date(yyyy, mm + 1, 0).getDate(); i > 0; i--) {
if([1, 2, 3, 4, 5].includes(new Date(yyyy, mm, i).getDay()))
return i;
}
if(exp.substring(0, 1) == "L-")
if(!isNaN(parseInt(exp.substring(2), 10)))
return new Date(yyyy, mm + 1, 0).getDate()+1 -
parseInt(exp.substring(2), 10);
}
function specialDay(exp, mm, yyyy) {
if(exp.includes("L"))
for(var i = new Date(yyyy, mm + 1, 0).getDate(); i > 0; i--)
{ // start from end of month and look for last specified weekday in that month
if(parseInt(exp.substring(0,1),10) == new Date(yyyy, mm, i).getDay())
return i;
}
if(exp.includes("#")) {
var n = 0;
for(i = 1; i <= new Date(yyyy, mm + 1, 0).getDate(); i++)
{ // start from beginning of the month and count (n)
// the occurences of specified weekday in that month
if(parseInt(exp.substring(0,1),10) == new Date(yyyy, mm, i).getDay()) {
n++;
if(n == parseInt(exp.substring(2,3),10)) return i;
i = i+6;
}
}
}
return undefined;
}
AWS设置
AWS Lambda
转到AWS Lambda并单击“创建函数”。
选择“Author from scratch”,输入函数名,即“cron-parse”,在“Runtime”下选择“Node.js”,点击“Create function”。
您将看到一个带有“Function overview”和“Code Source”部分的屏幕:
将您的代码粘贴到“index.js”下,然后单击“部署”。
在“测试”下拉菜单(箭头菜单)下,点击“配置测试事件”。有了这个,您可以指定一个测试表达式(JSON)并查看结果输出。你可以输入例如:
{
"key1": "5/15 12-15 3-5 ? 7,5,3 WED-FRI 2021-2022"
}
为您的活动命名,比方说test1,然后按“创建”按钮。
然后,当您单击“测试”按钮时,您应该得到以下输出:
AWS API网关
转到AWS API Gateway并单击“创建 API”。
点击“Rest API”下的“Build”。
选择“REST – 新API”。输入名称,例如cronAPI。
在“端点类型”下,选择“边缘优化”。
点击“创建API ”。
在“资源”下,转到“操作”菜单并选择“创建方法”。
在创建的方法中,选择“POST”并点击确认标记。
选择“Lambda函数”作为“集成类型”(确保您使用的区域与创建Lambda函数的区域相同),输入您的Lambda函数的名称 (cron-parse) 并单击“保存”,然后单击“确定”。
再次转到“操作”菜单,选择“启用CORS”,然后单击“启用CORS并替换现有...”
从“Actions”菜单中,选择“Deploy API”,“Deployment stage”=“New stage”,输入stag名称(例如:dev),然后点击“Deploy”。
从页面顶部复制“调用URL ”——稍后您将需要它。
AWS Amplify
转到AWS Amplify,一直往下走,然后单击“Develop”块中的“Get Started” 。
输入应用名称,例如“Cron parser”。确认部署并放松并享受AWS在后台为您做的一切。
转到“前端环境”选项卡,选择“不使用 Git提供程序部署”,然后单击“连接分支”。
在您的计算机上(本地),使用以下代码创建一个名为“index.html”的简单HTML文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Cron parser</title>
<script>
// define the callAPI function that takes a first name and last name as parameters
var callAPI = (key1)=>{
// instantiate a headers object
var myHeaders = new Headers();
// add content type header to object
myHeaders.append("Content-Type", "application/json");
// using built in JSON utility package turn object
// to string and store in a variable
var raw = JSON.stringify({"key1":key1});
// create a JSON object with parameters for API call and store in a variable
var requestOptions = {
method: 'POST',
headers: myHeaders,
body: raw,
redirect: 'follow'
};
// make API call with parameters and use promises to get response
fetch("YOUR API INVOKE URL HERE", requestOptions)
.then(response => response.text())
.then(result => document.getElementById('result').innerHTML =
result/*alert(JSON.parse(result).body)*/)
.catch(error => document.getElementById('result').innerHTML =
error/*console.log('error', error)*/);
}
</script>
</head>
<body>
<label for="incron">Enter Cron expression here </label>
<input type="text" name="incron" id="incron">
<button type="button" onclick="callAPI(document.getElementById('incron').value)">
Submit</button><br/><hr/><br/>
<label for="result">Result: </label><div name="result" id="result"></div>
</body>
</html>
将“YOUR API INVOKE URL HERE”替换为您在上一步中保存的URL。
保存“index.html”,然后ZIP(压缩)它(您可以随意命名ZIP)。
现在返回AWS控制台(您的Amplify应用程序),然后单击“选择文件”,然后打开包含“index.html”的zip 。保存并部署。
就是这样。现在您可以使用“Domain”下的URL打开您的HTML网页。输入一个cron表达式来测试它并单击Submit,您的API应该在Result下返回一个值。