diff --git a/.github/workflows/dashboard.yml b/.github/workflows/dashboard.yml index 047c2c0..0246c11 100644 --- a/.github/workflows/dashboard.yml +++ b/.github/workflows/dashboard.yml @@ -23,5 +23,6 @@ jobs: - name: Build and push dasbboard image run: | + go test -v ./... docker build -t ghcr.io/${{ github.repository_owner }}/nezha-dashboard -f Dockerfile.dashboard . docker push ghcr.io/${{ github.repository_owner }}/nezha-dashboard diff --git a/README.md b/README.md index ed5ef47..6d47f5d 100644 --- a/README.md +++ b/README.md @@ -80,10 +80,11 @@ #### 灵活通知方式 -Body 内容是`JSON` 格式的,值为 `key:value` 的形式,`#NEZHA#` 是面板消息占位符,面板触发通知时会自动替换占位符到实际消息 +`#NEZHA#` 是面板消息占位符,面板触发通知时会自动替换占位符到实际消息 -- 请求方式为 GET 时面板会将 `Body` 里面的参数拼接到 URL 的 query 里面 -- 请求方式为 POST 时会将 `Body` 里面的 `key:value` 拼接到请求体里面 +Body 内容是`JSON` 格式的:**当请求类型为FORM时**,值为 `key:value` 的形式,`value` 里面可放置占位符,通知时会自动替换。**当请求类型为JSON时** 只会简进行字符串替换后直接提交到`URL`。 + +URL 里面也可放置占位符,请求时会进行简单的字符串替换。 参考下方的示例,非常灵活。 @@ -91,17 +92,17 @@ Body 内容是`JSON` 格式的,值为 `key:value` 的形式,`#NEZHA#` 是面 - server酱示例 - 备注:server酱 - - URL:https://sc.ftqq.com/SCUrandomkeys.send + - URL:https://sc.ftqq.com/SCUrandomkeys.send?text=#NEZHA# - 请求方式: GET - - 请求类型: JSON/FORM 都可以,其他接入其他API时要选择其使用的类型 - - Body: `{"text": "#NEZHA#"}` + - 请求类型: 默认 + - Body: 空 - wxpusher示例 - 备注: wxpusher - URL:http://wxpusher.zjiecode.com/api/send/message - - 请求方式: GET + - 请求方式: POST - 请求类型: JSON - - Body: `{"appToken":"你的appToken","content":"#NEZHA#","contentType":"1","uid":"你的uid"}` + - Body: `{"appToken":"你的appToken","topicIds":[应用topicID],"content":"#NEZHA#","contentType":"1","uids":["你的uid"]}` 2. 添加一个离线报警 diff --git a/cmd/dashboard/controller/member_api.go b/cmd/dashboard/controller/member_api.go index c7e9af8..a62cc23 100644 --- a/cmd/dashboard/controller/member_api.go +++ b/cmd/dashboard/controller/member_api.go @@ -135,10 +135,6 @@ func (ma *memberAPI) addOrEditNotification(c *gin.Context) { var nf notificationForm var n model.Notification err := c.ShouldBindJSON(&nf) - if err == nil { - var data map[string]string - err = json.Unmarshal([]byte(nf.RequestBody), &data) - } if err == nil { n.Name = nf.Name n.RequestMethod = nf.RequestMethod diff --git a/go.mod b/go.mod index 4f3c056..e406529 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/shirou/gopsutil/v3 v3.20.11 github.com/spf13/cobra v0.0.5 github.com/spf13/viper v1.7.1 + github.com/stretchr/testify v1.6.1 golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 google.golang.org/grpc v1.33.1 google.golang.org/protobuf v1.25.0 diff --git a/model/notification.go b/model/notification.go index b77157e..907cdd3 100644 --- a/model/notification.go +++ b/model/notification.go @@ -3,6 +3,7 @@ package model import ( "crypto/tls" "encoding/json" + "errors" "fmt" "net/http" "net/url" @@ -32,6 +33,42 @@ type Notification struct { VerifySSL *bool } +func (n *Notification) reqURL(message string) string { + return replaceParamsInString(n.URL, message) +} + +func (n *Notification) reqBody(message string) (string, error) { + if n.RequestMethod == NotificationRequestMethodGET { + return "", nil + } + switch n.RequestType { + case NotificationRequestTypeJSON: + return replaceParamsInString(n.RequestBody, message), nil + case NotificationRequestTypeForm: + var data map[string]string + if err := json.Unmarshal([]byte(n.RequestBody), &data); err != nil { + return "", err + } + params := url.Values{} + for k, v := range data { + params.Add(k, replaceParamsInString(v, message)) + } + return params.Encode(), nil + } + return "", errors.New("不支持的请求类型") +} + +func (n *Notification) reqContentType() string { + if n.RequestMethod == NotificationRequestMethodGET { + return "" + } + if n.RequestType == NotificationRequestTypeForm { + return "application/x-www-form-urlencoded" + } else { + return "application/json" + } +} + func (n *Notification) Send(message string) error { var verifySSL bool @@ -39,39 +76,20 @@ func (n *Notification) Send(message string) error { verifySSL = true } - var err error transCfg := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: verifySSL}, } client := &http.Client{Transport: transCfg, Timeout: time.Minute * 10} - var reqURL *url.URL - reqURL, err = url.Parse(n.URL) - var data map[string]string - if err == nil && (n.RequestMethod == NotificationRequestMethodGET || n.RequestType == NotificationRequestTypeForm) { - err = json.Unmarshal([]byte(n.RequestBody), &data) - } + + reqBody, err := n.reqBody(message) var resp *http.Response if err == nil { if n.RequestMethod == NotificationRequestMethodGET { - var queryValue = reqURL.Query() - for k, v := range data { - queryValue.Set(k, replaceParamsInString(v, message)) - } - reqURL.RawQuery = queryValue.Encode() - resp, err = client.Get(reqURL.String()) + resp, err = client.Get(n.reqURL(message)) } else { - if n.RequestType == NotificationRequestTypeForm { - params := url.Values{} - for k, v := range data { - params.Add(k, replaceParamsInString(v, message)) - } - resp, err = client.PostForm(reqURL.String(), params) - } else { - jsonValue := replaceParamsInJSON(n.RequestBody, message) - resp, err = client.Post(reqURL.String(), "application/json", strings.NewReader(jsonValue)) - } + resp, err = client.Post(n.reqURL(message), n.reqContentType(), strings.NewReader(reqBody)) } } @@ -86,17 +104,3 @@ func replaceParamsInString(str string, message string) string { str = strings.ReplaceAll(str, "#NEZHA#", message) return str } - -func replaceParamsInJSON(str string, message string) string { - str = strings.ReplaceAll(str, "#NEZHA#", message) - return str -} - -func jsonEscape(raw interface{}) string { - b, _ := json.Marshal(raw) - strb := string(b) - if strings.HasPrefix(strb, "\"") { - return strb[1 : len(strb)-1] - } - return strb -} diff --git a/model/notification_test.go b/model/notification_test.go new file mode 100644 index 0000000..9d0e3a9 --- /dev/null +++ b/model/notification_test.go @@ -0,0 +1,98 @@ +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + msg = "msg" + reqTypeForm = "application/x-www-form-urlencoded" + reqTypeJSON = "application/json" +) + +type testSt struct { + url string + body string + reqType int + reqMethod int + expectURL string + expectBody string + expectType string +} + +func execCase(t *testing.T, item testSt) { + n := Notification{ + URL: item.url, + RequestMethod: item.reqMethod, + RequestType: item.reqType, + RequestBody: item.body, + } + assert.Equal(t, item.expectURL, n.reqURL(msg)) + reqBody, err := n.reqBody(msg) + assert.Nil(t, err) + assert.Equal(t, item.expectBody, reqBody) + assert.Equal(t, item.expectType, n.reqContentType()) +} + +func TestNotification(t *testing.T) { + cases := []testSt{ + { + url: "https://example.com", + body: `{"asd":"dsa"}`, + reqMethod: NotificationRequestMethodGET, + expectURL: "https://example.com", + expectBody: "", + expectType: "", + }, + { + url: "https://example.com/?m=#NEZHA#", + body: `{"asd":"dsa"}`, + reqMethod: NotificationRequestMethodGET, + expectURL: "https://example.com/?m=" + msg, + expectBody: "", + expectType: "", + }, + { + url: "https://example.com/?m=#NEZHA#", + body: `{"asd":"#NEZHA#"}`, + reqMethod: NotificationRequestMethodPOST, + reqType: NotificationRequestTypeForm, + expectURL: "https://example.com/?m=" + msg, + expectBody: "asd=" + msg, + expectType: reqTypeForm, + }, + { + url: "https://example.com/?m=#NEZHA#", + body: `{"#NEZHA#":"#NEZHA#"}`, + reqMethod: NotificationRequestMethodPOST, + reqType: NotificationRequestTypeForm, + expectURL: "https://example.com/?m=" + msg, + expectBody: "%23NEZHA%23=" + msg, + expectType: reqTypeForm, + }, + { + url: "https://example.com/?m=#NEZHA#", + body: `{"asd":"#NEZHA#"}`, + reqMethod: NotificationRequestMethodPOST, + reqType: NotificationRequestTypeJSON, + expectURL: "https://example.com/?m=" + msg, + expectBody: `{"asd":"msg"}`, + expectType: reqTypeJSON, + }, + { + url: "https://example.com/?m=#NEZHA#", + body: `{"#NEZHA#":"#NEZHA#"}`, + reqMethod: NotificationRequestMethodPOST, + reqType: NotificationRequestTypeJSON, + expectURL: "https://example.com/?m=" + msg, + expectBody: `{"msg":"msg"}`, + expectType: reqTypeJSON, + }, + } + + for _, c := range cases { + execCase(t, c) + } +}