chore: sync local updates

This commit is contained in:
2026-05-16 19:03:32 +08:00
parent 360a5f15b9
commit aff88ba97d
47 changed files with 3651 additions and 12175 deletions

View File

@@ -1,176 +1,93 @@
package config
import (
"encoding/json"
"fmt"
"os"
"sync"
"time"
)
// Config 应用配置
type Config struct {
Server ServerConfig `json:"server"`
Monitor MonitorConfig `json:"monitor"`
DataPath string `json:"data_path"`
}
// ServerConfig 服务器配置
type ServerConfig struct {
Port string `json:"port"`
Host string `json:"host"`
}
// MonitorConfig 监控配置
type MonitorConfig struct {
Interval time.Duration `json:"interval"` // 检测间隔
Timeout time.Duration `json:"timeout"` // 请求超时时间
RetryCount int `json:"retry_count"` // 重试次数
HistoryDays int `json:"history_days"` // 保留历史天数
}
var (
cfg *Config
once sync.Once
)
// GetConfig 获取配置单例
func GetConfig() *Config {
once.Do(func() {
cfg = &Config{
Server: ServerConfig{
Port: getEnv("SERVER_PORT", "8080"),
Host: getEnv("SERVER_HOST", "0.0.0.0"),
},
Monitor: MonitorConfig{
Interval: parseDuration(getEnv("MONITOR_INTERVAL", "1h"), 1*time.Hour),
Timeout: parseDuration(getEnv("MONITOR_TIMEOUT", "10s"), 10*time.Second),
RetryCount: parseInt(getEnv("MONITOR_RETRY_COUNT", "3"), 3),
HistoryDays: parseInt(getEnv("MONITOR_HISTORY_DAYS", "90"), 90),
},
DataPath: getEnv("DATA_PATH", "./data"),
}
// 尝试从配置文件加载(会覆盖环境变量配置)
loadConfigFromFile()
})
return cfg
}
// getEnv 获取环境变量,如果不存在则返回默认值
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// parseInt 解析整数环境变量
func parseInt(value string, defaultValue int) int {
if value == "" {
return defaultValue
}
var result int
if _, err := fmt.Sscanf(value, "%d", &result); err != nil {
return defaultValue
}
return result
}
// parseDuration 解析时间间隔环境变量
func parseDuration(value string, defaultValue time.Duration) time.Duration {
if value == "" {
return defaultValue
}
if duration, err := time.ParseDuration(value); err == nil {
return duration
}
return defaultValue
}
// loadConfigFromFile 从文件加载配置
func loadConfigFromFile() {
configFile := "./data/config.json"
if _, err := os.Stat(configFile); os.IsNotExist(err) {
return
}
data, err := os.ReadFile(configFile)
if err != nil {
return
}
var fileCfg struct {
Server ServerConfig `json:"server"`
Monitor struct {
IntervalMinutes int `json:"interval_minutes"`
TimeoutSeconds int `json:"timeout_seconds"`
RetryCount int `json:"retry_count"`
HistoryDays int `json:"history_days"`
} `json:"monitor"`
DataPath string `json:"data_path"`
}
if err := json.Unmarshal(data, &fileCfg); err != nil {
return
}
if fileCfg.Server.Port != "" {
cfg.Server.Port = fileCfg.Server.Port
}
if fileCfg.Server.Host != "" {
cfg.Server.Host = fileCfg.Server.Host
}
if fileCfg.Monitor.IntervalMinutes > 0 {
cfg.Monitor.Interval = time.Duration(fileCfg.Monitor.IntervalMinutes) * time.Minute
}
if fileCfg.Monitor.TimeoutSeconds > 0 {
cfg.Monitor.Timeout = time.Duration(fileCfg.Monitor.TimeoutSeconds) * time.Second
}
if fileCfg.Monitor.RetryCount > 0 {
cfg.Monitor.RetryCount = fileCfg.Monitor.RetryCount
}
if fileCfg.Monitor.HistoryDays > 0 {
cfg.Monitor.HistoryDays = fileCfg.Monitor.HistoryDays
}
if fileCfg.DataPath != "" {
cfg.DataPath = fileCfg.DataPath
}
}
// SaveConfig 保存配置到文件
func SaveConfig() error {
configFile := cfg.DataPath + "/config.json"
fileCfg := struct {
Server ServerConfig `json:"server"`
Monitor struct {
IntervalMinutes int `json:"interval_minutes"`
TimeoutSeconds int `json:"timeout_seconds"`
RetryCount int `json:"retry_count"`
HistoryDays int `json:"history_days"`
} `json:"monitor"`
DataPath string `json:"data_path"`
}{
Server: cfg.Server,
Monitor: struct {
IntervalMinutes int `json:"interval_minutes"`
TimeoutSeconds int `json:"timeout_seconds"`
RetryCount int `json:"retry_count"`
HistoryDays int `json:"history_days"`
}{
IntervalMinutes: int(cfg.Monitor.Interval.Minutes()),
TimeoutSeconds: int(cfg.Monitor.Timeout.Seconds()),
RetryCount: cfg.Monitor.RetryCount,
HistoryDays: cfg.Monitor.HistoryDays,
},
DataPath: cfg.DataPath,
}
data, err := json.MarshalIndent(fileCfg, "", " ")
if err != nil {
return err
}
return os.WriteFile(configFile, data, 0644)
}
package config
import (
"fmt"
"os"
"sync"
"time"
)
// Config 应用配置(持久化在 MySQL monitor_kv首次启动前由环境变量提供默认值MySQL 连接信息首次连接仍依赖环境变量/DB_DSN
type Config struct {
Server ServerConfig `json:"server"`
Monitor MonitorConfig `json:"monitor"`
DataPath string `json:"data_path"`
Database DatabaseConfig `json:"database"`
}
// ServerConfig 服务器配置
type ServerConfig struct {
Port string `json:"port"`
Host string `json:"host"`
}
// MonitorConfig 监控配置
type MonitorConfig struct {
Interval time.Duration `json:"interval"` // 检测间隔
Timeout time.Duration `json:"timeout"` // 请求超时时间
RetryCount int `json:"retry_count"` // 重试次数
HistoryDays int `json:"history_days"` // 保留历史天数
}
var (
cfg *Config
once sync.Once
)
// GetConfig 获取配置单例(连接 MySQL 后由 storage 根据 monitor_kv 覆盖)
func GetConfig() *Config {
once.Do(func() {
cfg = &Config{
Server: ServerConfig{
Port: getEnv("SERVER_PORT", "8080"),
Host: getEnv("SERVER_HOST", "0.0.0.0"),
},
Monitor: MonitorConfig{
Interval: parseDuration(getEnv("MONITOR_INTERVAL", "30m"), 30*time.Minute),
Timeout: parseDuration(getEnv("MONITOR_TIMEOUT", "10s"), 10*time.Second),
RetryCount: parseInt(getEnv("MONITOR_RETRY_COUNT", "3"), 3),
HistoryDays: parseInt(getEnv("MONITOR_HISTORY_DAYS", "90"), 90),
},
DataPath: getEnv("DATA_PATH", "./data"),
Database: DatabaseConfig{
Host: getEnv("DB_HOST", "192.168.1.100"),
Port: getEnv("DB_PORT", "3306"),
User: getEnv("DB_USER", "mengyaping"),
Password: getEnv("DB_PASSWORD", "mengyaping"),
Database: getEnv("DB_NAME", "mengyaping"),
},
}
})
return cfg
}
// getEnv 获取环境变量,如果不存在则返回默认值
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
// parseInt 解析整数环境变量
func parseInt(value string, defaultValue int) int {
if value == "" {
return defaultValue
}
var result int
if _, err := fmt.Sscanf(value, "%d", &result); err != nil {
return defaultValue
}
return result
}
// parseDuration 解析时间间隔环境变量
func parseDuration(value string, defaultValue time.Duration) time.Duration {
if value == "" {
return defaultValue
}
if duration, err := time.ParseDuration(value); err == nil {
return duration
}
return defaultValue
}

