You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
478 lines
11 KiB
478 lines
11 KiB
package service
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sync"
|
|
"time"
|
|
|
|
"market-data-service/api"
|
|
"market-data-service/pkg/config"
|
|
)
|
|
|
|
// ConfigService 配置管理服务接口
|
|
type ConfigService interface {
|
|
// GetConfigList 获取配置列表
|
|
GetConfigList(ctx context.Context, req *api.ConfigListRequest) (*api.ConfigListData, error)
|
|
|
|
// UpdateConfig 更新配置
|
|
UpdateConfig(ctx context.Context, req *api.ConfigUpdateRequest) (*api.ConfigUpdateData, error)
|
|
|
|
// ReloadConfig 热加载配置
|
|
ReloadConfig(ctx context.Context, req *api.ReloadRequest) (*api.ReloadData, error)
|
|
|
|
// GetSystemStatus 获取系统状态
|
|
GetSystemStatus(ctx context.Context) (*api.SystemStatusData, error)
|
|
|
|
// GetCurrentConfig 获取当前配置(内部使用)
|
|
GetCurrentConfig() *config.Config
|
|
}
|
|
|
|
// ConfigServiceImpl 配置服务实现
|
|
type ConfigServiceImpl struct {
|
|
configPath string
|
|
config *config.Config
|
|
mu sync.RWMutex
|
|
|
|
// 配置变更回调
|
|
callbacks map[api.ConfigType][]func()
|
|
cbMu sync.RWMutex
|
|
|
|
// 启动时间
|
|
startTime time.Time
|
|
|
|
// 配置版本
|
|
version string
|
|
}
|
|
|
|
// NewConfigService 创建配置服务
|
|
func NewConfigService(configPath string) (ConfigService, error) {
|
|
cfg, err := config.Load(configPath)
|
|
if err != nil {
|
|
// 如果加载失败,使用默认配置
|
|
cfg = getDefaultConfig()
|
|
}
|
|
|
|
return &ConfigServiceImpl{
|
|
configPath: configPath,
|
|
config: cfg,
|
|
callbacks: make(map[api.ConfigType][]func()),
|
|
startTime: time.Now(),
|
|
version: "1.0.0",
|
|
}, nil
|
|
}
|
|
|
|
// GetConfigList 获取配置列表
|
|
func (s *ConfigServiceImpl) GetConfigList(ctx context.Context, req *api.ConfigListRequest) (*api.ConfigListData, error) {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
|
|
sections := []api.ConfigSection{}
|
|
|
|
// 服务器配置
|
|
if req.Type == "" || req.Type == api.ConfigTypeServer {
|
|
sections = append(sections, api.ConfigSection{
|
|
Name: "服务器配置",
|
|
Type: api.ConfigTypeServer,
|
|
Description: "HTTP服务器相关配置",
|
|
Items: []api.ConfigItem{
|
|
{
|
|
Key: "port",
|
|
Value: s.config.Server.Port,
|
|
Type: "int",
|
|
Description: "服务端口",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
{
|
|
Key: "mode",
|
|
Value: s.config.Server.Mode,
|
|
Type: "string",
|
|
Description: "运行模式: debug/release",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
{
|
|
Key: "api_key",
|
|
Value: s.config.Server.APIKey,
|
|
Type: "string",
|
|
Description: "API认证密钥",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// 数据库配置
|
|
if req.Type == "" || req.Type == api.ConfigTypeDatabase {
|
|
sections = append(sections, api.ConfigSection{
|
|
Name: "数据库配置",
|
|
Type: api.ConfigTypeDatabase,
|
|
Description: "PostgreSQL数据库连接配置",
|
|
Items: []api.ConfigItem{
|
|
{
|
|
Key: "host",
|
|
Value: s.config.Database.Host,
|
|
Type: "string",
|
|
Description: "数据库主机地址",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
{
|
|
Key: "port",
|
|
Value: s.config.Database.Port,
|
|
Type: "int",
|
|
Description: "数据库端口",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
{
|
|
Key: "user",
|
|
Value: s.config.Database.User,
|
|
Type: "string",
|
|
Description: "数据库用户名",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
{
|
|
Key: "password",
|
|
Value: "********",
|
|
Type: "password",
|
|
Description: "数据库密码",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
{
|
|
Key: "database",
|
|
Value: s.config.Database.Database,
|
|
Type: "string",
|
|
Description: "数据库名",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// Redis配置
|
|
if req.Type == "" || req.Type == api.ConfigTypeRedis {
|
|
sections = append(sections, api.ConfigSection{
|
|
Name: "Redis配置",
|
|
Type: api.ConfigTypeRedis,
|
|
Description: "Redis缓存配置",
|
|
Items: []api.ConfigItem{
|
|
{
|
|
Key: "host",
|
|
Value: s.config.Redis.Host,
|
|
Type: "string",
|
|
Description: "Redis主机地址",
|
|
Editable: true,
|
|
Required: false,
|
|
},
|
|
{
|
|
Key: "port",
|
|
Value: s.config.Redis.Port,
|
|
Type: "int",
|
|
Description: "Redis端口",
|
|
Editable: true,
|
|
Required: false,
|
|
},
|
|
{
|
|
Key: "password",
|
|
Value: "********",
|
|
Type: "password",
|
|
Description: "Redis密码",
|
|
Editable: true,
|
|
Required: false,
|
|
},
|
|
{
|
|
Key: "db",
|
|
Value: s.config.Redis.DB,
|
|
Type: "int",
|
|
Description: "Redis数据库编号",
|
|
Editable: true,
|
|
Required: false,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// 数据源配置
|
|
if req.Type == "" || req.Type == api.ConfigTypeSource {
|
|
sections = append(sections, api.ConfigSection{
|
|
Name: "数据源配置",
|
|
Type: api.ConfigTypeSource,
|
|
Description: "股票和期货数据源配置",
|
|
Items: []api.ConfigItem{
|
|
{
|
|
Key: "stock_active",
|
|
Value: s.config.Sources.Stock.Active,
|
|
Type: "string",
|
|
Description: "股票数据源适配器",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
{
|
|
Key: "futures_active",
|
|
Value: s.config.Sources.Futures.Active,
|
|
Type: "string",
|
|
Description: "期货数据源适配器",
|
|
Editable: true,
|
|
Required: true,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
return &api.ConfigListData{
|
|
Sections: sections,
|
|
Version: s.version,
|
|
Updated: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// UpdateConfig 更新配置
|
|
func (s *ConfigServiceImpl) UpdateConfig(ctx context.Context, req *api.ConfigUpdateRequest) (*api.ConfigUpdateData, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
needRestart := false
|
|
|
|
switch req.Type {
|
|
case api.ConfigTypeServer:
|
|
if port, ok := req.Items["port"]; ok {
|
|
s.config.Server.Port = int(port.(float64))
|
|
needRestart = true
|
|
}
|
|
if mode, ok := req.Items["mode"]; ok {
|
|
s.config.Server.Mode = mode.(string)
|
|
}
|
|
if apiKey, ok := req.Items["api_key"]; ok {
|
|
s.config.Server.APIKey = apiKey.(string)
|
|
}
|
|
|
|
case api.ConfigTypeDatabase:
|
|
if host, ok := req.Items["host"]; ok {
|
|
s.config.Database.Host = host.(string)
|
|
needRestart = true
|
|
}
|
|
if port, ok := req.Items["port"]; ok {
|
|
s.config.Database.Port = int(port.(float64))
|
|
needRestart = true
|
|
}
|
|
if user, ok := req.Items["user"]; ok {
|
|
s.config.Database.User = user.(string)
|
|
needRestart = true
|
|
}
|
|
if password, ok := req.Items["password"]; ok && password.(string) != "********" {
|
|
s.config.Database.Password = password.(string)
|
|
needRestart = true
|
|
}
|
|
if database, ok := req.Items["database"]; ok {
|
|
s.config.Database.Database = database.(string)
|
|
needRestart = true
|
|
}
|
|
|
|
case api.ConfigTypeSource:
|
|
if stockActive, ok := req.Items["stock_active"]; ok {
|
|
s.config.Sources.Stock.Active = stockActive.(string)
|
|
}
|
|
if futuresActive, ok := req.Items["futures_active"]; ok {
|
|
s.config.Sources.Futures.Active = futuresActive.(string)
|
|
}
|
|
}
|
|
|
|
// 保存到文件
|
|
if err := s.saveConfig(); err != nil {
|
|
return &api.ConfigUpdateData{
|
|
Success: false,
|
|
Message: fmt.Sprintf("配置保存失败: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
// 触发回调
|
|
s.triggerCallbacks(req.Type)
|
|
|
|
message := "配置更新成功"
|
|
if needRestart {
|
|
message += ",部分配置需要重启服务后生效"
|
|
}
|
|
|
|
return &api.ConfigUpdateData{
|
|
Success: true,
|
|
NeedRestart: needRestart,
|
|
Message: message,
|
|
}, nil
|
|
}
|
|
|
|
// ReloadConfig 热加载配置
|
|
func (s *ConfigServiceImpl) ReloadConfig(ctx context.Context, req *api.ReloadRequest) (*api.ReloadData, error) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// 从文件重新加载
|
|
cfg, err := config.Load(s.configPath)
|
|
if err != nil {
|
|
return &api.ReloadData{
|
|
Success: false,
|
|
Message: fmt.Sprintf("加载配置失败: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
// 根据类型选择性更新
|
|
if req.ConfigType == "" {
|
|
s.config = cfg
|
|
} else {
|
|
switch req.ConfigType {
|
|
case api.ConfigTypeServer:
|
|
s.config.Server = cfg.Server
|
|
case api.ConfigTypeDatabase:
|
|
s.config.Database = cfg.Database
|
|
case api.ConfigTypeRedis:
|
|
s.config.Redis = cfg.Redis
|
|
case api.ConfigTypeSource:
|
|
s.config.Sources = cfg.Sources
|
|
}
|
|
}
|
|
|
|
// 触发回调
|
|
s.triggerCallbacks(req.ConfigType)
|
|
|
|
return &api.ReloadData{
|
|
Success: true,
|
|
Message: "配置热加载成功",
|
|
}, nil
|
|
}
|
|
|
|
// GetSystemStatus 获取系统状态
|
|
func (s *ConfigServiceImpl) GetSystemStatus(ctx context.Context) (*api.SystemStatusData, error) {
|
|
var m runtime.MemStats
|
|
runtime.ReadMemStats(&m)
|
|
|
|
uptime := time.Since(s.startTime)
|
|
uptimeStr := formatDuration(uptime)
|
|
|
|
return &api.SystemStatusData{
|
|
Status: "running",
|
|
Version: s.version,
|
|
StartTime: s.startTime,
|
|
Uptime: uptimeStr,
|
|
GoVersion: runtime.Version(),
|
|
MemoryUsage: api.MemoryInfo{
|
|
Alloc: m.Alloc,
|
|
TotalAlloc: m.TotalAlloc,
|
|
Sys: m.Sys,
|
|
NumGC: m.NumGC,
|
|
},
|
|
Goroutines: runtime.NumGoroutine(),
|
|
}, nil
|
|
}
|
|
|
|
// GetCurrentConfig 获取当前配置
|
|
func (s *ConfigServiceImpl) GetCurrentConfig() *config.Config {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.config
|
|
}
|
|
|
|
// RegisterCallback 注册配置变更回调
|
|
func (s *ConfigServiceImpl) RegisterCallback(configType api.ConfigType, callback func()) {
|
|
s.cbMu.Lock()
|
|
defer s.cbMu.Unlock()
|
|
|
|
s.callbacks[configType] = append(s.callbacks[configType], callback)
|
|
}
|
|
|
|
// triggerCallbacks 触发回调
|
|
func (s *ConfigServiceImpl) triggerCallbacks(configType api.ConfigType) {
|
|
s.cbMu.RLock()
|
|
defer s.cbMu.RUnlock()
|
|
|
|
// 触发特定类型的回调
|
|
if cbs, ok := s.callbacks[configType]; ok {
|
|
for _, cb := range cbs {
|
|
go cb()
|
|
}
|
|
}
|
|
|
|
// 触发通用回调
|
|
if cbs, ok := s.callbacks[""]; ok {
|
|
for _, cb := range cbs {
|
|
go cb()
|
|
}
|
|
}
|
|
}
|
|
|
|
// saveConfig 保存配置到文件
|
|
func (s *ConfigServiceImpl) saveConfig() error {
|
|
if s.configPath == "" {
|
|
return nil
|
|
}
|
|
|
|
// 确保目录存在
|
|
dir := filepath.Dir(s.configPath)
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
// 序列化为JSON
|
|
data, err := json.MarshalIndent(s.config, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(s.configPath, data, 0644)
|
|
}
|
|
|
|
// getDefaultConfig 获取默认配置
|
|
func getDefaultConfig() *config.Config {
|
|
return &config.Config{
|
|
Server: config.ServerConfig{
|
|
Port: 8080,
|
|
Mode: "debug",
|
|
APIKey: "default-api-key",
|
|
},
|
|
Database: config.DatabaseConfig{
|
|
Host: "localhost",
|
|
Port: 5432,
|
|
User: "user",
|
|
Password: "password",
|
|
Database: "marketdata",
|
|
},
|
|
Redis: config.RedisConfig{
|
|
Host: "localhost",
|
|
Port: 6379,
|
|
Password: "",
|
|
DB: 0,
|
|
},
|
|
Sources: config.SourcesConfig{
|
|
Stock: config.SourceConfig{
|
|
Active: "tushare",
|
|
},
|
|
Futures: config.SourceConfig{
|
|
Active: "tushare",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// formatDuration 格式化持续时间
|
|
func formatDuration(d time.Duration) string {
|
|
days := int(d.Hours()) / 24
|
|
hours := int(d.Hours()) % 24
|
|
minutes := int(d.Minutes()) % 60
|
|
|
|
if days > 0 {
|
|
return fmt.Sprintf("%d天%d小时%d分钟", days, hours, minutes)
|
|
}
|
|
if hours > 0 {
|
|
return fmt.Sprintf("%d小时%d分钟", hours, minutes)
|
|
}
|
|
return fmt.Sprintf("%d分钟", minutes)
|
|
}
|