package milky import ( "fmt" "github.com/bytedance/sonic" "github.com/valyala/fasthttp" "go.uber.org/zap" ) // WebhookServer Webhook 服务器 // 用于接收协议端 POST 推送的事件 type WebhookServer struct { server *fasthttp.Server eventChan chan []byte logger *zap.Logger addr string } // NewWebhookServer 创建 Webhook 服务器 func NewWebhookServer(addr string, logger *zap.Logger) *WebhookServer { return &WebhookServer{ eventChan: make(chan []byte, 100), logger: logger.Named("webhook-server"), addr: addr, } } // Start 启动服务器 func (s *WebhookServer) Start() error { s.server = &fasthttp.Server{ Handler: s.handleRequest, MaxConnsPerIP: 1000, MaxRequestsPerConn: 1000, } s.logger.Info("Starting webhook server", zap.String("addr", s.addr)) go func() { if err := s.server.ListenAndServe(s.addr); err != nil { s.logger.Error("Webhook server error", zap.Error(err)) } }() return nil } // handleRequest 处理请求 func (s *WebhookServer) handleRequest(ctx *fasthttp.RequestCtx) { // 只接受 POST 请求 if !ctx.IsPost() { s.logger.Warn("Received non-POST request", zap.String("method", string(ctx.Method()))) ctx.Error("Method Not Allowed", fasthttp.StatusMethodNotAllowed) return } // 检查 Content-Type contentType := string(ctx.Request.Header.ContentType()) if contentType != "application/json" { s.logger.Warn("Invalid content type", zap.String("content_type", contentType)) ctx.Error("Unsupported Media Type", fasthttp.StatusUnsupportedMediaType) return } // 获取请求体 body := ctx.PostBody() if len(body) == 0 { s.logger.Warn("Empty request body") ctx.Error("Bad Request", fasthttp.StatusBadRequest) return } // 验证 JSON 格式 var event Event if err := sonic.Unmarshal(body, &event); err != nil { s.logger.Error("Failed to parse event", zap.Error(err)) ctx.Error("Bad Request", fasthttp.StatusBadRequest) return } s.logger.Debug("Received webhook event", zap.String("event_type", event.EventType), zap.Int64("self_id", event.SelfID)) // 发送到事件通道 select { case s.eventChan <- body: default: s.logger.Warn("Event channel full, dropping event") } // 返回成功响应 ctx.SetContentType("application/json") ctx.SetStatusCode(fasthttp.StatusOK) ctx.SetBodyString(`{"status":"ok"}`) } // Events 获取事件通道 func (s *WebhookServer) Events() <-chan []byte { return s.eventChan } // Stop 停止服务器 func (s *WebhookServer) Stop() error { if s.server != nil { s.logger.Info("Stopping webhook server") if err := s.server.Shutdown(); err != nil { return fmt.Errorf("failed to shutdown webhook server: %w", err) } } close(s.eventChan) return nil }