React CRON定时器

  1. CRON概念
  2. REACT框架下制作CRON定时器
  3. 参考:在线CRON表达式生成器
  4. Github Repository - Cron Generator
  5. 使用UI组件库:Alibaba Fusion

一、CRON 概念

  1. 七个阈:秒、分、小时、日、月、星期、年
  2. 阈值详解:
cron Seconds Minutes Hours Day of Month Month Day of Week Year
阈名 小时 星期
数值范围 0-59 0-59 0-23 0-31 1-12 1-7 1970 - 2099
表达式 , - * / , - * / , - * / , - * /
? L W C
, - * / , - * /
? L C #
, - * /
  1. 表达式解释:
表达式 解释 样例
- 表示范围 周期从第1秒到第5秒:1-5
/ 起始触发事件/相隔时间 从第5min开始,每20min触发一次(5min,25min,45min):5/20
, 枚举值 5,12,20
L 用于“星期(Day of Week)”、“日(Day of Month)”中,表示最后。
备注:使用L的时候,不要指定范围
本月最后一个周五触发:5L
W 用于“日(Day of Month)”中,表示距离X日最近的一个本月的工作日 距离5号的最近的一个工作日:5W
LW 某月最后一个工作日(周五)
# 用于“星期(Day of Week)”,表示每月第几个星期几 第二周的周四:2#4
? 用于“星期(Day of Week)”、“日(Day of Month)”中,表示“不指定”

备注:关于C的解释暂未找到相关细节

  1. 特殊约束:
  • 选择月份的时候,日期不能为 * 和 ?
  • 星期默认值为 ?
  • 不能同时指定星期和日期,其中之一必须为 ?

二、构建核心代码模块

  1. 分析:
  • 七个阈值共同的部分:


    七个阈共同内容:, - * /
  • 日期特殊部分:


    日期特殊部分:? L W
  • 星期(周)特殊部分:


    星期特殊部分:? L #
  1. 根据cron七个阈,创建所需使用的state对象:
state = {
        // 各个cron阈表达式
        express: {
            second: '*',
            minute: '*',
            hour: '*',
            date: '*',
            month: '*',
            week: '?',
            year: '*'
        },

        // 每个阈选择的表达式类型
        expressType: {
            second: 'everyTime',
            minute: 'everyTime',
            hour: 'everyTime',
            date: 'everyTime',
            month: 'everyTime',
            week: 'everyTime',
            year: 'everyTime',
        },

        // 周期选择 - 最小时间 / 最大时间
        periodTime: {
            second: { max:2, min:1 },
            minute: { max: 2, min: 1 },
            hour: { max: 2, min: 1 },
            date: { max: 2, min: 1 },
            month: { max: 2, min: 1 },
            week: { max: 2, min: 1 },
            year: { max: 2020, min: 2020 },
        },

        // 循环选择 - 开始时间./ 执行周期
        loopTime: {
            second: { startTime:1, period:1 },
            minute: { startTime: 1, period: 1 },
            hour: { startTime: 1, period: 1 },
            date: { startTime: 1, period: 1 },
            month: { startTime: 1, period: 1 },
        },

        // 枚举指定选择 - 指定时间节点数组,例如日期数组
        enumTime: {
            second: [],
            minute: [],
            hour: [],
            date: [],
            month: [],
            week: []
        },

        // 最近工作日 - 距离X日最近的工作日
        mostRecentWorkDay: 1,

        // 当前月份最后一个星期X
        lastWeekDay: 1,

        // 指定X周的星期X - 例如:第一周的星期三
        weekday: {
            weekNum: 1,
            weekDayNum: 1
        },

        alertMsg: null
    }
  1. 选择每个阈的类型的时候,根据所选值,获取当前域的表达式
    /**
  * @name: 选择每个表达式的具体展示类型
  * @param {*} type 表达式内容类型:everyTime, period, loop, enum 等
  * @param {*} expressType cron类型:second, minute, hour, date, month, week, year
  * @return {*}
  */
    onSelectType = (type, expressType) => {
        let expressionTypeState = this.state.expressType
        expressionTypeState[expressType] = type

        this.setState({ expressType: expressionTypeState }, () => {
            // 特殊逻辑判断,获取最新表达式
            this.judgeSpecialCRON(type, expressType)
        })
    }

