|
|
@@ -0,0 +1,118 @@
|
|
|
+package service
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "crypto/hmac"
|
|
|
+ "crypto/sha256"
|
|
|
+ "encoding/hex"
|
|
|
+ "encoding/json"
|
|
|
+ "fmt"
|
|
|
+ "net/http"
|
|
|
+ "one-api/dto"
|
|
|
+ "one-api/setting"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// WebhookPayload webhook 通知的负载数据
|
|
|
+type WebhookPayload struct {
|
|
|
+ Type string `json:"type"`
|
|
|
+ Title string `json:"title"`
|
|
|
+ Content string `json:"content"`
|
|
|
+ Values []interface{} `json:"values,omitempty"`
|
|
|
+ Timestamp int64 `json:"timestamp"`
|
|
|
+}
|
|
|
+
|
|
|
+// generateSignature 生成 webhook 签名
|
|
|
+func generateSignature(secret string, payload []byte) string {
|
|
|
+ h := hmac.New(sha256.New, []byte(secret))
|
|
|
+ h.Write(payload)
|
|
|
+ return hex.EncodeToString(h.Sum(nil))
|
|
|
+}
|
|
|
+
|
|
|
+// SendWebhookNotify 发送 webhook 通知
|
|
|
+func SendWebhookNotify(webhookURL string, secret string, data dto.Notify) error {
|
|
|
+ // 处理占位符
|
|
|
+ content := data.Content
|
|
|
+ for _, value := range data.Values {
|
|
|
+ content = fmt.Sprintf(content, value)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建 webhook 负载
|
|
|
+ payload := WebhookPayload{
|
|
|
+ Type: data.Type,
|
|
|
+ Title: data.Title,
|
|
|
+ Content: content,
|
|
|
+ Values: data.Values,
|
|
|
+ Timestamp: time.Now().Unix(),
|
|
|
+ }
|
|
|
+
|
|
|
+ // 序列化负载
|
|
|
+ payloadBytes, err := json.Marshal(payload)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("failed to marshal webhook payload: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 创建 HTTP 请求
|
|
|
+ var req *http.Request
|
|
|
+ var resp *http.Response
|
|
|
+
|
|
|
+ if setting.EnableWorker() {
|
|
|
+ // 构建worker请求数据
|
|
|
+ workerReq := &WorkerRequest{
|
|
|
+ URL: webhookURL,
|
|
|
+ Key: setting.WorkerValidKey,
|
|
|
+ Method: http.MethodPost,
|
|
|
+ Headers: map[string]string{
|
|
|
+ "Content-Type": "application/json",
|
|
|
+ },
|
|
|
+ Body: payloadBytes,
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果有secret,添加签名到headers
|
|
|
+ if secret != "" {
|
|
|
+ signature := generateSignature(secret, payloadBytes)
|
|
|
+ workerReq.Headers["X-Webhook-Signature"] = signature
|
|
|
+ workerReq.Headers["Authorization"] = "Bearer " + secret
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, err = DoWorkerRequest(workerReq)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("failed to send webhook request through worker: %v", err)
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ // 检查响应状态
|
|
|
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
|
+ return fmt.Errorf("webhook request failed with status code: %d", resp.StatusCode)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ req, err = http.NewRequest(http.MethodPost, webhookURL, bytes.NewBuffer(payloadBytes))
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("failed to create webhook request: %v", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置请求头
|
|
|
+ req.Header.Set("Content-Type", "application/json")
|
|
|
+
|
|
|
+ // 如果有 secret,生成签名
|
|
|
+ if secret != "" {
|
|
|
+ signature := generateSignature(secret, payloadBytes)
|
|
|
+ req.Header.Set("X-Webhook-Signature", signature)
|
|
|
+ }
|
|
|
+
|
|
|
+ // 发送请求
|
|
|
+ client := GetImpatientHttpClient()
|
|
|
+ resp, err = client.Do(req)
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("failed to send webhook request: %v", err)
|
|
|
+ }
|
|
|
+ defer resp.Body.Close()
|
|
|
+
|
|
|
+ // 检查响应状态
|
|
|
+ if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
|
+ return fmt.Errorf("webhook request failed with status code: %d", resp.StatusCode)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|