refactor: modernize codebase for Go 1.26 and fix adapter issues

- Upgrade Go version from 1.24 to 1.26.1 with updated dependencies
- Replace interface{} with any type throughout codebase
- Add message deduplication in OneBot11 adapter to prevent duplicate event processing
- URL-encode access token in WebSocket connections to handle special characters
- Remove duplicate bot startup hooks in DI providers (now handled by botManager.StartAll)
- Use slices.Clone and errgroup.Go patterns for cleaner concurrent code
- Apply omitzero JSON tags for optional fields (Go 1.24+ feature)
This commit is contained in:
lafay
2026-03-18 01:22:49 +08:00
parent f3a72264af
commit f3220a2381
32 changed files with 2262 additions and 296 deletions

View File

@@ -3,6 +3,7 @@ package onebot11
import (
"context"
"fmt"
"net/url"
"sync"
"time"
@@ -28,6 +29,10 @@ type Adapter struct {
wsConnection *net.WebSocketConnection
ctx context.Context
cancel context.CancelFunc
// 消息去重
processedMessages map[int64]time.Time
messageMu sync.RWMutex
}
// Config OneBot11配置
@@ -65,16 +70,20 @@ func NewAdapter(config *Config, logger *zap.Logger, wsManager *net.WebSocketMana
}
adapter := &Adapter{
config: config,
logger: logger.Named("onebot11"),
wsManager: wsManager,
wsWaiter: NewWSResponseWaiter(timeout, logger),
eventBus: eventBus,
selfID: config.SelfID,
ctx: ctx,
cancel: cancel,
config: config,
logger: logger.Named("onebot11"),
wsManager: wsManager,
wsWaiter: NewWSResponseWaiter(timeout, logger),
eventBus: eventBus,
selfID: config.SelfID,
ctx: ctx,
cancel: cancel,
processedMessages: make(map[int64]time.Time),
}
// 启动定期清理过期消息的 goroutine
go adapter.cleanupProcessedMessages()
// 如果使用HTTP连接初始化HTTP客户端
if config.ConnectionType == "http" && config.HTTPUrl != "" {
adapter.httpClient = NewHTTPClient(config.HTTPUrl, config.AccessToken, timeout, logger)
@@ -83,6 +92,43 @@ func NewAdapter(config *Config, logger *zap.Logger, wsManager *net.WebSocketMana
return adapter
}
// cleanupProcessedMessages 定期清理过期的已处理消息记录
func (a *Adapter) cleanupProcessedMessages() {
ticker := time.NewTicker(5 * time.Minute)
defer ticker.Stop()
for {
select {
case <-a.ctx.Done():
return
case <-ticker.C:
a.messageMu.Lock()
now := time.Now()
for msgID, processedAt := range a.processedMessages {
if now.Sub(processedAt) > 10*time.Minute {
delete(a.processedMessages, msgID)
}
}
a.messageMu.Unlock()
}
}
}
// isMessageProcessed 检查消息是否已处理
func (a *Adapter) isMessageProcessed(messageID int64) bool {
a.messageMu.RLock()
_, exists := a.processedMessages[messageID]
a.messageMu.RUnlock()
return exists
}
// markMessageProcessed 标记消息为已处理
func (a *Adapter) markMessageProcessed(messageID int64) {
a.messageMu.Lock()
a.processedMessages[messageID] = time.Now()
a.messageMu.Unlock()
}
// Name 获取协议名称
func (a *Adapter) Name() string {
return "OneBot"
@@ -174,7 +220,7 @@ func (a *Adapter) GetSelfID() string {
}
// SendAction 发送动作
func (a *Adapter) SendAction(ctx context.Context, action protocol.Action) (map[string]interface{}, error) {
func (a *Adapter) SendAction(ctx context.Context, action protocol.Action) (map[string]any, error) {
// 序列化为OneBot11格式
data, err := a.SerializeAction(action)
if err != nil {
@@ -228,7 +274,7 @@ func (a *Adapter) SerializeAction(action protocol.Action) ([]byte, error) {
}
// 复制参数并转换消息链
params := make(map[string]interface{})
params := make(map[string]any)
for k, v := range action.GetParams() {
// 检查是否是消息链
if k == "message" {
@@ -259,10 +305,10 @@ func (a *Adapter) connectWebSocket(ctx context.Context) error {
zap.String("url", a.config.WSUrl),
zap.Bool("has_token", a.config.AccessToken != ""))
// 添加访问令牌到URL
// 添加访问令牌到URL需要对token进行URL编码以处理特殊字符
wsURL := a.config.WSUrl
if a.config.AccessToken != "" {
wsURL += "?access_token=" + a.config.AccessToken
wsURL += "?access_token=" + url.QueryEscape(a.config.AccessToken)
a.logger.Debug("Added access token to WebSocket URL")
}
@@ -342,7 +388,7 @@ func (a *Adapter) connectHTTPPost(ctx context.Context) error {
}
// sendActionWebSocket 通过WebSocket发送动作
func (a *Adapter) sendActionWebSocket(data []byte) (map[string]interface{}, error) {
func (a *Adapter) sendActionWebSocket(data []byte) (map[string]any, error) {
if a.wsConnection == nil {
return nil, fmt.Errorf("websocket connection not established")
}
@@ -383,7 +429,7 @@ func (a *Adapter) sendActionWebSocket(data []byte) (map[string]interface{}, erro
}
// sendActionHTTP 通过HTTP发送动作
func (a *Adapter) sendActionHTTP(data []byte) (map[string]interface{}, error) {
func (a *Adapter) sendActionHTTP(data []byte) (map[string]any, error) {
if a.httpClient == nil {
return nil, fmt.Errorf("http client not initialized")
}
@@ -439,7 +485,7 @@ func (a *Adapter) handleWebSocketMessages() {
zap.String("preview", string(message[:min(len(message), 200)])))
// 尝试解析为响应(先检查是否有 echo 字段)
var tempMap map[string]interface{}
var tempMap map[string]any
if err := sonic.Unmarshal(message, &tempMap); err == nil {
if echo, ok := tempMap["echo"].(string); ok && echo != "" {
// 有 echo 字段说明是API响应
@@ -478,6 +524,24 @@ func (a *Adapter) handleWebSocketMessages() {
continue
}
// 消息去重检查(基于 message_id
data := event.GetData()
// message_id 可能是 int32 或 int64
var messageID int64
if mid, ok := data["message_id"].(int32); ok && mid > 0 {
messageID = int64(mid)
} else if mid, ok := data["message_id"].(int64); ok && mid > 0 {
messageID = mid
}
if messageID > 0 {
if a.isMessageProcessed(messageID) {
a.logger.Warn("Duplicate message detected, skipping",
zap.Int64("message_id", messageID))
continue
}
a.markMessageProcessed(messageID)
}
// 发布事件到事件总线
a.logger.Info("Publishing event to event bus",
zap.String("event_type", string(event.GetType())),