mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-08-08 16:38:01 +08:00
RAGFlow go API server (#13240)
# RAGFlow Go Implementation Plan 🚀 This repository tracks the progress of porting RAGFlow to Go. We'll implement core features and provide performance comparisons between Python and Go versions. ## Implementation Checklist - [x] User Management APIs - [x] Dataset Management Operations - [x] Retrieval Test - [x] Chat Management Operations - [x] Infinity Go SDK --------- Signed-off-by: Jin Hai <haijin.chn@gmail.com> Co-authored-by: Yingfeng Zhang <yingfeng.zhang@gmail.com>
This commit is contained in:
294
internal/server/config.go
Normal file
294
internal/server/config.go
Normal file
@@ -0,0 +1,294 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// DefaultConnectTimeout default connection timeout for external services
|
||||
const DefaultConnectTimeout = 5 * time.Second
|
||||
|
||||
// Config application configuration
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
Log LogConfig `mapstructure:"log"`
|
||||
DocEngine DocEngineConfig `mapstructure:"doc_engine"`
|
||||
RegisterEnabled int `mapstructure:"register_enabled"`
|
||||
OAuth map[string]OAuthConfig `mapstructure:"oauth"`
|
||||
}
|
||||
|
||||
// OAuthConfig OAuth configuration for a channel
|
||||
type OAuthConfig struct {
|
||||
DisplayName string `mapstructure:"display_name"`
|
||||
Icon string `mapstructure:"icon"`
|
||||
}
|
||||
|
||||
// ServerConfig server configuration
|
||||
type ServerConfig struct {
|
||||
Mode string `mapstructure:"mode"` // debug, release
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
// DatabaseConfig database configuration
|
||||
type DatabaseConfig struct {
|
||||
Driver string `mapstructure:"driver"` // mysql
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Database string `mapstructure:"database"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
Charset string `mapstructure:"charset"`
|
||||
}
|
||||
|
||||
// LogConfig logging configuration
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"` // debug, info, warn, error
|
||||
Format string `mapstructure:"format"` // json, text
|
||||
}
|
||||
|
||||
// DocEngineConfig document engine configuration
|
||||
type DocEngineConfig struct {
|
||||
Type EngineType `mapstructure:"type"`
|
||||
ES *ElasticsearchConfig `mapstructure:"es"`
|
||||
Infinity *InfinityConfig `mapstructure:"infinity"`
|
||||
}
|
||||
|
||||
// EngineType document engine type
|
||||
type EngineType string
|
||||
|
||||
const (
|
||||
EngineElasticsearch EngineType = "elasticsearch"
|
||||
EngineInfinity EngineType = "infinity"
|
||||
)
|
||||
|
||||
// ElasticsearchConfig Elasticsearch configuration
|
||||
type ElasticsearchConfig struct {
|
||||
Hosts string `mapstructure:"hosts"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
}
|
||||
|
||||
// InfinityConfig Infinity configuration
|
||||
type InfinityConfig struct {
|
||||
URI string `mapstructure:"uri"`
|
||||
PostgresPort int `mapstructure:"postgres_port"`
|
||||
DBName string `mapstructure:"db_name"`
|
||||
}
|
||||
|
||||
// RedisConfig Redis configuration
|
||||
type RedisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
var (
|
||||
globalConfig *Config
|
||||
globalViper *viper.Viper
|
||||
zapLogger *zap.Logger
|
||||
)
|
||||
|
||||
// Init initialize configuration
|
||||
func Init(configPath string) error {
|
||||
v := viper.New()
|
||||
|
||||
// Set configuration file path
|
||||
if configPath != "" {
|
||||
v.SetConfigFile(configPath)
|
||||
} else {
|
||||
// Try to load service_conf.yaml from conf directory first
|
||||
v.SetConfigName("service_conf")
|
||||
v.SetConfigType("yaml")
|
||||
v.AddConfigPath("./conf")
|
||||
v.AddConfigPath(".")
|
||||
v.AddConfigPath("./config")
|
||||
v.AddConfigPath("./internal/config")
|
||||
v.AddConfigPath("/etc/ragflow/")
|
||||
}
|
||||
|
||||
// Read environment variables
|
||||
v.SetEnvPrefix("RAGFLOW")
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.AutomaticEnv()
|
||||
|
||||
// Read configuration file
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return fmt.Errorf("read config file error: %w", err)
|
||||
}
|
||||
zapLogger.Info("Config file not found, using environment variables only")
|
||||
}
|
||||
|
||||
// Save viper instance
|
||||
globalViper = v
|
||||
|
||||
// Unmarshal configuration to globalConfig
|
||||
// Note: This will only unmarshal fields that match the Config struct
|
||||
if err := v.Unmarshal(&globalConfig); err != nil {
|
||||
return fmt.Errorf("unmarshal config error: %w", err)
|
||||
}
|
||||
|
||||
// Load REGISTER_ENABLED from environment variable (default: 1)
|
||||
registerEnabled := 1
|
||||
if envVal := os.Getenv("REGISTER_ENABLED"); envVal != "" {
|
||||
if parsed, err := strconv.Atoi(envVal); err == nil {
|
||||
registerEnabled = parsed
|
||||
}
|
||||
}
|
||||
globalConfig.RegisterEnabled = registerEnabled
|
||||
|
||||
// If we loaded service_conf.yaml, map mysql fields to DatabaseConfig
|
||||
if globalConfig != nil && globalConfig.Database.Host == "" {
|
||||
// Try to map from mysql section
|
||||
if v.IsSet("mysql") {
|
||||
mysqlConfig := v.Sub("mysql")
|
||||
if mysqlConfig != nil {
|
||||
globalConfig.Database.Driver = "mysql"
|
||||
globalConfig.Database.Host = mysqlConfig.GetString("host")
|
||||
globalConfig.Database.Port = mysqlConfig.GetInt("port")
|
||||
globalConfig.Database.Database = mysqlConfig.GetString("name")
|
||||
globalConfig.Database.Username = mysqlConfig.GetString("user")
|
||||
globalConfig.Database.Password = mysqlConfig.GetString("password")
|
||||
globalConfig.Database.Charset = "utf8mb4"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map ragflow section to ServerConfig
|
||||
if globalConfig != nil && globalConfig.Server.Port == 0 {
|
||||
// Try to map from ragflow section
|
||||
if v.IsSet("ragflow") {
|
||||
ragflowConfig := v.Sub("ragflow")
|
||||
if ragflowConfig != nil {
|
||||
globalConfig.Server.Port = ragflowConfig.GetInt("http_port") + 2 // 9382, by default
|
||||
// globalConfig.Server.Port = ragflowConfig.GetInt("http_port") // Correct
|
||||
// If mode is not set, default to debug
|
||||
if globalConfig.Server.Mode == "" {
|
||||
globalConfig.Server.Mode = "release"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map redis section to RedisConfig
|
||||
if globalConfig != nil && globalConfig.Redis.Host != "" {
|
||||
if v.IsSet("redis") {
|
||||
redisConfig := v.Sub("redis")
|
||||
if redisConfig != nil {
|
||||
hostStr := redisConfig.GetString("host")
|
||||
// Handle host:port format (e.g., "localhost:6379")
|
||||
if hostStr == "" {
|
||||
return fmt.Errorf("Empty host of redis configuration")
|
||||
}
|
||||
|
||||
if idx := strings.LastIndex(hostStr, ":"); idx != -1 {
|
||||
globalConfig.Redis.Host = hostStr[:idx]
|
||||
if portStr := hostStr[idx+1:]; portStr != "" {
|
||||
if port, err := strconv.Atoi(portStr); err == nil {
|
||||
globalConfig.Redis.Port = port
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("Error address format of redis: %s", hostStr)
|
||||
}
|
||||
|
||||
globalConfig.Redis.Password = redisConfig.GetString("password")
|
||||
globalConfig.Redis.DB = redisConfig.GetInt("db")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Map doc_engine section to DocEngineConfig
|
||||
if globalConfig != nil && globalConfig.DocEngine.Type == "" {
|
||||
// Try to map from doc_engine section
|
||||
if v.IsSet("doc_engine") {
|
||||
docEngineConfig := v.Sub("doc_engine")
|
||||
if docEngineConfig != nil {
|
||||
globalConfig.DocEngine.Type = EngineType(docEngineConfig.GetString("type"))
|
||||
}
|
||||
}
|
||||
// Also check legacy es section for backward compatibility
|
||||
if v.IsSet("es") {
|
||||
esConfig := v.Sub("es")
|
||||
if esConfig != nil {
|
||||
if globalConfig.DocEngine.Type == "" {
|
||||
globalConfig.DocEngine.Type = EngineElasticsearch
|
||||
}
|
||||
if globalConfig.DocEngine.ES == nil {
|
||||
globalConfig.DocEngine.ES = &ElasticsearchConfig{
|
||||
Hosts: esConfig.GetString("hosts"),
|
||||
Username: esConfig.GetString("username"),
|
||||
Password: esConfig.GetString("password"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.IsSet("infinity") {
|
||||
infConfig := v.Sub("infinity")
|
||||
if infConfig != nil {
|
||||
if globalConfig.DocEngine.Type == "" {
|
||||
globalConfig.DocEngine.Type = EngineInfinity
|
||||
}
|
||||
if globalConfig.DocEngine.Infinity == nil {
|
||||
globalConfig.DocEngine.Infinity = &InfinityConfig{
|
||||
URI: infConfig.GetString("uri"),
|
||||
PostgresPort: infConfig.GetInt("postgres_port"),
|
||||
DBName: infConfig.GetString("db_name"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get get global configuration
|
||||
func GetConfig() *Config {
|
||||
return globalConfig
|
||||
}
|
||||
|
||||
// SetLogger sets the logger instance
|
||||
func SetLogger(l *zap.Logger) {
|
||||
zapLogger = l
|
||||
}
|
||||
|
||||
// PrintAll prints all configuration settings
|
||||
func PrintAll() {
|
||||
if globalViper == nil {
|
||||
zapLogger.Info("Configuration not initialized")
|
||||
return
|
||||
}
|
||||
|
||||
allSettings := globalViper.AllSettings()
|
||||
zapLogger.Info("=== All Configuration Settings ===")
|
||||
for key, value := range allSettings {
|
||||
zapLogger.Info("config", zap.String("key", key), zap.Any("value", value))
|
||||
}
|
||||
zapLogger.Info("=== End Configuration ===")
|
||||
}
|
||||
116
internal/server/model_provider.go
Normal file
116
internal/server/model_provider.go
Normal file
@@ -0,0 +1,116 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ModelProvider represents a model provider configuration
|
||||
type ModelProvider struct {
|
||||
Name string `json:"name"`
|
||||
Logo string `json:"logo"`
|
||||
Tags string `json:"tags"`
|
||||
Status string `json:"status"`
|
||||
Rank string `json:"rank"`
|
||||
LLMs []LLM `json:"llm"`
|
||||
DefaultEmbeddingURL string `json:"default_embedding_url,omitempty"`
|
||||
}
|
||||
|
||||
// LLM represents a language model within a provider
|
||||
type LLM struct {
|
||||
LLMName string `json:"llm_name"`
|
||||
Tags string `json:"tags"`
|
||||
MaxTokens int `json:"max_tokens"`
|
||||
ModelType string `json:"model_type"`
|
||||
IsTools bool `json:"is_tools"`
|
||||
}
|
||||
|
||||
var (
|
||||
modelProviders []ModelProvider
|
||||
modelProviderMap map[string]int // name -> index in modelProviders slice
|
||||
modelProvidersOnce sync.Once
|
||||
modelProvidersErr error
|
||||
)
|
||||
|
||||
// LoadModelProviders loads model providers from JSON file.
|
||||
// If path is empty, it defaults to "conf/model_providers.json" relative to current working directory.
|
||||
func LoadModelProviders(path string) error {
|
||||
modelProvidersOnce.Do(func() {
|
||||
if path == "" {
|
||||
path = "conf/llm_factories.json"
|
||||
//path = "conf/model_providers.json"
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
modelProvidersErr = fmt.Errorf("failed to read model providers file %s: %w", path, err)
|
||||
return
|
||||
}
|
||||
|
||||
var root struct {
|
||||
Providers []ModelProvider `json:"factory_llm_infos"`
|
||||
}
|
||||
if err := json.Unmarshal(data, &root); err != nil {
|
||||
modelProvidersErr = fmt.Errorf("failed to unmarshal model providers JSON: %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
modelProviders = root.Providers
|
||||
// Build name to index map for fast lookup
|
||||
modelProviderMap = make(map[string]int, len(modelProviders))
|
||||
for i, provider := range modelProviders {
|
||||
modelProviderMap[provider.Name] = i
|
||||
}
|
||||
})
|
||||
|
||||
return modelProvidersErr
|
||||
}
|
||||
|
||||
// GetModelProviders returns the loaded model providers.
|
||||
// Call LoadModelProviders first, otherwise returns empty slice.
|
||||
func GetModelProviders() []ModelProvider {
|
||||
return modelProviders
|
||||
}
|
||||
|
||||
// GetModelProviderByName returns the model provider with the given name.
|
||||
func GetModelProviderByName(name string) *ModelProvider {
|
||||
if modelProviderMap == nil {
|
||||
return nil
|
||||
}
|
||||
if idx, ok := modelProviderMap[name]; ok {
|
||||
return &modelProviders[idx]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLLMByProviderAndName returns the LLM with the given provider name and model name.
|
||||
func GetLLMByProviderAndName(providerName, modelName string) *LLM {
|
||||
provider := GetModelProviderByName(providerName)
|
||||
if provider == nil {
|
||||
return nil
|
||||
}
|
||||
for i := range provider.LLMs {
|
||||
if provider.LLMs[i].LLMName == modelName {
|
||||
return &provider.LLMs[i]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
259
internal/server/variable.go
Normal file
259
internal/server/variable.go
Normal file
@@ -0,0 +1,259 @@
|
||||
//
|
||||
// Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"ragflow/internal/utility"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"ragflow/internal/logger"
|
||||
)
|
||||
|
||||
// Variables holds all runtime variables that can be changed during system operation
|
||||
// Unlike Config, these can be modified at runtime
|
||||
type Variables struct {
|
||||
SecretKey string `json:"secret_key"`
|
||||
}
|
||||
|
||||
// VariableStore interface for persistent storage (e.g., Redis)
|
||||
type VariableStore interface {
|
||||
Get(key string) (string, error)
|
||||
Set(key string, value string, exp time.Duration) bool
|
||||
SetNX(key string, value string, exp time.Duration) bool
|
||||
}
|
||||
|
||||
var (
|
||||
globalVariables *Variables
|
||||
variablesOnce sync.Once
|
||||
variablesMu sync.RWMutex
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultSecretKey is used when no secret key is found in storage
|
||||
DefaultSecretKey = "infiniflow-token"
|
||||
// SecretKeyRedisKey is the Redis key for storing secret key
|
||||
SecretKeyRedisKey = "ragflow:system:secret_key"
|
||||
// SecretKeyTTL is the TTL for secret key in Redis (0 = no expiration)
|
||||
SecretKeyTTL = 0
|
||||
)
|
||||
|
||||
// InitVariables initializes all runtime variables from persistent storage
|
||||
// This should be called after Config and Cache are initialized
|
||||
func InitVariables(store VariableStore) error {
|
||||
var initErr error
|
||||
variablesOnce.Do(func() {
|
||||
globalVariables = &Variables{}
|
||||
|
||||
generatedKey, err := utility.GenerateSecretKey()
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("failed to generate secret key: %w", err)
|
||||
}
|
||||
|
||||
// Initialize SecretKey
|
||||
secretKey, err := GetOrCreateKey(store, SecretKeyRedisKey, generatedKey)
|
||||
if err != nil {
|
||||
initErr = fmt.Errorf("failed to initialize secret key: %w", err)
|
||||
} else {
|
||||
globalVariables.SecretKey = secretKey
|
||||
logger.Info("Secret key initialized from store")
|
||||
}
|
||||
|
||||
logger.Info("Server variables initialized successfully")
|
||||
})
|
||||
return initErr
|
||||
}
|
||||
|
||||
// GetVariables returns the global variables instance
|
||||
func GetVariables() *Variables {
|
||||
variablesMu.RLock()
|
||||
defer variablesMu.RUnlock()
|
||||
return globalVariables
|
||||
}
|
||||
|
||||
// GetSecretKey returns the current secret key
|
||||
func GetSecretKey() string {
|
||||
variablesMu.RLock()
|
||||
defer variablesMu.RUnlock()
|
||||
if globalVariables == nil {
|
||||
return DefaultSecretKey
|
||||
}
|
||||
return globalVariables.SecretKey
|
||||
}
|
||||
|
||||
// SetSecretKey updates the secret key at runtime
|
||||
func SetSecretKey(key string) {
|
||||
variablesMu.Lock()
|
||||
defer variablesMu.Unlock()
|
||||
if globalVariables != nil {
|
||||
globalVariables.SecretKey = key
|
||||
logger.Info("Secret key updated at runtime")
|
||||
}
|
||||
}
|
||||
|
||||
// GetOrCreateKey gets a key from store, or creates it if not exists
|
||||
// - If key exists in store, returns the stored value
|
||||
// - If key doesn't exist, calls createFn to generate value, stores it, and returns it
|
||||
// - Uses SetNX to ensure atomic creation (only one caller succeeds when key doesn't exist)
|
||||
func GetOrCreateKey(store VariableStore, key string, newValue string) (string, error) {
|
||||
if store == nil {
|
||||
err := fmt.Errorf("store is nil")
|
||||
logger.Warn("VariableStore is nil, cannot get or create key", zap.String("key", key))
|
||||
return "store is nil", err
|
||||
}
|
||||
|
||||
// Try to get existing value
|
||||
value, err := store.Get(key)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to get key from store", zap.String("key", key), zap.Error(err))
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Key exists, return the value
|
||||
if value != "" {
|
||||
logger.Debug("Key found in store", zap.String("key", key))
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// Key doesn't exist, generate new value
|
||||
logger.Info("Generating new value for key", zap.String("key", key))
|
||||
|
||||
// Try to set with NX (only if not exists) - ensures atomicity
|
||||
if store.SetNX(key, newValue, SecretKeyTTL) {
|
||||
logger.Info("New value stored successfully", zap.String("key", key))
|
||||
return newValue, nil
|
||||
}
|
||||
|
||||
// Another process might have set it, try to get again
|
||||
value, err = store.Get(key)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to get key after SetNX", zap.String("key", key), zap.Error(err))
|
||||
return newValue, nil // Return our generated value as fallback
|
||||
}
|
||||
|
||||
if value != "" {
|
||||
logger.Info("Using value set by another process", zap.String("key", key))
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// If still empty, use our generated value
|
||||
return newValue, nil
|
||||
}
|
||||
|
||||
// RefreshVariables refreshes all variables from storage
|
||||
// Call this when you want to reload variables from persistent storage
|
||||
func RefreshVariables(store VariableStore) error {
|
||||
if store == nil {
|
||||
return fmt.Errorf("store is nil")
|
||||
}
|
||||
|
||||
variablesMu.Lock()
|
||||
defer variablesMu.Unlock()
|
||||
|
||||
if globalVariables == nil {
|
||||
globalVariables = &Variables{}
|
||||
}
|
||||
|
||||
// Refresh SecretKey
|
||||
secretKey, err := store.Get(SecretKeyRedisKey)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to refresh secret key from store", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
if secretKey != "" {
|
||||
globalVariables.SecretKey = secretKey
|
||||
logger.Info("Secret key refreshed from store")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// VariableWatcher watches for variable changes in storage
|
||||
// This can be used to detect changes made by other instances
|
||||
type VariableWatcher struct {
|
||||
store VariableStore
|
||||
stopChan chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewVariableWatcher creates a new variable watcher
|
||||
func NewVariableWatcher(store VariableStore) *VariableWatcher {
|
||||
return &VariableWatcher{
|
||||
store: store,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts watching for variable changes
|
||||
func (w *VariableWatcher) Start(interval time.Duration) {
|
||||
w.wg.Add(1)
|
||||
go func() {
|
||||
defer w.wg.Done()
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := RefreshVariables(w.store); err != nil {
|
||||
logger.Debug("Failed to refresh variables", zap.Error(err))
|
||||
}
|
||||
case <-w.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
logger.Info("Variable watcher started", zap.Duration("interval", interval))
|
||||
}
|
||||
|
||||
// Stop stops the variable watcher
|
||||
func (w *VariableWatcher) Stop() {
|
||||
close(w.stopChan)
|
||||
w.wg.Wait()
|
||||
logger.Info("Variable watcher stopped")
|
||||
}
|
||||
|
||||
// SaveToStorage saves current variables to persistent storage
|
||||
func SaveToStorage(store VariableStore) error {
|
||||
if store == nil {
|
||||
return fmt.Errorf("store is nil")
|
||||
}
|
||||
|
||||
variablesMu.RLock()
|
||||
defer variablesMu.RUnlock()
|
||||
|
||||
if globalVariables == nil {
|
||||
return fmt.Errorf("variables not initialized")
|
||||
}
|
||||
|
||||
// Save SecretKey
|
||||
if !store.Set(SecretKeyRedisKey, globalVariables.SecretKey, SecretKeyTTL) {
|
||||
return fmt.Errorf("failed to save secret key to store")
|
||||
}
|
||||
|
||||
logger.Info("Variables saved to storage")
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithTimeout creates a context with timeout for variable operations
|
||||
func WithTimeout(timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), timeout)
|
||||
}
|
||||
Reference in New Issue
Block a user