View File

@@ -0,0 +1,25 @@
package config
import (
"fmt"
"os"
)
// DatabaseConfig MySQL 连接(环境变量 DB_* 可覆盖;亦支持 DB_DSN 整串)
type DatabaseConfig struct {
Host string `json:"host"`
Port string `json:"port"`
User string `json:"user"`
Password string `json:"password"`
Database string `json:"database"`
}
// DatabaseDSN 返回 GORM/MySQL driver 用连接串
func (c *Config) DatabaseDSN() string {
if dsn := os.Getenv("DB_DSN"); dsn != "" {
return dsn
}
d := c.Database
return fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
d.User, d.Password, d.Host, d.Port, d.Database)
}

View File

@@ -0,0 +1,38 @@
package config
import "slices"
// AllowedMonitorIntervalMinutes 后台预设的全局检测周期(分钟)
var AllowedMonitorIntervalMinutes = []int{10, 20, 30, 60, 180, 360}
// IsAllowedMonitorInterval 是否为允许的检测间隔(分钟)
func IsAllowedMonitorInterval(minutes int) bool {
return minutes > 0 && slices.Contains(AllowedMonitorIntervalMinutes, minutes)
}
// SnapMonitorIntervalMinutes 将分钟数裁剪到最近的预设值(用于读取旧配置)
func SnapMonitorIntervalMinutes(minutes int) int {
if minutes <= 0 {
return 30
}
if IsAllowedMonitorInterval(minutes) {
return minutes
}
best := AllowedMonitorIntervalMinutes[0]
bestDist := absInt(minutes - best)
for _, v := range AllowedMonitorIntervalMinutes[1:] {
d := absInt(minutes - v)
if d < bestDist {
bestDist = d
best = v
}
}
return best
}
func absInt(x int) int {
if x < 0 {
return -x
}
return x
}

View File

@@ -0,0 +1,24 @@
package config
import "testing"
func TestIsAllowedMonitorInterval(t *testing.T) {
if !IsAllowedMonitorInterval(10) || !IsAllowedMonitorInterval(360) {
t.Fatal("expected 10 and 360 allowed")
}
if IsAllowedMonitorInterval(5) || IsAllowedMonitorInterval(45) {
t.Fatal("expected 5 and 45 disallowed")
}
}
func TestSnapMonitorIntervalMinutes(t *testing.T) {
if SnapMonitorIntervalMinutes(5) != 10 {
t.Fatalf("5 -> 10, got %d", SnapMonitorIntervalMinutes(5))
}
if SnapMonitorIntervalMinutes(7) != 10 {
t.Fatalf("7 -> 10, got %d", SnapMonitorIntervalMinutes(7))
}
if SnapMonitorIntervalMinutes(25) != 20 {
t.Fatalf("25 -> 20, got %d", SnapMonitorIntervalMinutes(25))
}
}