/**
  * @name: 表达式校验并对表达式进行赋值
  * @param {*} type 当前选中阈值的表达式类型,例如:everyTime
  * @param {*} expressType 当前选中的阈值的类型,例如:date、minute
  * @return {*}
  */
    judgeSpecialCRON = (type, expressType) => {
        let expressState = JSON.parse(JSON.stringify(this.state.express))

        if (type === this.state.expressType[expressType]) {
            expressState[expressType] = this.getExpressStr(type, expressType)
        }

        // 特殊赋值逻辑
        switch (expressType) {
            case 'date': {
                // 日期和星期不可同时为?(不指定)
                if (expressState.date === '?' && expressState.week === '?') {
                    Message.warning('日期和星期不可同时均不指定任何值')
                    expressState.week = '*'
                }
                // 日期和星期不可同时指定内容(非?)
                if (expressState.date !== '?' && expressState.week !== '?') {
                    Message.warning('日期和星期不可同时指定任何值')
                    expressState.week = '?'
                }
                break
            }
            case 'month': {
                // 当月份选择为*(任意)时,星期设定为?(不指定)
                Message.warning('当月份选择为任意值时,星期设定为不指定模式(?)')
                if (expressState.month === '*') {
                    expressState.week = '?'
                    if (expressState.date === '?') {
                        expressState.date = '*'
                    }
                }
                break
            }
            case 'week': {
                // 日期和星期不可同时为?(不指定)
                if (expressState.week === '?' && expressState.date === '?') {
                    expressState.date = '*'
                    Message.warning('日期和星期不可同时均不指定任何值')
                }
                // 日期和星期不可同时指定内容(非?)
                if (expressState.week !== '?' && expressState.date !== '?') {
                    expressState.date = '?'
                    Message.warning('日期和星期不可同时指定任何值')
                }
                break
            }
            default: break
        }

        this.setState({ express: expressState })
    }
  1. 根据每个阈的规则,获取每个阈的cron表达式:
  /**
  * @name: 获取每个cron阈的表达式字符串
  * @param {*} type
  * @param {*} expressType
  * @return {*}
  */
    getExpressStr = (type, expressType) => {
            switch (type) {
                case 'everyTime': return '*'
                case 'none': return '?'
                case 'lastDay': return 'L'
                case 'period': return this.expressGenerator(type, this.state.periodTime[expressType])
                case 'loop': return this.expressGenerator(type, this.state.loopTime[expressType])
                case 'enum': return this.expressGenerator(type, this.state.enumTime[expressType])
                case 'mostRecentWorkDay': return this.state.mostRecentWorkDay + 'W'
                case 'lastWeekDay': return this.state.lastWeekDay + 'L'
                case 'weekday': return this.expressGenerator(type, this.state.weekday)
                case 'optional': return ''
                default: return
            }
    }

  /**
  * @name: 获取cron表达式
  * @param {type}
  * @return {type}
  */
    expressGenerator = (type, value) => {
        function getEnumString (values) {
            if (values.length > 0) {
                let str = ''
                values.map(item => {
                    str = str + ',' + item
                })
                str = str.substring(1, str.length)
                return str
            }
            else
                return '*'
        }
        switch (type) {
            case 'everyTime': return '*'
            case 'period': return value ? value.min + '-' + value.max : ''
            case 'loop': return value ? value.startTime + '/' + value.period : ''
            case 'enum': return getEnumString(value)
            case 'weekday': return value ? value.weekNum + '#' + value.weekDayNum : ''
            default: return '*'
        }
    }
  1. 反解析cron表达式到UI的方法:
