feat: 平台配置唯一ID — PlatformConfig.ID + adapter全链路传播

- PlatformConfig 新增 ID 字段 (8字节hex,创建时自动生成)
- QQ adapter 新增 configID + ConfigID() 访问器
- ChannelInfo 新增 AdapterID + AdapterName
- createSingleAdapter 接受 configID 参数
- 同一个平台类型的多个配置实例通过 ID 唯一区分

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-23 17:59:37 +08:00
parent 6bf59f7eee
commit 45fac267fe
8 changed files with 177 additions and 9 deletions
+5 -5
View File
@@ -287,7 +287,7 @@ func main() {
// Hot-reload: on config save/delete, dynamically replace adapters.
ch.SetOnConfigChanged(func(name, platform string, enabled bool, fields map[string]string) {
if enabled {
a := createSingleAdapter(cfg, platform, name, fields)
a := createSingleAdapter(cfg, platform, name, "", fields) // configID via Store.Set
if a == nil {
fmt.Printf("WARN: cannot create adapter for %s (platform=%s)\n", name, platform)
return
@@ -477,7 +477,7 @@ func createAdapters(cfg *config.Config, store *config.Store) []bridge.PlatformAd
platform = stored.Name
}
fields := mergeFields(cfg, platform, &stored)
a := createSingleAdapter(cfg, platform, stored.Name, fields)
a := createSingleAdapter(cfg, platform, stored.Name, stored.ID, fields)
if a != nil {
adapters = append(adapters, a)
seen[stored.Name] = true
@@ -491,7 +491,7 @@ func createAdapters(cfg *config.Config, store *config.Store) []bridge.PlatformAd
continue
}
fields := mergeFields(cfg, name, nil)
a := createSingleAdapter(cfg, name, name, fields)
a := createSingleAdapter(cfg, name, name, "", fields) // seed adapter, no config ID
if a != nil {
adapters = append(adapters, a)
}
@@ -501,7 +501,7 @@ func createAdapters(cfg *config.Config, store *config.Store) []bridge.PlatformAd
// createSingleAdapter creates one platform adapter from config fields.
// platform is the base platform type ("qq", "telegram", etc.), configName is the instance key.
func createSingleAdapter(cfg *config.Config, platform, configName string, fields map[string]string) bridge.PlatformAdapter {
func createSingleAdapter(cfg *config.Config, platform, configName, configID string, fields map[string]string) bridge.PlatformAdapter {
switch platform {
case "qq":
port := cfg.OBv11BotPort
@@ -528,7 +528,7 @@ func createSingleAdapter(cfg *config.Config, platform, configName string, fields
sendIntervalMs = n
}
}
return qqadapter.NewAdapter(configName, mode, port, token, remoteURL, sendIntervalMs)
return qqadapter.NewAdapter(configID, configName, mode, port, token, remoteURL, sendIntervalMs)
case "telegram":
token := cfg.TelegramToken
if t, ok := fields["bot_token"]; ok && t != "" {
@@ -26,6 +26,7 @@ var upgrader = websocket.Upgrader{
// - "server" (正向 WS): adapter starts a WS server, NapCat connects as client.
// - "client" (反向 WS): adapter connects to NapCat's WS server as a client.
type Adapter struct {
configID string // immutable unique ID from PlatformConfig
configName string // instance name, e.g. "qq-home"
mode string // "client" or "server"
port string
@@ -46,11 +47,12 @@ type Adapter struct {
respMu sync.Mutex
}
func NewAdapter(configName, mode, port, accessToken, remoteURL string, sendIntervalMs int) *Adapter {
func NewAdapter(configID, configName, mode, port, accessToken, remoteURL string, sendIntervalMs int) *Adapter {
if mode == "" {
mode = "server"
}
return &Adapter{
configID: configID,
configName: configName,
mode: mode,
port: port,
@@ -63,6 +65,7 @@ func NewAdapter(configName, mode, port, accessToken, remoteURL string, sendInter
}
func (a *Adapter) PlatformName() string { return "qq" }
func (a *Adapter) ConfigID() string { return a.configID }
func (a *Adapter) ConfigName() string { return a.configName }
func (a *Adapter) SendIntervalMs() int { return a.sendIntervalMs }
func (a *Adapter) SelfID() string { return a.selfID }
@@ -81,5 +81,5 @@ type OBv11Params struct {
UserID int64 `json:"user_id,omitempty"`
GroupID int64 `json:"group_id,omitempty"`
Message interface{} `json:"message"`
AutoEscape bool `json:"auto_escape,omitempty"`
AutoEscape bool `json:"auto_escape"`
}
@@ -32,6 +32,8 @@ type ChannelInfo struct {
ChannelType string `json:"channel_type"` // "group", "private"
ChannelID string `json:"channel_id"`
ChannelName string `json:"channel_name,omitempty"`
AdapterID string `json:"adapter_id,omitempty"` // config ID of the adapter instance
AdapterName string `json:"adapter_name,omitempty"` // config name, e.g. "qq-main"
}
// ChannelLister is an optional interface for adapters that can enumerate
@@ -1,6 +1,8 @@
package config
import (
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"os"
@@ -10,11 +12,13 @@ import (
// PlatformConfig holds persistent configuration for one platform adapter.
type PlatformConfig struct {
Name string `json:"name"`
Platform string `json:"platform"` // base platform type: "qq", "telegram", etc.
ID string `json:"id"` // immutable unique ID, generated at creation
Name string `json:"name"` // config key, e.g. "qq-main", "qq-work"
Platform string `json:"platform"` // base platform type: "qq", "telegram", etc.
Enabled bool `json:"enabled"`
Label string `json:"label"`
Fields map[string]string `json:"fields"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
@@ -102,11 +106,22 @@ func (s *Store) Set(cfg PlatformConfig) error {
if cfg.Fields == nil {
cfg.Fields = make(map[string]string)
}
// Generate stable ID for new configs.
if cfg.ID == "" {
cfg.ID = generateConfigID()
cfg.CreatedAt = time.Now()
}
cfg.UpdatedAt = time.Now()
s.configs[cfg.Name] = &cfg
return s.save()
}
func generateConfigID() string {
b := make([]byte, 8)
rand.Read(b)
return hex.EncodeToString(b)
}
// Delete removes a platform config and persists.
func (s *Store) Delete(name string) error {
s.mu.Lock()