crontabex/crontab.go
2018-12-25 10:17:15 +08:00

341 lines
7.6 KiB
Go

package crontab
import (
"errors"
"fmt"
"log"
"reflect"
"regexp"
"strings"
"time"
"474420502.top/eson/structure/circular_linked"
"github.com/Pallinder/go-randomdata"
"github.com/davecgh/go-spew/spew"
)
// StatusType 设置状态的类型
type StatusType int
const (
_ StatusType = iota
// SExecuteOK 设置这个次成功或者失败的状态
SExecuteOK
// SExecuteSleep 设置下次Sleep时间, 只影响一次
SExecuteSleep
// SExecuteCrontab 设置改写Crontab, 覆盖以前的Crontab
SExecuteCrontab
)
// Force 强制下次执行
type Force struct {
sleep time.Duration
}
// NextTime 下次执行时间
func (force *Force) NextTime() time.Time {
return time.Now().Add(force.sleep)
}
// Crontab 的string解析
type Crontab struct {
crontab string
force *Force
min []timePointer
hour []timePointer
day []timePointer
month []timePointer
week []timePointer
WillPlans []time.Time
SkipPlans []time.Time
YearPlan *trieYear
interval *clinked.CircularLinked
lastStatus bool
isCalculated bool
trueCount int
failCount int
nextTime time.Time
}
// NewCrontab create 一个crontab
func NewCrontab(crontab string) *Crontab {
cron := &Crontab{}
cron.crontab = strings.TrimSpace(crontab)
cron.FromString(cron.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 设置上次状态 true false
func (cron *Crontab) SetStatus(statusType StatusType, statusValue ...interface{}) {
switch statusType {
case SExecuteOK:
if cron.interval != nil {
cron.lastStatus = statusValue[0].(bool)
}
case SExecuteSleep:
force := new(Force)
ivalue := statusValue[0]
switch value := ivalue.(type) {
case int:
force.sleep = time.Duration(value) * time.Second
case int64:
force.sleep = time.Duration(value) * time.Second
case time.Duration:
force.sleep = value
default:
panic(errors.New("statusValue type is error, the type is" + reflect.TypeOf(ivalue).String()))
}
cron.force = force
case SExecuteCrontab:
crontab := statusValue[0].(string)
cron.crontab = strings.TrimSpace(crontab)
cron.FromString(cron.crontab)
default:
panic(errors.New("StatusType is unknown, the type " + reflect.TypeOf(statusType).String()))
}
}
// // GetStatus 获取上次状态 true false
// func (cron *Crontab) GetStatus() (status bool) {
// return cron.lastStatus
// }
// TimeUp 是否时间快到, 时间间隔调用完TimeUp后必须调用NextTime, 为了精准控制时间
func (cron *Crontab) TimeUp() bool {
if cron.interval != nil {
return cron.intervalTimeUp()
}
return cron.linuxTimeUp()
}
// NextTime 返回下次任务的时间
func (cron *Crontab) NextTime() time.Time {
if cron.force != nil {
nt := cron.force.NextTime()
cron.force = nil
return nt
}
if cron.interval != nil {
if !cron.isCalculated {
now := time.Now()
cron.intervalCalculateNextTime(now)
}
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 = cron.crontab
cron.interval = nil
cron.min = nil
cron.hour = nil
cron.day = nil
cron.month = nil
cron.week = nil
cron.WillPlans = nil
cron.SkipPlans = nil
cron.YearPlan = nil
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.isCalculated = false
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 { // 如果当前生成的计划表少于 限制的500. 就生成新的计划表
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++ {
// 统计过了多少计划任务时间表 i - 1
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
}
// IntervalCalculateNextTime 计算时间间隔的下次时间
func (cron *Crontab) intervalCalculateNextTime(now time.Time) {
iv := cron.interval.Cursor().GetValue().(*hInterval)
isecond := 0
if iv.PlanFailCount.Size() == 0 && len(iv.PlanFail) == 0 {
cron.lastStatus = true
}
if cron.lastStatus {
cron.trueCount++
cron.failCount = 0
isecond = intervalPriorityListISecond(&iv.PlanTrueCount, cron.trueCount)
if isecond == -1 {
if len(iv.PlanTrue) > 0 {
idx := randomdata.Number(len(iv.PlanTrue))
lr := iv.PlanTrue[idx]
isecond = randomdata.Number(lr.left, lr.right+1)
} else {
isecond = 0
}
}
log.Println("success:", cron.trueCount, "count time wait:", isecond, "s")
} else {
cron.failCount++
cron.trueCount = 0
isecond = intervalPriorityListISecond(&iv.PlanFailCount, cron.failCount)
if isecond == -1 {
if len(iv.PlanFail) > 0 {
idx := randomdata.Number(len(iv.PlanFail))
lr := iv.PlanFail[idx]
isecond = randomdata.Number(lr.left, lr.right+1)
} else {
isecond = 0
}
}
log.Println("fail:", cron.failCount, "count time wait:", isecond, "s")
}
iv.Count--
if iv.Count <= 0 {
iv.reset()
cron.interval.MoveNext()
}
cron.isCalculated = true
cron.nextTime = now.Add(time.Duration(isecond) * time.Second)
}
func (cron *Crontab) intervalTimeUp() bool {
if cron.isCalculated != false {
now := time.Now()
if now.Unix() >= cron.nextTime.Unix() {
cron.isCalculated = false
return true
}
}
return false
}