From 6066eb114b4e7df7f65337b0dd9434cbf7acd2fd Mon Sep 17 00:00:00 2001 From: ryan Date: Thu, 18 Jun 2026 10:31:11 +0800 Subject: [PATCH] refactor(auth): decouple auth from push via domain events Introduce internal/listener as a domain event bus so oauth and user modules emit AdminLoggedIn without depending on admin/push. Register push handlers explicitly at the router composition root, replacing init() side-effect registration and blank imports. --- .../admin/push/custom_events/admin_login.go | 17 +++----- .../apps/admin/push/custom_events/register.go | 17 ++++++++ internal/apps/oauth/sources.go | 4 +- internal/apps/user/routers.go | 4 +- internal/listener/.gitkeep | 0 internal/listener/admin_login.go | 42 +++++++++++++++++++ internal/router/router.go | 6 ++- 7 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 internal/apps/admin/push/custom_events/register.go delete mode 100644 internal/listener/.gitkeep create mode 100644 internal/listener/admin_login.go diff --git a/internal/apps/admin/push/custom_events/admin_login.go b/internal/apps/admin/push/custom_events/admin_login.go index cc115065..dab5811d 100644 --- a/internal/apps/admin/push/custom_events/admin_login.go +++ b/internal/apps/admin/push/custom_events/admin_login.go @@ -9,7 +9,7 @@ import ( "time" "github.com/Rain-kl/Wavelet/internal/apps/admin/push" - "github.com/Rain-kl/Wavelet/internal/model" + "github.com/Rain-kl/Wavelet/internal/listener" ) // AdminLogin is the metadata definition for the admin login event. @@ -24,20 +24,15 @@ var AdminLogin = push.EventMetadata{ Description: "当管理员成功登录系统时触发此通知", } -func init() { - push.RegisterBuiltInEvent(AdminLogin) -} - -// TriggerAdminLoginEvent triggers the admin login event asynchronously. -func TriggerAdminLoginEvent(ctx context.Context, user *model.User, ip string) { - if user == nil || !user.IsAdmin { +func handleAdminLogin(ctx context.Context, event listener.AdminLoggedIn) { + if event.User == nil { return } body := map[string]any{ - "user": user, - "ip": ip, + "user": event.User, + "ip": event.IP, "time": time.Now().Format("2006-01-02 15:04:05"), } push.DefaultTrigger.Trigger(ctx, AdminLogin, body) -} +} \ No newline at end of file diff --git a/internal/apps/admin/push/custom_events/register.go b/internal/apps/admin/push/custom_events/register.go new file mode 100644 index 00000000..e26bb4bc --- /dev/null +++ b/internal/apps/admin/push/custom_events/register.go @@ -0,0 +1,17 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +package custom_events + +import ( + "github.com/Rain-kl/Wavelet/internal/apps/admin/push" + "github.com/Rain-kl/Wavelet/internal/listener" +) + +// Register wires push notification handlers for domain events and registers +// built-in event metadata. Must be called once during application bootstrap +// before push.SyncEvents. +func Register() { + push.RegisterBuiltInEvent(AdminLogin) + listener.OnAdminLoggedIn(handleAdminLogin) +} \ No newline at end of file diff --git a/internal/apps/oauth/sources.go b/internal/apps/oauth/sources.go index 3194c0c8..d1caa0bb 100644 --- a/internal/apps/oauth/sources.go +++ b/internal/apps/oauth/sources.go @@ -14,11 +14,11 @@ import ( "strings" "time" - "github.com/Rain-kl/Wavelet/internal/apps/admin/push/custom_events" "github.com/Rain-kl/Wavelet/internal/common" "github.com/Rain-kl/Wavelet/internal/common/response" "github.com/Rain-kl/Wavelet/internal/config" "github.com/Rain-kl/Wavelet/internal/db" + "github.com/Rain-kl/Wavelet/internal/listener" "github.com/Rain-kl/Wavelet/internal/model" "github.com/Rain-kl/Wavelet/pkg/logger" "github.com/coreos/go-oidc/v3/oidc" @@ -678,7 +678,7 @@ func handleCallbackLogin(ctx context.Context, c *gin.Context, source *model.Auth logger.InfoF(ctx, "[LoginAudit] successful OAuth login via source: %s, external ID: %s, user: %s, ID: %d, IP: %s", source.Name, userInfo.Sub, user.Username, user.ID, c.ClientIP()) - custom_events.TriggerAdminLoginEvent(ctx, &user, c.ClientIP()) + listener.EmitAdminLoggedIn(ctx, &user, c.ClientIP()) c.JSON(http.StatusOK, response.OK(buildCallbackResult(&user, "logged_in"))) } diff --git a/internal/apps/user/routers.go b/internal/apps/user/routers.go index fad35df2..60efc1ac 100644 --- a/internal/apps/user/routers.go +++ b/internal/apps/user/routers.go @@ -8,12 +8,12 @@ import ("context" "strings" "time" - "github.com/Rain-kl/Wavelet/internal/apps/admin/push/custom_events" "github.com/Rain-kl/Wavelet/internal/apps/oauth" "github.com/Rain-kl/Wavelet/internal/common" "github.com/Rain-kl/Wavelet/internal/config" "github.com/Rain-kl/Wavelet/internal/db" "github.com/Rain-kl/Wavelet/internal/db/idgen" + "github.com/Rain-kl/Wavelet/internal/listener" "github.com/Rain-kl/Wavelet/internal/model" "github.com/Rain-kl/Wavelet/internal/util" "github.com/Rain-kl/Wavelet/pkg/logger" @@ -172,7 +172,7 @@ func Login(c *gin.Context) { logger.InfoF(ctx, "[LoginAudit] successful login for user: %s, ID: %d, IP: %s", user.Username, user.ID, c.ClientIP()) - custom_events.TriggerAdminLoginEvent(ctx, &user, c.ClientIP()) + listener.EmitAdminLoggedIn(ctx, &user, c.ClientIP()) c.JSON(http.StatusOK, response.OK(oauth.BuildBasicUserInfo(&user, needChangePassword))) } diff --git a/internal/listener/.gitkeep b/internal/listener/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/internal/listener/admin_login.go b/internal/listener/admin_login.go new file mode 100644 index 00000000..365e9ceb --- /dev/null +++ b/internal/listener/admin_login.go @@ -0,0 +1,42 @@ +// Copyright 2026 Arctel.net +// SPDX-License-Identifier: Apache-2.0 + +// Package listener provides domain event dispatch for cross-module integration. +// Core domains emit events here; operational modules (push, webhooks, etc.) +// subscribe at the application composition root. +package listener + +import ( + "context" + + "github.com/Rain-kl/Wavelet/internal/model" +) + +// AdminLoggedIn is emitted when an administrator successfully authenticates. +type AdminLoggedIn struct { + User *model.User + IP string +} + +// AdminLoggedInHandler handles administrator login domain events. +type AdminLoggedInHandler func(ctx context.Context, event AdminLoggedIn) + +var adminLoggedInHandlers []AdminLoggedInHandler + +// OnAdminLoggedIn registers a handler for administrator login events. +// Handlers must be registered during application bootstrap before serving traffic. +func OnAdminLoggedIn(handler AdminLoggedInHandler) { + adminLoggedInHandlers = append(adminLoggedInHandlers, handler) +} + +// EmitAdminLoggedIn dispatches an administrator login event to all registered handlers. +func EmitAdminLoggedIn(ctx context.Context, user *model.User, ip string) { + if user == nil || !user.IsAdmin { + return + } + + event := AdminLoggedIn{User: user, IP: ip} + for _, handler := range adminLoggedInHandlers { + handler(ctx, event) + } +} \ No newline at end of file diff --git a/internal/router/router.go b/internal/router/router.go index b059c4a7..415de15f 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -16,12 +16,11 @@ import ( "time" admin_push "github.com/Rain-kl/Wavelet/internal/apps/admin/push" + "github.com/Rain-kl/Wavelet/internal/apps/admin/push/custom_events" "github.com/Rain-kl/Wavelet/internal/apps/risk_control" router_root "github.com/Rain-kl/Wavelet/internal/router/root" v1 "github.com/Rain-kl/Wavelet/internal/router/v1" - // Swagger 文档生成 - _ "github.com/Rain-kl/Wavelet/internal/apps/admin/push/custom_events" "github.com/Rain-kl/Wavelet/internal/apps/oauth" "github.com/Rain-kl/Wavelet/internal/config" otel_trace "github.com/Rain-kl/Wavelet/pkg/trace" @@ -41,6 +40,9 @@ func Serve() { // 初始化 ClickHouse 异步日志写入器 risk_control.InitLogWriter() + // 装配推送模块与领域事件的集成(组合根显式注册,避免 init 副作用) + custom_events.Register() + // 运行内置事件同步 if err := admin_push.SyncEvents(context.Background()); err != nil { log.Printf("[API] sync push events failed: %v\n", err)