feat: Implement comprehensive webhook notification system
This commit is contained in:
118
service/webhook.go
Normal file
118
service/webhook.go
Normal file
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user