github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/js/job.go (about) 1 package js 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 "sync/atomic" 8 "time" 9 10 "github.com/angenalZZZ/gofunc/f" 11 "github.com/dop251/goja" 12 ) 13 14 // JobJs use cron job worker in javascript. 15 type JobJs struct { 16 /* 17 CRON Expression Format 18 ---------- ---------- -------------- -------------------------- 19 e.g. "@daily", "@hourly", "@every 1h30m", 20 "30 * * * *" every hour on the half hour 21 "30 3-6,20-23 * * *" in the range 3-6am, 8-11pm 22 "CRON_TZ=Asia/Tokyo 30 04 * * *" at 04:30 Tokyo time every day 23 ---------- ---------- -------------- -------------------------- 24 Field name | Mandatory? | Allowed values | Allowed special characters 25 ---------- | ---------- | -------------- | -------------------------- 26 Minutes | Yes | 0-59 | * / , - 27 Hours | Yes | 0-23 | * / , - 28 Day of month | Yes | 1-31 | * / , - ? 29 Month | Yes | 1-12 or JAN-DEC | * / , - 30 Day of week | Yes | 0-6 or SUN-SAT | * / , - ? 31 ---------- | ---------- | -------------- | -------------------------- 32 特定字符的含义如下: 33 * 表示匹配该域的任意值,假如在minute域使用*, 即表示每分钟都会触发事件 34 / 表示起始时间开始触发,然后每隔固定时间触发一次,例如在minute域使用5/20,则意味着在5分的时候开始触发一次,而25,45等分别触发一次 35 , 表示列出枚举值。例如:在minute域使用5,20,则意味着在5和20分每分钟触发一次 36 - 表示范围,例如在minute域使用5-20,表示从5分到20分每分钟触发一次 37 ? 字符仅被用于“日”和“周”两个子表达式,表示不指定值,当两个子表达式其中之一被指定了值以后,为了避免冲突,需要将另一个子表达式的值设为? 38 */ 39 Spec string 40 // the javascript content 41 Script string 42 // the javascript filepath 43 File string 44 // the javascript file modify time 45 FileModTime time.Time 46 // the last run time 47 LastRunTime time.Time 48 // the last run id 49 LastRunID uint64 50 51 // the job name 52 Name string 53 // the job parent list 54 Parent []*JobJs 55 // the job parent var'name 56 ParentName string 57 58 // R create a javascript runtime 59 R func(*GoRuntimeParam) *GoRuntime 60 // P create a javascript runtime input parameter 61 P *GoRuntimeParam 62 63 // Func func runner 64 Func func(goja.FunctionCall) goja.Value 65 // Self func this object 66 Self goja.Value 67 } 68 69 // RunLogTimeFormat the time layout. 70 var RunLogTimeFormat = "15:04:05.000" 71 72 // Run implementation cron.Job interface. 73 func (j *JobJs) Run() { 74 if j.R == nil { 75 return 76 } 77 78 r := j.R(j.P) 79 if r == nil { 80 return 81 } 82 defer func() { r.Clear() }() 83 84 id := atomic.AddUint64(&j.LastRunID, 1) 85 j.LastRunTime = time.Now() 86 s := fmt.Sprintf("%s [job] %q #%d started running. ", j.LastRunTime.Format(RunLogTimeFormat), j.Name, id) 87 88 var ( 89 res goja.Value 90 err error 91 ) 92 93 if j.Func != nil { 94 var jobs []*JobJs 95 jobs, err = NewJobs(r, j.Script, j.ParentName, j.Name) 96 if err == nil { 97 if len(jobs) == 0 { 98 j.RemoveFromParent() 99 return 100 } 101 j1 := jobs[0] // a named job is found 102 fmt.Println(s) 103 res = j1.Func(goja.FunctionCall{This: j1.Self}) 104 } 105 } else if j.Script != "" { 106 fmt.Println(s) 107 res, err = r.RunString(j.Script) 108 } else { 109 return 110 } 111 112 ts := time.Now() 113 fmt.Printf("%s [job] %q #%d takes %s ", ts.Format(RunLogTimeFormat), j.Name, id, ts.Sub(j.LastRunTime)) 114 if err != nil { 115 fmt.Printf("error: %v", err) 116 } else if res != nil { 117 if v := res.Export(); v != nil { 118 fmt.Printf("return: %+v", v) 119 } else { 120 fmt.Print("ok.") 121 } 122 } else { 123 fmt.Print("ok.") 124 } 125 fmt.Println() 126 fmt.Println() 127 128 return 129 } 130 131 // Init the javascript job if file is not empty. 132 func (j *JobJs) Init() error { 133 if j.File == "" { 134 return nil 135 } 136 if !j.FileIsMod() { 137 return os.ErrNotExist 138 } 139 if err := j.FileMod(); err != nil { 140 return err 141 } 142 return nil 143 } 144 145 // FileIsMod check javascript file is modify. 146 func (j *JobJs) FileIsMod() bool { 147 if j.File == "" { 148 return false 149 } 150 info, err := os.Stat(j.File) 151 if os.IsNotExist(err) { 152 return false 153 } 154 if t := info.ModTime(); t.Unix() != j.FileModTime.Unix() { 155 j.FileModTime = t 156 return true 157 } 158 return false 159 } 160 161 // FileMod read updated javascript file content. 162 func (j *JobJs) FileMod() error { 163 if j.File == "" { 164 return os.ErrNotExist 165 } 166 script, err := f.ReadFile(j.File) 167 if err != nil { 168 return err 169 } 170 j.Script = strings.TrimSpace(string(script)) 171 return err 172 } 173 174 // RemoveFromParent remove self from parent. 175 func (j *JobJs) RemoveFromParent() { 176 p := j.Parent 177 if p == nil { 178 return 179 } 180 for i, l := 0, len(p); i < l; i++ { 181 if j.Name != p[i].Name { 182 continue 183 } 184 // reset self object 185 j.Script, j.Func, j.R, j.Parent = "", nil, nil, nil 186 // update parent underlying array 187 p = append(p[:i], p[i+1:]...) 188 // maintain the correct index 189 return 190 } 191 }