Golang Gin框架中如何创建Webhook

Golang Gin框架中如何创建Webhook 如何在 Golang 和 Gin 中创建 Webhook

2 回复

你是想调用其他服务,还是其他服务在调用你的API?如果是前者,可以查看这份关于如何进行HTTP请求的指南:

Making HTTP requests in Go - LogRocket Blog

Making HTTP requests in Go - LogRocket Blog

探索如何在Go中进行HTTP请求、管理请求头和Cookie,以及使用Rest、Sling和Gentleman等第三方库。

预计阅读时间:12分钟

如果是后者,响应Webhook并没有什么特别之处。它只是一个会调用你公开暴露的Web API中某个特定端点的API。通常会有某种安全层(例如,可以参考Stripe的这些文档),你需要验证他们随请求发送的某种密钥,以确认请求确实由他们发出。但这本质上都是标准的RESTful API操作。

更多关于Golang Gin框架中如何创建Webhook的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Golang Gin框架中创建Webhook可以通过以下方式实现:

基本Webhook端点实现

package main

import (
    "encoding/json"
    "github.com/gin-gonic/gin"
    "io/ioutil"
    "net/http"
)

func main() {
    r := gin.Default()

    // 创建Webhook端点
    r.POST("/webhook", func(c *gin.Context) {
        // 验证请求签名(可选但推荐)
        signature := c.GetHeader("X-Hub-Signature")
        
        // 读取请求体
        body, err := ioutil.ReadAll(c.Request.Body)
        if err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "无法读取请求体"})
            return
        }

        // 解析JSON数据
        var payload map[string]interface{}
        if err := json.Unmarshal(body, &payload); err != nil {
            c.JSON(http.StatusBadRequest, gin.H{"error": "无效的JSON格式"})
            return
        }

        // 处理Webhook事件
        eventType := c.GetHeader("X-GitHub-Event") // GitHub示例
        go processWebhook(eventType, payload)

        // 立即响应
        c.JSON(http.StatusOK, gin.H{
            "status": "received",
            "event": eventType,
        })
    })

    r.Run(":8080")
}

func processWebhook(eventType string, payload map[string]interface{}) {
    // 异步处理Webhook逻辑
    switch eventType {
    case "push":
        // 处理push事件
        fmt.Printf("收到push事件: %v\n", payload)
    case "pull_request":
        // 处理PR事件
        fmt.Printf("收到pull_request事件: %v\n", payload)
    default:
        fmt.Printf("收到未知事件类型: %s\n", eventType)
    }
}

带验证的Webhook处理器

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "github.com/gin-gonic/gin"
    "io/ioutil"
    "net/http"
    "strings"
)

type WebhookHandler struct {
    secret string
}

func NewWebhookHandler(secret string) *WebhookHandler {
    return &WebhookHandler{secret: secret}
}

func (h *WebhookHandler) HandleGitHubWebhook(c *gin.Context) {
    // 验证签名
    signature := c.GetHeader("X-Hub-Signature-256")
    if !h.verifySignature(c, signature) {
        c.JSON(http.StatusUnauthorized, gin.H{"error": "签名验证失败"})
        return
    }

    // 读取并处理请求
    body, _ := ioutil.ReadAll(c.Request.Body)
    
    eventType := c.GetHeader("X-GitHub-Event")
    
    // 根据事件类型路由处理
    switch eventType {
    case "push":
        h.handlePushEvent(body)
    case "issues":
        h.handleIssuesEvent(body)
    case "ping":
        c.JSON(http.StatusOK, gin.H{"message": "pong"})
        return
    }

    c.JSON(http.StatusOK, gin.H{"status": "processed"})
}

func (h *WebhookHandler) verifySignature(c *gin.Context, signature string) bool {
    if h.secret == "" {
        return true // 如果没有设置密钥,跳过验证
    }

    body, _ := ioutil.ReadAll(c.Request.Body)
    
    // 重新设置请求体以便后续使用
    c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))

    mac := hmac.New(sha256.New, []byte(h.secret))
    mac.Write(body)
    expectedMAC := hex.EncodeToString(mac.Sum(nil))
    
    return "sha256="+expectedMAC == signature
}

func (h *WebhookHandler) handlePushEvent(body []byte) {
    // 处理push事件的具体逻辑
    var data struct {
        Ref        string `json:"ref"`
        Repository struct {
            Name string `json:"name"`
        } `json:"repository"`
    }
    
    json.Unmarshal(body, &data)
    fmt.Printf("仓库 %s 的 %s 分支有新的推送\n", data.Repository.Name, data.Ref)
}

func main() {
    r := gin.Default()
    
    webhookHandler := NewWebhookHandler("your-secret-token")
    
    r.POST("/github-webhook", webhookHandler.HandleGitHubWebhook)
    r.POST("/gitlab-webhook", handleGitLabWebhook)
    
    r.Run(":8080")
}

func handleGitLabWebhook(c *gin.Context) {
    // GitLab Webhook处理
    token := c.GetHeader("X-GitLab-Token")
    eventType := c.GetHeader("X-Gitlab-Event")
    
    // GitLab特定的处理逻辑
    c.JSON(http.StatusOK, gin.H{"event": eventType})
}

支持多种Webhook提供商的通用实现

package main

import (
    "github.com/gin-gonic/gin"
    "net/http"
)

type WebhookPayload struct {
    Provider string
    Event    string
    Data     interface{}
}

type WebhookProcessor interface {
    Process(payload WebhookPayload)
}

func main() {
    r := gin.Default()
    
    // 通用Webhook端点
    r.POST("/webhook/:provider", func(c *gin.Context) {
        provider := c.Param("provider")
        
        switch provider {
        case "github":
            handleProviderWebhook(c, "github")
        case "gitlab":
            handleProviderWebhook(c, "gitlab")
        case "bitbucket":
            handleProviderWebhook(c, "bitbucket")
        default:
            c.JSON(http.StatusBadRequest, gin.H{"error": "不支持的提供商"})
        }
    })
    
    r.Run(":8080")
}

func handleProviderWebhook(c *gin.Context, provider string) {
    var payload map[string]interface{}
    
    if err := c.ShouldBindJSON(&payload); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求数据"})
        return
    }
    
    // 获取事件类型
    var event string
    switch provider {
    case "github":
        event = c.GetHeader("X-GitHub-Event")
    case "gitlab":
        event = c.GetHeader("X-Gitlab-Event")
    case "bitbucket":
        event = c.GetHeader("X-Event-Key")
    }
    
    // 创建Webhook负载
    webhookPayload := WebhookPayload{
        Provider: provider,
        Event:    event,
        Data:     payload,
    }
    
    // 异步处理
    go processWebhookPayload(webhookPayload)
    
    c.JSON(http.StatusOK, gin.H{
        "provider": provider,
        "event":    event,
        "status":   "accepted",
    })
}

func processWebhookPayload(payload WebhookPayload) {
    // 根据提供商和事件类型处理Webhook
    fmt.Printf("收到来自 %s 的 %s 事件\n", payload.Provider, payload.Event)
}

这些示例展示了在Gin框架中创建Webhook的基本模式,包括请求验证、事件处理和异步响应。可以根据具体需求调整验证逻辑和事件处理程序。

回到顶部