feat: subscribe to RabbitMQ trade signals and notify by rules

Consume configurable queues, format signals (including period), share NotifyService with HTTP, and drop duplicate bodies within 1h.
This commit is contained in:
2026-08-15 17:34:49 +08:00
parent 1f4fe2fb75
commit 6f846a0a3c
25 changed files with 3129 additions and 114 deletions
+31 -7
View File
@@ -2,6 +2,7 @@ package main
import (
"context"
"errors"
"fmt"
"log/slog"
"net/http"
@@ -15,8 +16,11 @@ import (
"aiaa-notification-service/internal/config"
"aiaa-notification-service/internal/engine"
"aiaa-notification-service/internal/handler"
"aiaa-notification-service/internal/model"
"aiaa-notification-service/internal/notify"
"aiaa-notification-service/internal/safew"
"aiaa-notification-service/internal/store"
"aiaa-notification-service/internal/subscriber"
"github.com/gin-gonic/gin"
"github.com/logbull/logbull-go/logbull"
@@ -123,7 +127,8 @@ func main() {
router := engine.NewRouter(st, redisCache, senderFactory)
// Build handlers
notifyH := handler.NewNotifyHandler(st, redisCache, matcher, renderer, router)
notifySvc := notify.NewService(matcher, st, renderer, router, st)
notifyH := handler.NewNotifyHandler(notifySvc)
sourceH := handler.NewSourceHandler(st, redisCache)
templateH := handler.NewTemplateHandler(st, redisCache)
@@ -217,6 +222,28 @@ func main() {
Handler: r,
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
lookup := func(ctx context.Context, name string) (*model.Source, error) {
return st.GetSourceByName(ctx, name)
}
deduper := subscriber.NewCacheDeduper(redisCache, cfg.SubscriptionDedupTTL)
for _, sub := range cfg.ActiveSubscriptions() {
sub := sub
cons, err := subscriber.New(sub, lookup, notifySvc.Process, deduper)
if err != nil {
slog.Error("subscriber init", "name", sub.Name, "error", err)
os.Exit(1)
}
go func() {
if err := cons.Run(ctx); err != nil && !errors.Is(err, context.Canceled) {
slog.Error("subscriber stopped", "name", sub.Name, "error", err)
}
}()
slog.Info("subscriber started", "name", sub.Name, "queue", sub.Queue, "source", sub.Source)
}
go func() {
slog.Info("server starting", "port", cfg.Server.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
@@ -225,15 +252,12 @@ func main() {
}
}()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
<-ctx.Done()
slog.Info("shutting down...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
if err := srv.Shutdown(shutdownCtx); err != nil {
slog.Error("forced shutdown", "error", err)
}
slog.Info("server stopped")