From 32bfb63d8f248d14de96198c2a347531e67a1270 Mon Sep 17 00:00:00 2001 From: marvzhang Date: Wed, 15 Jan 2020 13:03:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90=E9=92=89=E9=92=89=E6=9C=BA?= =?UTF-8?q?=E5=99=A8=E4=BA=BA=E6=B6=88=E6=81=AF=E9=80=9A=E7=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/model/user.go | 5 +- backend/routes/user.go | 16 ++- backend/services/notification/ding_talk.go | 111 +++++++-------------- backend/services/task.go | 90 ++++++++++++++--- frontend/src/i18n/zh.js | 3 +- 5 files changed, 124 insertions(+), 101 deletions(-) diff --git a/backend/model/user.go b/backend/model/user.go index 3e24e096..096f0ba3 100644 --- a/backend/model/user.go +++ b/backend/model/user.go @@ -24,9 +24,8 @@ type User struct { } type UserSetting struct { - NotificationTrigger string `json:"notification_trigger" bson:"notification_trigger"` - DingTalkAppKey string `json:"ding_talk_app_key" bson:"ding_talk_app_key"` - DingTalkAppSecret string `json:"ding_talk_app_secret" bson:"ding_talk_app_secret"` + NotificationTrigger string `json:"notification_trigger" bson:"notification_trigger"` + DingTalkRobotWebhook string `json:"ding_talk_robot_webhook" bson:"ding_talk_robot_webhook"` } func (user *User) Save() error { diff --git a/backend/routes/user.go b/backend/routes/user.go index 9afddffc..95fd2951 100644 --- a/backend/routes/user.go +++ b/backend/routes/user.go @@ -213,11 +213,10 @@ func GetMe(c *gin.Context) { func PostMe(c *gin.Context) { type ReqBody struct { - Email string `json:"email"` - Password string `json:"password"` - NotificationTrigger string `json:"notification_trigger"` - DingTalkAppKey string `json:"ding_talk_app_key"` - DingTalkAppSecret string `json:"ding_talk_app_secret"` + Email string `json:"email"` + Password string `json:"password"` + NotificationTrigger string `json:"notification_trigger"` + DingTalkRobotWebhook string `json:"ding_talk_robot_webhook"` } ctx := context.WithGinContext(c) user := ctx.User() @@ -239,11 +238,8 @@ func PostMe(c *gin.Context) { if reqBody.NotificationTrigger != "" { user.Setting.NotificationTrigger = reqBody.NotificationTrigger } - if reqBody.DingTalkAppKey != "" { - user.Setting.DingTalkAppKey = reqBody.DingTalkAppKey - } - if reqBody.DingTalkAppSecret != "" { - user.Setting.DingTalkAppSecret = reqBody.DingTalkAppSecret + if reqBody.DingTalkRobotWebhook != "" { + user.Setting.DingTalkRobotWebhook = reqBody.DingTalkRobotWebhook } if err := user.Save(); err != nil { HandleError(http.StatusInternalServerError, c, err) diff --git a/backend/services/notification/ding_talk.go b/backend/services/notification/ding_talk.go index ae1756aa..963334b8 100644 --- a/backend/services/notification/ding_talk.go +++ b/backend/services/notification/ding_talk.go @@ -1,95 +1,58 @@ package notification import ( - "crawlab/model" "errors" - "fmt" "github.com/apex/log" "github.com/imroc/req" - "github.com/royeo/dingrobot" "runtime/debug" ) -func SendDingTalkNotification(t model.Task, s model.Spider) error { - // 获取用户 - user, _ := model.GetUser(t.UserId) - - // 如果AppKey或AppSecret未设置,则返回错误 - if user.Setting.DingTalkAppKey == "" || user.Setting.DingTalkAppSecret == "" { - return errors.New("ding_talk_app_key or ding_talk_app_secret is empty") - } - - // 获取access_token - accessToken, err := GetDingTalkAccessToken(user) - if err != nil { - return err - } - - // 时间戳 - //timestamp := time.Now().Unix() - - // 计算sign - //signRawString := fmt.Sprintf("%d\n%s", timestamp, user.Setting.DingTalkAppSecret) - //sign := utils.ComputeHmacSha256(signRawString, user.Setting.DingTalkAppSecret) - - // 请求数据 - url := fmt.Sprintf("https://oapi.dingtalk.com/robot/send?access_token=%s", accessToken) - robot := dingrobot.NewRobot(url) - - text := "it works" - if err := robot.SendText(text, []string{}, false); err != nil { - log.Errorf(err.Error()) - debug.PrintStack() - return err - } - //header := req.Header{ - // "Content-Type": "application/json; charset=utf-8", - // "timestamp": fmt.Sprintf("%d000", timestamp), - // "sign": sign, - //} - //data := req.Param{ - // "msgtype": "text", - // "text": req.Param{ - // "text": "it works", - // }, - // "at": req.Param{ - // "atMobiles": []string{}, - // "isAtAll": false, - // }, - //} - //res, err := req.Post(url, header, req.BodyJSON(&data)) - //if err != nil { - // log.Errorf("dingtalk notification error: " + err.Error()) - // debug.PrintStack() - // return err - //} - //log.Infof(fmt.Sprintf("%+v", res)) - return nil -} - -func GetDingTalkAccessToken(u model.User) (string, error) { +func SendDingTalkNotification(webhook string, title string, content string) error { type ResBody struct { - ErrCode int `json:"errcode"` - ErrMsg string `json:"errmsg"` - AccessToken string `json:"access_token"` + ErrCode int `json:"errcode"` + ErrMsg string `json:"errmsg"` + } + + // 请求头 + header := req.Header{ + "Content-Type": "application/json; charset=utf-8", } // 请求数据 - url := fmt.Sprintf("https://oapi.dingtalk.com/gettoken?appkey=%s&appsecret=%s", u.Setting.DingTalkAppKey, u.Setting.DingTalkAppSecret) - res, err := req.Get(url) - if err != nil { - log.Errorf("get dingtalk access_token error: " + err.Error()) - debug.PrintStack() - return "", err + data := req.Param{ + "msgtype": "markdown", + "markdown": req.Param{ + "title": title, + "text": content, + }, + "at": req.Param{ + "atMobiles": []string{}, + "isAtAll": false, + }, } - // 解析相应body + // 发起请求 + res, err := req.Post(webhook, header, req.BodyJSON(&data)) + if err != nil { + log.Errorf("dingtalk notification error: " + err.Error()) + debug.PrintStack() + return err + } + + // 解析响应 var resBody ResBody if err := res.ToJSON(&resBody); err != nil { - log.Errorf("get dingtalk access_token error: " + err.Error()) + log.Errorf("dingtalk notification error: " + err.Error()) debug.PrintStack() - return "", err + return err } - return resBody.AccessToken, nil + // 判断响应是否报错 + if resBody.ErrCode != 0 { + log.Errorf("dingtalk notification error: " + resBody.ErrMsg) + debug.PrintStack() + return errors.New(resBody.ErrMsg) + } + + return nil } diff --git a/backend/services/task.go b/backend/services/task.go index 12c3580a..2e4d1e09 100644 --- a/backend/services/task.go +++ b/backend/services/task.go @@ -489,9 +489,14 @@ func ExecuteTask(id int) { // 如果发生错误,则发送通知 t, _ = model.GetTask(t.Id) - if user.Email != "" && - (user.Setting.NotificationTrigger == constants.NotificationTriggerOnTaskEnd || user.Setting.NotificationTrigger == constants.NotificationTriggerOnTaskError) { - SendTaskEmail(user, t, spider) + if user.Setting.NotificationTrigger == constants.NotificationTriggerOnTaskEnd || user.Setting.NotificationTrigger == constants.NotificationTriggerOnTaskError { + if user.Email != "" { + SendTaskEmail(user, t, spider) + } + + if user.Setting.DingTalkRobotWebhook != "" { + SendTaskDingTalk(user, t, spider) + } } return } @@ -516,12 +521,13 @@ func ExecuteTask(id int) { t.TotalDuration = t.FinishTs.Sub(t.CreateTs).Seconds() // 总时长 // 如果是任务结束时发送通知,则发送通知 - if user.Email != "" && - user.Setting.NotificationTrigger == constants.NotificationTriggerOnTaskEnd { - SendTaskEmail(user, t, spider) - if err := notification.SendDingTalkNotification(t, spider); err != nil { - log.Errorf(err.Error()) - debug.PrintStack() + if user.Setting.NotificationTrigger == constants.NotificationTriggerOnTaskEnd { + if user.Email != "" { + SendTaskEmail(user, t, spider) + } + + if user.Setting.DingTalkRobotWebhook != "" { + SendTaskDingTalk(user, t, spider) } } @@ -695,7 +701,7 @@ func HandleTaskError(t model.Task, err error) { debug.PrintStack() } -func GetTaskEmailContent(t model.Task, s model.Spider) string { +func GetTaskEmailMarkdownContent(t model.Task, s model.Spider) string { n, _ := model.GetNode(t.NodeId) errMsg := "" statusMsg := fmt.Sprintf(`%s`, t.Status) @@ -743,22 +749,82 @@ Please login to Crawlab to view the details. ) } +func GetTaskDingTalkMarkdownContent(t model.Task, s model.Spider) string { + n, _ := model.GetNode(t.NodeId) + errMsg := "" + statusMsg := fmt.Sprintf(`%s`, t.Status) + if t.Status == constants.StatusError { + errMsg = `(有错误)` + statusMsg = fmt.Sprintf(`%s`, t.Status) + } + return fmt.Sprintf(` +您的任务已完成%s,请查看任务信息如下。 + +> **任务ID:** %s +> **任务状态:** %s +> **任务参数:** %s +> **爬虫ID:** %s +> **爬虫名称:** %s +> **节点:** %s +> **创建时间:** %s +> **开始时间:** %s +> **完成时间:** %s +> **等待时间:** %.0f秒 +> **运行时间:** %.0f秒 +> **总时间:** %.0f秒 +> **结果数:** %d +> **错误:** %s + +请登录Crawlab查看详情。 +`, + errMsg, + t.Id, + statusMsg, + t.Param, + s.Id.Hex(), + s.Name, + n.Name, + utils.GetLocalTimeString(t.CreateTs), + utils.GetLocalTimeString(t.StartTs), + utils.GetLocalTimeString(t.FinishTs), + t.WaitDuration, + t.RuntimeDuration, + t.TotalDuration, + t.ResultCount, + t.Error, + ) +} + func SendTaskEmail(u model.User, t model.Task, s model.Spider) { statusMsg := "has finished" if t.Status == constants.StatusError { statusMsg = "has an error" } + title := fmt.Sprintf("[Crawlab] Task for \"%s\" %s", s.Name, statusMsg) if err := notification.SendMail( u.Email, u.Username, - fmt.Sprintf("[Crawlab] Task for \"%s\" %s", s.Name, statusMsg), - GetTaskEmailContent(t, s), + title, + GetTaskEmailMarkdownContent(t, s), ); err != nil { log.Errorf("mail error: " + err.Error()) debug.PrintStack() } } +func SendTaskDingTalk(u model.User, t model.Task, s model.Spider) { + statusMsg := "has finished" + if t.Status == constants.StatusError { + statusMsg = "has an error" + } + title := fmt.Sprintf("[Crawlab] Task for \"%s\" %s", s.Name, statusMsg) + content := GetTaskDingTalkMarkdownContent(t, s) + if err := notification.SendDingTalkNotification(u.Setting.DingTalkRobotWebhook, title, content); err != nil { + log.Errorf(err.Error()) + debug.PrintStack() + } +} + func InitTaskExecutor() error { c := cron.New(cron.WithSeconds()) Exec = &Executor{ diff --git a/frontend/src/i18n/zh.js b/frontend/src/i18n/zh.js index 740408a2..a3ea2768 100644 --- a/frontend/src/i18n/zh.js +++ b/frontend/src/i18n/zh.js @@ -353,8 +353,7 @@ export default { 'On Task End': '当任务结束', 'On Task Error': '当任务发生错误', 'Never': '从不', - 'DingTalk AppKey': '钉钉 AppKey', - 'DingTalk AppSecret': '钉钉 AppSecret', + 'DingTalk Robot Webhook': '钉钉机器人 Webhook', // 其他 tagsView: {