mirror of
https://github.com/crawlab-team/crawlab.git
synced 2026-01-21 17:21:09 +01:00
feat: added modules
This commit is contained in:
73
core/notification/base_test.go
Normal file
73
core/notification/base_test.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/crawlab-team/crawlab/core/interfaces"
|
||||
"github.com/crawlab-team/crawlab/core/models/delegate"
|
||||
"github.com/crawlab-team/crawlab/core/models/models"
|
||||
"github.com/gavv/httpexpect/v2"
|
||||
"github.com/spf13/viper"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func init() {
|
||||
viper.Set("mongo.db", "crawlab_test")
|
||||
var err error
|
||||
T, err = NewTest()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
type Test struct {
|
||||
svc *Service
|
||||
svr *httptest.Server
|
||||
|
||||
// test data
|
||||
TestNode interfaces.Node
|
||||
TestSpider interfaces.Spider
|
||||
TestTask interfaces.Task
|
||||
TestTaskStat interfaces.TaskStat
|
||||
}
|
||||
|
||||
func (t *Test) Setup(t2 *testing.T) {
|
||||
_ = t.svc.Start()
|
||||
t2.Cleanup(t.Cleanup)
|
||||
}
|
||||
|
||||
func (t *Test) Cleanup() {
|
||||
_ = t.svc.Stop()
|
||||
}
|
||||
|
||||
func (t *Test) NewExpect(t2 *testing.T) (e *httpexpect.Expect) {
|
||||
e = httpexpect.New(t2, t.svr.URL)
|
||||
return e
|
||||
}
|
||||
|
||||
var T *Test
|
||||
|
||||
func NewTest() (res *Test, err error) {
|
||||
// test
|
||||
t := &Test{
|
||||
svc: NewService(),
|
||||
}
|
||||
|
||||
// test node
|
||||
t.TestNode = &models.Node{Id: primitive.NewObjectID(), Name: "test-node"}
|
||||
_ = delegate.NewModelDelegate(t.TestNode).Add()
|
||||
|
||||
// test spider
|
||||
t.TestSpider = &models.Spider{Id: primitive.NewObjectID(), Name: "test-spider"}
|
||||
_ = delegate.NewModelDelegate(t.TestSpider).Add()
|
||||
|
||||
// test task
|
||||
t.TestTask = &models.Task{Id: primitive.NewObjectID(), SpiderId: t.TestSpider.GetId(), NodeId: t.TestNode.GetId()}
|
||||
_ = delegate.NewModelDelegate(t.TestTask).Add()
|
||||
|
||||
// test task stat
|
||||
t.TestTaskStat = &models.TaskStat{Id: t.TestTask.GetId()}
|
||||
_ = delegate.NewModelDelegate(t.TestTaskStat).Add()
|
||||
|
||||
return t, nil
|
||||
}
|
||||
10
core/notification/constants.go
Normal file
10
core/notification/constants.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package notification
|
||||
|
||||
const (
|
||||
TypeMail = "mail"
|
||||
TypeMobile = "mobile"
|
||||
)
|
||||
|
||||
const (
|
||||
SettingsColName = "notification_settings"
|
||||
)
|
||||
178
core/notification/mail.go
Normal file
178
core/notification/mail.go
Normal file
@@ -0,0 +1,178 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/crawlab-team/crawlab/core/models/models"
|
||||
"github.com/matcornic/hermes/v2"
|
||||
"gopkg.in/gomail.v2"
|
||||
"net/mail"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func SendMail(m *models.NotificationSettingMail, to, cc, title, content string) error {
|
||||
// theme
|
||||
theme := new(MailThemeFlat)
|
||||
|
||||
// hermes instance
|
||||
h := hermes.Hermes{
|
||||
Theme: theme,
|
||||
Product: hermes.Product{
|
||||
Logo: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICAgIDxnIGZpbGw9Im5vbmUiPgogICAgICAgIDxjaXJjbGUgY3g9IjE1MCIgY3k9IjE1MCIgcj0iMTMwIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjQwIiBzdHJva2U9IiM0MDllZmYiPgogICAgICAgIDwvY2lyY2xlPgogICAgICAgIDxjaXJjbGUgY3g9IjE1MCIgY3k9IjE1MCIgcj0iMTEwIiBmaWxsPSJ3aGl0ZSI+CiAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgPGNpcmNsZSBjeD0iMTUwIiBjeT0iMTUwIiByPSI3MCIgZmlsbD0iIzQwOWVmZiI+CiAgICAgICAgPC9jaXJjbGU+CiAgICAgICAgPHBhdGggZD0iCiAgICAgICAgICAgIE0gMTUwLDE1MAogICAgICAgICAgICBMIDI4MCwyMjUKICAgICAgICAgICAgQSAxNTAsMTUwIDkwIDAgMCAyODAsNzUKICAgICAgICAgICAgIiBmaWxsPSIjNDA5ZWZmIj4KICAgICAgICA8L3BhdGg+CiAgICA8L2c+Cjwvc3ZnPgo=",
|
||||
Name: "Crawlab",
|
||||
Copyright: "© 2024 Crawlab-Team",
|
||||
},
|
||||
}
|
||||
|
||||
// config
|
||||
port, _ := strconv.Atoi(m.Port)
|
||||
password := m.Password // test password: ALWVDPRHBEXOENXD
|
||||
SMTPUser := m.User
|
||||
smtpConfig := smtpAuthentication{
|
||||
Server: m.Server,
|
||||
Port: port,
|
||||
SenderEmail: m.SenderEmail,
|
||||
SenderIdentity: m.SenderIdentity,
|
||||
SMTPPassword: password,
|
||||
SMTPUser: SMTPUser,
|
||||
}
|
||||
options := sendOptions{
|
||||
To: to,
|
||||
Cc: cc,
|
||||
Subject: title,
|
||||
}
|
||||
|
||||
// add style
|
||||
content += theme.GetStyle()
|
||||
|
||||
// markdown
|
||||
markdown := hermes.Markdown(content + GetFooter())
|
||||
|
||||
// email instance
|
||||
email := hermes.Email{
|
||||
Body: hermes.Body{
|
||||
Signature: "Happy Crawling ☺",
|
||||
FreeMarkdown: markdown,
|
||||
},
|
||||
}
|
||||
|
||||
// generate html
|
||||
html, err := h.GenerateHTML(email)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
debug.PrintStack()
|
||||
return err
|
||||
}
|
||||
|
||||
// generate text
|
||||
text, err := h.GeneratePlainText(email)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
debug.PrintStack()
|
||||
return err
|
||||
}
|
||||
|
||||
// send the email
|
||||
if err := send(smtpConfig, options, html, text); err != nil {
|
||||
log.Errorf(err.Error())
|
||||
debug.PrintStack()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type smtpAuthentication struct {
|
||||
Server string
|
||||
Port int
|
||||
SenderEmail string
|
||||
SenderIdentity string
|
||||
SMTPUser string
|
||||
SMTPPassword string
|
||||
}
|
||||
|
||||
// sendOptions are options for sending an email
|
||||
type sendOptions struct {
|
||||
To string
|
||||
Subject string
|
||||
Cc string
|
||||
}
|
||||
|
||||
// send email
|
||||
func send(smtpConfig smtpAuthentication, options sendOptions, htmlBody string, txtBody string) error {
|
||||
if smtpConfig.Server == "" {
|
||||
return errors.New("SMTP server config is empty")
|
||||
}
|
||||
|
||||
if smtpConfig.Port == 0 {
|
||||
return errors.New("SMTP port config is empty")
|
||||
}
|
||||
|
||||
if smtpConfig.SMTPUser == "" {
|
||||
return errors.New("SMTP user is empty")
|
||||
}
|
||||
|
||||
if smtpConfig.SenderIdentity == "" {
|
||||
return errors.New("SMTP sender identity is empty")
|
||||
}
|
||||
|
||||
if smtpConfig.SenderEmail == "" {
|
||||
return errors.New("SMTP sender email is empty")
|
||||
}
|
||||
|
||||
if options.To == "" {
|
||||
return errors.New("no receiver emails configured")
|
||||
}
|
||||
|
||||
from := mail.Address{
|
||||
Name: smtpConfig.SenderIdentity,
|
||||
Address: smtpConfig.SenderEmail,
|
||||
}
|
||||
|
||||
var toList []string
|
||||
if strings.Contains(options.To, ";") {
|
||||
toList = strings.Split(options.To, ";")
|
||||
// trim space
|
||||
for i, to := range toList {
|
||||
toList[i] = strings.TrimSpace(to)
|
||||
}
|
||||
} else {
|
||||
toList = []string{options.To}
|
||||
}
|
||||
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", from.String())
|
||||
m.SetHeader("To", getRecipientList(options.To)...)
|
||||
m.SetHeader("Subject", options.Subject)
|
||||
if options.Cc != "" {
|
||||
m.SetHeader("Cc", getRecipientList(options.Cc)...)
|
||||
}
|
||||
|
||||
m.SetBody("text/plain", txtBody)
|
||||
m.AddAlternative("text/html", htmlBody)
|
||||
|
||||
d := gomail.NewDialer(smtpConfig.Server, smtpConfig.Port, smtpConfig.SMTPUser, smtpConfig.SMTPPassword)
|
||||
|
||||
return d.DialAndSend(m)
|
||||
}
|
||||
|
||||
func getRecipientList(value string) (values []string) {
|
||||
if strings.Contains(value, ";") {
|
||||
values = strings.Split(value, ";")
|
||||
// trim space
|
||||
for i, v := range values {
|
||||
values[i] = strings.TrimSpace(v)
|
||||
}
|
||||
} else {
|
||||
values = []string{value}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func GetFooter() string {
|
||||
return `
|
||||
[Github](https://github.com/crawlab-team/crawlab) | [Documentation](http://docs.crawlab.cn) | [Docker](https://hub.docker.com/r/tikazyq/crawlab)
|
||||
`
|
||||
}
|
||||
8
core/notification/mail_theme.go
Normal file
8
core/notification/mail_theme.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package notification
|
||||
|
||||
import "github.com/matcornic/hermes/v2"
|
||||
|
||||
type MailTheme interface {
|
||||
hermes.Theme
|
||||
GetStyle() string
|
||||
}
|
||||
287
core/notification/mail_theme_flat.go
Normal file
287
core/notification/mail_theme_flat.go
Normal file
@@ -0,0 +1,287 @@
|
||||
package notification
|
||||
|
||||
// MailThemeFlat is a theme
|
||||
type MailThemeFlat struct{}
|
||||
|
||||
// Name returns the name of the flat theme
|
||||
func (dt *MailThemeFlat) Name() string {
|
||||
return "flat"
|
||||
}
|
||||
|
||||
// HTMLTemplate returns a Golang template that will generate an HTML email.
|
||||
func (dt *MailThemeFlat) HTMLTemplate() string {
|
||||
return `
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
</head>
|
||||
<body dir="{{.Hermes.TextDirection}}">
|
||||
|
||||
<table class="email-wrapper" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="content">
|
||||
<table class="email-content" width="100%" cellpadding="0" cellspacing="0">
|
||||
<!-- Logo -->
|
||||
<tr>
|
||||
<td class="email-masthead">
|
||||
<a class="email-masthead_name" href="{{.Hermes.Product.Link}}" target="_blank">
|
||||
{{ if .Hermes.Product.Logo }}
|
||||
<img src="{{.Hermes.Product.Logo | url }}" class="email-logo" style="height: 48px"/>
|
||||
<span style="font-size:36px;font-weight:600;margin-left:12px;color:#409eff">{{ .Hermes.Product.Name}} </span>
|
||||
{{ else }}
|
||||
{{ .Hermes.Product.Name }}
|
||||
{{ end }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<!-- Email Body -->
|
||||
<tr>
|
||||
<td class="email-body" width="100%">
|
||||
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0">
|
||||
<!-- Body content -->
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
{{ if (ne .Email.Body.FreeMarkdown "") }}
|
||||
{{ .Email.Body.FreeMarkdown.ToHTML }}
|
||||
{{ else }}
|
||||
|
||||
{{ with .Email.Body.Dictionary }}
|
||||
{{ if gt (len .) 0 }}
|
||||
<dl class="body-dictionary">
|
||||
{{ range $entry := . }}
|
||||
<dt>{{ $entry.Key }}:</dt>
|
||||
<dd>{{ $entry.Value }}</dd>
|
||||
{{ end }}
|
||||
</dl>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<!-- Table -->
|
||||
{{ with .Email.Body.Table }}
|
||||
{{ $data := .Data }}
|
||||
{{ $columns := .Columns }}
|
||||
{{ if gt (len $data) 0 }}
|
||||
<table class="data-wrapper" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<table class="data-table" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
{{ $col := index $data 0 }}
|
||||
{{ range $entry := $col }}
|
||||
<th
|
||||
{{ with $columns }}
|
||||
{{ $width := index .CustomWidth $entry.Key }}
|
||||
{{ with $width }}
|
||||
width="{{ . }}"
|
||||
{{ end }}
|
||||
{{ $align := index .CustomAlignment $entry.Key }}
|
||||
{{ with $align }}
|
||||
style="text-align:{{ . }}"
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
>
|
||||
<p>{{ $entry.Key }}</p>
|
||||
</th>
|
||||
{{ end }}
|
||||
</tr>
|
||||
{{ range $row := $data }}
|
||||
<tr>
|
||||
{{ range $cell := $row }}
|
||||
<td
|
||||
{{ with $columns }}
|
||||
{{ $align := index .CustomAlignment $cell.Key }}
|
||||
{{ with $align }}
|
||||
style="text-align:{{ . }}"
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
>
|
||||
{{ $cell.Value }}
|
||||
</td>
|
||||
{{ end }}
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<!-- Action -->
|
||||
{{ with .Email.Body.Actions }}
|
||||
{{ if gt (len .) 0 }}
|
||||
{{ range $action := . }}
|
||||
<p>{{ $action.Instructions }}</p>
|
||||
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<div>
|
||||
<a href="{{ $action.Button.Link }}" class="button" style="background-color: {{ $action.Button.Color }}; color: {{ $action.Button.TextColor }}" target="_blank">
|
||||
{{ $action.Button.Text }}
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
{{ with .Email.Body.Outros }}
|
||||
{{ if gt (len .) 0 }}
|
||||
{{ range $line := . }}
|
||||
<p>{{ $line }}</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
<p>
|
||||
{{.Email.Body.Signature}}
|
||||
</p>
|
||||
|
||||
{{ if (eq .Email.Body.FreeMarkdown "") }}
|
||||
{{ with .Email.Body.Actions }}
|
||||
<table class="body-sub">
|
||||
<tbody>
|
||||
{{ range $action := . }}
|
||||
<tr>
|
||||
<td>
|
||||
<p class="sub">{{$.Hermes.Product.TroubleText | replace "{ACTION}" $action.Button.Text}}</p>
|
||||
<p class="sub"><a href="{{ $action.Button.Link }}">{{ $action.Button.Link }}</a></p>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
<td class="content-cell">
|
||||
<p class="sub center">
|
||||
{{.Hermes.Product.Copyright}}
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
}
|
||||
|
||||
// PlainTextTemplate returns a Golang template that will generate an plain text email.
|
||||
func (dt *MailThemeFlat) PlainTextTemplate() string {
|
||||
return `{{ with .Email.Body.Intros }}
|
||||
{{ range $line := . }}
|
||||
<p>{{ $line }}</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if (ne .Email.Body.FreeMarkdown "") }}
|
||||
{{ .Email.Body.FreeMarkdown.ToHTML }}
|
||||
{{ else }}
|
||||
{{ with .Email.Body.Dictionary }}
|
||||
<ul>
|
||||
{{ range $entry := . }}
|
||||
<li>{{ $entry.Key }}: {{ $entry.Value }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
{{ with .Email.Body.Table }}
|
||||
{{ $data := .Data }}
|
||||
{{ $columns := .Columns }}
|
||||
{{ if gt (len $data) 0 }}
|
||||
<table class="data-table" width="100%" cellpadding="0" cellspacing="0">
|
||||
<tr>
|
||||
{{ $col := index $data 0 }}
|
||||
{{ range $entry := $col }}
|
||||
<th>{{ $entry.Key }} </th>
|
||||
{{ end }}
|
||||
</tr>
|
||||
{{ range $row := $data }}
|
||||
<tr>
|
||||
{{ range $cell := $row }}
|
||||
<td>
|
||||
{{ $cell.Value }}
|
||||
</td>
|
||||
{{ end }}
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ with .Email.Body.Actions }}
|
||||
{{ range $action := . }}
|
||||
<p>{{ $action.Instructions }} {{ $action.Button.Link }}</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ with .Email.Body.Outros }}
|
||||
{{ range $line := . }}
|
||||
<p>{{ $line }}<p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<p>{{.Email.Body.Signature}},<br>{{.Hermes.Product.Name}} - {{.Hermes.Product.Link}}</p>
|
||||
|
||||
<p>{{.Hermes.Product.Copyright}}</p>
|
||||
`
|
||||
}
|
||||
|
||||
func (dt *MailThemeFlat) GetStyle() string {
|
||||
return `
|
||||
<style>
|
||||
.content-cell table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.content-cell table,
|
||||
.content-cell th,
|
||||
.content-cell td {
|
||||
border: 1px solid #EDEFF2;
|
||||
}
|
||||
.content-cell th,
|
||||
.content-cell td {
|
||||
padding: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
.content-cell th {
|
||||
background: #409eff;
|
||||
color: white;
|
||||
}
|
||||
.content-cell td {
|
||||
color: #606266;
|
||||
}
|
||||
.content-cell p {
|
||||
color: #606266;
|
||||
}
|
||||
.content-cell a {
|
||||
color: #409eff;
|
||||
}
|
||||
.email-masthead .email-masthead_name {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
color: #409eff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
}
|
||||
62
core/notification/mobile.go
Normal file
62
core/notification/mobile.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/crawlab-team/go-trace"
|
||||
"github.com/imroc/req"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ResBody struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
}
|
||||
|
||||
func SendMobileNotification(webhook string, title string, content string) error {
|
||||
// request header
|
||||
header := req.Header{
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
}
|
||||
|
||||
// request data
|
||||
data := req.Param{
|
||||
"msgtype": "markdown",
|
||||
"markdown": req.Param{
|
||||
"title": title,
|
||||
"text": content,
|
||||
"content": content,
|
||||
},
|
||||
"at": req.Param{
|
||||
"atMobiles": []string{},
|
||||
"isAtAll": false,
|
||||
},
|
||||
"text": content,
|
||||
}
|
||||
if strings.Contains(strings.ToLower(webhook), "feishu") {
|
||||
data = req.Param{
|
||||
"msg_type": "text",
|
||||
"content": req.Param{
|
||||
"text": content,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// perform request
|
||||
res, err := req.Post(webhook, header, req.BodyJSON(&data))
|
||||
if err != nil {
|
||||
return trace.TraceError(err)
|
||||
}
|
||||
|
||||
// parse response
|
||||
var resBody ResBody
|
||||
if err := res.ToJSON(&resBody); err != nil {
|
||||
return trace.TraceError(err)
|
||||
}
|
||||
|
||||
// validate response code
|
||||
if resBody.ErrCode != 0 {
|
||||
return errors.New(resBody.ErrMsg)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
32
core/notification/models.go
Normal file
32
core/notification/models.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package notification
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
type NotificationSetting struct {
|
||||
Id primitive.ObjectID `json:"_id" bson:"_id"`
|
||||
Type string `json:"type" bson:"type"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Description string `json:"description" bson:"description"`
|
||||
Enabled bool `json:"enabled" bson:"enabled"`
|
||||
Global bool `json:"global" bson:"global"`
|
||||
Title string `json:"title,omitempty" bson:"title,omitempty"`
|
||||
Template string `json:"template,omitempty" bson:"template,omitempty"`
|
||||
TaskTrigger string `json:"task_trigger" bson:"task_trigger"`
|
||||
Mail NotificationSettingMail `json:"mail,omitempty" bson:"mail,omitempty"`
|
||||
Mobile NotificationSettingMobile `json:"mobile,omitempty" bson:"mobile,omitempty"`
|
||||
}
|
||||
|
||||
type NotificationSettingMail struct {
|
||||
Server string `json:"server" bson:"server"`
|
||||
Port string `json:"port,omitempty" bson:"port,omitempty"`
|
||||
User string `json:"user,omitempty" bson:"user,omitempty"`
|
||||
Password string `json:"password,omitempty" bson:"password,omitempty"`
|
||||
SenderEmail string `json:"sender_email,omitempty" bson:"sender_email,omitempty"`
|
||||
SenderIdentity string `json:"sender_identity,omitempty" bson:"sender_identity,omitempty"`
|
||||
To string `json:"to,omitempty" bson:"to,omitempty"`
|
||||
Cc string `json:"cc,omitempty" bson:"cc,omitempty"`
|
||||
}
|
||||
|
||||
type NotificationSettingMobile struct {
|
||||
Webhook string `json:"webhook" bson:"webhook"`
|
||||
}
|
||||
8
core/notification/payload.go
Normal file
8
core/notification/payload.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package notification
|
||||
|
||||
import "go.mongodb.org/mongo-driver/bson/primitive"
|
||||
|
||||
type SendPayload struct {
|
||||
TaskId primitive.ObjectID `json:"task_id"`
|
||||
Data string `json:"data"`
|
||||
}
|
||||
395
core/notification/service.go
Normal file
395
core/notification/service.go
Normal file
@@ -0,0 +1,395 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
mongo2 "github.com/crawlab-team/crawlab-db/mongo"
|
||||
"github.com/crawlab-team/crawlab/core/constants"
|
||||
"github.com/crawlab-team/crawlab/core/entity"
|
||||
"github.com/crawlab-team/crawlab/core/models/models"
|
||||
"github.com/crawlab-team/crawlab/core/models/service"
|
||||
"github.com/crawlab-team/crawlab/core/utils"
|
||||
parser "github.com/crawlab-team/template-parser"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
col *mongo2.Col // notification settings
|
||||
modelSvc service.ModelService
|
||||
}
|
||||
|
||||
func (svc *Service) Init() (err error) {
|
||||
if !utils.IsPro() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) Start() (err error) {
|
||||
// initialize data
|
||||
if err := svc.initData(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) Stop() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) initData() (err error) {
|
||||
total, err := svc.col.Count(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if total > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// data to initialize
|
||||
settings := []NotificationSetting{
|
||||
{
|
||||
Id: primitive.NewObjectID(),
|
||||
Type: TypeMail,
|
||||
Enabled: true,
|
||||
Name: "任务通知(邮件)",
|
||||
Description: "这是默认的邮件通知。您可以使用您自己的设置进行编辑。",
|
||||
TaskTrigger: constants.NotificationTriggerTaskFinish,
|
||||
Title: "[Crawlab] 爬虫任务更新: {{$.status}}",
|
||||
Template: `尊敬的 {{$.user.username}},
|
||||
|
||||
请查看下面的任务数据。
|
||||
|
||||
|键|值|
|
||||
|:-:|:--|
|
||||
|任务状态|{{$.status}}|
|
||||
|任务优先级|{{$.priority}}|
|
||||
|任务模式|{{$.mode}}|
|
||||
|执行命令|{{$.cmd}}|
|
||||
|执行参数|{{$.param}}|
|
||||
|错误信息|{{$.error}}|
|
||||
|节点|{{$.node.name}}|
|
||||
|爬虫|{{$.spider.name}}|
|
||||
|项目|{{$.spider.project.name}}|
|
||||
|定时任务|{{$.schedule.name}}|
|
||||
|结果数|{{$.:task_stat.result_count}}|
|
||||
|等待时间(秒)|{#{{$.:task_stat.wait_duration}}/1000#}|
|
||||
|运行时间(秒)|{#{{$.:task_stat.runtime_duration}}/1000#}|
|
||||
|总时间(秒)|{#{{$.:task_stat.total_duration}}/1000#}|
|
||||
|平均结果数/秒|{#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}|
|
||||
`,
|
||||
Mail: NotificationSettingMail{
|
||||
Server: "smtp.163.com",
|
||||
Port: "465",
|
||||
To: "{{$.user[create].email}}",
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: primitive.NewObjectID(),
|
||||
Type: TypeMail,
|
||||
Enabled: true,
|
||||
Name: "Task Change (Mail)",
|
||||
Description: "This is the default mail notification. You can edit it with your own settings",
|
||||
TaskTrigger: constants.NotificationTriggerTaskFinish,
|
||||
Title: "[Crawlab] Task Update: {{$.status}}",
|
||||
Template: `Dear {{$.user.username}},
|
||||
|
||||
Please find the task data as below.
|
||||
|
||||
|Key|Value|
|
||||
|:-:|:--|
|
||||
|Task Status|{{$.status}}|
|
||||
|Task Priority|{{$.priority}}|
|
||||
|Task Mode|{{$.mode}}|
|
||||
|Task Command|{{$.cmd}}|
|
||||
|Task Params|{{$.param}}|
|
||||
|Error Message|{{$.error}}|
|
||||
|Node|{{$.node.name}}|
|
||||
|Spider|{{$.spider.name}}|
|
||||
|Project|{{$.spider.project.name}}|
|
||||
|Schedule|{{$.schedule.name}}|
|
||||
|Result Count|{{$.:task_stat.result_count}}|
|
||||
|Wait Duration (sec)|{#{{$.:task_stat.wait_duration}}/1000#}|
|
||||
|Runtime Duration (sec)|{#{{$.:task_stat.runtime_duration}}/1000#}|
|
||||
|Total Duration (sec)|{#{{$.:task_stat.total_duration}}/1000#}|
|
||||
|Avg Results / Sec|{#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}|
|
||||
`,
|
||||
Mail: NotificationSettingMail{
|
||||
Server: "smtp.163.com",
|
||||
Port: "465",
|
||||
To: "{{$.user[create].email}}",
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: primitive.NewObjectID(),
|
||||
Type: TypeMobile,
|
||||
Enabled: true,
|
||||
Name: "任务通知(移动端)",
|
||||
Description: "这是默认的手机通知。您可以使用您自己的设置进行编辑。",
|
||||
TaskTrigger: constants.NotificationTriggerTaskFinish,
|
||||
Title: "[Crawlab] 任务更新: {{$.status}}",
|
||||
Template: `尊敬的 {{$.user.username}},
|
||||
|
||||
请查看下面的任务数据。
|
||||
|
||||
- **任务状态**: {{$.status}}
|
||||
- **任务优先级**: {{$.priority}}
|
||||
- **任务模式**: {{$.mode}}
|
||||
- **执行命令**: {{$.cmd}}
|
||||
- **执行参数**: {{$.param}}
|
||||
- **错误信息**: {{$.error}}
|
||||
- **节点**: {{$.node.name}}
|
||||
- **爬虫**: {{$.spider.name}}
|
||||
- **项目**: {{$.spider.project.name}}
|
||||
- **定时任务**: {{$.schedule.name}}
|
||||
- **结果数**: {{$.:task_stat.result_count}}
|
||||
- **等待时间(秒)**: {#{{$.:task_stat.wait_duration}}/1000#}
|
||||
- **运行时间(秒)**: {#{{$.:task_stat.runtime_duration}}/1000#}
|
||||
- **总时间(秒)**: {#{{$.:task_stat.total_duration}}/1000#}
|
||||
- **平均结果数/秒**: {#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}`,
|
||||
Mobile: NotificationSettingMobile{},
|
||||
},
|
||||
{
|
||||
Id: primitive.NewObjectID(),
|
||||
Type: TypeMobile,
|
||||
Enabled: true,
|
||||
Name: "Task Change (Mobile)",
|
||||
Description: "This is the default mobile notification. You can edit it with your own settings",
|
||||
TaskTrigger: constants.NotificationTriggerTaskError,
|
||||
Title: "[Crawlab] Task Update: {{$.status}}",
|
||||
Template: `Dear {{$.user.username}},
|
||||
|
||||
Please find the task data as below.
|
||||
|
||||
- **Task Status**: {{$.status}}
|
||||
- **Task Priority**: {{$.priority}}
|
||||
- **Task Mode**: {{$.mode}}
|
||||
- **Task Command**: {{$.cmd}}
|
||||
- **Task Params**: {{$.param}}
|
||||
- **Error Message**: {{$.error}}
|
||||
- **Node**: {{$.node.name}}
|
||||
- **Spider**: {{$.spider.name}}
|
||||
- **Project**: {{$.spider.project.name}}
|
||||
- **Schedule**: {{$.schedule.name}}
|
||||
- **Result Count**: {{$.:task_stat.result_count}}
|
||||
- **Wait Duration (sec)**: {#{{$.:task_stat.wait_duration}}/1000#}
|
||||
- **Runtime Duration (sec)**: {#{{$.:task_stat.runtime_duration}}/1000#}
|
||||
- **Total Duration (sec)**: {#{{$.:task_stat.total_duration}}/1000#}
|
||||
- **Avg Results / Sec**: {#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}`,
|
||||
Mobile: NotificationSettingMobile{},
|
||||
},
|
||||
}
|
||||
var data []interface{}
|
||||
for _, s := range settings {
|
||||
data = append(data, s)
|
||||
}
|
||||
_, err = svc.col.InsertMany(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) Send(s NotificationSetting, entity bson.M) (err error) {
|
||||
switch s.Type {
|
||||
case TypeMail:
|
||||
return svc.SendMail(s, entity)
|
||||
case TypeMobile:
|
||||
return svc.SendMobile(s, entity)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) SendMail(s NotificationSetting, entity bson.M) (err error) {
|
||||
// to
|
||||
to, err := parser.Parse(s.Mail.To, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'to' error: %v", err)
|
||||
}
|
||||
if to == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// cc
|
||||
cc, err := parser.Parse(s.Mail.Cc, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'cc' error: %v", err)
|
||||
}
|
||||
|
||||
// title
|
||||
title, err := parser.Parse(s.Title, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'title' error: %v", err)
|
||||
}
|
||||
|
||||
// content
|
||||
content, err := parser.Parse(s.Template, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'content' error: %v", err)
|
||||
}
|
||||
|
||||
// send mail
|
||||
if err := SendMail(&models.NotificationSettingMail{
|
||||
Server: s.Mail.Server,
|
||||
Port: s.Mail.Port,
|
||||
User: s.Mail.User,
|
||||
Password: s.Mail.Password,
|
||||
SenderEmail: s.Mail.SenderEmail,
|
||||
SenderIdentity: s.Mail.SenderIdentity,
|
||||
To: s.Mail.To,
|
||||
Cc: s.Mail.Cc,
|
||||
}, to, cc, title, content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) SendMobile(s NotificationSetting, entity bson.M) (err error) {
|
||||
// webhook
|
||||
webhook, err := parser.Parse(s.Mobile.Webhook, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'webhook' error: %v", err)
|
||||
}
|
||||
if webhook == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// title
|
||||
title, err := parser.Parse(s.Title, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'title' error: %v", err)
|
||||
}
|
||||
|
||||
// content
|
||||
content, err := parser.Parse(s.Template, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'content' error: %v", err)
|
||||
}
|
||||
|
||||
// send
|
||||
if err := SendMobileNotification(webhook, title, content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetSettingList(query bson.M, pagination *entity.Pagination, sort bson.D) (res []NotificationSetting, total int, err error) {
|
||||
// options
|
||||
var options *mongo2.FindOptions
|
||||
if pagination != nil || sort != nil {
|
||||
options = new(mongo2.FindOptions)
|
||||
if pagination != nil {
|
||||
options.Skip = pagination.Size * (pagination.Page - 1)
|
||||
options.Limit = pagination.Size
|
||||
}
|
||||
if sort != nil {
|
||||
options.Sort = sort
|
||||
}
|
||||
}
|
||||
|
||||
// get list
|
||||
var list []NotificationSetting
|
||||
if err := svc.col.Find(query, options).All(&list); err != nil {
|
||||
if err.Error() == mongo.ErrNoDocuments.Error() {
|
||||
return nil, 0, nil
|
||||
} else {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// total count
|
||||
total, err = svc.col.Count(query)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (svc *Service) GetSetting(id primitive.ObjectID) (res *NotificationSetting, err error) {
|
||||
var s NotificationSetting
|
||||
if err := svc.col.FindId(id).One(&s); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (svc *Service) PosSetting(s *NotificationSetting) (err error) {
|
||||
s.Id = primitive.NewObjectID()
|
||||
if _, err := svc.col.Insert(s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) PutSetting(id primitive.ObjectID, s NotificationSetting) (err error) {
|
||||
if err := svc.col.ReplaceId(id, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteSetting(id primitive.ObjectID) (err error) {
|
||||
if err := svc.col.DeleteId(id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) EnableSetting(id primitive.ObjectID) (err error) {
|
||||
return svc._toggleSettingFunc(true)(id)
|
||||
}
|
||||
|
||||
func (svc *Service) DisableSetting(id primitive.ObjectID) (err error) {
|
||||
return svc._toggleSettingFunc(false)(id)
|
||||
}
|
||||
|
||||
func (svc *Service) _toggleSettingFunc(value bool) func(id primitive.ObjectID) error {
|
||||
return func(id primitive.ObjectID) (err error) {
|
||||
var s NotificationSetting
|
||||
if err := svc.col.FindId(id).One(&s); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Enabled = value
|
||||
if err := svc.col.ReplaceId(id, s); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewService() *Service {
|
||||
// service
|
||||
svc := &Service{
|
||||
col: mongo2.GetMongoCol(SettingsColName),
|
||||
}
|
||||
|
||||
// model service
|
||||
modelSvc, err := service.GetService()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
svc.modelSvc = modelSvc
|
||||
|
||||
if err := svc.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
var _service *Service
|
||||
|
||||
func GetService() *Service {
|
||||
if _service == nil {
|
||||
_service = NewService()
|
||||
}
|
||||
return _service
|
||||
}
|
||||
19
core/notification/service_test.go
Normal file
19
core/notification/service_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestService_sendMobile(t *testing.T) {
|
||||
T.Setup(t)
|
||||
e := T.NewExpect(t)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
data := map[string]interface{}{
|
||||
"task_id": T.TestTask.GetId().Hex(),
|
||||
}
|
||||
e.POST("/send/mobile").WithJSON(data).
|
||||
Expect().Status(http.StatusOK)
|
||||
}
|
||||
362
core/notification/service_v2.go
Normal file
362
core/notification/service_v2.go
Normal file
@@ -0,0 +1,362 @@
|
||||
package notification
|
||||
|
||||
import (
|
||||
"github.com/apex/log"
|
||||
mongo2 "github.com/crawlab-team/crawlab-db/mongo"
|
||||
"github.com/crawlab-team/crawlab/core/constants"
|
||||
"github.com/crawlab-team/crawlab/core/entity"
|
||||
"github.com/crawlab-team/crawlab/core/models/models"
|
||||
"github.com/crawlab-team/crawlab/core/models/service"
|
||||
parser "github.com/crawlab-team/template-parser"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
type ServiceV2 struct {
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) Start() (err error) {
|
||||
// initialize data
|
||||
if err := svc.initData(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) Stop() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) initData() (err error) {
|
||||
total, err := service.NewModelServiceV2[models.NotificationSettingV2]().Count(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if total > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// data to initialize
|
||||
settings := []models.NotificationSettingV2{
|
||||
{
|
||||
Id: primitive.NewObjectID(),
|
||||
Type: TypeMail,
|
||||
Enabled: true,
|
||||
Name: "任务通知(邮件)",
|
||||
Description: "这是默认的邮件通知。您可以使用您自己的设置进行编辑。",
|
||||
TaskTrigger: constants.NotificationTriggerTaskFinish,
|
||||
Title: "[Crawlab] 爬虫任务更新: {{$.status}}",
|
||||
Template: `尊敬的 {{$.user.username}},
|
||||
|
||||
请查看下面的任务数据。
|
||||
|
||||
|键|值|
|
||||
|:-:|:--|
|
||||
|任务状态|{{$.status}}|
|
||||
|任务优先级|{{$.priority}}|
|
||||
|任务模式|{{$.mode}}|
|
||||
|执行命令|{{$.cmd}}|
|
||||
|执行参数|{{$.param}}|
|
||||
|错误信息|{{$.error}}|
|
||||
|节点|{{$.node.name}}|
|
||||
|爬虫|{{$.spider.name}}|
|
||||
|项目|{{$.spider.project.name}}|
|
||||
|定时任务|{{$.schedule.name}}|
|
||||
|结果数|{{$.:task_stat.result_count}}|
|
||||
|等待时间(秒)|{#{{$.:task_stat.wait_duration}}/1000#}|
|
||||
|运行时间(秒)|{#{{$.:task_stat.runtime_duration}}/1000#}|
|
||||
|总时间(秒)|{#{{$.:task_stat.total_duration}}/1000#}|
|
||||
|平均结果数/秒|{#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}|
|
||||
`,
|
||||
Mail: models.NotificationSettingMail{
|
||||
Server: "smtp.163.com",
|
||||
Port: "465",
|
||||
To: "{{$.user[create].email}}",
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: primitive.NewObjectID(),
|
||||
Type: TypeMail,
|
||||
Enabled: true,
|
||||
Name: "Task Change (Mail)",
|
||||
Description: "This is the default mail notification. You can edit it with your own settings",
|
||||
TaskTrigger: constants.NotificationTriggerTaskFinish,
|
||||
Title: "[Crawlab] Task Update: {{$.status}}",
|
||||
Template: `Dear {{$.user.username}},
|
||||
|
||||
Please find the task data as below.
|
||||
|
||||
|Key|Value|
|
||||
|:-:|:--|
|
||||
|Task Status|{{$.status}}|
|
||||
|Task Priority|{{$.priority}}|
|
||||
|Task Mode|{{$.mode}}|
|
||||
|Task Command|{{$.cmd}}|
|
||||
|Task Params|{{$.param}}|
|
||||
|Error Message|{{$.error}}|
|
||||
|Node|{{$.node.name}}|
|
||||
|Spider|{{$.spider.name}}|
|
||||
|Project|{{$.spider.project.name}}|
|
||||
|Schedule|{{$.schedule.name}}|
|
||||
|Result Count|{{$.:task_stat.result_count}}|
|
||||
|Wait Duration (sec)|{#{{$.:task_stat.wait_duration}}/1000#}|
|
||||
|Runtime Duration (sec)|{#{{$.:task_stat.runtime_duration}}/1000#}|
|
||||
|Total Duration (sec)|{#{{$.:task_stat.total_duration}}/1000#}|
|
||||
|Avg Results / Sec|{#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}|
|
||||
`,
|
||||
Mail: models.NotificationSettingMail{
|
||||
Server: "smtp.163.com",
|
||||
Port: "465",
|
||||
To: "{{$.user[create].email}}",
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: primitive.NewObjectID(),
|
||||
Type: TypeMobile,
|
||||
Enabled: true,
|
||||
Name: "任务通知(移动端)",
|
||||
Description: "这是默认的手机通知。您可以使用您自己的设置进行编辑。",
|
||||
TaskTrigger: constants.NotificationTriggerTaskFinish,
|
||||
Title: "[Crawlab] 任务更新: {{$.status}}",
|
||||
Template: `尊敬的 {{$.user.username}},
|
||||
|
||||
请查看下面的任务数据。
|
||||
|
||||
- **任务状态**: {{$.status}}
|
||||
- **任务优先级**: {{$.priority}}
|
||||
- **任务模式**: {{$.mode}}
|
||||
- **执行命令**: {{$.cmd}}
|
||||
- **执行参数**: {{$.param}}
|
||||
- **错误信息**: {{$.error}}
|
||||
- **节点**: {{$.node.name}}
|
||||
- **爬虫**: {{$.spider.name}}
|
||||
- **项目**: {{$.spider.project.name}}
|
||||
- **定时任务**: {{$.schedule.name}}
|
||||
- **结果数**: {{$.:task_stat.result_count}}
|
||||
- **等待时间(秒)**: {#{{$.:task_stat.wait_duration}}/1000#}
|
||||
- **运行时间(秒)**: {#{{$.:task_stat.runtime_duration}}/1000#}
|
||||
- **总时间(秒)**: {#{{$.:task_stat.total_duration}}/1000#}
|
||||
- **平均结果数/秒**: {#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}`,
|
||||
Mobile: models.NotificationSettingMobile{},
|
||||
},
|
||||
{
|
||||
Id: primitive.NewObjectID(),
|
||||
Type: TypeMobile,
|
||||
Enabled: true,
|
||||
Name: "Task Change (Mobile)",
|
||||
Description: "This is the default mobile notification. You can edit it with your own settings",
|
||||
TaskTrigger: constants.NotificationTriggerTaskError,
|
||||
Title: "[Crawlab] Task Update: {{$.status}}",
|
||||
Template: `Dear {{$.user.username}},
|
||||
|
||||
Please find the task data as below.
|
||||
|
||||
- **Task Status**: {{$.status}}
|
||||
- **Task Priority**: {{$.priority}}
|
||||
- **Task Mode**: {{$.mode}}
|
||||
- **Task Command**: {{$.cmd}}
|
||||
- **Task Params**: {{$.param}}
|
||||
- **Error Message**: {{$.error}}
|
||||
- **Node**: {{$.node.name}}
|
||||
- **Spider**: {{$.spider.name}}
|
||||
- **Project**: {{$.spider.project.name}}
|
||||
- **Schedule**: {{$.schedule.name}}
|
||||
- **Result Count**: {{$.:task_stat.result_count}}
|
||||
- **Wait Duration (sec)**: {#{{$.:task_stat.wait_duration}}/1000#}
|
||||
- **Runtime Duration (sec)**: {#{{$.:task_stat.runtime_duration}}/1000#}
|
||||
- **Total Duration (sec)**: {#{{$.:task_stat.total_duration}}/1000#}
|
||||
- **Avg Results / Sec**: {#{{$.:task_stat.result_count}}/({{$.:task_stat.total_duration}}/1000)#}`,
|
||||
Mobile: models.NotificationSettingMobile{},
|
||||
},
|
||||
}
|
||||
_, err = service.NewModelServiceV2[models.NotificationSettingV2]().InsertMany(settings)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) Send(s *models.NotificationSettingV2, entity bson.M) (err error) {
|
||||
switch s.Type {
|
||||
case TypeMail:
|
||||
return svc.SendMail(s, entity)
|
||||
case TypeMobile:
|
||||
return svc.SendMobile(s, entity)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) SendMail(s *models.NotificationSettingV2, entity bson.M) (err error) {
|
||||
// to
|
||||
to, err := parser.Parse(s.Mail.To, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'to' error: %v", err)
|
||||
}
|
||||
if to == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// cc
|
||||
cc, err := parser.Parse(s.Mail.Cc, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'cc' error: %v", err)
|
||||
}
|
||||
|
||||
// title
|
||||
title, err := parser.Parse(s.Title, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'title' error: %v", err)
|
||||
}
|
||||
|
||||
// content
|
||||
content, err := parser.Parse(s.Template, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'content' error: %v", err)
|
||||
}
|
||||
|
||||
// send mail
|
||||
if err := SendMail(&s.Mail, to, cc, title, content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) SendMobile(s *models.NotificationSettingV2, entity bson.M) (err error) {
|
||||
// webhook
|
||||
webhook, err := parser.Parse(s.Mobile.Webhook, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'webhook' error: %v", err)
|
||||
}
|
||||
if webhook == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// title
|
||||
title, err := parser.Parse(s.Title, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'title' error: %v", err)
|
||||
}
|
||||
|
||||
// content
|
||||
content, err := parser.Parse(s.Template, entity)
|
||||
if err != nil {
|
||||
log.Warnf("parsing 'content' error: %v", err)
|
||||
}
|
||||
|
||||
// send
|
||||
if err := SendMobileNotification(webhook, title, content); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) GetSettingList(query bson.M, pagination *entity.Pagination, sort bson.D) (res []models.NotificationSettingV2, total int, err error) {
|
||||
// options
|
||||
var options *mongo2.FindOptions
|
||||
if pagination != nil || sort != nil {
|
||||
options = new(mongo2.FindOptions)
|
||||
if pagination != nil {
|
||||
options.Skip = pagination.Size * (pagination.Page - 1)
|
||||
options.Limit = pagination.Size
|
||||
}
|
||||
if sort != nil {
|
||||
options.Sort = sort
|
||||
}
|
||||
}
|
||||
|
||||
// get list
|
||||
list, err := service.NewModelServiceV2[models.NotificationSettingV2]().GetMany(query, options)
|
||||
if err != nil {
|
||||
if err.Error() == mongo.ErrNoDocuments.Error() {
|
||||
return nil, 0, nil
|
||||
} else {
|
||||
return nil, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// total count
|
||||
total, err = service.NewModelServiceV2[models.NotificationSettingV2]().Count(query)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) GetSetting(id primitive.ObjectID) (res *models.NotificationSettingV2, err error) {
|
||||
s, err := service.NewModelServiceV2[models.NotificationSettingV2]().GetById(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) PosSetting(s *models.NotificationSettingV2) (err error) {
|
||||
s.Id = primitive.NewObjectID()
|
||||
_, err = service.NewModelServiceV2[models.NotificationSettingV2]().InsertOne(*s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) PutSetting(id primitive.ObjectID, s models.NotificationSettingV2) (err error) {
|
||||
err = service.NewModelServiceV2[models.NotificationSettingV2]().ReplaceById(id, s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) DeleteSetting(id primitive.ObjectID) (err error) {
|
||||
err = service.NewModelServiceV2[models.NotificationSettingV2]().DeleteById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) EnableSetting(id primitive.ObjectID) (err error) {
|
||||
return svc._toggleSettingFunc(true)(id)
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) DisableSetting(id primitive.ObjectID) (err error) {
|
||||
return svc._toggleSettingFunc(false)(id)
|
||||
}
|
||||
|
||||
func (svc *ServiceV2) _toggleSettingFunc(value bool) func(id primitive.ObjectID) error {
|
||||
return func(id primitive.ObjectID) (err error) {
|
||||
s, err := service.NewModelServiceV2[models.NotificationSettingV2]().GetById(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Enabled = value
|
||||
err = service.NewModelServiceV2[models.NotificationSettingV2]().ReplaceById(id, *s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewServiceV2() *ServiceV2 {
|
||||
// service
|
||||
svc := &ServiceV2{}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
var _serviceV2 *ServiceV2
|
||||
|
||||
func GetServiceV2() *ServiceV2 {
|
||||
if _serviceV2 == nil {
|
||||
_serviceV2 = NewServiceV2()
|
||||
}
|
||||
return _serviceV2
|
||||
}
|
||||
Reference in New Issue
Block a user