Files
smartmate/backend/infra/outbox/service_catalog.go
Losita a6c1e5d077 Version: 0.9.64.dev.260503
后端:
1. 服务级 outbox 基础设施全量落地——新增 service route / service catalog / route registry,重构 outbox engine、repository、event bus 和 model,按 `event_type -> service -> table/topic/group` 统一写入与投递,保留 `agent` 兼容壳但不再依赖共享 outbox
2. Kafka 投递、消费与启动装配同步切换——更新 kafka config、consumer、envelope,接入服务级 topic 与 consumer group,并同步调整 mysql 初始化、start/main/router 装配,保证各服务 relay / consumer 独立装配
3. 业务事件处理器按服务归属重接新 bus——`active-scheduler` 触发链路,以及 `agent` / `memory` / `notification` / `task` 相关 outbox handler 统一切到新路由注册与服务目录,避免新流量回流共享表
4. 同步更新《微服务四步迁移与第二阶段并行开发计划》,把阶段 1 改成当前基线并补齐结构图、阶段快照、风险回退和多代理执行口径
2026-05-03 20:29:00 +08:00

169 lines
4.7 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 outbox
import (
"fmt"
"sort"
"strings"
"sync"
"github.com/spf13/viper"
)
const (
ServiceAgent = "agent"
ServiceTask = "task"
ServiceMemory = "memory"
ServiceActiveScheduler = "active-scheduler"
ServiceNotification = "notification"
)
// ServiceConfig 描述一个服务级 outbox 的固定归属。
//
// 职责边界:
// 1. 只描述“事件属于哪个服务、写哪张表、发哪个 topic、用哪个 group”。
// 2. 不承载具体业务 handler也不承载 Kafka 消息体格式。
// 3. 服务级写入、扫描和消费都应从这里读取同一份映射,避免配置漂移。
type ServiceConfig struct {
Name string
Topic string
GroupID string
TableName string
}
var serviceCatalogCache = struct {
sync.RWMutex
loaded bool
entries map[string]ServiceConfig
}{
entries: make(map[string]ServiceConfig),
}
// LoadServiceConfigs 读取服务级 outbox 目录。
//
// 说明:
// 1. 先给出默认终态映射,再允许通过配置中心覆盖 topic/groupID/table
// 2. 该目录只负责服务级 outbox 基础设施,不混入业务逻辑;
// 3. 若某个服务配置缺失,直接使用默认值,避免启动期因为非关键配置崩掉。
func LoadServiceConfigs() map[string]ServiceConfig {
serviceCatalogCache.Lock()
defer serviceCatalogCache.Unlock()
if serviceCatalogCache.loaded {
return cloneServiceConfigs(serviceCatalogCache.entries)
}
entries := map[string]ServiceConfig{
ServiceAgent: {
Name: ServiceAgent,
Topic: "smartflow.agent.outbox",
GroupID: "smartflow-agent-outbox-consumer",
TableName: "agent_outbox_messages",
},
ServiceTask: {
Name: ServiceTask,
Topic: "smartflow.task.outbox",
GroupID: "smartflow-task-outbox-consumer",
TableName: "task_outbox_messages",
},
ServiceMemory: {
Name: ServiceMemory,
Topic: "smartflow.memory.outbox",
GroupID: "smartflow-memory-outbox-consumer",
TableName: "memory_outbox_messages",
},
ServiceActiveScheduler: {
Name: ServiceActiveScheduler,
Topic: "smartflow.active-scheduler.outbox",
GroupID: "smartflow-active-scheduler-outbox-consumer",
TableName: "active_scheduler_outbox_messages",
},
ServiceNotification: {
Name: ServiceNotification,
Topic: "smartflow.notification.outbox",
GroupID: "smartflow-notification-outbox-consumer",
TableName: "notification_outbox_messages",
},
}
for name, entry := range entries {
entries[name] = overrideServiceConfig(entry)
}
serviceCatalogCache.entries = entries
serviceCatalogCache.loaded = true
return cloneServiceConfigs(entries)
}
// ResolveServiceConfig 查询某个服务的 outbox 目录。
func ResolveServiceConfig(serviceName string) (ServiceConfig, bool) {
serviceName = strings.TrimSpace(serviceName)
if serviceName == "" {
return ServiceConfig{}, false
}
entries := LoadServiceConfigs()
cfg, ok := entries[serviceName]
return cfg, ok
}
// ResolveEventServiceConfig 先解析事件归属服务,再返回该服务的 outbox 目录。
func ResolveEventServiceConfig(eventType string) (ServiceConfig, bool) {
serviceName, ok := ResolveEventService(eventType)
if !ok {
return ServiceConfig{}, false
}
return ResolveServiceConfig(serviceName)
}
// ServiceTables 返回当前目录中的所有 outbox 表名。
func ServiceTables() []string {
entries := LoadServiceConfigs()
tables := make([]string, 0, len(entries))
for _, entry := range entries {
tables = append(tables, entry.TableName)
}
sort.Strings(tables)
return tables
}
// ServiceNames 返回当前目录中的所有服务名。
func ServiceNames() []string {
entries := LoadServiceConfigs()
names := make([]string, 0, len(entries))
for name := range entries {
names = append(names, name)
}
sort.Strings(names)
return names
}
func overrideServiceConfig(entry ServiceConfig) ServiceConfig {
upperName := strings.TrimSpace(entry.Name)
if upperName == "" {
return entry
}
topicKey := fmt.Sprintf("outbox.services.%s.topic", upperName)
groupKey := fmt.Sprintf("outbox.services.%s.groupID", upperName)
tableKey := fmt.Sprintf("outbox.services.%s.table", upperName)
if topic := strings.TrimSpace(viper.GetString(topicKey)); topic != "" {
entry.Topic = topic
}
if groupID := strings.TrimSpace(viper.GetString(groupKey)); groupID != "" {
entry.GroupID = groupID
}
if tableName := strings.TrimSpace(viper.GetString(tableKey)); tableName != "" {
entry.TableName = tableName
}
return entry
}
func cloneServiceConfigs(entries map[string]ServiceConfig) map[string]ServiceConfig {
cloned := make(map[string]ServiceConfig, len(entries))
for name, entry := range entries {
cloned[name] = entry
}
return cloned
}