后端:
1.阶段 6 CP4/CP5 目录收口与共享边界纯化
- 将 backend 根目录收口为 services、client、gateway、cmd、shared 五个一级目录
- 收拢 bootstrap、inits、infra/kafka、infra/outbox、conv、respond、pkg、middleware,移除根目录旧实现与空目录
- 将 utils 下沉到 services/userauth/internal/auth,将 logic 下沉到 services/schedule/core/planning
- 将迁移期 runtime 桥接实现统一收拢到 services/runtime/{conv,dao,eventsvc,model},删除 shared/legacy 与未再被 import 的旧 service 实现
- 将 gateway/shared/respond 收口为 HTTP/Gin 错误写回适配,shared/respond 仅保留共享错误语义与状态映射
- 将 HTTP IdempotencyMiddleware 与 RateLimitMiddleware 收口到 gateway/middleware
- 将 GormCachePlugin 下沉到 shared/infra/gormcache,将共享 RateLimiter 下沉到 shared/infra/ratelimit,将 agent token budget 下沉到 services/agent/shared
- 删除 InitEino 兼容壳,收缩 cmd/internal/coreinit 仅保留旧组合壳残留域初始化语义
- 更新微服务迁移计划与桌面 checklist,补齐 CP4/CP5 当前切流点、目录终态与验证结果
- 完成 go test ./...、git diff --check 与最终真实 smoke;health、register/login、task/create+get、schedule/today、task-class/list、memory/items、agent chat/meta/timeline/context-stats 全部 200,SSE 合并结果为 CP5_OK 且 [DONE] 只有 1 个
137 lines
4.2 KiB
Go
137 lines
4.2 KiB
Go
package outbox
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"strings"
|
||
"sync"
|
||
)
|
||
|
||
var outboxRouteRegistry = struct {
|
||
sync.RWMutex
|
||
eventToService map[string]string
|
||
serviceRoutes map[string]ServiceRoute
|
||
}{
|
||
eventToService: make(map[string]string),
|
||
serviceRoutes: make(map[string]ServiceRoute),
|
||
}
|
||
|
||
// RegisterServiceRoute 注册或覆盖某个服务的物理 outbox 路由。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只登记“服务 -> table/topic/group”目录,不登记事件归属;
|
||
// 2. 同服务重复注册时以后者覆盖前者,方便显式配置覆盖默认目录;
|
||
// 3. 空服务名直接报错,避免把共享 topic 误当成新终态。
|
||
func RegisterServiceRoute(route ServiceRoute) error {
|
||
route = normalizeServiceRoute(route)
|
||
if route.ServiceName == "" {
|
||
return errors.New("serviceName is empty")
|
||
}
|
||
|
||
outboxRouteRegistry.Lock()
|
||
defer outboxRouteRegistry.Unlock()
|
||
|
||
outboxRouteRegistry.serviceRoutes[route.ServiceName] = route
|
||
return nil
|
||
}
|
||
|
||
// RegisterEventService 记录“事件类型 -> 服务归属”的全局路由。
|
||
//
|
||
// 职责边界:
|
||
// 1. 只登记跨进程都要识别的事件归属,不承载 handler 逻辑;
|
||
// 2. 同一 event_type 只能归属一个服务,重复登记同值视为幂等;
|
||
// 3. 若该服务还没有显式路由,则先写入默认服务目录,保证后续能查到 table/topic/group。
|
||
func RegisterEventService(eventType, serviceName string) error {
|
||
eventType = strings.TrimSpace(eventType)
|
||
if eventType == "" {
|
||
return errors.New("eventType is empty")
|
||
}
|
||
serviceName = normalizeServiceName(serviceName)
|
||
if serviceName == "" {
|
||
return errors.New("serviceName is empty")
|
||
}
|
||
|
||
outboxRouteRegistry.Lock()
|
||
defer outboxRouteRegistry.Unlock()
|
||
|
||
if existing, ok := outboxRouteRegistry.eventToService[eventType]; ok {
|
||
if existing != serviceName {
|
||
return fmt.Errorf("eventType %s already registered to service %s", eventType, existing)
|
||
}
|
||
return nil
|
||
}
|
||
|
||
outboxRouteRegistry.eventToService[eventType] = serviceName
|
||
return nil
|
||
}
|
||
|
||
// ResolveEventService 查询某个事件类型的归属服务。
|
||
//
|
||
// 返回值说明:
|
||
// 1. serviceName 为登记结果;
|
||
// 2. ok=false 表示当前路由表里还没有这个事件类型的归属信息。
|
||
func ResolveEventService(eventType string) (serviceName string, ok bool) {
|
||
eventType = strings.TrimSpace(eventType)
|
||
if eventType == "" {
|
||
return "", false
|
||
}
|
||
|
||
outboxRouteRegistry.RLock()
|
||
defer outboxRouteRegistry.RUnlock()
|
||
|
||
serviceName, ok = outboxRouteRegistry.eventToService[eventType]
|
||
return serviceName, ok
|
||
}
|
||
|
||
// ResolveServiceRoute 查询某个服务的物理 outbox 配置。
|
||
//
|
||
// 返回值说明:
|
||
// 1. route 始终返回一个可执行的目录结果,未显式注册时回退默认目录;
|
||
// 2. ok=true 表示命中显式注册目录,ok=false 表示走默认目录;
|
||
// 3. 这样既能支持显式配置覆盖,也能让基础设施在启动初期就有稳定默认值。
|
||
func ResolveServiceRoute(serviceName string) (route ServiceRoute, ok bool) {
|
||
serviceName = normalizeServiceName(serviceName)
|
||
if serviceName == "" {
|
||
return DefaultServiceRoute(""), false
|
||
}
|
||
|
||
outboxRouteRegistry.RLock()
|
||
route, ok = outboxRouteRegistry.serviceRoutes[serviceName]
|
||
outboxRouteRegistry.RUnlock()
|
||
if ok {
|
||
return normalizeServiceRoute(route), true
|
||
}
|
||
if route, ok = configuredServiceRoute(serviceName); ok {
|
||
return route, true
|
||
}
|
||
return DefaultServiceRoute(serviceName), false
|
||
}
|
||
|
||
// ResolveEventRoute 先按事件查服务,再按服务查物理目录。
|
||
//
|
||
// 返回值说明:
|
||
// 1. route 包含事件所在服务的 table/topic/group;
|
||
// 2. ok=true 只表示“事件 -> 服务归属”已登记;
|
||
// 3. 服务目录若未显式注册,会自动回退到默认目录。
|
||
func ResolveEventRoute(eventType string) (route ServiceRoute, ok bool) {
|
||
serviceName, ok := ResolveEventService(eventType)
|
||
if !ok {
|
||
return ServiceRoute{}, false
|
||
}
|
||
route, _ = ResolveServiceRoute(serviceName)
|
||
return route, true
|
||
}
|
||
|
||
func configuredServiceRoute(serviceName string) (ServiceRoute, bool) {
|
||
cfg, ok := ResolveServiceConfig(serviceName)
|
||
if !ok {
|
||
return ServiceRoute{}, false
|
||
}
|
||
return normalizeServiceRoute(ServiceRoute{
|
||
ServiceName: cfg.Name,
|
||
TableName: cfg.TableName,
|
||
Topic: cfg.Topic,
|
||
GroupID: cfg.GroupID,
|
||
}), true
|
||
}
|