mirror of https://github.com/allinssl/allinssl
213 lines
5.3 KiB
Go
213 lines
5.3 KiB
Go
package report
|
|
|
|
import (
|
|
"ALLinSSL/backend/public"
|
|
"context"
|
|
"crypto/tls"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/go-resty/resty/v2"
|
|
"net/http"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type ReportConfig struct {
|
|
Url string `json:"url"`
|
|
Data string `json:"data,omitempty"`
|
|
Method string `json:"method,omitempty"`
|
|
Headers string `json:"headers,omitempty"`
|
|
IgnoreSSL bool `json:"ignore_ssl,omitempty"`
|
|
}
|
|
|
|
type WebHookReporter struct {
|
|
config *ReportConfig
|
|
logger *public.Logger
|
|
httpClient *resty.Client
|
|
}
|
|
|
|
func NewWebHookReporter(config *ReportConfig, logger *public.Logger) *WebHookReporter {
|
|
client := resty.New()
|
|
client.SetTimeout(30 * time.Second)
|
|
|
|
if config.IgnoreSSL {
|
|
client.SetTLSClientConfig(&tls.Config{InsecureSkipVerify: true})
|
|
}
|
|
|
|
if config.Data == "" {
|
|
config.Data = "{}" // 默认数据为空JSON对象
|
|
}
|
|
|
|
return &WebHookReporter{
|
|
config: config,
|
|
logger: logger,
|
|
httpClient: client,
|
|
}
|
|
}
|
|
|
|
func (w *WebHookReporter) Send(ctx context.Context) error {
|
|
// 确定HTTP方法
|
|
method := strings.ToUpper(w.config.Method)
|
|
if method == "" {
|
|
method = http.MethodPost // 默认使用POST方法
|
|
}
|
|
|
|
// 创建基础请求
|
|
req := w.httpClient.R().
|
|
SetContext(ctx)
|
|
|
|
// 设置请求头
|
|
if w.config.Headers != "" {
|
|
reqHeader, err := w.ParseHeaders(w.config.Headers)
|
|
if err != nil {
|
|
return fmt.Errorf("解析请求头错误: %w", err)
|
|
}
|
|
req.Header = reqHeader
|
|
}
|
|
|
|
switch method {
|
|
case http.MethodPost:
|
|
{
|
|
contentType := req.Header.Get("application/json")
|
|
if contentType == "" {
|
|
contentType = "application/json"
|
|
}
|
|
switch contentType {
|
|
case "application/json":
|
|
req.SetHeader("Content-Type", "application/json")
|
|
var reqData interface{}
|
|
err := json.Unmarshal([]byte(w.config.Data), &reqData)
|
|
if err != nil {
|
|
return fmt.Errorf("webhook数据解析失败err: %w", err)
|
|
}
|
|
req.SetBody(reqData)
|
|
case "application/x-www-form-urlencoded":
|
|
req.SetHeader("Content-Type", "application/x-www-form-urlencoded")
|
|
reqData := make(map[string]string)
|
|
err := json.Unmarshal([]byte(w.config.Data), &reqData)
|
|
if err != nil {
|
|
return fmt.Errorf("webhook数据解析失败err: %w", err)
|
|
}
|
|
req.SetFormData(reqData)
|
|
case "multipart/form-data":
|
|
req.SetHeader("Content-Type", "multipart/form-data")
|
|
reqData := make(map[string]string)
|
|
err := json.Unmarshal([]byte(w.config.Data), &reqData)
|
|
if err != nil {
|
|
return fmt.Errorf("webhook数据解析失败err: %w", err)
|
|
}
|
|
req.SetMultipartFormData(reqData)
|
|
}
|
|
}
|
|
case http.MethodGet:
|
|
{
|
|
reqData := make(map[string]string)
|
|
err := json.Unmarshal([]byte(w.config.Data), &reqData)
|
|
if err != nil {
|
|
return fmt.Errorf("webhook数据解析失败err: %w", err)
|
|
}
|
|
req.SetQueryParams(reqData)
|
|
}
|
|
default:
|
|
return fmt.Errorf("暂不支持的HTTP方法: %s", method)
|
|
}
|
|
|
|
// 发送请求
|
|
resp, err := req.Execute(method, w.config.Url)
|
|
if err != nil {
|
|
if w.logger != nil {
|
|
w.logger.Error(fmt.Sprintf("Webhook请求失败%s %v", w.config.Url, err))
|
|
}
|
|
|
|
return fmt.Errorf("webhook请求失败: %w", err)
|
|
}
|
|
|
|
// 处理响应
|
|
if resp.IsError() {
|
|
if w.logger != nil {
|
|
w.logger.Error(fmt.Sprintf("Webhook返回错误响应%s %d", w.config.Url, resp.StatusCode()))
|
|
}
|
|
return fmt.Errorf("webhook返回错误状态码: %d", resp.StatusCode())
|
|
}
|
|
|
|
if w.logger != nil {
|
|
w.logger.Debug(fmt.Sprintf("Webhook请求成功 %s", w.config.Url))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w *WebHookReporter) ParseHeaders(headerStr string) (http.Header, error) {
|
|
headers := make(http.Header)
|
|
lines := strings.Split(headerStr, "\n")
|
|
|
|
for i, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
parts := strings.SplitN(line, ":", 2)
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("解析请求头错误 第%d行: %s", i+1, line)
|
|
}
|
|
key := strings.TrimSpace(parts[0])
|
|
value := strings.TrimSpace(parts[1])
|
|
if key == "" || value == "" {
|
|
return nil, fmt.Errorf("请求头Key第%d行为空", i+1)
|
|
}
|
|
canonicalKey := http.CanonicalHeaderKey(key)
|
|
headers.Add(canonicalKey, value)
|
|
}
|
|
|
|
return headers, nil
|
|
}
|
|
|
|
func NotifyWebHook(params map[string]any) error {
|
|
if params == nil {
|
|
return fmt.Errorf("缺少参数")
|
|
}
|
|
providerID := params["provider_id"].(string)
|
|
|
|
var logger *public.Logger
|
|
|
|
if params["logger"] != nil {
|
|
logger = params["logger"].(*public.Logger)
|
|
}
|
|
|
|
providerData, err := GetReport(providerID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
configStr := providerData["config"].(string)
|
|
var config ReportConfig
|
|
err = json.Unmarshal([]byte(configStr), &config)
|
|
if err != nil {
|
|
return fmt.Errorf("解析配置失败: %v", err)
|
|
}
|
|
config.Data, err = ReplaceJSONPlaceholders(config.Data, params)
|
|
if err != nil {
|
|
return fmt.Errorf("替换JSON占位符失败: %w", err)
|
|
}
|
|
|
|
reporter := NewWebHookReporter(&config, logger)
|
|
httpctx := context.Background()
|
|
err = reporter.Send(httpctx)
|
|
if err != nil {
|
|
return fmt.Errorf("webhook发送失败: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ReplaceJSONPlaceholders(jsonStr string, vars map[string]any) (string, error) {
|
|
re := regexp.MustCompile(`__([a-zA-Z0-9_]+)__`)
|
|
result := re.ReplaceAllStringFunc(jsonStr, func(match string) string {
|
|
key := re.FindStringSubmatch(match)[1]
|
|
if val, ok := vars[key]; ok {
|
|
return fmt.Sprintf("%v", val) // 将 any 类型转换为字符串
|
|
}
|
|
return match // 未匹配到变量则保留原样
|
|
})
|
|
|
|
return result, nil
|
|
}
|