AWS Lambda中的Cron表达式解析器

目录

介绍

解释代码

解析函数

特别案例

AWS设置

AWS Lambda

AWS API网关

AWS Amplify


介绍

这个项目对我来说有点锻炼。我最近开始在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;
    }

特别案例

所有特殊值,例如LW_#_都在resolve函数之外解析,因为它们只有在我们已经缩小时间范围后才能解析。它们在函数specialDayspecialDate中解析,直接从主函数调用:

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 overviewCode 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 APIDeployment 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.htmlzip 。保存并部署。

就是这样。现在您可以使用Domain下的URL打开您的HTML网页。输入一个cron表达式来测试它并单击Submit,您的API应该在Result下返回一个值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值