React Antd 前端生成cron表达式

不知道你在玩游戏的时候是否发现过以下情况:

(1)玩某些游戏的时候,发现他的排行榜并不是时时更新的,而是每半个小时,或者一个小时更新一次。

(2)又比如很火的王者荣耀手游,它的日常任务,都是每天5点进行更新。

那么,这些时间控制,到底是由谁控制得如此精准呢?原来,这些都是corn表达式的功劳。

【1】什么是cron表达式 ?

在了解之前,我们先举几个例子,看看corn表达式长什么样子
cron="0 */5 * * * ?"

cron="0 30 8,10,12,14,16 * * ?"

cron="0 0 */1 * * ?"

可以看到,corn表达式就是:由若干数字、空格、符号按一定的规则,组成的一组字符串,从而表达时间的信息。 

好像和正则表达式有点类似哈,都是一个字符串表示一些信息。 但比正则表达式要简单很多,在刚接触正则的时候,我们形容他是鬼画符(“鬼才知道你写什么”),道士的符号只有道士能看懂。而cron表达式看完下面内容你半小时就懂了,并且会写了

【2】cron 表达式标准结构

那么这个规则究竟是怎样的呢?我们继续往下了解。

从上面的例子中,我们可以看到,这个字符串被5个空格分成了6个部分。

假设我们以ABCDEF举例,它的标准格式为:"A B C D E F" 。

【3】cron 表达式具体含义

这个ABCDEF到底是什么意思呢?接下来是画重点的地方,认真看:

A表示秒,B表示分,C表示小时,D表示日,E表示月,F表示星期

故:"A B C D E F" --> "秒 分 时 日 月 星期"

这里也有带年的,星期后面就是年,但这个年可加可不加,加上就是 "A B C D E F G" 格式。

为什么通常不加年呢?你见过哪些程序会指定在哪一年跑的?或者每几年跑一次的?

所以,年的实用性不大,加上又为了书写方便,规则上就干脆省掉了!当然加上也没错!

【4】前例解释

cron="0 */5 * * * ?"

我们可以了解到:

它的秒位为0,表示每个0秒,分位为*/5,意思是每5分钟。所以总的来说就是每5分钟(每5分0秒)时执行一次;

cron="0 30 8,10,12,14,16 * * ?"

它的秒位为0,分位为30,时位为一串集合,则它的意思是每天 8点半、10点半、12点半、14点半、16点半各执行一次;

cron="0 0 */1 * * ?"

我想你已经知道它的意思了,没错,它就是每个小时整点(整点0分0秒)执行一次。

从上面三个列子中,我想你已经了解了orcn表达式的大致意思了:数字则表示具体时间,* 则表示任意时间,*/x 则表示每多少时间,还可以用集合表示具体的几个时间点。

【5】更多例子

(5.1)用短横线(-)表示时间段:

比如:我们的上班时间朝9晚6为(周一到周五的早上9点到晚上6点),则cron表达式为:0 0 9-18 * * MON-FRI

星期一到星期天的英文为:Monday,Tuesday、Wednesday、Thursday、Friday、Saturday 、Sunday ,取前三个字母,然后大写表示星期。

(5.2)用L表示最后,L是单词Last(最后的)的首字母:

比如:假设每个最后一天,下午2点发工资的时间,则cron表达式为:0 0 14 L * ?

注:如果没有具体说明是星期几,通常用问号代替。

其他不常用的功能 #,W 这里不就介绍了,想知道的朋友可以自己去查阅资料,因为在实际工作中,用得极少。

而我们最常用的就是在某个时间点,或者某些时间点执行程序,也就是【4】中的前例解释。

【6】cron 表达式用途

如前言所示,cron 表达式最主要的就是在程序中做一些定时任务,比如某些系统的报表数据,某些游戏的排行榜,由于这些数据量实时统计非常消耗程序性能,所以就每隔一段时间,通过自动任务跑一次,这样可以极大的提升用户浏览体验,要是在游戏里,还可以增加一种神秘感。

