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: {