package net import ( "context" "fmt" "sync" "time" "cellbot/internal/engine" "cellbot/internal/protocol" "github.com/bytedance/sonic" "github.com/valyala/fasthttp" "go.uber.org/zap" ) // HTTPClient HTTP客户端(用于正向HTTP连接) type HTTPClient struct { client *fasthttp.Client logger *zap.Logger eventBus *engine.EventBus botID string baseURL string timeout time.Duration retryCount int } // HTTPClientConfig HTTP客户端配置 type HTTPClientConfig struct { BotID string BaseURL string Timeout time.Duration RetryCount int } // NewHTTPClient 创建HTTP客户端 func NewHTTPClient(config HTTPClientConfig, logger *zap.Logger, eventBus *engine.EventBus) *HTTPClient { if config.Timeout == 0 { config.Timeout = 30 * time.Second } if config.RetryCount == 0 { config.RetryCount = 3 } return &HTTPClient{ client: &fasthttp.Client{ ReadTimeout: config.Timeout, WriteTimeout: config.Timeout, MaxConnsPerHost: 100, }, logger: logger.Named("http-client"), eventBus: eventBus, botID: config.BotID, baseURL: config.BaseURL, timeout: config.Timeout, retryCount: config.RetryCount, } } // SendAction 发送动作请求(正向HTTP) func (hc *HTTPClient) SendAction(ctx context.Context, action protocol.Action) (map[string]interface{}, error) { // 序列化动作为JSON data, err := sonic.Marshal(action) if err != nil { return nil, fmt.Errorf("failed to marshal action: %w", err) } req := fasthttp.AcquireRequest() resp := fasthttp.AcquireResponse() defer fasthttp.ReleaseRequest(req) defer fasthttp.ReleaseResponse(resp) url := hc.baseURL + "/action" req.SetRequestURI(url) req.Header.SetMethod("POST") req.Header.SetContentType("application/json") req.SetBody(data) hc.logger.Debug("Sending action", zap.String("url", url), zap.String("action", string(action.GetType()))) // 重试机制 var lastErr error for i := 0; i <= hc.retryCount; i++ { if i > 0 { hc.logger.Info("Retrying action request", zap.Int("attempt", i), zap.Int("max", hc.retryCount)) time.Sleep(time.Duration(i) * time.Second) } err := hc.client.DoTimeout(req, resp, hc.timeout) if err != nil { lastErr = fmt.Errorf("request failed: %w", err) continue } if resp.StatusCode() != fasthttp.StatusOK { lastErr = fmt.Errorf("unexpected status code: %d", resp.StatusCode()) continue } // 解析响应 var result map[string]interface{} if err := sonic.Unmarshal(resp.Body(), &result); err != nil { lastErr = fmt.Errorf("failed to parse response: %w", err) continue } hc.logger.Info("Action sent successfully", zap.String("action", string(action.GetType()))) return result, nil } return nil, fmt.Errorf("action failed after %d retries: %w", hc.retryCount, lastErr) } // PollEvents 轮询事件(正向HTTP) func (hc *HTTPClient) PollEvents(ctx context.Context, interval time.Duration) error { ticker := time.NewTicker(interval) defer ticker.Stop() hc.logger.Info("Starting event polling", zap.Duration("interval", interval)) for { select { case <-ticker.C: if err := hc.fetchEvents(ctx); err != nil { hc.logger.Error("Failed to fetch events", zap.Error(err)) } case <-ctx.Done(): hc.logger.Info("Event polling stopped") return ctx.Err() } } } // fetchEvents 获取事件 func (hc *HTTPClient) fetchEvents(ctx context.Context) error { req := fasthttp.AcquireRequest() resp := fasthttp.AcquireResponse() defer fasthttp.ReleaseRequest(req) defer fasthttp.ReleaseResponse(resp) url := hc.baseURL + "/events" req.SetRequestURI(url) req.Header.SetMethod("GET") err := hc.client.DoTimeout(req, resp, hc.timeout) if err != nil { return fmt.Errorf("request failed: %w", err) } if resp.StatusCode() != fasthttp.StatusOK { return fmt.Errorf("unexpected status code: %d", resp.StatusCode()) } // 解析事件列表 var events []protocol.BaseEvent if err := sonic.Unmarshal(resp.Body(), &events); err != nil { return fmt.Errorf("failed to parse events: %w", err) } // 发布事件到事件总线 for i := range events { hc.logger.Debug("Event received", zap.String("type", string(events[i].Type)), zap.String("detail_type", events[i].DetailType)) hc.eventBus.Publish(&events[i]) } return nil } // HTTPWebhookServer HTTP Webhook服务器(用于反向HTTP连接) type HTTPWebhookServer struct { server *fasthttp.Server logger *zap.Logger eventBus *engine.EventBus handlers map[string]*WebhookHandler mu sync.RWMutex } // WebhookHandler Webhook处理器 type WebhookHandler struct { BotID string Secret string Validator func([]byte, string) bool } // NewHTTPWebhookServer 创建HTTP Webhook服务器 func NewHTTPWebhookServer(logger *zap.Logger, eventBus *engine.EventBus) *HTTPWebhookServer { return &HTTPWebhookServer{ logger: logger.Named("webhook-server"), eventBus: eventBus, handlers: make(map[string]*WebhookHandler), } } // RegisterWebhook 注册Webhook处理器 func (hws *HTTPWebhookServer) RegisterWebhook(path string, handler *WebhookHandler) { hws.mu.Lock() defer hws.mu.Unlock() hws.handlers[path] = handler hws.logger.Info("Webhook registered", zap.String("path", path), zap.String("bot_id", handler.BotID)) } // UnregisterWebhook 注销Webhook处理器 func (hws *HTTPWebhookServer) UnregisterWebhook(path string) { hws.mu.Lock() defer hws.mu.Unlock() delete(hws.handlers, path) hws.logger.Info("Webhook unregistered", zap.String("path", path)) } // Start 启动Webhook服务器 func (hws *HTTPWebhookServer) Start(addr string) error { hws.server = &fasthttp.Server{ Handler: hws.handleWebhook, } hws.logger.Info("Starting webhook server", zap.String("address", addr)) go func() { if err := hws.server.ListenAndServe(addr); err != nil { hws.logger.Error("Webhook server error", zap.Error(err)) } }() return nil } // Stop 停止Webhook服务器 func (hws *HTTPWebhookServer) Stop() error { if hws.server != nil { hws.logger.Info("Stopping webhook server") return hws.server.Shutdown() } return nil } // handleWebhook 处理Webhook请求 func (hws *HTTPWebhookServer) handleWebhook(ctx *fasthttp.RequestCtx) { path := string(ctx.Path()) hws.mu.RLock() handler, exists := hws.handlers[path] hws.mu.RUnlock() if !exists { ctx.Error("Webhook not found", fasthttp.StatusNotFound) return } // 验证签名(如果配置了) if handler.Secret != "" && handler.Validator != nil { signature := string(ctx.Request.Header.Peek("X-Signature")) if !handler.Validator(ctx.PostBody(), signature) { hws.logger.Warn("Invalid webhook signature", zap.String("path", path), zap.String("bot_id", handler.BotID)) ctx.Error("Invalid signature", fasthttp.StatusUnauthorized) return } } // 解析事件 var event protocol.BaseEvent if err := sonic.Unmarshal(ctx.PostBody(), &event); err != nil { hws.logger.Error("Failed to parse webhook event", zap.Error(err), zap.String("path", path)) ctx.Error("Invalid event format", fasthttp.StatusBadRequest) return } // 设置BotID if event.SelfID == "" { event.SelfID = handler.BotID } // 设置时间戳 if event.Timestamp == 0 { event.Timestamp = time.Now().Unix() } // 确保Data字段不为nil if event.Data == nil { event.Data = make(map[string]interface{}) } hws.logger.Info("Webhook event received", zap.String("path", path), zap.String("bot_id", handler.BotID), zap.String("type", string(event.Type)), zap.String("detail_type", event.DetailType)) // 发布到事件总线 hws.eventBus.Publish(&event) // 返回成功响应 ctx.SetContentType("application/json") ctx.SetBodyString(`{"success":true}`) }