另外,某些具体点的数据拉取,比如你如果从事平台对接工作,要从某些平台下载你的订单,那么肯定是每隔多久抓一次。

目前常见的有2种Cron表达式,Linux Java,这里不多做扩展, 感兴趣的同学可以自行学习, 我这里做了一种Java的生成,废话不多说, 上图

 
先上index整合文件

import React, { useState, useEffect } from 'react';
import { Tabs, Button } from 'antd';
import MinutePane from './MinutePane';
import HourPane from './HourPane';
import DayPane from './DayPane';
import MonthPane from './MonthPane';
import WeekPane from './WeekPane';
import YearPane from './YearPane';
import { minuteRegex, hourRegex, dayRegex, monthRegex, weekRegex, yearRegex } from './cron-regex';
import styles from './index.less';

const { TabPane } = Tabs;
const tabPaneStyle = { paddingLeft: 10, paddingBottom: 8, marginTop: -10 };
const getTabTitle = (text) => <div style={{ width: 50, textAlign: 'center' }}>{text}</div>;

function Cron(props) {
  const { style, footerStyle, value, onOk } = props;
  const [currentTab, setCurrentTab] = useState('2');
  const [second, setSecond] = useState('0');
  const [minute, setMinute] = useState('0');
  const [hour, setHour] = useState('0');
  const [day, setDay] = useState('*');
  const [month, setMonth] = useState('*');
  const [week, setWeek] = useState('?');
  const [year, setYear] = useState('*');

  const onParse = () => {
    if (value) {
      try {
        let [secondVal, minuteValue, hourVal, dayVal, monthVal, weekVal, yearVal] = value.split(' ');
        secondVal = 0;
        minuteValue = minuteRegex.test(minuteValue) ? minuteValue : '0';
        hourVal = hourRegex.test(hourVal) ? hourVal : '0';
        dayVal = dayRegex.test(dayVal) ? dayVal : '*';
        monthVal = monthRegex.test(monthVal) ? monthVal : '*';
        weekVal = weekRegex.test(weekVal) ? weekVal : '?';
        weekVal = dayVal !== '?' ? '?' : weekVal;
        yearVal = yearRegex.test(yearVal) ? yearVal : '*';
        setSecond(secondVal);
        setMinute(minuteValue);
        setHour(hourVal);
        setDay(dayVal);
        setMonth(monthVal);
        setWeek(weekVal);
        setYear(yearVal);
      } catch (error) {
        setSecond('0');
        setMinute('0');
        setHour('0');
        setDay('*');
        setMonth('*');
        setWeek('?');
        setYear('*');
      }
    }
  };

  useEffect(() => {
    onParse();
  }, [value]);

  const onGenerate = () => {
    if (onOk) {
      onOk([second, minute, hour, day, month, week, year].join(' '));
    }
  };

  const onChangeDay = (v) => {
    setDay(v);
    if (v !== '?') {
      setWeek('?');
    }
  };

  const onChangeWeek = (v) => {
    setWeek(v);
    if (v !== '?') {
      setDay('?');
    }
  };

  return (
    <div
      className={styles.root}
      style={{
        borderRadius: '4px',
        outline: 'none',
        boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)',
        ...style,
      }}
    >
      <Tabs tabBarGutter={0} animated destroyInactiveTabPane activeKey={currentTab} onChange={setCurrentTab}>
        <TabPane tab={getTabTitle('分')} key="2" style={tabPaneStyle}>
          <MinutePane value={minute} onChange={setMinute} />
        </TabPane>
        <TabPane tab={getTabTitle('时')} key="3" style={tabPaneStyle}>
          <HourPane value={hour} onChange={setHour} />
        </TabPane>
        <TabPane tab={getTabTitle('日')} key="4" style={tabPaneStyle}>
          <DayPane value={day} onChange={onChangeDay} />
        </TabPane>
        <TabPane tab={getTabTitle('月')} key="5" style={tabPaneStyle}>
          <MonthPane value={month} onChange={setMonth} />
        </TabPane>
        <TabPane tab={getTabTitle('周')} key="6" style={tabPaneStyle}>
          <WeekPane value={week} onChange={onChangeWeek} />
        </TabPane>
        <TabPane tab={getTabTitle('年')} key="7" style={tabPaneStyle}>
          <YearPane value={year} onChange={setYear} />
        </TabPane>
      </Tabs>
      <div style={{ borderTop: '1px solid #e8e8e8', padding: 10, textAlign: 'right', ...footerStyle }}>
        <Button type="primary" onClick={onGenerate}>
          生成
        </Button>
      </div>
    </div>
  );
}

