Add monitoring service and database schema

- Implement monitoring service with heartbeat, lease tasks, resume tasks, and task result handling.
- Create monitoring time utilities for business date calculations.
- Add unit tests for date window resolution and business day handling.
- Define database schema for monitoring-related tables including quotas, daily reports, and task management.
- Establish migration scripts for creating and dropping monitoring tables.
This commit is contained in:
2026-04-12 09:56:18 +08:00
parent 9b4dd09780
commit 6066f43a7d
67 changed files with 16312 additions and 125 deletions
+78 -11
View File
@@ -10,17 +10,19 @@ import (
)
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
Qdrant QdrantConfig `mapstructure:"qdrant"`
ObjectStorage ObjectStorageConfig `mapstructure:"object_storage"`
Cache CacheConfig `mapstructure:"cache"`
JWT JWTConfig `mapstructure:"jwt"`
Log LogConfig `mapstructure:"log"`
LLM LLMConfig `mapstructure:"llm"`
Retrieval RetrievalConfig `mapstructure:"retrieval"`
Generation GenerationConfig `mapstructure:"generation"`
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
MonitoringDatabase DatabaseConfig `mapstructure:"monitoring_database"`
RabbitMQ RabbitMQConfig `mapstructure:"rabbitmq"`
Redis RedisConfig `mapstructure:"redis"`
Qdrant QdrantConfig `mapstructure:"qdrant"`
ObjectStorage ObjectStorageConfig `mapstructure:"object_storage"`
Cache CacheConfig `mapstructure:"cache"`
JWT JWTConfig `mapstructure:"jwt"`
Log LogConfig `mapstructure:"log"`
LLM LLMConfig `mapstructure:"llm"`
Retrieval RetrievalConfig `mapstructure:"retrieval"`
Generation GenerationConfig `mapstructure:"generation"`
}
type ServerConfig struct {
@@ -49,6 +51,23 @@ type RedisConfig struct {
DB int `mapstructure:"db"`
}
type RabbitMQConfig struct {
URL string `mapstructure:"url"`
MonitorResultExchange string `mapstructure:"monitor_result_exchange"`
MonitorResultRoutingKey string `mapstructure:"monitor_result_routing_key"`
MonitorResultQueue string `mapstructure:"monitor_result_queue"`
MonitorResultDLX string `mapstructure:"monitor_result_dlx"`
MonitorResultDLQ string `mapstructure:"monitor_result_dlq"`
MonitorResultDLQRouteKey string `mapstructure:"monitor_result_dlq_routing_key"`
MonitorProjectionExchange string `mapstructure:"monitor_projection_exchange"`
MonitorProjectionRoutingKey string `mapstructure:"monitor_projection_routing_key"`
MonitorProjectionQueue string `mapstructure:"monitor_projection_queue"`
MonitorProjectionDLX string `mapstructure:"monitor_projection_dlx"`
MonitorProjectionDLQ string `mapstructure:"monitor_projection_dlq"`
MonitorProjectionDLQRouteKey string `mapstructure:"monitor_projection_dlq_routing_key"`
ConsumerPrefetch int `mapstructure:"consumer_prefetch"`
}
type QdrantConfig struct {
URL string `mapstructure:"url"`
APIKey string `mapstructure:"api_key"`
@@ -139,6 +158,7 @@ func Load(configPath string) (*Config, error) {
}
applyEnvOverrides(&cfg)
normalizeRabbitMQConfig(&cfg.RabbitMQ)
return &cfg, nil
}
@@ -157,6 +177,9 @@ func applyEnvOverrides(cfg *Config) {
if apiKey, ok := lookupNonEmptyEnv("QDRANT_API_KEY"); ok {
cfg.Qdrant.APIKey = apiKey
}
if rabbitmqURL, ok := lookupNonEmptyEnv("RABBITMQ_URL"); ok {
cfg.RabbitMQ.URL = rabbitmqURL
}
if url, ok := lookupNonEmptyEnv("QDRANT_URL"); ok {
cfg.Qdrant.URL = url
}
@@ -201,6 +224,50 @@ func applyEnvOverrides(cfg *Config) {
}
}
func normalizeRabbitMQConfig(cfg *RabbitMQConfig) {
if cfg == nil {
return
}
if strings.TrimSpace(cfg.MonitorResultExchange) == "" {
cfg.MonitorResultExchange = "monitor.result"
}
if strings.TrimSpace(cfg.MonitorResultRoutingKey) == "" {
cfg.MonitorResultRoutingKey = "monitor.result.ingest"
}
if strings.TrimSpace(cfg.MonitorResultQueue) == "" {
cfg.MonitorResultQueue = "monitor.result.ingest"
}
if strings.TrimSpace(cfg.MonitorResultDLX) == "" {
cfg.MonitorResultDLX = "monitor.result.dlx"
}
if strings.TrimSpace(cfg.MonitorResultDLQ) == "" {
cfg.MonitorResultDLQ = "monitor.result.ingest.dlq"
}
if strings.TrimSpace(cfg.MonitorResultDLQRouteKey) == "" {
cfg.MonitorResultDLQRouteKey = "monitor.result.ingest.dlq"
}
if strings.TrimSpace(cfg.MonitorProjectionExchange) == "" {
cfg.MonitorProjectionExchange = "monitor.projection"
}
if strings.TrimSpace(cfg.MonitorProjectionRoutingKey) == "" {
cfg.MonitorProjectionRoutingKey = "monitor.projection.rebuild"
}
if strings.TrimSpace(cfg.MonitorProjectionQueue) == "" {
cfg.MonitorProjectionQueue = "monitor.projection.rebuild"
}
if strings.TrimSpace(cfg.MonitorProjectionDLX) == "" {
cfg.MonitorProjectionDLX = "monitor.projection.dlx"
}
if strings.TrimSpace(cfg.MonitorProjectionDLQ) == "" {
cfg.MonitorProjectionDLQ = "monitor.projection.rebuild.dlq"
}
if strings.TrimSpace(cfg.MonitorProjectionDLQRouteKey) == "" {
cfg.MonitorProjectionDLQRouteKey = "monitor.projection.rebuild.dlq"
}
}
func lookupFirstNonEmptyEnv(keys ...string) (string, bool) {
for _, key := range keys {
if value, ok := lookupNonEmptyEnv(key); ok {