Files
ai-code-review/handlers/webhook.go

162 lines
3.8 KiB
Go

package handlers
import (
"bytes"
"code-review/config"
"code-review/services"
"code-review/services/platforms"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"github.com/gin-gonic/gin"
)
type WebhookHandler struct {
reviewService *services.ReviewService
}
func NewWebhookHandler(reviewService *services.ReviewService) *WebhookHandler {
return &WebhookHandler{
reviewService: reviewService,
}
}
func (h *WebhookHandler) HandleWebhook(c *gin.Context) {
platform := c.Param("platform")
log.Printf("收到 webhook 请求: platform=%s", platform)
// 验证 webhook 签名
if !h.verifySignature(c, platform) {
log.Printf("webhook 签名验证失败: platform=%s", platform)
c.JSON(400, gin.H{"error": "无效的签名"})
return
}
// 解析请求体
event, err := h.parseWebhookEvent(c, platform)
if err != nil {
log.Printf("解析 webhook 事件失败: platform=%s, error=%v", platform, err)
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 启动代码审查
if err := h.reviewService.Review(event); err != nil {
log.Printf("代码审查失败: platform=%s, error=%v", platform, err)
c.JSON(500, gin.H{"error": err.Error()})
return
}
log.Printf("代码审查完成: platform=%s", platform)
c.JSON(200, gin.H{"message": "success"})
}
func (h *WebhookHandler) verifySignature(c *gin.Context, platform string) bool {
body, err := c.GetRawData()
if err != nil {
return false
}
c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
cfg := config.GetConfig()
// 查找对应的平台配置
var gitConfig config.GitConfig
found := false
for _, p := range cfg.Git {
if p.Name == platform {
gitConfig = p
found = true
break
}
}
if !found {
return false
}
// 获取签名
signature := c.GetHeader(gitConfig.SignatureHeader)
if signature == "" {
return false
}
mac := hmac.New(sha256.New, []byte(gitConfig.Secret))
mac.Write(body)
expectedMAC := hex.EncodeToString(mac.Sum(nil))
return hmac.Equal([]byte(signature), []byte(expectedMAC)) || hmac.Equal([]byte(signature), []byte(gitConfig.Secret))
}
func (h *WebhookHandler) parseWebhookEvent(c *gin.Context, platform string) (services.WebhookEvent, error) {
cfg := config.GetConfig()
// 查找对应的平台配置
var platformConfig config.GitConfig
found := false
for _, p := range cfg.Git {
if p.Name == platform {
platformConfig = p
found = true
break
}
}
if !found {
return nil, fmt.Errorf("不支持的平台: %s", platform)
}
// 获取事件类型
eventType := c.GetHeader(platformConfig.EventHeader)
if eventType == "" {
return nil, fmt.Errorf("未找到事件类型 header: %s", platformConfig.EventHeader)
}
// 创建认证配置
auth := &platforms.AuthConfig{
Token: platformConfig.Token,
Username: platformConfig.Username,
Password: platformConfig.Password,
SudoUser: platformConfig.SudoUser,
TOTP: platformConfig.TOTP,
UseBasicAuth: platformConfig.Username != "" && platformConfig.Password != "",
UseSudoHeader: platformConfig.SudoUser != "",
UseSudoParam: platformConfig.SudoUser != "",
UseTOTPHeader: platformConfig.TOTP != "",
}
var event services.WebhookEvent
switch platformConfig.Type {
case "gitea":
event = platforms.NewGiteaEvent(
platformConfig.APIBase,
auth,
eventType,
)
// case "gitee":
// event = platforms.NewGiteeEvent(
// platformConfig.APIBase,
// auth,
// )
case "gitlab":
event = platforms.NewGitlabEvent(
platformConfig.APIBase,
auth,
eventType,
)
default:
return nil, fmt.Errorf("不支持的平台: %s", platform)
}
// 只处理 push 和 pull_request 事件
//if eventType != "push" && eventType != "pull_request" {
// return nil, fmt.Errorf("不支持的事件类型: %s", eventType)
//}
if err := c.ShouldBindJSON(event); err != nil {
return nil, err
}
return event, nil
}