chore: update dependencies and improve bot configuration
- Upgrade Go version to 1.24.0 and update toolchain. - Update various dependencies in go.mod and go.sum, including: - Upgrade `fasthttp/websocket` to v1.5.12 - Upgrade `fsnotify/fsnotify` to v1.9.0 - Upgrade `valyala/fasthttp` to v1.58.0 - Add new dependencies for `bytedance/sonic` and `google/uuid`. - Refactor bot configuration in config.toml to support multiple bot protocols, including "milky" and "onebot11". - Modify internal configuration structures to accommodate new bot settings. - Enhance event dispatcher with metrics tracking and asynchronous processing capabilities. - Implement WebSocket connection management with heartbeat and reconnection logic. - Update server handling for bot management and event publishing.
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"cellbot/internal/engine"
|
||||
"cellbot/internal/protocol"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/fasthttp/websocket"
|
||||
"github.com/valyala/fasthttp"
|
||||
"go.uber.org/zap"
|
||||
@@ -40,27 +41,45 @@ func NewWebSocketManager(logger *zap.Logger, eventBus *engine.EventBus) *WebSock
|
||||
}
|
||||
}
|
||||
|
||||
// ConnectionType 连接类型
|
||||
type ConnectionType string
|
||||
|
||||
const (
|
||||
ConnectionTypeReverse ConnectionType = "reverse" // 反向连接(被动接受)
|
||||
ConnectionTypeForward ConnectionType = "forward" // 正向连接(主动发起)
|
||||
)
|
||||
|
||||
// WebSocketConnection WebSocket连接
|
||||
type WebSocketConnection struct {
|
||||
ID string
|
||||
Conn *websocket.Conn
|
||||
BotID string
|
||||
Logger *zap.Logger
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
ID string
|
||||
Conn *websocket.Conn
|
||||
BotID string
|
||||
Logger *zap.Logger
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
Type ConnectionType
|
||||
RemoteAddr string
|
||||
reconnectURL string // 用于正向连接重连
|
||||
maxReconnect int // 最大重连次数
|
||||
reconnectCount int // 当前重连次数
|
||||
heartbeatTick time.Duration // 心跳间隔
|
||||
}
|
||||
|
||||
// NewWebSocketConnection 创建WebSocket连接
|
||||
func NewWebSocketConnection(conn *websocket.Conn, botID string, logger *zap.Logger) *WebSocketConnection {
|
||||
func NewWebSocketConnection(conn *websocket.Conn, botID string, connType ConnectionType, logger *zap.Logger) *WebSocketConnection {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
connID := generateConnID()
|
||||
return &WebSocketConnection{
|
||||
ID: connID,
|
||||
Conn: conn,
|
||||
BotID: botID,
|
||||
Logger: logger.With(zap.String("conn_id", connID)),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
ID: connID,
|
||||
Conn: conn,
|
||||
BotID: botID,
|
||||
Logger: logger.With(zap.String("conn_id", connID)),
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
Type: connType,
|
||||
RemoteAddr: conn.RemoteAddr().String(),
|
||||
maxReconnect: 5,
|
||||
heartbeatTick: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,21 +104,23 @@ func (wsm *WebSocketManager) UpgradeWebSocket(ctx *fasthttp.RequestCtx) (*WebSoc
|
||||
|
||||
// 等待连接在回调中建立
|
||||
conn := <-connChan
|
||||
|
||||
// 创建连接对象
|
||||
wsConn := NewWebSocketConnection(conn, botID, wsm.logger)
|
||||
|
||||
// 创建连接对象(反向连接)
|
||||
wsConn := NewWebSocketConnection(conn, botID, ConnectionTypeReverse, wsm.logger)
|
||||
|
||||
// 存储连接
|
||||
wsm.mu.Lock()
|
||||
wsm.connections[wsConn.ID] = wsConn
|
||||
wsm.mu.Unlock()
|
||||
|
||||
wsm.logger.Info("WebSocket connection established",
|
||||
wsm.logger.Info("WebSocket reverse connection established",
|
||||
zap.String("conn_id", wsConn.ID),
|
||||
zap.String("bot_id", botID))
|
||||
zap.String("bot_id", botID),
|
||||
zap.String("remote_addr", wsConn.RemoteAddr))
|
||||
|
||||
// 启动读取循环
|
||||
// 启动读取循环和心跳
|
||||
go wsConn.readLoop(wsm.eventBus)
|
||||
go wsConn.heartbeatLoop()
|
||||
|
||||
return wsConn, nil
|
||||
}
|
||||
@@ -119,9 +140,15 @@ func (wsc *WebSocketConnection) readLoop(eventBus *engine.EventBus) {
|
||||
return
|
||||
}
|
||||
|
||||
// 只处理文本消息,忽略其他类型
|
||||
if messageType != websocket.TextMessage {
|
||||
wsc.Logger.Warn("Received non-text message, ignoring",
|
||||
zap.Int("message_type", messageType))
|
||||
continue
|
||||
}
|
||||
|
||||
// 处理消息
|
||||
wsc.handleMessage(message, eventBus)
|
||||
// messageType 可用于区分文本或二进制消息
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,18 +157,41 @@ func (wsc *WebSocketConnection) readLoop(eventBus *engine.EventBus) {
|
||||
func (wsc *WebSocketConnection) handleMessage(data []byte, eventBus *engine.EventBus) {
|
||||
wsc.Logger.Debug("Received message", zap.ByteString("data", data))
|
||||
|
||||
// TODO: 解析消息为Event对象
|
||||
// 这里简化实现,实际应该根据协议解析
|
||||
event := &protocol.BaseEvent{
|
||||
Type: protocol.EventTypeMessage,
|
||||
DetailType: "private",
|
||||
Timestamp: time.Now().Unix(),
|
||||
SelfID: wsc.BotID,
|
||||
Data: make(map[string]interface{}),
|
||||
// 解析JSON消息为BaseEvent
|
||||
var event protocol.BaseEvent
|
||||
if err := sonic.Unmarshal(data, &event); err != nil {
|
||||
wsc.Logger.Error("Failed to parse message", zap.Error(err), zap.ByteString("data", data))
|
||||
return
|
||||
}
|
||||
|
||||
// 验证必需字段
|
||||
if event.Type == "" {
|
||||
wsc.Logger.Warn("Event type is empty", zap.ByteString("data", data))
|
||||
return
|
||||
}
|
||||
|
||||
// 如果没有时间戳,使用当前时间
|
||||
if event.Timestamp == 0 {
|
||||
event.Timestamp = time.Now().Unix()
|
||||
}
|
||||
|
||||
// 如果没有SelfID,使用连接的BotID
|
||||
if event.SelfID == "" {
|
||||
event.SelfID = wsc.BotID
|
||||
}
|
||||
|
||||
// 确保Data字段不为nil
|
||||
if event.Data == nil {
|
||||
event.Data = make(map[string]interface{})
|
||||
}
|
||||
|
||||
wsc.Logger.Info("Event received",
|
||||
zap.String("type", string(event.Type)),
|
||||
zap.String("detail_type", event.DetailType),
|
||||
zap.String("self_id", event.SelfID))
|
||||
|
||||
// 发布到事件总线
|
||||
eventBus.Publish(event)
|
||||
eventBus.Publish(&event)
|
||||
}
|
||||
|
||||
// SendMessage 发送消息
|
||||
@@ -155,6 +205,80 @@ func (wsc *WebSocketConnection) SendMessage(data []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// heartbeatLoop 心跳循环
|
||||
func (wsc *WebSocketConnection) heartbeatLoop() {
|
||||
ticker := time.NewTicker(wsc.heartbeatTick)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// 发送ping消息
|
||||
if err := wsc.Conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(10*time.Second)); err != nil {
|
||||
wsc.Logger.Warn("Failed to send ping", zap.Error(err))
|
||||
return
|
||||
}
|
||||
wsc.Logger.Debug("Heartbeat ping sent")
|
||||
|
||||
case <-wsc.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// reconnectLoop 重连循环(仅用于正向连接)
|
||||
func (wsc *WebSocketConnection) reconnectLoop(wsm *WebSocketManager) {
|
||||
<-wsc.ctx.Done() // 等待连接断开
|
||||
|
||||
if wsc.Type != ConnectionTypeForward || wsc.reconnectURL == "" {
|
||||
return
|
||||
}
|
||||
|
||||
wsc.Logger.Info("Connection closed, attempting to reconnect",
|
||||
zap.Int("max_reconnect", wsc.maxReconnect))
|
||||
|
||||
for wsc.reconnectCount < wsc.maxReconnect {
|
||||
wsc.reconnectCount++
|
||||
backoff := time.Duration(wsc.reconnectCount) * 5 * time.Second
|
||||
|
||||
wsc.Logger.Info("Reconnecting",
|
||||
zap.Int("attempt", wsc.reconnectCount),
|
||||
zap.Int("max", wsc.maxReconnect),
|
||||
zap.Duration("backoff", backoff))
|
||||
|
||||
time.Sleep(backoff)
|
||||
|
||||
// 尝试重新连接
|
||||
conn, _, err := websocket.DefaultDialer.Dial(wsc.reconnectURL, nil)
|
||||
if err != nil {
|
||||
wsc.Logger.Error("Reconnect failed", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 更新连接
|
||||
wsc.Conn = conn
|
||||
wsc.RemoteAddr = conn.RemoteAddr().String()
|
||||
wsc.ctx, wsc.cancel = context.WithCancel(context.Background())
|
||||
wsc.reconnectCount = 0 // 重置重连计数
|
||||
|
||||
wsc.Logger.Info("Reconnected successfully",
|
||||
zap.String("remote_addr", wsc.RemoteAddr))
|
||||
|
||||
// 重新启动读取循环和心跳
|
||||
go wsc.readLoop(wsm.eventBus)
|
||||
go wsc.heartbeatLoop()
|
||||
go wsc.reconnectLoop(wsm)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
wsc.Logger.Error("Max reconnect attempts reached, giving up",
|
||||
zap.Int("attempts", wsc.reconnectCount))
|
||||
|
||||
// 从管理器中移除连接
|
||||
wsm.RemoveConnection(wsc.ID)
|
||||
}
|
||||
|
||||
// close 关闭连接
|
||||
func (wsc *WebSocketConnection) close() {
|
||||
wsc.cancel()
|
||||
@@ -209,31 +333,64 @@ func (wsm *WebSocketManager) BroadcastToBot(botID string, data []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
// Dial 建立WebSocket客户端连接
|
||||
// DialConfig WebSocket客户端连接配置
|
||||
type DialConfig struct {
|
||||
URL string
|
||||
BotID string
|
||||
MaxReconnect int
|
||||
HeartbeatTick time.Duration
|
||||
}
|
||||
|
||||
// Dial 建立WebSocket客户端连接(正向连接)
|
||||
func (wsm *WebSocketManager) Dial(addr string, botID string) (*WebSocketConnection, error) {
|
||||
u, err := url.Parse(addr)
|
||||
return wsm.DialWithConfig(DialConfig{
|
||||
URL: addr,
|
||||
BotID: botID,
|
||||
MaxReconnect: 5,
|
||||
HeartbeatTick: 30 * time.Second,
|
||||
})
|
||||
}
|
||||
|
||||
// DialWithConfig 使用配置建立WebSocket客户端连接
|
||||
func (wsm *WebSocketManager) DialWithConfig(config DialConfig) (*WebSocketConnection, error) {
|
||||
u, err := url.Parse(config.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid URL: %w", err)
|
||||
}
|
||||
|
||||
conn, _, err := websocket.DefaultDialer.Dial(addr, nil)
|
||||
// 验证URL scheme必须是ws或wss
|
||||
if u.Scheme != "ws" && u.Scheme != "wss" {
|
||||
return nil, fmt.Errorf("invalid URL scheme: %s, expected ws or wss", u.Scheme)
|
||||
}
|
||||
|
||||
conn, _, err := websocket.DefaultDialer.Dial(config.URL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to dial: %w", err)
|
||||
}
|
||||
|
||||
wsConn := NewWebSocketConnection(conn, botID, wsm.logger)
|
||||
wsConn := NewWebSocketConnection(conn, config.BotID, ConnectionTypeForward, wsm.logger)
|
||||
wsConn.reconnectURL = config.URL
|
||||
wsConn.maxReconnect = config.MaxReconnect
|
||||
wsConn.heartbeatTick = config.HeartbeatTick
|
||||
|
||||
wsm.mu.Lock()
|
||||
wsm.connections[wsConn.ID] = wsConn
|
||||
wsm.mu.Unlock()
|
||||
|
||||
wsm.logger.Info("WebSocket client connected",
|
||||
wsm.logger.Info("WebSocket forward connection established",
|
||||
zap.String("conn_id", wsConn.ID),
|
||||
zap.String("bot_id", botID),
|
||||
zap.String("addr", addr))
|
||||
zap.String("bot_id", config.BotID),
|
||||
zap.String("addr", config.URL),
|
||||
zap.String("remote_addr", wsConn.RemoteAddr))
|
||||
|
||||
// 启动读取循环
|
||||
// 启动读取循环和心跳
|
||||
go wsConn.readLoop(wsm.eventBus)
|
||||
go wsConn.heartbeatLoop()
|
||||
|
||||
// 如果是正向连接,启动重连监控
|
||||
if wsConn.Type == ConnectionTypeForward {
|
||||
go wsConn.reconnectLoop(wsm)
|
||||
}
|
||||
|
||||
return wsConn, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user