export default Cron;

cron-regex cronValue的正则取值

/* eslint-disable max-len */
export const secondRegex = /^\*$|(^([0-9]|[1-5][0-9])-([0-9]|[1-5][0-9])$)|(^([0-9]|[1-5][0-9])\/\d+$)|(^(([0-9]|[1-5][0-9]),)*([0-9]|[1-5][0-9])$)/;
export const minuteRegex = /^\*$|(^([0-9]|[1-5][0-9])-([0-9]|[1-5][0-9])$)|(^([0-9]|[1-5][0-9])\/\d+$)|(^(([0-9]|[1-5][0-9]),)*([0-9]|[1-5][0-9])$)/;
export const hourRegex = /(^\*$)|(^([0-9]|(1[0-9])|(2[0-3]))-([0-9]|(1[0-9])|(2[0-3]))$)|(^([0-9]|(1[0-9])|(2[0-3]))\/\d+$)|(^(([0-9]|(1[0-9])|(2[0-3])),)*([0-9]|(1[0-9])|(2[0-3]))$)/;
export const dayRegex = /^\*$|^\?$|(^([1-9]|[1-2][0-9]|3[0-1])-([1-9]|[1-2][0-9]|3[0-1])$)|(^([1-9]|[1-2][0-9]|3[0-1])\/\d+$)|(^(([1-9]|[1-2][0-9]|3[0-1]),)*([1-9]|[1-2][0-9]|3[0-1])$)/;
export const monthRegex = /^\*$|(^([1-9]|1[0-2])-([1-9]|1[0-2])$)|(^([1-9]|1[0-2])\/\d+$)|(^(([1-9]|1[0-2]),)*([1-9]|1[0-2])$)/;
export const weekRegex = /^\*$|^\?$|(^(SUN|MON|TUE|WED|THU|FRI|SAT)-(SUN|MON|TUE|WED|THU|FRI|SAT)$)|(^(SUN|MON|TUE|WED|THU|FRI|SAT)#\d+$)|(^(SUN|MON|TUE|WED|THU|FRI|SAT)L$)|(^((SUN|MON|TUE|WED|THU|FRI|SAT),)*(SUN|MON|TUE|WED|THU|FRI|SAT)$)/;
export const yearRegex = /^\*$|^\?$|(^(2019|20[2-5][0-9]|206[0-6])-(2019|20[2-5][0-9]|206[0-6])$)|(^(2019|20[2-5][0-9]|206[0-6])\/\d+$)|(^((2019|20[2-5][0-9]|206[0-6]),)*(2019|20[2-5][0-9]|206[0-6])$)/;

进入其中一个模块(我这里秒分时日月周年都一样 ,随机以月份month为例)

 

 

// index布局页面
import { Radio } from 'antd';
import React from 'react';
import InputFromInterval from './InputFromInterval';
import InputFromTo from './InputFromTo';
import InputSpecified from './InputSpecified';

const radioStyle = { display: 'block', lineHeight: '32px' };

function MonthPane(props) {
    const { value, onChange } = props;
    let currentRadio = 0;
    if (value === '*') {
        currentRadio = 0;
    } else if (value.indexOf('-') > -1) {
        currentRadio = 1;
    } else if (value.indexOf('/') > -1) {
        currentRadio = 2;
    } else {
        currentRadio = 3;
    }

    const onChangeRadio = (e) => {
        const valueType = e.target.value;
        const defaultValues = ['*', '1-1', '1/1', '1'];
        onChange(defaultValues[valueType]);
    };

    return (
        <Radio.Group style={{ width: '100%' }} value={currentRadio} onChange={onChangeRadio}>
            <Radio style={radioStyle} value={0}>
                每一月
            </Radio>
            <Radio style={radioStyle} value={1}>
                <InputFromTo disabled={currentRadio !== 1} value={value} onChange={onChange} />
            </Radio>
            <Radio style={radioStyle} value={2}>
                <InputFromInterval disabled={currentRadio !== 2} value={value} onChange={onChange} />
            </Radio>
            <Radio style={radioStyle} value={3}>
                <InputSpecified disabled={currentRadio !== 3} value={value} onChange={onChange} />
            </Radio>
        </Radio.Group>
    );
}

export default MonthPane;

从X月到Y月,每月执行一次

import React from 'react';
import { InputNumber } from 'antd';

function InputFromTo(props) {
    const { disabled, value, onChange } = props;
    let from = 0;
    let to = 0;
    if (!disabled) {
        [from, to] = value.split('-').map((v) => parseInt(v, 10));
    }
    const onChangeFrom = (v) => onChange(`${v || 0}-${to}`);
    const onChangeTo = (v) => onChange(`${from}-${v || 0}`);

    return (
        <React.Fragment>
            从&nbsp;
            <InputNumber
                disabled={disabled}
                min={1}
                max={12}
                value={from}
                size="small"
                onChange={onChangeFrom}
                style={{ width: 100 }}
            />
            &nbsp;-&nbsp;
            <InputNumber
                disabled={disabled}
                min={1}
                max={12}
                value={to}
                size="small"
                onChange={onChangeTo}
                style={{ width: 100 }}
            />
            &nbsp;月,每月执行一次
        </React.Fragment>
    );
}

export default InputFromTo;

从X月开始,每N个月执行一次
 

import React from 'react';
import { InputNumber } from 'antd';

function InputFromInterval(props) {
    const { disabled, value, onChange } = props;
    let from = 0;
    let interval = 0;
    if (!disabled) {
        [from, interval] = value.split('/').map((v) => parseInt(v, 10));
    }
    const onChangeFrom = (v) => onChange(`${v || 0}/${interval}`);
    const onChangeInterval = (v) => onChange(`${from}/${v || 0}`);

    return (
        <React.Fragment>
            从&nbsp;
            <InputNumber
                disabled={disabled}
                min={1}
                max={12}
                value={from}
                size="small"
                onChange={onChangeFrom}
                style={{ width: 100 }}
            />
            &nbsp;月开始, 每&nbsp;
            <InputNumber
                disabled={disabled}
                min={1}
                max={12}
                value={interval}
                size="small"
                onChange={onChangeInterval}
                style={{ width: 100 }}
            />
            &nbsp;月执行一次
        </React.Fragment>
    );
}

export default InputFromInterval;

最后,指定某个月份执行

import React, { useMemo } from 'react';
import { Checkbox, Row, Col } from 'antd';

function InputSpecified(props) {
    const { disabled, value, onChange } = props;
    let selected = [];
    if (!disabled) {
        selected = value.split(',').map((v) => parseInt(v, 10));
    }
    const onChangeSelected = (v) => onChange(v.length === 0 ? '1' : v.join(','));

    const checkList = useMemo(() => {
        const checks = [];
        for (let i = 1; i < 13; i++) {
            checks.push(
                <Col key={i} span={4}>
                    <Checkbox disabled={disabled} value={i}>
                        {i}
                    </Checkbox>
                </Col>,
            );
        }
        return checks;
    }, [disabled]);

    return (
        <React.Fragment>
            指定
            <br />
            <Checkbox.Group style={{ width: '100%' }} value={selected} onChange={onChangeSelected}>
                <Row>{checkList}</Row>
            </Checkbox.Group>
        </React.Fragment>
    );
}

export default InputSpecified;

如有不妥,请指教,谢谢

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值