/**
  * @name: 反编译解析表达式
  * @param {*} express 表达式对象
  * @return {*}
  */ 
    reverseGenerateCRON = (express) => {
        let newExpressType = JSON.parse(JSON.stringify(this.state.expressType))

        newExpressType.second = this.getType(express.second, 'second')
        newExpressType.minute = this.getType(express.minute, 'minute')
        newExpressType.hour = this.getType(express.hour, 'hour')
        newExpressType.date = this.getType(express.date, 'date')
        newExpressType.month = this.getType(express.month, 'month')
        newExpressType.week = this.getType(express.week, 'week')
        newExpressType.year = this.getType(express.year, 'year')

        this.setState({ expressType: newExpressType })

    }

 /**
  * @name: 反解析cron表达式到UI时,每个阈内容进行解析 - 获取每个阈使用的类型和具体值
  * @param {*} expStr cron表达式内容,例如:*,5L
  * @param {*} expressType cron阈类型,例如:minute,hour,week
  * @return {*} 每个阈使用的类型
  */ 
    getType = (expStr, expressType) => {
        if (expStr === '*') return 'everyTime'

        else if (expStr === '?') return 'none'

        else if (expStr === 'L') return 'lastDay'

        else if (new RegExp('[-]').test(expStr)) {
            let newPeriodTime = this.state.periodTime
            let values = expStr.split('-')
            newPeriodTime[expressType] = { max: parseInt(values[1]), min: parseInt(values[0]) }

            this.setState({ periodTime: newPeriodTime })
            return 'period'
        }

        else if (new RegExp('[/]').test(expStr)) {
            let newLoopTime = this.state.loopTime
            let values = expStr.split('/')
            newLoopTime[expressType] = { startTime: parseInt(values[0]), period: parseInt(values[1]) }

            this.setState({ loopTime: newLoopTime })
            return 'loop'
        }

        else if (new RegExp('[,]').test(expStr)) {
            let newEnumTime = this.state.enumTime
            let values = expStr.split(',')
            for (let i = 0; i < values.length; i++) {
                values[i] = parseInt(values[i])
            }
            newEnumTime[expressType] = values

            this.setState({ enumTime: newEnumTime })
            return 'enum'
        }

        else if (new RegExp('[W]').test(expStr)) {
            let value = parseInt(expStr.substring(0, 1))
            console.log(value)
            this.setState({ mostRecentWorkDay: value })
            return 'mostRecentWorkDay'
        }

        else if (new RegExp('[L]').test(expStr)) {
            this.setState({ lastWeekDay: parseInt(expStr.substring(0, 1)) })
            return 'lastWeekDay'
        }

        else if (new RegExp('[#]').test(expStr)) {
            let newWeekday = this.state.weekday
            let values = expStr.split('#')

            newWeekday = { weekNum: parseInt(values[0]), weekDayNum: parseInt(values[1]) }

            this.setState({ weekday: newWeekday })
            return 'weekday'
        }

        else if (expStr === '') {
            return 'optional'
        }

        else return
    }
  1. render()渲染部分代码:请参考github代码内容

  2. 组件引用方法:

import CronGenerator from '@/components/CronGenerator'
export default class App extends React.Component {
  state = {
    cronGeneratorVisible: false,
    cronString: '* * * ? * * *'
  }

  onClose = () => {
    this.setState({ cronGeneratorVisible: false })
  }
  
  onConfirm = (val) => {
    this.setState({ cronString: val})
  }

  render () {
    return (
      <div className="cronGeneratorApp">
          <p>CRON Generator Sample:</p>
          <div className="cron_input">
            <Input value={cronString} />
            <Button type="primary" onClick={() => { this.setState({ cronGeneratorVisible: true }) }}>CRON</Button>
          </div>

          <CronGenerator
            isPreview
            initCron={cronString}
            dialogVisible={cronGeneratorVisible}
            onClose={this.onClose}
            onConfirm={this.onConfirm}
          />
        </div>
    )
  }
}
参数名称 参数描述 参数类型 默认值 备注
isPreview 是否开启预览模式 Boolean false 该模式下,将不会显示新增取消按钮
initCron 需传入解析到UI界面的CRON字符串 String - -
dialogVisible CRON对话框显示 Boolean false -
onClose 关闭对话框对应方法,且取消获取CRON Function - -
onConfirm 关闭对话框且获取到最新的CRON表达式 Function - 该方法中会传入一个参数val,代表生成的cron表达式

三、结果页面:

首页 - 文本输入框 + 获取cron表达式按钮,包含初始化默认表达式
second
minute
hour
Day of Month
month
Day of Week
year
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容