包含Go API项目的所有源代码、配置文件、Docker配置、文档和前端资源 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
119 lines
2.9 KiB
Go
119 lines
2.9 KiB
Go
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 := GetHttpClient()
|
||
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
|
||
}
|