Files
cellbot/internal/adapter/milky/webhook_server.go

116 lines
2.7 KiB
Go
Raw Normal View History

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
}