Skip to content

Commit b177c20

Browse files
committed
【新增】【通知】WebHOOK通知类型
1 parent 39f66a9 commit b177c20

File tree

5 files changed

+276
-49
lines changed

5 files changed

+276
-49
lines changed

backend/internal/report/report.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ func NotifyTest(id string) error {
124124
switch providerData["type"] {
125125
case "mail":
126126
err = NotifyMail(params)
127+
case "webhook":
128+
err = NotifyWebHook(params)
127129
}
128130
return err
129131
}
@@ -141,6 +143,8 @@ func Notify(params map[string]any) error {
141143
return NotifyMail(params)
142144
// case "btpanel-site":
143145
// return NotifyBt(params)
146+
case "webhook":
147+
return NotifyWebHook(params)
144148
default:
145149
return fmt.Errorf("不支持的通知类型")
146150
}

backend/internal/report/webhook.go

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package report
2+
3+
import (
4+
"ALLinSSL/backend/public"
5+
"context"
6+
"crypto/tls"
7+
"encoding/json"
8+
"fmt"
9+
"github.com/go-resty/resty/v2"
10+
"net/http"
11+
"strings"
12+
"time"
13+
)
14+
15+
type ReportConfig struct {
16+
Url string `json:"url"`
17+
Data string `json:"data,omitempty"`
18+
Method string `json:"method,omitempty"`
19+
Headers string `json:"headers,omitempty"`
20+
IgnoreSSL bool `json:"ignore_ssl,omitempty"`
21+
}
22+
23+
type WebHookReporter struct {
24+
config *ReportConfig
25+
logger *public.Logger
26+
httpClient *resty.Client
27+
}
28+
29+
func NewWebHookReporter(config *ReportConfig, logger *public.Logger) *WebHookReporter {
30+
client := resty.New()
31+
client.SetTimeout(30 * time.Second)
32+
33+
if config.IgnoreSSL {
34+
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
35+
}
36+
37+
return &WebHookReporter{
38+
config: config,
39+
logger: logger,
40+
httpClient: client,
41+
}
42+
}
43+
44+
func (w *WebHookReporter) Send(ctx context.Context) error {
45+
// 确定HTTP方法
46+
method := strings.ToUpper(w.config.Method)
47+
if method == "" {
48+
method = http.MethodPost // 默认使用POST方法
49+
}
50+
51+
// 创建基础请求
52+
req := w.httpClient.R().
53+
SetContext(ctx)
54+
55+
// 设置请求头
56+
if w.config.Headers != "" {
57+
reqHeader, err := w.ParseHeaders(w.config.Headers)
58+
if err != nil {
59+
return fmt.Errorf("解析请求头错误: %w", err)
60+
}
61+
req.Header = reqHeader
62+
}
63+
64+
switch method {
65+
case http.MethodPost:
66+
{
67+
contentType := req.Header.Get("application/json")
68+
if contentType == "" {
69+
contentType = "application/json"
70+
}
71+
switch contentType {
72+
case "application/json":
73+
req.SetHeader("Content-Type", "application/json")
74+
var reqData interface{}
75+
err := json.Unmarshal([]byte(w.config.Data), &reqData)
76+
if err != nil {
77+
return fmt.Errorf("webhook数据解析失败err: %w", err)
78+
}
79+
req.SetBody(reqData)
80+
case "application/x-www-form-urlencoded":
81+
req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
82+
reqData := make(map[string]string)
83+
err := json.Unmarshal([]byte(w.config.Data), &reqData)
84+
if err != nil {
85+
return fmt.Errorf("webhook数据解析失败err: %w", err)
86+
}
87+
req.SetFormData(reqData)
88+
case "multipart/form-data":
89+
req.SetHeader("Content-Type", "multipart/form-data")
90+
reqData := make(map[string]string)
91+
err := json.Unmarshal([]byte(w.config.Data), &reqData)
92+
if err != nil {
93+
return fmt.Errorf("webhook数据解析失败err: %w", err)
94+
}
95+
req.SetMultipartFormData(reqData)
96+
}
97+
}
98+
case http.MethodGet:
99+
{
100+
reqData := make(map[string]string)
101+
err := json.Unmarshal([]byte(w.config.Data), &reqData)
102+
if err != nil {
103+
return fmt.Errorf("webhook数据解析失败err: %w", err)
104+
}
105+
req.SetQueryParams(reqData)
106+
}
107+
default:
108+
return fmt.Errorf("暂不支持的HTTP方法: %s", method)
109+
}
110+
111+
// 发送请求
112+
resp, err := req.Execute(method, w.config.Url)
113+
if err != nil {
114+
if w.logger != nil {
115+
w.logger.Error(fmt.Sprintf("Webhook请求失败%s %v", w.config.Url, err))
116+
}
117+
118+
return fmt.Errorf("webhook请求失败: %w", err)
119+
}
120+
121+
// 处理响应
122+
if resp.IsError() {
123+
if w.logger != nil {
124+
w.logger.Error(fmt.Sprintf("Webhook返回错误响应%s %d", w.config.Url, resp.StatusCode()))
125+
}
126+
return fmt.Errorf("webhook返回错误状态码: %d", resp.StatusCode())
127+
}
128+
129+
if w.logger != nil {
130+
w.logger.Debug(fmt.Sprintf("Webhook请求成功 %s", w.config.Url))
131+
}
132+
return nil
133+
}
134+
135+
func (w *WebHookReporter) ParseHeaders(headerStr string) (http.Header, error) {
136+
headers := make(http.Header)
137+
lines := strings.Split(headerStr, "\n")
138+
139+
for i, line := range lines {
140+
line = strings.TrimSpace(line)
141+
if line == "" {
142+
continue
143+
}
144+
parts := strings.SplitN(line, ":", 2)
145+
if len(parts) != 2 {
146+
return nil, fmt.Errorf("解析请求头错误 第%d行: %s", i+1, line)
147+
}
148+
key := strings.TrimSpace(parts[0])
149+
value := strings.TrimSpace(parts[1])
150+
if key == "" || value == "" {
151+
return nil, fmt.Errorf("请求头Key第%d行为空", i+1)
152+
}
153+
canonicalKey := http.CanonicalHeaderKey(key)
154+
headers.Add(canonicalKey, value)
155+
}
156+
157+
return headers, nil
158+
}
159+
160+
func NotifyWebHook(params map[string]any) error {
161+
if params == nil {
162+
return fmt.Errorf("缺少参数")
163+
}
164+
providerID := params["provider_id"].(string)
165+
166+
var logger *public.Logger
167+
168+
if params["logger"] != nil {
169+
logger = params["logger"].(*public.Logger)
170+
}
171+
172+
providerData, err := GetReport(providerID)
173+
if err != nil {
174+
return err
175+
}
176+
configStr := providerData["config"].(string)
177+
var config ReportConfig
178+
err = json.Unmarshal([]byte(configStr), &config)
179+
if err != nil {
180+
return fmt.Errorf("解析配置失败: %v", err)
181+
}
182+
183+
reporter := NewWebHookReporter(&config, logger)
184+
httpctx := context.Background()
185+
err = reporter.Send(httpctx)
186+
if err != nil {
187+
return fmt.Errorf("webhook发送失败: %w", err)
188+
}
189+
return nil
190+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package report
2+
3+
import (
4+
"ALLinSSL/backend/public"
5+
"context"
6+
"testing"
7+
)
8+
9+
func TestSend(test *testing.T) {
10+
logger, _ := public.NewLogger("/tmp/test.log")
11+
12+
jsonConfig := &ReportConfig{
13+
Url: "http://localhost:9939/demo/any",
14+
Method: "GET",
15+
Headers: `X-Auth-Token: secret123`,
16+
Data: `{"username": "zszs", "password": "get"}`,
17+
}
18+
19+
jsonConfig1 := &ReportConfig{
20+
Url: "http://localhost:9939/demo/any",
21+
Method: "post",
22+
Headers: `
23+
Content-Type: application/json
24+
X-Auth-Token: secret123`,
25+
Data: `{"username": "zszs", "password": "post-json"}`,
26+
}
27+
28+
jsonConfig2 := &ReportConfig{
29+
Url: "http://localhost:9939/demo/any",
30+
Method: "post",
31+
Headers: `
32+
Content-Type: application/x-www-form-urlencoded
33+
X-Auth-Token: secret123`,
34+
Data: `{"username": "zszs", "password": "post-form-urlencoded"}`,
35+
}
36+
37+
jsonConfig3 := &ReportConfig{
38+
Url: "http://localhost:9939/demo/any",
39+
Method: "post",
40+
Headers: `
41+
Content-Type: multipart/form-data
42+
X-Auth-Token: secret123`,
43+
Data: `{"username": "zszs", "password": "post-form-data"}`,
44+
}
45+
46+
reqs := []*ReportConfig{jsonConfig, jsonConfig1, jsonConfig2, jsonConfig3}
47+
48+
for _, req := range reqs {
49+
// 创建报告器
50+
jsonReporter := NewWebHookReporter(req, logger)
51+
52+
// 发送请求
53+
ctx := context.Background()
54+
if err := jsonReporter.Send(ctx); err != nil {
55+
test.Error("JSON Webhook发送失败", "error", err)
56+
continue
57+
}
58+
59+
test.Log("JSON Webhook发送成功", "url", req.Url, "method", req.Method)
60+
}
61+
}
62+
63+
func TestNotifyWebHook(test *testing.T) {
64+
params := map[string]any{
65+
"provider_id": "2",
66+
"body": "测试消息通道",
67+
"subject": "测试消息通道",
68+
}
69+
70+
err := NotifyWebHook(params)
71+
if err != nil {
72+
test.Error("NotifyWebHook failed", "error", err)
73+
} else {
74+
test.Log("NotifyWebHook success")
75+
}
76+
}

go.mod

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ require (
1717
github.com/gin-contrib/sessions v1.0.3
1818
github.com/gin-gonic/gin v1.10.0
1919
github.com/go-acme/lego/v4 v4.23.1
20+
github.com/go-resty/resty/v2 v2.16.5
2021
github.com/google/uuid v1.6.0
2122
github.com/joho/godotenv v1.5.1
2223
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
@@ -30,13 +31,6 @@ require (
3031
)
3132

3233
require (
33-
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.1 // indirect
34-
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 // indirect
35-
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
36-
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns v1.2.0 // indirect
37-
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/privatedns/armprivatedns v1.3.0 // indirect
38-
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
39-
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 // indirect
4034
github.com/BurntSushi/toml v1.5.0 // indirect
4135
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
4236
github.com/alibabacloud-go/debug v1.0.1 // indirect
@@ -71,7 +65,6 @@ require (
7165
github.com/go-playground/universal-translator v0.18.1 // indirect
7266
github.com/go-playground/validator/v10 v10.26.0 // indirect
7367
github.com/goccy/go-json v0.10.5 // indirect
74-
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
7568
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
7669
github.com/google/go-querystring v1.1.0 // indirect
7770
github.com/gorilla/context v1.1.2 // indirect
@@ -81,7 +74,7 @@ require (
8174
github.com/jmespath/go-jmespath v0.4.0 // indirect
8275
github.com/json-iterator/go v1.1.12 // indirect
8376
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
84-
github.com/kylelemons/godebug v1.1.0 // indirect
77+
github.com/kr/pretty v0.3.1 // indirect
8578
github.com/leodido/go-urn v1.4.0 // indirect
8679
github.com/mattn/go-isatty v0.0.20 // indirect
8780
github.com/miekg/dns v1.1.64 // indirect
@@ -91,10 +84,10 @@ require (
9184
github.com/nrdcg/mailinabox v0.2.0 // indirect
9285
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
9386
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
94-
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
9587
github.com/pkg/errors v0.9.1 // indirect
9688
github.com/quasoft/memstore v0.0.0-20191010062613-2bce066d2b0b // indirect
9789
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
90+
github.com/rogpeppe/go-internal v1.12.0 // indirect
9891
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1128 // indirect
9992
github.com/tjfoc/gmsm v1.4.1 // indirect
10093
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect

0 commit comments

Comments
 (0)