- 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.
413 lines
10 KiB
Go
413 lines
10 KiB
Go
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))
|
||
}
|