diff --git a/attack_event/fw.go b/attack_event/fw.go new file mode 100644 index 0000000..25c95f7 --- /dev/null +++ b/attack_event/fw.go @@ -0,0 +1,40 @@ +package attackevent + +import ( + "bytes" + "dt_automate/conn" + "dt_automate/tool" + "encoding/json" + "fmt" + "log" + "net/url" + "time" +) + +// var cookieStr string + +func Fw_event() { + //当前时间 + EndTime := time.Unix(tool.Timestamp("second"), 0).Format("2006-01-02T15:04:05") + //今天0点 + StartTime := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Now().Location()).Format("2006-01-02T15:04:05") + // 构建 x-www-form-urlencoded 格式的请求体 + values := url.Values{} + values.Add("xml", "1"+StartTime+""+EndTime+"1200{"SrcZoneName":"Untrust","DestZoneName":"Trust"}") + values.Add("req_menu", "M_Monitor/M_AtkLog/M_ThreatLog") + header := map[string]string{ + "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "referer": "https://11.2.68.146/wnm/frame/index.php", + "cookie": cookieStr, + } + datas := conn.DT_POST("https://11.2.68.146/wnm/get.j", header, bytes.NewBufferString(values.Encode()))["NTOP"].(map[string]interface{})["LogPaging"] + var a map[string]interface{} + for v, k := range datas.([]interface{}) { + b := k.(map[string]interface{})["OutputJSON"].(string) + if err := json.Unmarshal([]byte(b), &a); err != nil { + log.Fatalf("Failed to unmarshal JSON: %v", err) + } + fmt.Println(v, a) + // fmt.Printf("序号:%d,攻击时间:[%s],安全域:%s-%s##攻击源IP:%s#目的源IP:%s:%s,攻击类型:%s,应用协议:%s,请求域名:%s\n", v, a["Time"], a["SrcZoneName"], a["DestZoneName"], a["SrcIPAddr"], a["DestIPAddr"], strconv.FormatFloat(a["DestPort"].(float64), 'f', 0, 64), a["ThreatName"], a["Application"], a["HttpHost"]) + } +} diff --git a/attack_event/job.go b/attack_event/job.go new file mode 100644 index 0000000..18c2efb --- /dev/null +++ b/attack_event/job.go @@ -0,0 +1,109 @@ +package attackevent + +import ( + "dt_automate/conn" + "dt_automate/tool" + "fmt" + "log" + "time" + + "github.com/robfig/cron/v3" +) + +var cookieStr = "vindex==39=16=0AB00=0R; supportLang=cn%2Cen; lang=cn; sessionid=200001dc6ccf3da4c61484899a31aaf741de; loginid=975d5428764bcee5451c191f464cb5ad; 200001dc6ccf3da4c61484899a31aaf741de=true; abcd1234=true; login=false" + +func FW_Job() { + job := cron.New(cron.WithSeconds()) + + j := &myJob{ + t: time.Now(), + } + j1 := &myJob1{ + t: time.Now(), + } + job.Schedule(cron.Every(3*60*time.Second), j) + job.Schedule(cron.Every(3*time.Second), j1) + job.Start() + select {} +} + +type myJob struct { + i int + t time.Time +} + +func (j *myJob) Run() { //心跳//三分钟触发一次 + //当前时间 + // EndTime := time.Unix(tool.Timestamp("second"), 0).Format("2006-01-02T15:04:05") + // //今天0点 + // StartTime := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Now().Location()).Format("2006-01-02T15:04:05") + // 构建 x-www-form-urlencoded 格式的请求体 + // values := url.Values{} + // values.Add("xml", "1"+StartTime+""+EndTime+"1200{"SrcZoneName":"Untrust","DestZoneName":"Trust"}") + // values.Add("req_menu", "M_Monitor/M_AtkLog/M_ThreatLog") + header := map[string]string{ + // "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + "referer": "https://11.2.68.146/wnm/frame/index.php", + "cookie": cookieStr, + } + datas := conn.DT_POST("https://11.2.68.146/wnm/keepalive.j", header, nil) + // var a map[string]interface{} + // for v, k := range datas.([]interface{}) { + // b := k.(map[string]interface{})["OutputJSON"].(string) + // if err := json.Unmarshal([]byte(b), &a); err != nil { + // log.Fatalf("Failed to unmarshal JSON: %v", err) + // } + // fmt.Println(v, a) + // // fmt.Printf("序号:%d,攻击时间:[%s],安全域:%s-%s##攻击源IP:%s#目的源IP:%s:%s,攻击类型:%s,应用协议:%s,请求域名:%s\n", v, a["Time"], a["SrcZoneName"], a["DestZoneName"], a["SrcIPAddr"], a["DestIPAddr"], strconv.FormatFloat(a["DestPort"].(float64), 'f', 0, 64), a["ThreatName"], a["Application"], a["HttpHost"]) + // } + log.Println(datas) + //################################# + j.i++ + now := time.Now() + sub := now.Sub(j.t) + j.t = now + fmt.Printf("心跳: %d, duraction : %d ms %f \n", j.i, sub.Milliseconds(), sub.Minutes()) +} + +type myJob1 struct { + i int + t time.Time +} + +func (j1 *myJob1) Run() { //定时任务1小时执行一次,获取威胁数据 + now := time.Now() + // 当前时间 + EndTime := time.Unix(tool.Timestamp("second"), 0).Format("2006-01-02T15:04:05") + h, _ := time.ParseDuration("-1h") + h1 := now.Add(1 * h).Format("2006-01-02T15:04:05") + log.Println(EndTime) + fmt.Println(h1) + // //今天0点 + // StartTime := time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.Now().Location()).Format("2006-01-02T15:04:05") + // // 构建 x-www-form-urlencoded 格式的请求体 + // values := url.Values{} + // values.Add("xml", "1"+StartTime+""+EndTime+"1200{"SrcZoneName":"Untrust","DestZoneName":"Trust"}") + // values.Add("req_menu", "M_Monitor/M_AtkLog/M_ThreatLog") + // header := map[string]string{ + // "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8", + // "referer": "https://11.2.68.146/wnm/frame/index.php", + // "cookie": cookieStr, + // } + // datas := conn.DT_POST("https://11.2.68.146/wnm/get.j", header, bytes.NewBufferString(values.Encode()))["NTOP"].(map[string]interface{})["LogPaging"] + // var a map[string]interface{} + // for v, k := range datas.([]interface{}) { + // b := k.(map[string]interface{})["OutputJSON"].(string) + // if err := json.Unmarshal([]byte(b), &a); err != nil { + // log.Fatalf("Failed to unmarshal JSON: %v", err) + // } + // fmt.Println(v, a) + // fmt.Printf("序号:%d,攻击时间:[%s],安全域:%s-%s##攻击源IP:%s#目的源IP:%s:%s,攻击类型:%s,应用协议:%s,请求域名:%s\n", v, a["Time"], a["SrcZoneName"], a["DestZoneName"], a["SrcIPAddr"], a["DestIPAddr"], strconv.FormatFloat(a["DestPort"].(float64), 'f', 0, 64), a["ThreatName"], a["Application"], a["HttpHost"]) + // } + // ############################################### + j1.i++ + // now := time.Now() + sub := now.Sub(j1.t) + j1.t = now + fmt.Printf("获取数据: %d, duraction : %d ms %f\n", j1.i, sub.Milliseconds(), sub.Hours()) + +} diff --git a/go.mod b/go.mod index 5358da9..56bcc24 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/go-sql-driver/mysql v1.9.0 github.com/kbinani/screenshot v0.0.0-20250118074034-a3924b7bbc8c github.com/playwright-community/playwright-go v0.5001.0 + github.com/robfig/cron/v3 v3.0.1 github.com/super-l/machine-code v0.0.0-20241121142923-4cb40646deba github.com/unidoc/unioffice v1.39.0 github.com/unidoc/unioffice/v2 v2.0.0 diff --git a/go.sum b/go.sum index 32727a0..b3120f9 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= diff --git a/img/afc2000_1.png b/img/afc2000_1.png index 9faa51e..57631b1 100644 Binary files a/img/afc2000_1.png and b/img/afc2000_1.png differ diff --git a/img/afc2000_2.png b/img/afc2000_2.png index e634ef7..d9f9440 100644 Binary files a/img/afc2000_2.png and b/img/afc2000_2.png differ diff --git a/img/capt.png b/img/capt.png index 76aa882..fac3fb0 100644 Binary files a/img/capt.png and b/img/capt.png differ diff --git a/img/hw_dtcloud_01.png b/img/hw_dtcloud_01.png new file mode 100644 index 0000000..f7b06e3 Binary files /dev/null and b/img/hw_dtcloud_01.png differ diff --git a/img/hw_dtcloud_02.png b/img/hw_dtcloud_02.png new file mode 100644 index 0000000..c2e1498 Binary files /dev/null and b/img/hw_dtcloud_02.png differ diff --git a/img/hw_dtcloud_03.png b/img/hw_dtcloud_03.png new file mode 100644 index 0000000..6f3e033 Binary files /dev/null and b/img/hw_dtcloud_03.png differ diff --git a/img/hw_dtcloud_04.png b/img/hw_dtcloud_04.png new file mode 100644 index 0000000..2e2f066 Binary files /dev/null and b/img/hw_dtcloud_04.png differ diff --git a/img/hw_dtcloud_05.png b/img/hw_dtcloud_05.png new file mode 100644 index 0000000..a49e149 Binary files /dev/null and b/img/hw_dtcloud_05.png differ diff --git a/img/hw_dtcloud_06.png b/img/hw_dtcloud_06.png new file mode 100644 index 0000000..71ff4fb Binary files /dev/null and b/img/hw_dtcloud_06.png differ diff --git a/img/hw_dtcloud_07.png b/img/hw_dtcloud_07.png new file mode 100644 index 0000000..0d75377 Binary files /dev/null and b/img/hw_dtcloud_07.png differ diff --git a/img/hw_dtcloud_08.png b/img/hw_dtcloud_08.png new file mode 100644 index 0000000..8ba5c71 Binary files /dev/null and b/img/hw_dtcloud_08.png differ diff --git a/img/hw_dtcloud_man.png b/img/hw_dtcloud_man.png new file mode 100644 index 0000000..e4f0d6c Binary files /dev/null and b/img/hw_dtcloud_man.png differ diff --git a/img/hw_fw1.png b/img/hw_fw1.png index d298976..2bc5eab 100644 Binary files a/img/hw_fw1.png and b/img/hw_fw1.png differ diff --git a/img/hw_tsgz.png b/img/hw_tsgz.png index e1d6be1..1eae9bc 100644 Binary files a/img/hw_tsgz.png and b/img/hw_tsgz.png differ diff --git a/img/zww_dtcloud_01.png b/img/zww_dtcloud_01.png new file mode 100644 index 0000000..620aced Binary files /dev/null and b/img/zww_dtcloud_01.png differ diff --git a/img/zww_dtcloud_02.png b/img/zww_dtcloud_02.png new file mode 100644 index 0000000..d1656c3 Binary files /dev/null and b/img/zww_dtcloud_02.png differ diff --git a/img/zww_dtcloud_03.png b/img/zww_dtcloud_03.png new file mode 100644 index 0000000..5ad9d95 Binary files /dev/null and b/img/zww_dtcloud_03.png differ diff --git a/img/zww_dtcloud_04.png b/img/zww_dtcloud_04.png new file mode 100644 index 0000000..c3751e2 Binary files /dev/null and b/img/zww_dtcloud_04.png differ diff --git a/img/zww_dtcloud_05.png b/img/zww_dtcloud_05.png new file mode 100644 index 0000000..5d96852 Binary files /dev/null and b/img/zww_dtcloud_05.png differ diff --git a/img/zww_dtcloud_06.png b/img/zww_dtcloud_06.png new file mode 100644 index 0000000..e0cfcae Binary files /dev/null and b/img/zww_dtcloud_06.png differ diff --git a/img/zww_dtcloud_07.png b/img/zww_dtcloud_07.png new file mode 100644 index 0000000..192af08 Binary files /dev/null and b/img/zww_dtcloud_07.png differ diff --git a/img/zww_dtcloud_08.png b/img/zww_dtcloud_08.png new file mode 100644 index 0000000..20bae84 Binary files /dev/null and b/img/zww_dtcloud_08.png differ diff --git a/img/zww_dtcloud_man.png b/img/zww_dtcloud_man.png new file mode 100644 index 0000000..5b48247 Binary files /dev/null and b/img/zww_dtcloud_man.png differ diff --git a/img/zww_fw1.png b/img/zww_fw1.png index 6b3c00e..c051faa 100644 Binary files a/img/zww_fw1.png and b/img/zww_fw1.png differ diff --git a/img/zww_tsgz.png b/img/zww_tsgz.png index 5a030dc..5e8f386 100644 Binary files a/img/zww_tsgz.png and b/img/zww_tsgz.png differ diff --git a/main.go b/main.go index 09a7125..aa457c1 100644 --- a/main.go +++ b/main.go @@ -1,8 +1,8 @@ package main import ( + "dt_automate/method" "dt_automate/tool" - "dt_automate/wps" "flag" "fmt" "log" @@ -93,11 +93,14 @@ func main() { // method.HW_FAC1() //截取流量清洗的图片 // method.HW_FW1() //截取防火墙的图片 // method.HW_TSGZ() //截取态势感知的图片 + // method.HW_DTCLOUD() //截取运维中心的图片 // //政务网区 // method.ZWW_FW1() //截取防火墙的图片 // method.ZWW_TSGZ() //截取态势感知的图片 - wps.Word() + // wps.Word() // wps.Damo() + method.ZWW_DTCLOUD() //截取运维中心的图片 + // attackevent.FW_Job() } } else { log.Println("没有授权") diff --git a/method/hw_dtcloud.go b/method/hw_dtcloud.go new file mode 100644 index 0000000..449afb2 --- /dev/null +++ b/method/hw_dtcloud.go @@ -0,0 +1,152 @@ +package method + +import ( + "fmt" + "log" + + "github.com/playwright-community/playwright-go" +) + +func HW_DTCLOUD() { + // 启动 Playwright + pw, err := playwright.Run() + if err != nil { + log.Fatalf("could not start Playwright: %v", err) + } + defer pw.Stop() + + // 启动浏览器(Chromium) + browser, err := pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{ + Headless: playwright.Bool(false), // 设置为 false 以显示浏览器界面 + Args: []string{ + "--ignore-certificate-errors", // 忽略证书错误[^28^][^29^] + }, + }) + if err != nil { + log.Fatalf("could not launch browser: %v", err) + } + defer browser.Close() + + // 创建一个新页面 + page, err := browser.NewPage() + if err != nil { + log.Fatalf("could not create page: %v", err) + } + // 设置浏览器窗口大小 + err = page.SetViewportSize(1920, 1080) // 宽度为 1200px,高度为 800px + if err != nil { + log.Fatalf("无法设置浏览器窗口大小: %v", err) + } + + // 导航到指定网址 + _, err = page.Goto("https://ops.hlw.dtcloud.com/login", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) + if err != nil { + log.Fatalf("could not go to the page: %v", err) + } + + // page.WaitForTimeout(4000) + //输入用户名 + page.Locator(`.el-input__inner`).Nth(0).Fill("opsadmin") + //输入密码 + page.Locator(`.el-input__inner`).Nth(1).Fill(`DTXCY@2024#`) + var name string + fmt.Print("请输入验证码:") + fmt.Scanf("%s", &name) + //输入验证码 + page.Locator(`.el-input__inner`).Nth(2).Fill(name) + log.Println("当前验证码为:", name) + page.WaitForTimeout(2000) + //登录 + if err := page.Locator(".el-button.login-form-item.login-btn.el-button--primary").Click(); err != nil { + log.Fatalf("could not click button: %v", err) + } + page.WaitForTimeout(7000) + //运维中心主页 + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_man.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_man.png") + + page.Goto("https://ops.hlw.dtcloud.com/console/om/capacity/overview?regionId=-1") //跳转到运维中心概览 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_01.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_01.png") + + page.Goto("https://ops.hlw.dtcloud.com/console/om/capacity/resource?regionId=-1") //运维-资源容量 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_02.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_02.png") + + page.Goto("https://ops.hlw.dtcloud.com/region/dtcloud1/console/om/monitor/overview") //监控-概览 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_03.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_03.png") + + page.Goto("https://ops.hlw.dtcloud.com/region/dtcloud1/console/om/monitor/resource") //监控-资源监控 + page.WaitForTimeout(4000) + page.Locator(`.arco-tree.arco-tree-show-line`).Locator(`div`).Nth(4).Locator(`span`).Nth(1).Locator(`span[role="button"]`).Click() + page.Locator(`div[data-test-id="PhysicalServer"]`).Click() + page.WaitForTimeout(1000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_04.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_04.png") + + page.Goto("https://ops.hlw.dtcloud.com/region/dtcloud1/console/om/alert/view") //监控-告警-告警查看 + page.WaitForTimeout(4000) + page.Locator(`.flex-between mb20`).Locator(`.arco-material-last-days`).Locator(`.arco-space.arco-space-horizontal.arco-space-align-center`).Locator(`div`).Nth(0).Locator(`button`).Click() + page.WaitForTimeout(2000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_05.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_05.png") + + page.Goto("https://ops.hlw.dtcloud.com/region/dtcloud1/console/om/alert/history") //监控-告警历史 + page.WaitForTimeout(4000) + page.Locator(`.arco-btn.arco-btn-outline.arco-btn-size-default.arco-btn-shape-square`).Nth(0).Click() + page.WaitForTimeout(500) + page.Locator(`.arco-input-tag-input.arco-input-tag-input-size-default.arco-input-tag-input-autowidth`).Click() + page.Locator(`.arco-select-option-wrapper`).Nth(1).Locator(`.arco-checkbox-mask`).Click() + page.Locator(`.arco-btn.arco-btn-primary.arco-btn-size-mini.arco-btn-shape-square`).Click() + page.WaitForTimeout(1000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_06.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_06.png") + + page.Goto("https://ops.hlw.dtcloud.com/region/dtcloud1/console/om/health-inspection/outputs?page=1&size=10&order=createTime%3Adesc&searchResult=%7B%7D") //运维-监控巡检-巡检结果 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_07.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_07.png") + + page.Goto("https://ops.hlw.dtcloud.com/console/om/capacity/service?regionId=-1") //运维-容量-云服务容量 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/hw_dtcloud_08.png"), + FullPage: playwright.Bool(false), + }) + // 获取页面标题 + title, err := page.Title() + if err != nil { + log.Fatalf("could not get title: %v", err) + } + log.Printf("Page title is: %s\n", title) + + page.WaitForTimeout(5000) +} diff --git a/method/hw_fw1.go b/method/hw_fw1.go index 07578f1..f82912d 100644 --- a/method/hw_fw1.go +++ b/method/hw_fw1.go @@ -2,6 +2,7 @@ package method import ( "dt_automate/tool" + "fmt" "log" "github.com/playwright-community/playwright-go" @@ -44,7 +45,7 @@ func HW_FW1() { log.Fatalf("could not go to the page: %v", err) } - page.WaitForTimeout(2000) + page.WaitForTimeout(3000) //输入 if err := page.Locator(`#user_name`).Fill("xcy_user"); err != nil { log.Fatalf("could not fill input: %v", err) @@ -103,6 +104,16 @@ func HW_FW1() { log.Fatalf("could not get title: %v", err) } log.Printf("Page title is: %s\n", title) + cookies, err := page.Context().Cookies("https://11.2.68.146/wnm/get.j") + if err != nil { + log.Println(err) + } + var cookieStr string + for i, cookie := range cookies { + cookieStr += fmt.Sprintf("%s=%s;", cookie.Name, cookie.Value) + log.Printf("Cookie %d: %+v", i, cookie) + } + log.Println(cookieStr) // StartBlocker() page.WaitForTimeout(5000) diff --git a/method/zww_dtcloud.go b/method/zww_dtcloud.go new file mode 100644 index 0000000..8963cc8 --- /dev/null +++ b/method/zww_dtcloud.go @@ -0,0 +1,152 @@ +package method + +import ( + "fmt" + "log" + + "github.com/playwright-community/playwright-go" +) + +func ZWW_DTCLOUD() { + // 启动 Playwright + pw, err := playwright.Run() + if err != nil { + log.Fatalf("could not start Playwright: %v", err) + } + defer pw.Stop() + + // 启动浏览器(Chromium) + browser, err := pw.Chromium.Launch(playwright.BrowserTypeLaunchOptions{ + Headless: playwright.Bool(false), // 设置为 false 以显示浏览器界面 + Args: []string{ + "--ignore-certificate-errors", // 忽略证书错误[^28^][^29^] + }, + }) + if err != nil { + log.Fatalf("could not launch browser: %v", err) + } + defer browser.Close() + + // 创建一个新页面 + page, err := browser.NewPage() + if err != nil { + log.Fatalf("could not create page: %v", err) + } + // 设置浏览器窗口大小 + err = page.SetViewportSize(1920, 1080) // 宽度为 1200px,高度为 800px + if err != nil { + log.Fatalf("无法设置浏览器窗口大小: %v", err) + } + + // 导航到指定网址 + _, err = page.Goto("https://ops.zww.dtcloud.com/login", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) + if err != nil { + log.Fatalf("could not go to the page: %v", err) + } + + // page.WaitForTimeout(4000) + //输入用户名 + page.Locator(`.el-input__inner`).Nth(0).Fill("opsadmin") + //输入密码 + page.Locator(`.el-input__inner`).Nth(1).Fill(`DTXCY@2024#`) + var name string + fmt.Print("请输入验证码:") + fmt.Scanf("%s", &name) + //输入验证码 + page.Locator(`.el-input__inner`).Nth(2).Fill(name) + log.Println("当前验证码为:", name) + page.WaitForTimeout(2000) + //登录 + if err := page.Locator(".el-button.login-form-item.login-btn.el-button--primary").Click(); err != nil { + log.Fatalf("could not click button: %v", err) + } + page.WaitForTimeout(7000) + //运维中心主页 + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_man.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_man.png") + + page.Goto("https://ops.zww.dtcloud.com/console/om/capacity/overview?regionId=-1", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) //跳转到运维中心概览 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_01.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_01.png") + + page.Goto("https://ops.zww.dtcloud.com/console/om/capacity/resource?regionId=-1", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) //运维-资源容量 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_02.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_02.png") + + page.Goto("https://ops.zww.dtcloud.com/region/dtcloud2/console/om/monitor/overview", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) //监控-概览 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_03.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_03.png") + + page.Goto("https://ops.zww.dtcloud.com/region/dtcloud2/console/om/monitor/resource", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) //监控-资源监控-物理服务器 + page.WaitForTimeout(4000) + page.Locator(`.arco-tree.arco-tree-show-line`).Locator(`div`).Nth(4).Locator(`span`).Nth(1).Locator(`span[role="button"]`).Click() + page.Locator(`div[data-test-id="PhysicalServer"]`).Click() + page.WaitForTimeout(1000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_04.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_04.png") + + page.Goto("https://ops.zww.dtcloud.com/region/dtcloud2/console/om/alert/view", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) //监控-告警-告警查看 + page.WaitForTimeout(4000) + page.Locator(`.flex-between mb20`).Locator(`.arco-material-last-days`).Locator(`.arco-space.arco-space-horizontal.arco-space-align-center`).Locator(`div`).Nth(0).Locator(`button`).Click() + page.WaitForTimeout(2000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_05.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_05.png") + + page.Goto("https://ops.zww.dtcloud.com/region/dtcloud2/console/om/alert/history", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) //监控-告警历史 + page.WaitForTimeout(4000) + page.Locator(`.arco-btn.arco-btn-outline.arco-btn-size-default.arco-btn-shape-square`).Nth(0).Click() + page.WaitForTimeout(500) + page.Locator(`.arco-input-tag-input.arco-input-tag-input-size-default.arco-input-tag-input-autowidth`).Click() + page.Locator(`.arco-select-option-wrapper`).Nth(1).Locator(`.arco-checkbox-mask`).Click() + page.Locator(`.arco-btn.arco-btn-primary.arco-btn-size-mini.arco-btn-shape-square`).Click() + page.WaitForTimeout(1000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_06.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_06.png") + + page.Goto("https://ops.zww.dtcloud.com/region/dtcloud2/console/om/health-inspection/outputs?page=1&size=10&order=createTime%3Adesc&searchResult=%7B%7D", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) //运维-监控巡检-巡检结果 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_07.png"), + FullPage: playwright.Bool(false), + }) + // tool.Jietu("img/hw_dtcloud_07.png") + + page.Goto("https://ops.zww.dtcloud.com/console/om/capacity/service?regionId=-1", playwright.PageGotoOptions{WaitUntil: playwright.WaitUntilStateDomcontentloaded}) //运维-容量-云服务容量 + page.WaitForTimeout(4000) + page.Screenshot(playwright.PageScreenshotOptions{ + Path: playwright.String("img/zww_dtcloud_08.png"), + FullPage: playwright.Bool(false), + }) + // 获取页面标题 + title, err := page.Title() + if err != nil { + log.Fatalf("could not get title: %v", err) + } + log.Printf("Page title is: %s\n", title) + + page.WaitForTimeout(5000) +} diff --git a/vendor/github.com/robfig/cron/v3/.gitignore b/vendor/github.com/robfig/cron/v3/.gitignore new file mode 100644 index 0000000..0026861 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/.gitignore @@ -0,0 +1,22 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe diff --git a/vendor/github.com/robfig/cron/v3/.travis.yml b/vendor/github.com/robfig/cron/v3/.travis.yml new file mode 100644 index 0000000..4f2ee4d --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/.travis.yml @@ -0,0 +1 @@ +language: go diff --git a/vendor/github.com/robfig/cron/v3/LICENSE b/vendor/github.com/robfig/cron/v3/LICENSE new file mode 100644 index 0000000..3a0f627 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/LICENSE @@ -0,0 +1,21 @@ +Copyright (C) 2012 Rob Figueiredo +All Rights Reserved. + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/robfig/cron/v3/README.md b/vendor/github.com/robfig/cron/v3/README.md new file mode 100644 index 0000000..984c537 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/README.md @@ -0,0 +1,125 @@ +[![GoDoc](http://godoc.org/github.com/robfig/cron?status.png)](http://godoc.org/github.com/robfig/cron) +[![Build Status](https://travis-ci.org/robfig/cron.svg?branch=master)](https://travis-ci.org/robfig/cron) + +# cron + +Cron V3 has been released! + +To download the specific tagged release, run: + + go get github.com/robfig/cron/v3@v3.0.0 + +Import it in your program as: + + import "github.com/robfig/cron/v3" + +It requires Go 1.11 or later due to usage of Go Modules. + +Refer to the documentation here: +http://godoc.org/github.com/robfig/cron + +The rest of this document describes the the advances in v3 and a list of +breaking changes for users that wish to upgrade from an earlier version. + +## Upgrading to v3 (June 2019) + +cron v3 is a major upgrade to the library that addresses all outstanding bugs, +feature requests, and rough edges. It is based on a merge of master which +contains various fixes to issues found over the years and the v2 branch which +contains some backwards-incompatible features like the ability to remove cron +jobs. In addition, v3 adds support for Go Modules, cleans up rough edges like +the timezone support, and fixes a number of bugs. + +New features: + +- Support for Go modules. Callers must now import this library as + `github.com/robfig/cron/v3`, instead of `gopkg.in/...` + +- Fixed bugs: + - 0f01e6b parser: fix combining of Dow and Dom (#70) + - dbf3220 adjust times when rolling the clock forward to handle non-existent midnight (#157) + - eeecf15 spec_test.go: ensure an error is returned on 0 increment (#144) + - 70971dc cron.Entries(): update request for snapshot to include a reply channel (#97) + - 1cba5e6 cron: fix: removing a job causes the next scheduled job to run too late (#206) + +- Standard cron spec parsing by default (first field is "minute"), with an easy + way to opt into the seconds field (quartz-compatible). Although, note that the + year field (optional in Quartz) is not supported. + +- Extensible, key/value logging via an interface that complies with + the https://github.com/go-logr/logr project. + +- The new Chain & JobWrapper types allow you to install "interceptors" to add + cross-cutting behavior like the following: + - Recover any panics from jobs + - Delay a job's execution if the previous run hasn't completed yet + - Skip a job's execution if the previous run hasn't completed yet + - Log each job's invocations + - Notification when jobs are completed + +It is backwards incompatible with both v1 and v2. These updates are required: + +- The v1 branch accepted an optional seconds field at the beginning of the cron + spec. This is non-standard and has led to a lot of confusion. The new default + parser conforms to the standard as described by [the Cron wikipedia page]. + + UPDATING: To retain the old behavior, construct your Cron with a custom + parser: + + // Seconds field, required + cron.New(cron.WithSeconds()) + + // Seconds field, optional + cron.New( + cron.WithParser( + cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor)) + +- The Cron type now accepts functional options on construction rather than the + previous ad-hoc behavior modification mechanisms (setting a field, calling a setter). + + UPDATING: Code that sets Cron.ErrorLogger or calls Cron.SetLocation must be + updated to provide those values on construction. + +- CRON_TZ is now the recommended way to specify the timezone of a single + schedule, which is sanctioned by the specification. The legacy "TZ=" prefix + will continue to be supported since it is unambiguous and easy to do so. + + UPDATING: No update is required. + +- By default, cron will no longer recover panics in jobs that it runs. + Recovering can be surprising (see issue #192) and seems to be at odds with + typical behavior of libraries. Relatedly, the `cron.WithPanicLogger` option + has been removed to accommodate the more general JobWrapper type. + + UPDATING: To opt into panic recovery and configure the panic logger: + + cron.New(cron.WithChain( + cron.Recover(logger), // or use cron.DefaultLogger + )) + +- In adding support for https://github.com/go-logr/logr, `cron.WithVerboseLogger` was + removed, since it is duplicative with the leveled logging. + + UPDATING: Callers should use `WithLogger` and specify a logger that does not + discard `Info` logs. For convenience, one is provided that wraps `*log.Logger`: + + cron.New( + cron.WithLogger(cron.VerbosePrintfLogger(logger))) + + +### Background - Cron spec format + +There are two cron spec formats in common usage: + +- The "standard" cron format, described on [the Cron wikipedia page] and used by + the cron Linux system utility. + +- The cron format used by [the Quartz Scheduler], commonly used for scheduled + jobs in Java software + +[the Cron wikipedia page]: https://en.wikipedia.org/wiki/Cron +[the Quartz Scheduler]: http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/tutorial-lesson-06.html + +The original version of this package included an optional "seconds" field, which +made it incompatible with both of these formats. Now, the "standard" format is +the default format accepted, and the Quartz format is opt-in. diff --git a/vendor/github.com/robfig/cron/v3/chain.go b/vendor/github.com/robfig/cron/v3/chain.go new file mode 100644 index 0000000..9565b41 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/chain.go @@ -0,0 +1,92 @@ +package cron + +import ( + "fmt" + "runtime" + "sync" + "time" +) + +// JobWrapper decorates the given Job with some behavior. +type JobWrapper func(Job) Job + +// Chain is a sequence of JobWrappers that decorates submitted jobs with +// cross-cutting behaviors like logging or synchronization. +type Chain struct { + wrappers []JobWrapper +} + +// NewChain returns a Chain consisting of the given JobWrappers. +func NewChain(c ...JobWrapper) Chain { + return Chain{c} +} + +// Then decorates the given job with all JobWrappers in the chain. +// +// This: +// NewChain(m1, m2, m3).Then(job) +// is equivalent to: +// m1(m2(m3(job))) +func (c Chain) Then(j Job) Job { + for i := range c.wrappers { + j = c.wrappers[len(c.wrappers)-i-1](j) + } + return j +} + +// Recover panics in wrapped jobs and log them with the provided logger. +func Recover(logger Logger) JobWrapper { + return func(j Job) Job { + return FuncJob(func() { + defer func() { + if r := recover(); r != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + err, ok := r.(error) + if !ok { + err = fmt.Errorf("%v", r) + } + logger.Error(err, "panic", "stack", "...\n"+string(buf)) + } + }() + j.Run() + }) + } +} + +// DelayIfStillRunning serializes jobs, delaying subsequent runs until the +// previous one is complete. Jobs running after a delay of more than a minute +// have the delay logged at Info. +func DelayIfStillRunning(logger Logger) JobWrapper { + return func(j Job) Job { + var mu sync.Mutex + return FuncJob(func() { + start := time.Now() + mu.Lock() + defer mu.Unlock() + if dur := time.Since(start); dur > time.Minute { + logger.Info("delay", "duration", dur) + } + j.Run() + }) + } +} + +// SkipIfStillRunning skips an invocation of the Job if a previous invocation is +// still running. It logs skips to the given logger at Info level. +func SkipIfStillRunning(logger Logger) JobWrapper { + return func(j Job) Job { + var ch = make(chan struct{}, 1) + ch <- struct{}{} + return FuncJob(func() { + select { + case v := <-ch: + j.Run() + ch <- v + default: + logger.Info("skip") + } + }) + } +} diff --git a/vendor/github.com/robfig/cron/v3/constantdelay.go b/vendor/github.com/robfig/cron/v3/constantdelay.go new file mode 100644 index 0000000..cd6e7b1 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/constantdelay.go @@ -0,0 +1,27 @@ +package cron + +import "time" + +// ConstantDelaySchedule represents a simple recurring duty cycle, e.g. "Every 5 minutes". +// It does not support jobs more frequent than once a second. +type ConstantDelaySchedule struct { + Delay time.Duration +} + +// Every returns a crontab Schedule that activates once every duration. +// Delays of less than a second are not supported (will round up to 1 second). +// Any fields less than a Second are truncated. +func Every(duration time.Duration) ConstantDelaySchedule { + if duration < time.Second { + duration = time.Second + } + return ConstantDelaySchedule{ + Delay: duration - time.Duration(duration.Nanoseconds())%time.Second, + } +} + +// Next returns the next time this should be run. +// This rounds so that the next activation time will be on the second. +func (schedule ConstantDelaySchedule) Next(t time.Time) time.Time { + return t.Add(schedule.Delay - time.Duration(t.Nanosecond())*time.Nanosecond) +} diff --git a/vendor/github.com/robfig/cron/v3/cron.go b/vendor/github.com/robfig/cron/v3/cron.go new file mode 100644 index 0000000..c7e9176 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/cron.go @@ -0,0 +1,355 @@ +package cron + +import ( + "context" + "sort" + "sync" + "time" +) + +// Cron keeps track of any number of entries, invoking the associated func as +// specified by the schedule. It may be started, stopped, and the entries may +// be inspected while running. +type Cron struct { + entries []*Entry + chain Chain + stop chan struct{} + add chan *Entry + remove chan EntryID + snapshot chan chan []Entry + running bool + logger Logger + runningMu sync.Mutex + location *time.Location + parser ScheduleParser + nextID EntryID + jobWaiter sync.WaitGroup +} + +// ScheduleParser is an interface for schedule spec parsers that return a Schedule +type ScheduleParser interface { + Parse(spec string) (Schedule, error) +} + +// Job is an interface for submitted cron jobs. +type Job interface { + Run() +} + +// Schedule describes a job's duty cycle. +type Schedule interface { + // Next returns the next activation time, later than the given time. + // Next is invoked initially, and then each time the job is run. + Next(time.Time) time.Time +} + +// EntryID identifies an entry within a Cron instance +type EntryID int + +// Entry consists of a schedule and the func to execute on that schedule. +type Entry struct { + // ID is the cron-assigned ID of this entry, which may be used to look up a + // snapshot or remove it. + ID EntryID + + // Schedule on which this job should be run. + Schedule Schedule + + // Next time the job will run, or the zero time if Cron has not been + // started or this entry's schedule is unsatisfiable + Next time.Time + + // Prev is the last time this job was run, or the zero time if never. + Prev time.Time + + // WrappedJob is the thing to run when the Schedule is activated. + WrappedJob Job + + // Job is the thing that was submitted to cron. + // It is kept around so that user code that needs to get at the job later, + // e.g. via Entries() can do so. + Job Job +} + +// Valid returns true if this is not the zero entry. +func (e Entry) Valid() bool { return e.ID != 0 } + +// byTime is a wrapper for sorting the entry array by time +// (with zero time at the end). +type byTime []*Entry + +func (s byTime) Len() int { return len(s) } +func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byTime) Less(i, j int) bool { + // Two zero times should return false. + // Otherwise, zero is "greater" than any other time. + // (To sort it at the end of the list.) + if s[i].Next.IsZero() { + return false + } + if s[j].Next.IsZero() { + return true + } + return s[i].Next.Before(s[j].Next) +} + +// New returns a new Cron job runner, modified by the given options. +// +// Available Settings +// +// Time Zone +// Description: The time zone in which schedules are interpreted +// Default: time.Local +// +// Parser +// Description: Parser converts cron spec strings into cron.Schedules. +// Default: Accepts this spec: https://en.wikipedia.org/wiki/Cron +// +// Chain +// Description: Wrap submitted jobs to customize behavior. +// Default: A chain that recovers panics and logs them to stderr. +// +// See "cron.With*" to modify the default behavior. +func New(opts ...Option) *Cron { + c := &Cron{ + entries: nil, + chain: NewChain(), + add: make(chan *Entry), + stop: make(chan struct{}), + snapshot: make(chan chan []Entry), + remove: make(chan EntryID), + running: false, + runningMu: sync.Mutex{}, + logger: DefaultLogger, + location: time.Local, + parser: standardParser, + } + for _, opt := range opts { + opt(c) + } + return c +} + +// FuncJob is a wrapper that turns a func() into a cron.Job +type FuncJob func() + +func (f FuncJob) Run() { f() } + +// AddFunc adds a func to the Cron to be run on the given schedule. +// The spec is parsed using the time zone of this Cron instance as the default. +// An opaque ID is returned that can be used to later remove it. +func (c *Cron) AddFunc(spec string, cmd func()) (EntryID, error) { + return c.AddJob(spec, FuncJob(cmd)) +} + +// AddJob adds a Job to the Cron to be run on the given schedule. +// The spec is parsed using the time zone of this Cron instance as the default. +// An opaque ID is returned that can be used to later remove it. +func (c *Cron) AddJob(spec string, cmd Job) (EntryID, error) { + schedule, err := c.parser.Parse(spec) + if err != nil { + return 0, err + } + return c.Schedule(schedule, cmd), nil +} + +// Schedule adds a Job to the Cron to be run on the given schedule. +// The job is wrapped with the configured Chain. +func (c *Cron) Schedule(schedule Schedule, cmd Job) EntryID { + c.runningMu.Lock() + defer c.runningMu.Unlock() + c.nextID++ + entry := &Entry{ + ID: c.nextID, + Schedule: schedule, + WrappedJob: c.chain.Then(cmd), + Job: cmd, + } + if !c.running { + c.entries = append(c.entries, entry) + } else { + c.add <- entry + } + return entry.ID +} + +// Entries returns a snapshot of the cron entries. +func (c *Cron) Entries() []Entry { + c.runningMu.Lock() + defer c.runningMu.Unlock() + if c.running { + replyChan := make(chan []Entry, 1) + c.snapshot <- replyChan + return <-replyChan + } + return c.entrySnapshot() +} + +// Location gets the time zone location +func (c *Cron) Location() *time.Location { + return c.location +} + +// Entry returns a snapshot of the given entry, or nil if it couldn't be found. +func (c *Cron) Entry(id EntryID) Entry { + for _, entry := range c.Entries() { + if id == entry.ID { + return entry + } + } + return Entry{} +} + +// Remove an entry from being run in the future. +func (c *Cron) Remove(id EntryID) { + c.runningMu.Lock() + defer c.runningMu.Unlock() + if c.running { + c.remove <- id + } else { + c.removeEntry(id) + } +} + +// Start the cron scheduler in its own goroutine, or no-op if already started. +func (c *Cron) Start() { + c.runningMu.Lock() + defer c.runningMu.Unlock() + if c.running { + return + } + c.running = true + go c.run() +} + +// Run the cron scheduler, or no-op if already running. +func (c *Cron) Run() { + c.runningMu.Lock() + if c.running { + c.runningMu.Unlock() + return + } + c.running = true + c.runningMu.Unlock() + c.run() +} + +// run the scheduler.. this is private just due to the need to synchronize +// access to the 'running' state variable. +func (c *Cron) run() { + c.logger.Info("start") + + // Figure out the next activation times for each entry. + now := c.now() + for _, entry := range c.entries { + entry.Next = entry.Schedule.Next(now) + c.logger.Info("schedule", "now", now, "entry", entry.ID, "next", entry.Next) + } + + for { + // Determine the next entry to run. + sort.Sort(byTime(c.entries)) + + var timer *time.Timer + if len(c.entries) == 0 || c.entries[0].Next.IsZero() { + // If there are no entries yet, just sleep - it still handles new entries + // and stop requests. + timer = time.NewTimer(100000 * time.Hour) + } else { + timer = time.NewTimer(c.entries[0].Next.Sub(now)) + } + + for { + select { + case now = <-timer.C: + now = now.In(c.location) + c.logger.Info("wake", "now", now) + + // Run every entry whose next time was less than now + for _, e := range c.entries { + if e.Next.After(now) || e.Next.IsZero() { + break + } + c.startJob(e.WrappedJob) + e.Prev = e.Next + e.Next = e.Schedule.Next(now) + c.logger.Info("run", "now", now, "entry", e.ID, "next", e.Next) + } + + case newEntry := <-c.add: + timer.Stop() + now = c.now() + newEntry.Next = newEntry.Schedule.Next(now) + c.entries = append(c.entries, newEntry) + c.logger.Info("added", "now", now, "entry", newEntry.ID, "next", newEntry.Next) + + case replyChan := <-c.snapshot: + replyChan <- c.entrySnapshot() + continue + + case <-c.stop: + timer.Stop() + c.logger.Info("stop") + return + + case id := <-c.remove: + timer.Stop() + now = c.now() + c.removeEntry(id) + c.logger.Info("removed", "entry", id) + } + + break + } + } +} + +// startJob runs the given job in a new goroutine. +func (c *Cron) startJob(j Job) { + c.jobWaiter.Add(1) + go func() { + defer c.jobWaiter.Done() + j.Run() + }() +} + +// now returns current time in c location +func (c *Cron) now() time.Time { + return time.Now().In(c.location) +} + +// Stop stops the cron scheduler if it is running; otherwise it does nothing. +// A context is returned so the caller can wait for running jobs to complete. +func (c *Cron) Stop() context.Context { + c.runningMu.Lock() + defer c.runningMu.Unlock() + if c.running { + c.stop <- struct{}{} + c.running = false + } + ctx, cancel := context.WithCancel(context.Background()) + go func() { + c.jobWaiter.Wait() + cancel() + }() + return ctx +} + +// entrySnapshot returns a copy of the current cron entry list. +func (c *Cron) entrySnapshot() []Entry { + var entries = make([]Entry, len(c.entries)) + for i, e := range c.entries { + entries[i] = *e + } + return entries +} + +func (c *Cron) removeEntry(id EntryID) { + var entries []*Entry + for _, e := range c.entries { + if e.ID != id { + entries = append(entries, e) + } + } + c.entries = entries +} diff --git a/vendor/github.com/robfig/cron/v3/doc.go b/vendor/github.com/robfig/cron/v3/doc.go new file mode 100644 index 0000000..fa5d08b --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/doc.go @@ -0,0 +1,231 @@ +/* +Package cron implements a cron spec parser and job runner. + +Installation + +To download the specific tagged release, run: + + go get github.com/robfig/cron/v3@v3.0.0 + +Import it in your program as: + + import "github.com/robfig/cron/v3" + +It requires Go 1.11 or later due to usage of Go Modules. + +Usage + +Callers may register Funcs to be invoked on a given schedule. Cron will run +them in their own goroutines. + + c := cron.New() + c.AddFunc("30 * * * *", func() { fmt.Println("Every hour on the half hour") }) + c.AddFunc("30 3-6,20-23 * * *", func() { fmt.Println(".. in the range 3-6am, 8-11pm") }) + c.AddFunc("CRON_TZ=Asia/Tokyo 30 04 * * *", func() { fmt.Println("Runs at 04:30 Tokyo time every day") }) + c.AddFunc("@hourly", func() { fmt.Println("Every hour, starting an hour from now") }) + c.AddFunc("@every 1h30m", func() { fmt.Println("Every hour thirty, starting an hour thirty from now") }) + c.Start() + .. + // Funcs are invoked in their own goroutine, asynchronously. + ... + // Funcs may also be added to a running Cron + c.AddFunc("@daily", func() { fmt.Println("Every day") }) + .. + // Inspect the cron job entries' next and previous run times. + inspect(c.Entries()) + .. + c.Stop() // Stop the scheduler (does not stop any jobs already running). + +CRON Expression Format + +A cron expression represents a set of times, using 5 space-separated fields. + + Field name | Mandatory? | Allowed values | Allowed special characters + ---------- | ---------- | -------------- | -------------------------- + Minutes | Yes | 0-59 | * / , - + Hours | Yes | 0-23 | * / , - + Day of month | Yes | 1-31 | * / , - ? + Month | Yes | 1-12 or JAN-DEC | * / , - + Day of week | Yes | 0-6 or SUN-SAT | * / , - ? + +Month and Day-of-week field values are case insensitive. "SUN", "Sun", and +"sun" are equally accepted. + +The specific interpretation of the format is based on the Cron Wikipedia page: +https://en.wikipedia.org/wiki/Cron + +Alternative Formats + +Alternative Cron expression formats support other fields like seconds. You can +implement that by creating a custom Parser as follows. + + cron.New( + cron.WithParser( + cron.NewParser( + cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor))) + +Since adding Seconds is the most common modification to the standard cron spec, +cron provides a builtin function to do that, which is equivalent to the custom +parser you saw earlier, except that its seconds field is REQUIRED: + + cron.New(cron.WithSeconds()) + +That emulates Quartz, the most popular alternative Cron schedule format: +http://www.quartz-scheduler.org/documentation/quartz-2.x/tutorials/crontrigger.html + +Special Characters + +Asterisk ( * ) + +The asterisk indicates that the cron expression will match for all values of the +field; e.g., using an asterisk in the 5th field (month) would indicate every +month. + +Slash ( / ) + +Slashes are used to describe increments of ranges. For example 3-59/15 in the +1st field (minutes) would indicate the 3rd minute of the hour and every 15 +minutes thereafter. The form "*\/..." is equivalent to the form "first-last/...", +that is, an increment over the largest possible range of the field. The form +"N/..." is accepted as meaning "N-MAX/...", that is, starting at N, use the +increment until the end of that specific range. It does not wrap around. + +Comma ( , ) + +Commas are used to separate items of a list. For example, using "MON,WED,FRI" in +the 5th field (day of week) would mean Mondays, Wednesdays and Fridays. + +Hyphen ( - ) + +Hyphens are used to define ranges. For example, 9-17 would indicate every +hour between 9am and 5pm inclusive. + +Question mark ( ? ) + +Question mark may be used instead of '*' for leaving either day-of-month or +day-of-week blank. + +Predefined schedules + +You may use one of several pre-defined schedules in place of a cron expression. + + Entry | Description | Equivalent To + ----- | ----------- | ------------- + @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 1 1 * + @monthly | Run once a month, midnight, first of month | 0 0 1 * * + @weekly | Run once a week, midnight between Sat/Sun | 0 0 * * 0 + @daily (or @midnight) | Run once a day, midnight | 0 0 * * * + @hourly | Run once an hour, beginning of hour | 0 * * * * + +Intervals + +You may also schedule a job to execute at fixed intervals, starting at the time it's added +or cron is run. This is supported by formatting the cron spec like this: + + @every + +where "duration" is a string accepted by time.ParseDuration +(http://golang.org/pkg/time/#ParseDuration). + +For example, "@every 1h30m10s" would indicate a schedule that activates after +1 hour, 30 minutes, 10 seconds, and then every interval after that. + +Note: The interval does not take the job runtime into account. For example, +if a job takes 3 minutes to run, and it is scheduled to run every 5 minutes, +it will have only 2 minutes of idle time between each run. + +Time zones + +By default, all interpretation and scheduling is done in the machine's local +time zone (time.Local). You can specify a different time zone on construction: + + cron.New( + cron.WithLocation(time.UTC)) + +Individual cron schedules may also override the time zone they are to be +interpreted in by providing an additional space-separated field at the beginning +of the cron spec, of the form "CRON_TZ=Asia/Tokyo". + +For example: + + # Runs at 6am in time.Local + cron.New().AddFunc("0 6 * * ?", ...) + + # Runs at 6am in America/New_York + nyc, _ := time.LoadLocation("America/New_York") + c := cron.New(cron.WithLocation(nyc)) + c.AddFunc("0 6 * * ?", ...) + + # Runs at 6am in Asia/Tokyo + cron.New().AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...) + + # Runs at 6am in Asia/Tokyo + c := cron.New(cron.WithLocation(nyc)) + c.SetLocation("America/New_York") + c.AddFunc("CRON_TZ=Asia/Tokyo 0 6 * * ?", ...) + +The prefix "TZ=(TIME ZONE)" is also supported for legacy compatibility. + +Be aware that jobs scheduled during daylight-savings leap-ahead transitions will +not be run! + +Job Wrappers + +A Cron runner may be configured with a chain of job wrappers to add +cross-cutting functionality to all submitted jobs. For example, they may be used +to achieve the following effects: + + - Recover any panics from jobs (activated by default) + - Delay a job's execution if the previous run hasn't completed yet + - Skip a job's execution if the previous run hasn't completed yet + - Log each job's invocations + +Install wrappers for all jobs added to a cron using the `cron.WithChain` option: + + cron.New(cron.WithChain( + cron.SkipIfStillRunning(logger), + )) + +Install wrappers for individual jobs by explicitly wrapping them: + + job = cron.NewChain( + cron.SkipIfStillRunning(logger), + ).Then(job) + +Thread safety + +Since the Cron service runs concurrently with the calling code, some amount of +care must be taken to ensure proper synchronization. + +All cron methods are designed to be correctly synchronized as long as the caller +ensures that invocations have a clear happens-before ordering between them. + +Logging + +Cron defines a Logger interface that is a subset of the one defined in +github.com/go-logr/logr. It has two logging levels (Info and Error), and +parameters are key/value pairs. This makes it possible for cron logging to plug +into structured logging systems. An adapter, [Verbose]PrintfLogger, is provided +to wrap the standard library *log.Logger. + +For additional insight into Cron operations, verbose logging may be activated +which will record job runs, scheduling decisions, and added or removed jobs. +Activate it with a one-off logger as follows: + + cron.New( + cron.WithLogger( + cron.VerbosePrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags)))) + + +Implementation + +Cron entries are stored in an array, sorted by their next activation time. Cron +sleeps until the next job is due to be run. + +Upon waking: + - it runs each entry that is active on that second + - it calculates the next run times for the jobs that were run + - it re-sorts the array of entries by next activation time. + - it goes to sleep until the soonest job. +*/ +package cron diff --git a/vendor/github.com/robfig/cron/v3/logger.go b/vendor/github.com/robfig/cron/v3/logger.go new file mode 100644 index 0000000..b4efcc0 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/logger.go @@ -0,0 +1,86 @@ +package cron + +import ( + "io/ioutil" + "log" + "os" + "strings" + "time" +) + +// DefaultLogger is used by Cron if none is specified. +var DefaultLogger Logger = PrintfLogger(log.New(os.Stdout, "cron: ", log.LstdFlags)) + +// DiscardLogger can be used by callers to discard all log messages. +var DiscardLogger Logger = PrintfLogger(log.New(ioutil.Discard, "", 0)) + +// Logger is the interface used in this package for logging, so that any backend +// can be plugged in. It is a subset of the github.com/go-logr/logr interface. +type Logger interface { + // Info logs routine messages about cron's operation. + Info(msg string, keysAndValues ...interface{}) + // Error logs an error condition. + Error(err error, msg string, keysAndValues ...interface{}) +} + +// PrintfLogger wraps a Printf-based logger (such as the standard library "log") +// into an implementation of the Logger interface which logs errors only. +func PrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger { + return printfLogger{l, false} +} + +// VerbosePrintfLogger wraps a Printf-based logger (such as the standard library +// "log") into an implementation of the Logger interface which logs everything. +func VerbosePrintfLogger(l interface{ Printf(string, ...interface{}) }) Logger { + return printfLogger{l, true} +} + +type printfLogger struct { + logger interface{ Printf(string, ...interface{}) } + logInfo bool +} + +func (pl printfLogger) Info(msg string, keysAndValues ...interface{}) { + if pl.logInfo { + keysAndValues = formatTimes(keysAndValues) + pl.logger.Printf( + formatString(len(keysAndValues)), + append([]interface{}{msg}, keysAndValues...)...) + } +} + +func (pl printfLogger) Error(err error, msg string, keysAndValues ...interface{}) { + keysAndValues = formatTimes(keysAndValues) + pl.logger.Printf( + formatString(len(keysAndValues)+2), + append([]interface{}{msg, "error", err}, keysAndValues...)...) +} + +// formatString returns a logfmt-like format string for the number of +// key/values. +func formatString(numKeysAndValues int) string { + var sb strings.Builder + sb.WriteString("%s") + if numKeysAndValues > 0 { + sb.WriteString(", ") + } + for i := 0; i < numKeysAndValues/2; i++ { + if i > 0 { + sb.WriteString(", ") + } + sb.WriteString("%v=%v") + } + return sb.String() +} + +// formatTimes formats any time.Time values as RFC3339. +func formatTimes(keysAndValues []interface{}) []interface{} { + var formattedArgs []interface{} + for _, arg := range keysAndValues { + if t, ok := arg.(time.Time); ok { + arg = t.Format(time.RFC3339) + } + formattedArgs = append(formattedArgs, arg) + } + return formattedArgs +} diff --git a/vendor/github.com/robfig/cron/v3/option.go b/vendor/github.com/robfig/cron/v3/option.go new file mode 100644 index 0000000..09e4278 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/option.go @@ -0,0 +1,45 @@ +package cron + +import ( + "time" +) + +// Option represents a modification to the default behavior of a Cron. +type Option func(*Cron) + +// WithLocation overrides the timezone of the cron instance. +func WithLocation(loc *time.Location) Option { + return func(c *Cron) { + c.location = loc + } +} + +// WithSeconds overrides the parser used for interpreting job schedules to +// include a seconds field as the first one. +func WithSeconds() Option { + return WithParser(NewParser( + Second | Minute | Hour | Dom | Month | Dow | Descriptor, + )) +} + +// WithParser overrides the parser used for interpreting job schedules. +func WithParser(p ScheduleParser) Option { + return func(c *Cron) { + c.parser = p + } +} + +// WithChain specifies Job wrappers to apply to all jobs added to this cron. +// Refer to the Chain* functions in this package for provided wrappers. +func WithChain(wrappers ...JobWrapper) Option { + return func(c *Cron) { + c.chain = NewChain(wrappers...) + } +} + +// WithLogger uses the provided logger. +func WithLogger(logger Logger) Option { + return func(c *Cron) { + c.logger = logger + } +} diff --git a/vendor/github.com/robfig/cron/v3/parser.go b/vendor/github.com/robfig/cron/v3/parser.go new file mode 100644 index 0000000..3cf8879 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/parser.go @@ -0,0 +1,434 @@ +package cron + +import ( + "fmt" + "math" + "strconv" + "strings" + "time" +) + +// Configuration options for creating a parser. Most options specify which +// fields should be included, while others enable features. If a field is not +// included the parser will assume a default value. These options do not change +// the order fields are parse in. +type ParseOption int + +const ( + Second ParseOption = 1 << iota // Seconds field, default 0 + SecondOptional // Optional seconds field, default 0 + Minute // Minutes field, default 0 + Hour // Hours field, default 0 + Dom // Day of month field, default * + Month // Month field, default * + Dow // Day of week field, default * + DowOptional // Optional day of week field, default * + Descriptor // Allow descriptors such as @monthly, @weekly, etc. +) + +var places = []ParseOption{ + Second, + Minute, + Hour, + Dom, + Month, + Dow, +} + +var defaults = []string{ + "0", + "0", + "0", + "*", + "*", + "*", +} + +// A custom Parser that can be configured. +type Parser struct { + options ParseOption +} + +// NewParser creates a Parser with custom options. +// +// It panics if more than one Optional is given, since it would be impossible to +// correctly infer which optional is provided or missing in general. +// +// Examples +// +// // Standard parser without descriptors +// specParser := NewParser(Minute | Hour | Dom | Month | Dow) +// sched, err := specParser.Parse("0 0 15 */3 *") +// +// // Same as above, just excludes time fields +// subsParser := NewParser(Dom | Month | Dow) +// sched, err := specParser.Parse("15 */3 *") +// +// // Same as above, just makes Dow optional +// subsParser := NewParser(Dom | Month | DowOptional) +// sched, err := specParser.Parse("15 */3") +// +func NewParser(options ParseOption) Parser { + optionals := 0 + if options&DowOptional > 0 { + optionals++ + } + if options&SecondOptional > 0 { + optionals++ + } + if optionals > 1 { + panic("multiple optionals may not be configured") + } + return Parser{options} +} + +// Parse returns a new crontab schedule representing the given spec. +// It returns a descriptive error if the spec is not valid. +// It accepts crontab specs and features configured by NewParser. +func (p Parser) Parse(spec string) (Schedule, error) { + if len(spec) == 0 { + return nil, fmt.Errorf("empty spec string") + } + + // Extract timezone if present + var loc = time.Local + if strings.HasPrefix(spec, "TZ=") || strings.HasPrefix(spec, "CRON_TZ=") { + var err error + i := strings.Index(spec, " ") + eq := strings.Index(spec, "=") + if loc, err = time.LoadLocation(spec[eq+1 : i]); err != nil { + return nil, fmt.Errorf("provided bad location %s: %v", spec[eq+1:i], err) + } + spec = strings.TrimSpace(spec[i:]) + } + + // Handle named schedules (descriptors), if configured + if strings.HasPrefix(spec, "@") { + if p.options&Descriptor == 0 { + return nil, fmt.Errorf("parser does not accept descriptors: %v", spec) + } + return parseDescriptor(spec, loc) + } + + // Split on whitespace. + fields := strings.Fields(spec) + + // Validate & fill in any omitted or optional fields + var err error + fields, err = normalizeFields(fields, p.options) + if err != nil { + return nil, err + } + + field := func(field string, r bounds) uint64 { + if err != nil { + return 0 + } + var bits uint64 + bits, err = getField(field, r) + return bits + } + + var ( + second = field(fields[0], seconds) + minute = field(fields[1], minutes) + hour = field(fields[2], hours) + dayofmonth = field(fields[3], dom) + month = field(fields[4], months) + dayofweek = field(fields[5], dow) + ) + if err != nil { + return nil, err + } + + return &SpecSchedule{ + Second: second, + Minute: minute, + Hour: hour, + Dom: dayofmonth, + Month: month, + Dow: dayofweek, + Location: loc, + }, nil +} + +// normalizeFields takes a subset set of the time fields and returns the full set +// with defaults (zeroes) populated for unset fields. +// +// As part of performing this function, it also validates that the provided +// fields are compatible with the configured options. +func normalizeFields(fields []string, options ParseOption) ([]string, error) { + // Validate optionals & add their field to options + optionals := 0 + if options&SecondOptional > 0 { + options |= Second + optionals++ + } + if options&DowOptional > 0 { + options |= Dow + optionals++ + } + if optionals > 1 { + return nil, fmt.Errorf("multiple optionals may not be configured") + } + + // Figure out how many fields we need + max := 0 + for _, place := range places { + if options&place > 0 { + max++ + } + } + min := max - optionals + + // Validate number of fields + if count := len(fields); count < min || count > max { + if min == max { + return nil, fmt.Errorf("expected exactly %d fields, found %d: %s", min, count, fields) + } + return nil, fmt.Errorf("expected %d to %d fields, found %d: %s", min, max, count, fields) + } + + // Populate the optional field if not provided + if min < max && len(fields) == min { + switch { + case options&DowOptional > 0: + fields = append(fields, defaults[5]) // TODO: improve access to default + case options&SecondOptional > 0: + fields = append([]string{defaults[0]}, fields...) + default: + return nil, fmt.Errorf("unknown optional field") + } + } + + // Populate all fields not part of options with their defaults + n := 0 + expandedFields := make([]string, len(places)) + copy(expandedFields, defaults) + for i, place := range places { + if options&place > 0 { + expandedFields[i] = fields[n] + n++ + } + } + return expandedFields, nil +} + +var standardParser = NewParser( + Minute | Hour | Dom | Month | Dow | Descriptor, +) + +// ParseStandard returns a new crontab schedule representing the given +// standardSpec (https://en.wikipedia.org/wiki/Cron). It requires 5 entries +// representing: minute, hour, day of month, month and day of week, in that +// order. It returns a descriptive error if the spec is not valid. +// +// It accepts +// - Standard crontab specs, e.g. "* * * * ?" +// - Descriptors, e.g. "@midnight", "@every 1h30m" +func ParseStandard(standardSpec string) (Schedule, error) { + return standardParser.Parse(standardSpec) +} + +// getField returns an Int with the bits set representing all of the times that +// the field represents or error parsing field value. A "field" is a comma-separated +// list of "ranges". +func getField(field string, r bounds) (uint64, error) { + var bits uint64 + ranges := strings.FieldsFunc(field, func(r rune) bool { return r == ',' }) + for _, expr := range ranges { + bit, err := getRange(expr, r) + if err != nil { + return bits, err + } + bits |= bit + } + return bits, nil +} + +// getRange returns the bits indicated by the given expression: +// number | number "-" number [ "/" number ] +// or error parsing range. +func getRange(expr string, r bounds) (uint64, error) { + var ( + start, end, step uint + rangeAndStep = strings.Split(expr, "/") + lowAndHigh = strings.Split(rangeAndStep[0], "-") + singleDigit = len(lowAndHigh) == 1 + err error + ) + + var extra uint64 + if lowAndHigh[0] == "*" || lowAndHigh[0] == "?" { + start = r.min + end = r.max + extra = starBit + } else { + start, err = parseIntOrName(lowAndHigh[0], r.names) + if err != nil { + return 0, err + } + switch len(lowAndHigh) { + case 1: + end = start + case 2: + end, err = parseIntOrName(lowAndHigh[1], r.names) + if err != nil { + return 0, err + } + default: + return 0, fmt.Errorf("too many hyphens: %s", expr) + } + } + + switch len(rangeAndStep) { + case 1: + step = 1 + case 2: + step, err = mustParseInt(rangeAndStep[1]) + if err != nil { + return 0, err + } + + // Special handling: "N/step" means "N-max/step". + if singleDigit { + end = r.max + } + if step > 1 { + extra = 0 + } + default: + return 0, fmt.Errorf("too many slashes: %s", expr) + } + + if start < r.min { + return 0, fmt.Errorf("beginning of range (%d) below minimum (%d): %s", start, r.min, expr) + } + if end > r.max { + return 0, fmt.Errorf("end of range (%d) above maximum (%d): %s", end, r.max, expr) + } + if start > end { + return 0, fmt.Errorf("beginning of range (%d) beyond end of range (%d): %s", start, end, expr) + } + if step == 0 { + return 0, fmt.Errorf("step of range should be a positive number: %s", expr) + } + + return getBits(start, end, step) | extra, nil +} + +// parseIntOrName returns the (possibly-named) integer contained in expr. +func parseIntOrName(expr string, names map[string]uint) (uint, error) { + if names != nil { + if namedInt, ok := names[strings.ToLower(expr)]; ok { + return namedInt, nil + } + } + return mustParseInt(expr) +} + +// mustParseInt parses the given expression as an int or returns an error. +func mustParseInt(expr string) (uint, error) { + num, err := strconv.Atoi(expr) + if err != nil { + return 0, fmt.Errorf("failed to parse int from %s: %s", expr, err) + } + if num < 0 { + return 0, fmt.Errorf("negative number (%d) not allowed: %s", num, expr) + } + + return uint(num), nil +} + +// getBits sets all bits in the range [min, max], modulo the given step size. +func getBits(min, max, step uint) uint64 { + var bits uint64 + + // If step is 1, use shifts. + if step == 1 { + return ^(math.MaxUint64 << (max + 1)) & (math.MaxUint64 << min) + } + + // Else, use a simple loop. + for i := min; i <= max; i += step { + bits |= 1 << i + } + return bits +} + +// all returns all bits within the given bounds. (plus the star bit) +func all(r bounds) uint64 { + return getBits(r.min, r.max, 1) | starBit +} + +// parseDescriptor returns a predefined schedule for the expression, or error if none matches. +func parseDescriptor(descriptor string, loc *time.Location) (Schedule, error) { + switch descriptor { + case "@yearly", "@annually": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: 1 << hours.min, + Dom: 1 << dom.min, + Month: 1 << months.min, + Dow: all(dow), + Location: loc, + }, nil + + case "@monthly": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: 1 << hours.min, + Dom: 1 << dom.min, + Month: all(months), + Dow: all(dow), + Location: loc, + }, nil + + case "@weekly": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: 1 << hours.min, + Dom: all(dom), + Month: all(months), + Dow: 1 << dow.min, + Location: loc, + }, nil + + case "@daily", "@midnight": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: 1 << hours.min, + Dom: all(dom), + Month: all(months), + Dow: all(dow), + Location: loc, + }, nil + + case "@hourly": + return &SpecSchedule{ + Second: 1 << seconds.min, + Minute: 1 << minutes.min, + Hour: all(hours), + Dom: all(dom), + Month: all(months), + Dow: all(dow), + Location: loc, + }, nil + + } + + const every = "@every " + if strings.HasPrefix(descriptor, every) { + duration, err := time.ParseDuration(descriptor[len(every):]) + if err != nil { + return nil, fmt.Errorf("failed to parse duration %s: %s", descriptor, err) + } + return Every(duration), nil + } + + return nil, fmt.Errorf("unrecognized descriptor: %s", descriptor) +} diff --git a/vendor/github.com/robfig/cron/v3/spec.go b/vendor/github.com/robfig/cron/v3/spec.go new file mode 100644 index 0000000..fa1e241 --- /dev/null +++ b/vendor/github.com/robfig/cron/v3/spec.go @@ -0,0 +1,188 @@ +package cron + +import "time" + +// SpecSchedule specifies a duty cycle (to the second granularity), based on a +// traditional crontab specification. It is computed initially and stored as bit sets. +type SpecSchedule struct { + Second, Minute, Hour, Dom, Month, Dow uint64 + + // Override location for this schedule. + Location *time.Location +} + +// bounds provides a range of acceptable values (plus a map of name to value). +type bounds struct { + min, max uint + names map[string]uint +} + +// The bounds for each field. +var ( + seconds = bounds{0, 59, nil} + minutes = bounds{0, 59, nil} + hours = bounds{0, 23, nil} + dom = bounds{1, 31, nil} + months = bounds{1, 12, map[string]uint{ + "jan": 1, + "feb": 2, + "mar": 3, + "apr": 4, + "may": 5, + "jun": 6, + "jul": 7, + "aug": 8, + "sep": 9, + "oct": 10, + "nov": 11, + "dec": 12, + }} + dow = bounds{0, 6, map[string]uint{ + "sun": 0, + "mon": 1, + "tue": 2, + "wed": 3, + "thu": 4, + "fri": 5, + "sat": 6, + }} +) + +const ( + // Set the top bit if a star was included in the expression. + starBit = 1 << 63 +) + +// Next returns the next time this schedule is activated, greater than the given +// time. If no time can be found to satisfy the schedule, return the zero time. +func (s *SpecSchedule) Next(t time.Time) time.Time { + // General approach + // + // For Month, Day, Hour, Minute, Second: + // Check if the time value matches. If yes, continue to the next field. + // If the field doesn't match the schedule, then increment the field until it matches. + // While incrementing the field, a wrap-around brings it back to the beginning + // of the field list (since it is necessary to re-verify previous field + // values) + + // Convert the given time into the schedule's timezone, if one is specified. + // Save the original timezone so we can convert back after we find a time. + // Note that schedules without a time zone specified (time.Local) are treated + // as local to the time provided. + origLocation := t.Location() + loc := s.Location + if loc == time.Local { + loc = t.Location() + } + if s.Location != time.Local { + t = t.In(s.Location) + } + + // Start at the earliest possible time (the upcoming second). + t = t.Add(1*time.Second - time.Duration(t.Nanosecond())*time.Nanosecond) + + // This flag indicates whether a field has been incremented. + added := false + + // If no time is found within five years, return zero. + yearLimit := t.Year() + 5 + +WRAP: + if t.Year() > yearLimit { + return time.Time{} + } + + // Find the first applicable month. + // If it's this month, then do nothing. + for 1< 12 { + t = t.Add(time.Duration(24-t.Hour()) * time.Hour) + } else { + t = t.Add(time.Duration(-t.Hour()) * time.Hour) + } + } + + if t.Day() == 1 { + goto WRAP + } + } + + for 1< 0 + dowMatch bool = 1< 0 + ) + if s.Dom&starBit > 0 || s.Dow&starBit > 0 { + return domMatch && dowMatch + } + return domMatch || dowMatch +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9c54090..fd43ab4 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -54,6 +54,9 @@ github.com/pmezard/go-difflib/difflib # github.com/richardlehane/msoleps v1.0.3 ## explicit github.com/richardlehane/msoleps/types +# github.com/robfig/cron/v3 v3.0.1 +## explicit; go 1.12 +github.com/robfig/cron/v3 # github.com/sirupsen/logrus v1.9.3 ## explicit; go 1.13 github.com/sirupsen/logrus diff --git a/wps/word.go b/wps/word.go index 4ead381..2b6373a 100644 --- a/wps/word.go +++ b/wps/word.go @@ -1,6 +1,7 @@ package wps import ( + "dt_automate/tool" "log" "strings" "time" @@ -45,7 +46,7 @@ func Word() { for _, p := range paragraphs { for _, r := range p.Runs() { if strings.Contains(r.Text(), "{{DATE_A}}") { - Replacetext(r, "{{DATE_A}}", time.Date(2025, time.February, 25, 0, 0, 0, 0, time.Local).Format("2006年01月02日")) + Replacetext(r, "{{DATE_A}}", time.Unix(tool.Timestamp("second"), 0).Format("2006年01月02日")) } else if strings.Contains(r.Text(), "{{HLW_FW_1}}") { //插入互联网防火墙图片 Replaceimg(r, doc, "img/hw_fw1.png") } else if strings.Contains(r.Text(), "{{HLW_AFC2000_1}}") { //插入互联网流量清洗图片 @@ -91,14 +92,14 @@ func Word() { } cell.Properties().SetVerticalAlignment(wml.ST_VerticalJcCenter) // log.Println(time.Date(2025, time.February, 25, 0, 0, 0, 0, time.Local).Format("2006年01月02日")) - cell.Paragraphs()[k].AddRun().AddText(time.Date(2025, time.February, 25, 0, 0, 0, 0, time.Local).Format("2006年01月02日")) + cell.Paragraphs()[k].AddRun().AddText(time.Unix(tool.Timestamp("second"), 0).Format("2006年01月02日")) } // log.Println(b) b = "" } } } - doc.SaveToFile("大同信创云平台安全运维日报" + time.Date(2025, time.February, 25, 0, 0, 0, 0, time.Local).Format("20060102") + ".docx") + doc.SaveToFile("大同信创云平台安全运维日报" + time.Unix(tool.Timestamp("second"), 0).Format("20060102") + ".docx") } // 文字替换 @@ -113,7 +114,7 @@ func Replacetext(r document.Run, src, ends string) { // 插入图片 func Replaceimg(r document.Run, doc *document.Document, path_img string) { r.ClearContent() - img1, err := common.ImageFromFile("img/hw_fw1.png") + img1, err := common.ImageFromFile(path_img) if err != nil { log.Fatalf("unable to create image: %s", err) } diff --git a/大同信创云5.2.0 互联网区-2025年02月26日巡检报告.docx b/大同信创云5.2.0 互联网区-2025年02月26日巡检报告.docx new file mode 100644 index 0000000..0fd6f49 Binary files /dev/null and b/大同信创云5.2.0 互联网区-2025年02月26日巡检报告.docx differ diff --git a/大同信创云5.2.0 政务外网区-2025年02月26日巡检报告.docx b/大同信创云5.2.0 政务外网区-2025年02月26日巡检报告.docx new file mode 100644 index 0000000..6c96170 Binary files /dev/null and b/大同信创云5.2.0 政务外网区-2025年02月26日巡检报告.docx differ diff --git a/大同信创云巡检日报-2025-02-26.xlsx b/大同信创云巡检日报-2025-02-26.xlsx new file mode 100644 index 0000000..243939f Binary files /dev/null and b/大同信创云巡检日报-2025-02-26.xlsx differ diff --git a/大同信创云平台安全运维日报20250225.docx b/大同信创云平台安全运维日报20250225.docx deleted file mode 100644 index 2190431..0000000 Binary files a/大同信创云平台安全运维日报20250225.docx and /dev/null differ diff --git a/大同信创云平台安全运维日报20250226.docx b/大同信创云平台安全运维日报20250226.docx new file mode 100644 index 0000000..c1238a1 Binary files /dev/null and b/大同信创云平台安全运维日报20250226.docx differ