chore: update dependencies and refactor webhook handling

- Added new dependencies for SQLite support and improved HTTP client functionality in go.mod and go.sum.
- Refactored webhook server implementation to utilize a simplified version, enhancing code maintainability.
- Updated API client to leverage a generic request method, streamlining API interactions.
- Modified configuration to include access token for webhook server, improving security.
- Enhanced event handling and request processing in the API client for better performance.
This commit is contained in:
2026-01-05 18:42:45 +08:00
parent fb5fae1524
commit f3a72264af
10 changed files with 346 additions and 181 deletions

View File

@@ -25,6 +25,22 @@ type HTTPClient struct {
retryCount int
}
// GenericRequestConfig 通用请求配置
type GenericRequestConfig struct {
URL string
Method string // GET, POST, etc.
Headers map[string]string
Body []byte
AccessToken string
}
// GenericResponse 通用响应
type GenericResponse struct {
StatusCode int
Headers map[string]string
Body []byte
}
// HTTPClientConfig HTTP客户端配置
type HTTPClientConfig struct {
BotID string
@@ -117,6 +133,103 @@ func (hc *HTTPClient) SendAction(ctx context.Context, action protocol.Action) (m
return nil, fmt.Errorf("action failed after %d retries: %w", hc.retryCount, lastErr)
}
// DoGenericRequest 执行通用HTTP请求
// 用于支持适配器的特定API调用需求
func (hc *HTTPClient) DoGenericRequest(ctx context.Context, config GenericRequestConfig) (*GenericResponse, error) {
// 如果未提供 Method默认为 POST
if config.Method == "" {
config.Method = "POST"
}
// 重试机制
var lastErr error
for i := 0; i <= hc.retryCount; i++ {
if i > 0 {
hc.logger.Info("Retrying generic request",
zap.String("url", config.URL),
zap.Int("attempt", i),
zap.Int("max", hc.retryCount))
time.Sleep(time.Duration(i) * time.Second)
}
resp, err := hc.doGenericRequest(ctx, config)
if err != nil {
lastErr = err
continue
}
return resp, nil
}
return nil, fmt.Errorf("generic request failed after %d retries: %w", hc.retryCount, lastErr)
}
// doGenericRequest 执行单次通用请求
func (hc *HTTPClient) doGenericRequest(ctx context.Context, config GenericRequestConfig) (*GenericResponse, error) {
req := fasthttp.AcquireRequest()
resp := fasthttp.AcquireResponse()
defer fasthttp.ReleaseRequest(req)
defer fasthttp.ReleaseResponse(resp)
// 设置请求URL和方法
req.SetRequestURI(config.URL)
req.Header.SetMethod(config.Method)
// 设置默认 Content-Type
if config.Method == "POST" || config.Method == "PUT" || config.Method == "PATCH" {
if req.Header.ContentType() == nil || len(req.Header.ContentType()) == 0 {
req.Header.SetContentType("application/json")
}
}
// 设置请求体
if config.Body != nil {
req.SetBody(config.Body)
}
// 设置自定义 Headers
for key, value := range config.Headers {
req.Header.Set(key, value)
}
// 设置 Authorization 头
if config.AccessToken != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", config.AccessToken))
}
hc.logger.Debug("Sending generic HTTP request",
zap.String("method", config.Method),
zap.String("url", config.URL))
// 发送请求
err := hc.client.DoTimeout(req, resp, hc.timeout)
if err != nil {
return nil, fmt.Errorf("request failed: %w", err)
}
// 构建响应
genericResp := &GenericResponse{
StatusCode: resp.StatusCode(),
Body: make([]byte, len(resp.Body())),
Headers: make(map[string]string),
}
// 复制响应体
copy(genericResp.Body, resp.Body())
// 复制响应头
resp.Header.VisitAll(func(key, value []byte) {
genericResp.Headers[string(key)] = string(value)
})
hc.logger.Debug("Generic HTTP request completed",
zap.String("method", config.Method),
zap.String("url", config.URL),
zap.Int("status_code", resp.StatusCode()))
return genericResp, nil
}
// PollEvents 轮询事件正向HTTP
func (hc *HTTPClient) PollEvents(ctx context.Context, interval time.Duration) error {
ticker := time.NewTicker(interval)
@@ -186,6 +299,27 @@ type HTTPWebhookServer struct {
mu sync.RWMutex
}
// SimpleWebhookConfig 简化版 Webhook 配置
type SimpleWebhookConfig struct {
Addr string
AccessToken string // 可选的访问令牌验证
EventChannel chan []byte // 用于传递原始事件数据
}
// SimpleWebhookHandler 简化版 Webhook 处理函数
type SimpleWebhookHandler func(ctx *fasthttp.RequestCtx)
// SimpleWebhookServer 简化版 Webhook 服务器
// 用于接收协议端推送的事件,提供原始字节流通道
type SimpleWebhookServer struct {
server *fasthttp.Server
logger *zap.Logger
addr string
accessToken string
eventChan chan []byte
handler SimpleWebhookHandler
}
// WebhookHandler Webhook处理器
type WebhookHandler struct {
BotID string
@@ -248,6 +382,112 @@ func (hws *HTTPWebhookServer) Stop() error {
return nil
}
// NewSimpleWebhookServer 创建简化版 Webhook 服务器
func NewSimpleWebhookServer(config SimpleWebhookConfig, logger *zap.Logger) *SimpleWebhookServer {
return &SimpleWebhookServer{
addr: config.Addr,
accessToken: config.AccessToken,
eventChan: config.EventChannel,
logger: logger.Named("simple-webhook-server"),
}
}
// Start 启动简化版 Webhook 服务器
func (s *SimpleWebhookServer) Start() error {
s.server = &fasthttp.Server{
Handler: s.handleRequest,
MaxConnsPerIP: 1000,
MaxRequestsPerConn: 1000,
}
s.logger.Info("Starting simple webhook server", zap.String("addr", s.addr))
go func() {
if err := s.server.ListenAndServe(s.addr); err != nil {
s.logger.Error("Simple webhook server error", zap.Error(err))
}
}()
return nil
}
// handleRequest 处理 Webhook 请求
func (s *SimpleWebhookServer) 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
}
// 验证访问令牌(如果配置了)
if s.accessToken != "" {
authHeader := string(ctx.Request.Header.Peek("Authorization"))
if authHeader == "" {
s.logger.Warn("Missing authorization header")
ctx.Error("Unauthorized", fasthttp.StatusUnauthorized)
return
}
// 验证 Bearer token
expectedAuth := "Bearer " + s.accessToken
if authHeader != expectedAuth {
s.logger.Warn("Invalid authorization token")
ctx.Error("Unauthorized", fasthttp.StatusUnauthorized)
return
}
}
// 获取请求体
body := ctx.PostBody()
if len(body) == 0 {
s.logger.Warn("Empty request body")
ctx.Error("Bad Request", fasthttp.StatusBadRequest)
return
}
s.logger.Debug("Received webhook event",
zap.Int("body_length", len(body)))
// 发送到事件通道
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 *SimpleWebhookServer) Events() <-chan []byte {
return s.eventChan
}
// Stop 停止简化版 Webhook 服务器
func (s *SimpleWebhookServer) Stop() error {
if s.server != nil {
s.logger.Info("Stopping simple webhook server")
if err := s.server.Shutdown(); err != nil {
return fmt.Errorf("failed to shutdown: %w", err)
}
}
return nil
}
// handleWebhook 处理Webhook请求
func (hws *HTTPWebhookServer) handleWebhook(ctx *fasthttp.RequestCtx) {
path := string(ctx.Path())