package crontab import ( "errors" "fmt" "log" "regexp" "strconv" "strings" "time" clinked "474420502.top/eson/structure/circular_linked" "github.com/Pallinder/go-randomdata" "github.com/davecgh/go-spew/spew" ) type randLR struct { left, right int } type hInterval struct { PlanFail []randLR PlanNormal []randLR Count int ConstCount int } func (interval *hInterval) reset() { interval.Count = interval.ConstCount } type timePointer struct { left, right int leftlimit, rightlimit int per int isAll bool } func (tp *timePointer) String() string { return fmt.Sprintf("left: %d, right: %d, leftlimit: %d, rightlimit: %d, per: %d", tp.left, tp.right, tp.leftlimit, tp.rightlimit, tp.per) } // Crontab 的string解析 type Crontab struct { min []timePointer hour []timePointer day []timePointer month []timePointer week []timePointer WillPlans []time.Time SkipPlans []time.Time YearPlan *trieYear interval *clinked.CircularLinked lastStatus bool nextTime time.Time } // NewCrontab create 一个crontab func NewCrontab(crontab string) *Crontab { cron := &Crontab{} cron.FromString(crontab) return cron } // UnmarshalYAML 添加序列化接口 Marshal没实现, 需要的时候可以自己添加 func (cron *Crontab) UnmarshalYAML(unmarshal func(interface{}) error) error { var buf string err := unmarshal(&buf) if err != nil { return nil } if err := cron.FromString(buf); err != nil { return err } return nil } // SetStatus 设置状态 接口定义 func (cron *Crontab) SetStatus(status interface{}) { if cron.interval != nil { cron.lastStatus = status.(bool) } } // GetStatus 设置状态 接口定义 func (cron *Crontab) GetStatus() (status interface{}) { if cron.interval != nil { return cron.lastStatus } return nil } // TimeUp 是否时间快到 func (cron *Crontab) TimeUp() bool { if cron.interval != nil { return cron.intervalTimeUp() } return cron.linuxTimeUp() } // NextTime 返回下次任务的时间 func (cron *Crontab) NextTime() time.Time { if cron.interval != nil { return cron.nextTime } if len(cron.WillPlans) > 0 { return cron.WillPlans[0] } return time.Now().Add(time.Second * 2) } func (cron *Crontab) String() string { return fmt.Sprintf("min:%s\nhour:%s\nday:%s\nmonth:%s\nweek:%s\n", spew.Sdump(cron.min), spew.Sdump(cron.hour), spew.Sdump(cron.day), spew.Sdump(cron.month), spew.Sdump(cron.week)) } // FromString 解析crontab 的 表达式 func (cron *Crontab) FromString(crontab string) error { crontab = strings.TrimSpace(crontab) matches := regexp.MustCompile("[^ ]+").FindAllString(crontab, -1) mlen := len(matches) switch mlen { case 1: // "f1-2|5-10x5,f1|10m,10-15,f1" cron.lastStatus = true cron.interval = clinked.NewCircularLinked() var intervalList []interface{} intervalList = parseIntervalString(matches[0]) cron.interval.Append(intervalList...) cron.TimeUp() case 5: cron.min = createTimePointer(matches[0], 0, 59, true) cron.hour = createTimePointer(matches[1], 0, 23, true) cron.day = createTimePointer(matches[2], 1, 31, false) cron.month = createTimePointer(matches[3], 1, 12, true) cron.week = createTimePointer(matches[4], 0, 6, true) cron.createYearPlan() cron.TimeUp() default: return errors.New("mathches len != want, check crontab string") } return nil } // createYearPlan 创建年度计划 func (cron *Crontab) createYearPlan() { cron.YearPlan = newTrieYear() cron.YearPlan.FromCrontab(cron) } func (cron *Crontab) linuxTimeUp() bool { now := time.Now() maxlen := 1000 createlen := 500 plen := len(cron.WillPlans) if plen <= createlen { var lastplan time.Time if plen == 0 { lastplan = now } else { lastplan = cron.WillPlans[plen-1].Add(time.Minute) } if !cron.YearPlan.CheckYear() { cron.createYearPlan() } timeplans := cron.YearPlan.GetPlanTime(cron, lastplan, uint(maxlen-plen)) cron.WillPlans = append(cron.WillPlans, timeplans...) } if len(cron.WillPlans) > 0 { istimeup := false for i := 0; i < maxlen; i++ { if now.Unix() >= cron.WillPlans[i].Unix() { istimeup = true } else { if istimeup { if i-1 > 0 { cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans[0:i-1]...) if len(cron.SkipPlans) >= maxlen+200 { cron.SkipPlans = cron.SkipPlans[200:] } } cron.WillPlans = cron.WillPlans[i:] return istimeup } return istimeup } } cron.SkipPlans = append(cron.SkipPlans, cron.WillPlans...) cron.WillPlans = nil return istimeup } log.Panicln("error willplans range") return false } func (cron *Crontab) intervalTimeUp() bool { now := time.Now() if now.Unix() >= cron.nextTime.Unix() { iv := cron.interval.Cursor().GetValue().(hInterval) isecond := 0 if cron.lastStatus == false && len(iv.PlanFail) > 0 { idx := randomdata.Number(len(iv.PlanFail)) lr := iv.PlanFail[idx] isecond = randomdata.Number(lr.left, lr.right+1) } else { idx := randomdata.Number(len(iv.PlanNormal)) lr := iv.PlanNormal[idx] isecond = randomdata.Number(lr.left, lr.right+1) } iv.Count-- if iv.Count <= 0 { iv.reset() cron.interval.MoveNext() } cron.nextTime = now.Add(time.Duration(isecond) * time.Second) return true } return false } func createTimePointer(min string, llimit, rlimit int, fixedLeftRight bool) []timePointer { var result []timePointer exelist := strings.Split(min, ",") for _, exe := range exelist { tp := timePointer{} takeper := strings.Split(exe, "/") // per var rangevalue, per string if len(takeper) == 1 { rangevalue = exe per = "1" } else { rangevalue = takeper[0] per = takeper[1] } // takeRange be := strings.Split(rangevalue, "-") var left, rigth string switch len(be) { case 1: left = be[0] rigth = be[0] case 2: left = be[0] rigth = be[1] default: panic(errors.New("range value is > 2")) } if left == "*" { tp.left = llimit } else { ileft, err := strconv.Atoi(strings.Replace(left, "^", "-", -1)) if err != nil { panic(err) } tp.left = ileft } if rigth == "*" { tp.right = rlimit } else { iright, err := strconv.Atoi(strings.Replace(rigth, "^", "-", -1)) if err != nil { panic(err) } tp.right = iright } iper, err := strconv.Atoi(per) if err != nil { panic(err) } tp.per = iper tp.leftlimit = llimit tp.rightlimit = rlimit // 修正左值 leftfixed := tp.left if leftfixed < 0 { leftfixed += tp.rightlimit + 1 if fixedLeftRight { tp.left = leftfixed } } rightfixed := tp.right if rightfixed < 0 { rightfixed += tp.rightlimit + 1 if fixedLeftRight { tp.right = rightfixed } } // 全部符合 当左等于左 且 右等于右最大 并且 per == 1 TODO: 如果加入时间间隔 就需要 加多一个 附加值 if leftfixed == tp.leftlimit && rightfixed == tp.rightlimit && tp.per == 1 { tp.isAll = true } result = append(result, tp) } return result } func parseIntervalString(crontab string) []interface{} { var result []interface{} values := strings.Split(crontab, ",") for _, value := range values { interval := hInterval{} // 次数 valuesCounts := strings.Split(value, "x") switch len(valuesCounts) { case 1: interval.ConstCount = 1 case 2: count, err := strconv.Atoi(valuesCounts[1]) if err != nil { panic(err) } interval.ConstCount = count default: panic("valuesCounts error, the len is not in range") } // 统计失败与普通间隔值的数组 failAndNormal := valuesCounts[0] valuesFN := strings.Split(failAndNormal, "|") for _, FN := range valuesFN { if FN == "" { continue } switch FN[0] { case 'f', 'F': fvalue := FN[1:] interval.PlanFail = append(interval.PlanFail, parseRandLR(fvalue)) case 'n': value := FN[1:] interval.PlanNormal = append(interval.PlanNormal, parseRandLR(value)) default: interval.PlanNormal = append(interval.PlanNormal, parseRandLR(FN)) } } interval.reset() result = append(result, interval) } return result } func parseRandLR(value string) randLR { vlen := len(value) lastchar := value[vlen-1] lr := strings.Split(value, "-") switch len(lr) { case 1: lr := randLR{parseTimeValue(lr[0], lastchar), parseTimeValue(lr[0], lastchar)} return lr case 2: lr := randLR{parseTimeValue(lr[0], lastchar), parseTimeValue(lr[1], lastchar)} return lr default: panic("lr is error") } } func getInt(v string) int { vint, err := strconv.Atoi(v) if err != nil { panic(err) } return vint } func parseTimeValue(v string, lastchar byte) int { vlen := len(v) switch lastchar { case 's': return getInt(v[:vlen-1]) case 'm': return getInt(v[:vlen-1]) * 60 case 'h': return getInt(v[:vlen-1]) * 3600 case 'd': return getInt(v[:vlen-1]) * 3600 * 24 default: return getInt(v) } }