Files
cellbot/pkg/net/server.go
lafay 44fe05ff62 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.
2026-01-05 00:40:09 +08:00

413 lines
10 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package net
import (
"net"
"strconv"
"cellbot/internal/engine"
"cellbot/internal/protocol"
"github.com/bytedance/sonic"
"github.com/valyala/fasthttp"
"go.uber.org/zap"
)
// Server HTTP服务器
type Server struct {
host string
port int
logger *zap.Logger
botManager *protocol.BotManager
eventBus *engine.EventBus
wsManager *WebSocketManager
server *fasthttp.Server
}
// NewServer 创建HTTP服务器
func NewServer(host string, port int, logger *zap.Logger, botManager *protocol.BotManager, eventBus *engine.EventBus) *Server {
wsManager := NewWebSocketManager(logger, eventBus)
return &Server{
host: host,
port: port,
logger: logger.Named("server"),
botManager: botManager,
eventBus: eventBus,
wsManager: wsManager,
}
}
// Start 启动服务器
func (s *Server) Start() error {
s.server = &fasthttp.Server{
Handler: s.requestHandler,
MaxConnsPerIP: 1000,
MaxRequestsPerConn: 1000,
ReadTimeout: 0,
WriteTimeout: 0,
IdleTimeout: 0,
DisableKeepalive: false,
}
addr := net.JoinHostPort(s.host, strconv.Itoa(s.port))
s.logger.Info("Starting HTTP server", zap.String("address", addr))
go func() {
if err := s.server.ListenAndServe(addr); err != nil {
s.logger.Error("Server error", zap.Error(err))
}
}()
return nil
}
// Stop 停止服务器
func (s *Server) Stop() error {
if s.server != nil {
s.logger.Info("Stopping HTTP server")
return s.server.Shutdown()
}
return nil
}
// requestHandler 请求处理器
func (s *Server) requestHandler(ctx *fasthttp.RequestCtx) {
path := string(ctx.Path())
method := string(ctx.Method())
s.logger.Debug("Received request",
zap.String("path", path),
zap.String("method", method))
// 路由处理
switch path {
case "/":
s.handleRoot(ctx)
case "/health":
s.handleHealth(ctx)
case "/bots":
s.handleBots(ctx)
case "/bots/create":
s.handleCreateBot(ctx)
case "/events/publish":
s.handlePublishEvent(ctx)
case "/events/subscribe":
s.handleSubscribeEvent(ctx)
default:
ctx.Error("Not Found", fasthttp.StatusNotFound)
}
}
// handleRoot 根路径处理
func (s *Server) handleRoot(ctx *fasthttp.RequestCtx) {
ctx.SetContentType("application/json")
ctx.SetBodyString(`{"message":"CellBot Server","version":"1.0.0"}`)
}
// handleHealth 健康检查
func (s *Server) handleHealth(ctx *fasthttp.RequestCtx) {
ctx.SetContentType("application/json")
ctx.SetBodyString(`{"status":"ok"}`)
}
// BotInfo 机器人信息结构
type BotInfo struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Status string `json:"status"`
SelfID string `json:"self_id"`
Connected bool `json:"connected"`
}
// handleBots 获取机器人列表
func (s *Server) handleBots(ctx *fasthttp.RequestCtx) {
bots := s.botManager.GetAll()
ctx.SetContentType("application/json")
// 构建机器人信息列表
botInfos := make([]BotInfo, 0, len(bots))
for _, bot := range bots {
botInfos = append(botInfos, BotInfo{
ID: bot.GetID(),
Name: bot.Name(),
Version: bot.Version(),
Status: string(bot.GetStatus()),
SelfID: bot.GetSelfID(),
Connected: bot.IsConnected(),
})
}
// 序列化为JSON
response := map[string]interface{}{
"bots": botInfos,
"count": len(botInfos),
}
data, err := sonic.Marshal(response)
if err != nil {
s.logger.Error("Failed to marshal bots response", zap.Error(err))
ctx.Error("Internal Server Error", fasthttp.StatusInternalServerError)
return
}
ctx.SetBody(data)
}
// CreateBotRequest 创建机器人请求
type CreateBotRequest struct {
ID string `json:"id"`
Protocol string `json:"protocol"`
Config map[string]interface{} `json:"config"`
}
// handleCreateBot 创建机器人
func (s *Server) handleCreateBot(ctx *fasthttp.RequestCtx) {
if string(ctx.Method()) != "POST" {
ctx.Error("Method Not Allowed", fasthttp.StatusMethodNotAllowed)
return
}
ctx.SetContentType("application/json")
// 解析请求体
var req CreateBotRequest
if err := sonic.Unmarshal(ctx.PostBody(), &req); err != nil {
s.logger.Error("Failed to parse create bot request", zap.Error(err))
response := map[string]interface{}{
"success": false,
"error": "Invalid request body",
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBody(data)
return
}
// 验证必需字段
if req.ID == "" {
response := map[string]interface{}{
"success": false,
"error": "Bot ID is required",
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBody(data)
return
}
if req.Protocol == "" {
response := map[string]interface{}{
"success": false,
"error": "Protocol is required",
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBody(data)
return
}
// 检查机器人是否已存在
if _, exists := s.botManager.Get(req.ID); exists {
response := map[string]interface{}{
"success": false,
"error": "Bot with this ID already exists",
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusConflict)
ctx.SetBody(data)
return
}
// TODO: 根据协议类型创建相应的机器人实例
// 这里需要协议工厂来创建不同类型的机器人
// 目前返回成功但提示需要实现协议适配器
s.logger.Info("Bot creation requested",
zap.String("bot_id", req.ID),
zap.String("protocol", req.Protocol))
response := map[string]interface{}{
"success": true,
"message": "Bot creation queued (protocol adapter implementation required)",
"bot_id": req.ID,
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusAccepted)
ctx.SetBody(data)
}
// handlePublishEvent 发布事件
func (s *Server) handlePublishEvent(ctx *fasthttp.RequestCtx) {
if string(ctx.Method()) != "POST" {
ctx.Error("Method Not Allowed", fasthttp.StatusMethodNotAllowed)
return
}
ctx.SetContentType("application/json")
// 解析请求体为事件对象
var event protocol.BaseEvent
if err := sonic.Unmarshal(ctx.PostBody(), &event); err != nil {
s.logger.Error("Failed to parse event", zap.Error(err))
response := map[string]interface{}{
"success": false,
"error": "Invalid event format",
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBody(data)
return
}
// 验证事件类型
if event.Type == "" {
response := map[string]interface{}{
"success": false,
"error": "Event type is required",
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBody(data)
return
}
// 如果没有时间戳,使用当前时间
if event.Timestamp == 0 {
event.Timestamp = ctx.Time().Unix()
}
// 确保Data字段不为nil
if event.Data == nil {
event.Data = make(map[string]interface{})
}
s.logger.Info("Publishing event",
zap.String("type", string(event.Type)),
zap.String("detail_type", event.DetailType),
zap.String("self_id", event.SelfID))
// 发布到事件总线
s.eventBus.Publish(&event)
response := map[string]interface{}{
"success": true,
"message": "Event published successfully",
"timestamp": event.Timestamp,
}
data, _ := sonic.Marshal(response)
ctx.SetBody(data)
}
// handleSubscribeEvent 订阅事件WebSocket升级
func (s *Server) handleSubscribeEvent(ctx *fasthttp.RequestCtx) {
// 检查是否为WebSocket升级请求
if !ctx.IsGet() {
ctx.Error("Method Not Allowed", fasthttp.StatusMethodNotAllowed)
return
}
// 检查是否为WebSocket升级请求
if string(ctx.Request.Header.Peek("Upgrade")) != "websocket" {
ctx.SetContentType("application/json")
response := map[string]interface{}{
"success": false,
"error": "WebSocket upgrade required",
"message": "This endpoint requires WebSocket connection",
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusBadRequest)
ctx.SetBody(data)
return
}
// 获取订阅者ID可选
subscriberID := string(ctx.QueryArgs().Peek("subscriber_id"))
if subscriberID == "" {
subscriberID = "event-subscriber"
}
// 获取要订阅的事件类型(可选,为空则订阅所有类型)
eventTypeStr := string(ctx.QueryArgs().Peek("event_type"))
s.logger.Info("WebSocket event subscription request",
zap.String("subscriber_id", subscriberID),
zap.String("event_type", eventTypeStr),
zap.String("remote_addr", ctx.RemoteAddr().String()))
// 升级为WebSocket连接
wsConn, err := s.wsManager.UpgradeWebSocket(ctx)
if err != nil {
s.logger.Error("Failed to upgrade WebSocket", zap.Error(err))
ctx.SetContentType("application/json")
response := map[string]interface{}{
"success": false,
"error": "Failed to upgrade WebSocket connection",
}
data, _ := sonic.Marshal(response)
ctx.SetStatusCode(fasthttp.StatusInternalServerError)
ctx.SetBody(data)
return
}
// 订阅所有主要事件类型
eventTypes := []protocol.EventType{
protocol.EventTypeMessage,
protocol.EventTypeNotice,
protocol.EventTypeRequest,
protocol.EventTypeMeta,
protocol.EventTypeMessageSent,
protocol.EventTypeNoticeSent,
protocol.EventTypeRequestSent,
}
// 如果指定了事件类型,只订阅该类型
if eventTypeStr != "" {
eventTypes = []protocol.EventType{protocol.EventType(eventTypeStr)}
}
// 为每种事件类型创建订阅
for _, eventType := range eventTypes {
eventChan := s.eventBus.Subscribe(eventType, nil) // nil filter means accept all events
// 启动goroutine监听事件并发送到WebSocket
go func(ch chan protocol.Event, et protocol.EventType) {
for {
select {
case event, ok := <-ch:
if !ok {
return
}
// 序列化事件为JSON
data, err := sonic.Marshal(event)
if err != nil {
s.logger.Error("Failed to marshal event", zap.Error(err))
continue
}
// 发送到WebSocket连接
if err := wsConn.SendMessage(data); err != nil {
s.logger.Error("Failed to send event to WebSocket",
zap.String("conn_id", wsConn.ID),
zap.Error(err))
// 连接可能已断开,取消订阅
s.eventBus.Unsubscribe(et, ch)
return
}
case <-wsConn.ctx.Done():
// 连接关闭,取消订阅
s.eventBus.Unsubscribe(et, ch)
return
}
}
}(eventChan, eventType)
}
s.logger.Info("WebSocket event subscription established",
zap.String("conn_id", wsConn.ID),
zap.String("subscriber_id", subscriberID))
}