优化数据库,日志,命令模块
This commit is contained in:
@@ -3,7 +3,6 @@ package initcmd
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sunhpc/internal/middler/auth"
|
"sunhpc/internal/middler/auth"
|
||||||
"sunhpc/pkg/config"
|
|
||||||
"sunhpc/pkg/database"
|
"sunhpc/pkg/database"
|
||||||
"sunhpc/pkg/logger"
|
"sunhpc/pkg/logger"
|
||||||
|
|
||||||
@@ -28,26 +27,29 @@ func NewInitDBCmd() *cobra.Command {
|
|||||||
|
|
||||||
logger.Debug("执行数据库初始化...")
|
logger.Debug("执行数据库初始化...")
|
||||||
|
|
||||||
cfg, err := config.LoadConfig()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("加载配置失败: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化数据库
|
// 初始化数据库
|
||||||
db, err := database.GetInstance(&cfg.Database, nil)
|
db, err := database.GetDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("数据库连接失败: %w", err)
|
return fmt.Errorf("数据库连接失败: %w", err)
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
if err := db.InitTables(force); err != nil {
|
if err := database.InitTables(db, force); err != nil {
|
||||||
return fmt.Errorf("数据库初始化失败: %w", err)
|
return fmt.Errorf("数据库初始化失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 测试数据库连接
|
||||||
|
if err := database.TestNodeInsert(db); err != nil {
|
||||||
|
return fmt.Errorf("数据库测试失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Flags().BoolVarP(&force, "force", "f", false, "强制重新初始化")
|
cmd.Flags().BoolVarP(
|
||||||
|
&force, "force", "f", false,
|
||||||
|
"强制重新初始化")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
initcmd "sunhpc/internal/cli/init"
|
initcmd "sunhpc/internal/cli/init"
|
||||||
"sunhpc/pkg/config"
|
"sunhpc/pkg/config"
|
||||||
"sunhpc/pkg/logger"
|
"sunhpc/pkg/logger"
|
||||||
@@ -20,21 +23,49 @@ func NewRootCmd() *cobra.Command {
|
|||||||
Use: "sunhpc",
|
Use: "sunhpc",
|
||||||
Short: "SunHPC - HPC集群一体化运维工具",
|
Short: "SunHPC - HPC集群一体化运维工具",
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
// 加载全局配置(只加载一次)
|
// 设置 CLI 参数
|
||||||
|
config.CLIParams.Verbose = verbose
|
||||||
|
config.CLIParams.NoColor = noColor
|
||||||
|
config.CLIParams.Config = cfgFile
|
||||||
|
|
||||||
|
// 初始化配置目录和默认文件
|
||||||
|
if err := config.InitConfigs(); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "初始化配置目录失败: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载配置(后续调用直接返回缓存)
|
||||||
cfg, err := config.LoadConfig()
|
cfg, err := config.LoadConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 配置加载失败,使用默认日志配置初始化
|
fmt.Fprintf(os.Stderr, "加载配置失败: %v\n", err)
|
||||||
logger.Warnf("加载配置失败,使用默认日志配置: %v", err)
|
os.Exit(1)
|
||||||
logger.Init(logger.LogConfig{})
|
}
|
||||||
|
|
||||||
|
// 初始化日志
|
||||||
|
logger.Init(cfg.Log)
|
||||||
|
|
||||||
|
// 记录启动信息
|
||||||
|
logger.Debugf("SunHPC 启动中...")
|
||||||
|
logger.Debugf("配置文件: %s", viper.ConfigFileUsed())
|
||||||
|
logger.Debugf("日志级别: %s", cfg.Log.Level)
|
||||||
|
logger.Debugf("日志格式: %s", cfg.Log.Format)
|
||||||
|
logger.Debugf("日志输出: %s", cfg.Log.Output)
|
||||||
|
logger.Debugf("日志文件: %s", cfg.Log.LogFile)
|
||||||
|
logger.Debugf("显示颜色: %v", cfg.Log.ShowColor)
|
||||||
|
|
||||||
|
// 跳过数据库检查,仅在 init db 时检查
|
||||||
|
if isInitDbCommand(cmd) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 初始化全局日志(全局只执行一次)
|
// 检查数据库文件是否存在
|
||||||
logger.Init(logger.LogConfig{
|
dbPath := filepath.Join(cfg.Database.Path, cfg.Database.Name)
|
||||||
Verbose: cfg.Log.Verbose,
|
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
|
||||||
ShowColor: !cfg.Log.ShowColor,
|
logger.Warnf("数据库文件不存在: %s", dbPath)
|
||||||
LogFile: cfg.Log.LogFile,
|
logger.Errorf("请先运行 sunhpc init db 初始化数据库")
|
||||||
})
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
logger.Debugf("数据库文件存在: %s", dbPath)
|
||||||
},
|
},
|
||||||
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
@@ -42,23 +73,17 @@ func NewRootCmd() *cobra.Command {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.PersistentFlags().StringVarP(
|
cmd.PersistentFlags().BoolVar(
|
||||||
&config.CLIParams.Config,
|
&verbose, "verbose", false,
|
||||||
"config", "c",
|
"启用详细日志输出")
|
||||||
"", "配置文件路径 (默认:/etc/sunhpc/config.yaml)")
|
|
||||||
|
|
||||||
cmd.PersistentFlags().BoolVarP(
|
|
||||||
&config.CLIParams.Verbose,
|
|
||||||
"verbose", "v", false, "启用详细日志输出")
|
|
||||||
|
|
||||||
cmd.PersistentFlags().BoolVar(
|
cmd.PersistentFlags().BoolVar(
|
||||||
&config.CLIParams.NoColor,
|
&noColor, "no-color", false,
|
||||||
"no-color", false, "禁用彩色输出")
|
"禁用彩色输出")
|
||||||
|
|
||||||
// 如果指定了 --config 参数,优先使用该配置文件
|
cmd.PersistentFlags().StringVar(
|
||||||
if config.CLIParams.Config != "" {
|
&cfgFile, "config", "",
|
||||||
viper.SetConfigFile(config.CLIParams.Config)
|
"配置文件路径 (默认:/etc/sunhpc/config.yaml)")
|
||||||
}
|
|
||||||
|
|
||||||
cmd.AddCommand(initcmd.NewInitCmd())
|
cmd.AddCommand(initcmd.NewInitCmd())
|
||||||
return cmd
|
return cmd
|
||||||
@@ -67,3 +92,14 @@ func NewRootCmd() *cobra.Command {
|
|||||||
func Execute() error {
|
func Execute() error {
|
||||||
return NewRootCmd().Execute()
|
return NewRootCmd().Execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isInitDbCommand(cmd *cobra.Command) bool {
|
||||||
|
// 检查当前命令是否是 db 子命令
|
||||||
|
if cmd.Name() == "db" {
|
||||||
|
// 检查父命令是否是 init
|
||||||
|
if parent := cmd.Parent(); parent != nil {
|
||||||
|
return parent.Name() == "init"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,70 +4,145 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sunhpc/pkg/logger"
|
||||||
|
"sunhpc/pkg/utils"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.yaml.in/yaml/v3"
|
"go.yaml.in/yaml/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ==============================================================
|
||||||
|
// 全局变量
|
||||||
|
// ==============================================================
|
||||||
|
var (
|
||||||
|
GlobalConfig *Config
|
||||||
|
configOnce sync.Once // 确保配置只加载一次
|
||||||
|
configMutex sync.RWMutex // 读写锁保护 GlobalConfig
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==============================================================
|
||||||
|
// 目录常量 (可在 main.go 中通过 flag 覆盖默认值)
|
||||||
|
// ==============================================================
|
||||||
|
var (
|
||||||
|
BaseDir string = utils.DefaultBaseDir
|
||||||
|
TmplDir string = utils.DefaultTmplDir
|
||||||
|
LogDir string = utils.DefaultLogDir
|
||||||
|
)
|
||||||
|
|
||||||
|
// ==============================================================
|
||||||
|
// 配置结构体
|
||||||
|
// ==============================================================
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Database DatabaseConfig `yaml:"database"`
|
Log logger.LogConfig `mapstructure:"log" yaml:"log"`
|
||||||
Log LogConfig `yaml:"log"`
|
Database DatabaseConfig `mapstructure:"database" yaml:"database"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
type DatabaseConfig struct {
|
||||||
DSN string `yaml:"dsn"` // 数据库连接字符串
|
DSN string `mapstructure:"dsn" yaml:"dsn"` // 数据库连接字符串
|
||||||
Path string `yaml:"path"` // SQLite: 目录路径
|
Path string `mapstructure:"path" yaml:"path"` // SQLite: 目录路径
|
||||||
Name string `yaml:"name"` // SQLite: 文件名
|
Name string `mapstructure:"name" yaml:"name"` // SQLite: 文件名
|
||||||
|
Args string `mapstructure:"args" yaml:"args"` // 数据库连接参数
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogConfig struct {
|
type CLIParamsType struct {
|
||||||
Level string `yaml:"level"`
|
Verbose bool // -v/--verbose
|
||||||
Format string `yaml:"format"`
|
NoColor bool // --no-color
|
||||||
Output string `yaml:"output"`
|
Config string // -c/--config
|
||||||
Verbose bool `yaml:"verbose"`
|
|
||||||
LogFile string `yaml:"log_file"`
|
|
||||||
ShowColor bool `yaml:"show_color"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------- 全局单例配置(核心) ---------------------------------
|
var CLIParams CLIParamsType // 命令行参数
|
||||||
var (
|
|
||||||
// GlobalConfig 全局配置单例实例
|
// InitConfigs 初始化所有配置目录等数据
|
||||||
GlobalConfig *Config
|
func InitConfigs() error {
|
||||||
// 命令行参数配置(全局、由root命令绑定)
|
dirs := []string{
|
||||||
CLIParams = struct {
|
BaseDir,
|
||||||
Verbose bool // -v/--verbose
|
TmplDir,
|
||||||
NoColor bool // --no-color
|
LogDir,
|
||||||
Config string // -c/--config
|
}
|
||||||
}{}
|
for _, d := range dirs {
|
||||||
BaseDir string = "/etc/sunhpc"
|
if err := os.MkdirAll(d, 0755); err != nil {
|
||||||
LogDir string = "/var/log/sunhpc"
|
return fmt.Errorf("创建目录 %s 失败: %w", d, err)
|
||||||
TmplDir string = BaseDir + "/tmpl.d"
|
}
|
||||||
appName string = "sunhpc"
|
}
|
||||||
defaultDBPath string = "/var/lib/sunhpc"
|
|
||||||
defaultDBName string = "sunhpc.db"
|
// 检查配置文件是否存在,不存在则创建默认配置
|
||||||
)
|
configPath := filepath.Join(BaseDir, "sunhpc.yaml")
|
||||||
|
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||||
|
if err := createDefaultConfig(configPath); err != nil {
|
||||||
|
return fmt.Errorf("创建默认配置文件失败: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDefaultConfig(configPath string) error {
|
||||||
|
defaultConfig := &Config{
|
||||||
|
Database: DatabaseConfig{
|
||||||
|
Path: utils.DefaultDBPath,
|
||||||
|
Name: utils.DefaultDBName,
|
||||||
|
Args: utils.DefaultDBArgs,
|
||||||
|
},
|
||||||
|
Log: logger.LogConfig{
|
||||||
|
Level: "info",
|
||||||
|
Format: "text",
|
||||||
|
Output: "stdout",
|
||||||
|
Verbose: false,
|
||||||
|
LogFile: utils.DefaultLogFile,
|
||||||
|
ShowColor: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保数据库目录存在
|
||||||
|
if err := os.MkdirAll(defaultConfig.Database.Path, 0755); err != nil {
|
||||||
|
return fmt.Errorf("创建数据库目录失败: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 序列号并写入
|
||||||
|
data, err := yaml.Marshal(defaultConfig)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("序列化配置失败: %w", err)
|
||||||
|
}
|
||||||
|
return os.WriteFile(configPath, data, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------- 配置加载(只加载一次) -----------------------------------
|
// ----------------------------------- 配置加载(只加载一次) -----------------------------------
|
||||||
func LoadConfig() (*Config, error) {
|
func LoadConfig() (*Config, error) {
|
||||||
// 如果已经加载过,直接返回
|
configMutex.RLock()
|
||||||
if GlobalConfig != nil {
|
if GlobalConfig != nil {
|
||||||
|
// 如果已经加载过,直接返回
|
||||||
|
configMutex.RUnlock()
|
||||||
|
return GlobalConfig, nil
|
||||||
|
}
|
||||||
|
configMutex.RUnlock()
|
||||||
|
configMutex.Lock()
|
||||||
|
defer configMutex.Unlock()
|
||||||
|
|
||||||
|
// 双重检查
|
||||||
|
if GlobalConfig != nil {
|
||||||
|
// 如果已经加载过,直接返回
|
||||||
return GlobalConfig, nil
|
return GlobalConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
viper.SetConfigName("sunhpc")
|
// 配置文件路径
|
||||||
viper.SetConfigType("yaml")
|
if CLIParams.Config != "" {
|
||||||
viper.AddConfigPath(BaseDir)
|
viper.SetConfigFile(CLIParams.Config)
|
||||||
viper.AddConfigPath(".")
|
} else {
|
||||||
viper.AddConfigPath(filepath.Join(os.Getenv("HOME"), "."))
|
viper.SetConfigName("sunhpc")
|
||||||
|
viper.SetConfigType("yaml")
|
||||||
|
viper.AddConfigPath(BaseDir)
|
||||||
|
viper.AddConfigPath(".")
|
||||||
|
viper.AddConfigPath(filepath.Join(os.Getenv("HOME"), "."))
|
||||||
|
}
|
||||||
|
|
||||||
// Step 1: 设置默认值(最低优先级)
|
// Step 1: 设置默认值(最低优先级)
|
||||||
viper.SetDefault("log.level", "info")
|
viper.SetDefault("log.level", "info")
|
||||||
viper.SetDefault("log.format", "text")
|
viper.SetDefault("log.format", "text")
|
||||||
viper.SetDefault("log.output", "stdout")
|
viper.SetDefault("log.output", "stdout")
|
||||||
viper.SetDefault("log.verbose", false)
|
viper.SetDefault("log.verbose", false)
|
||||||
viper.SetDefault("log.log_file", filepath.Join(LogDir, "sunhpc.log"))
|
viper.SetDefault("log.log_file", utils.DefaultLogFile)
|
||||||
viper.SetDefault("database.name", "sunhpc.db")
|
viper.SetDefault("database.name", utils.DefaultDBName)
|
||||||
viper.SetDefault("database.path", "/var/lib/sunhpc")
|
viper.SetDefault("database.path", utils.DefaultDBPath)
|
||||||
|
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
// 配置文件不存在时,使用默认值
|
// 配置文件不存在时,使用默认值
|
||||||
@@ -87,10 +162,12 @@ func LoadConfig() (*Config, error) {
|
|||||||
viper.Set("log.show_color", false)
|
viper.Set("log.show_color", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := filepath.Join(
|
// 计算派生配置 (如数据库 DSN)
|
||||||
viper.GetString("database.path"), viper.GetString("database.name"))
|
dbPath := viper.GetString("database.path")
|
||||||
dsn := fmt.Sprintf(
|
dbName := viper.GetString("database.name")
|
||||||
"%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000", fullPath)
|
dbArgs := viper.GetString("database.args")
|
||||||
|
fullPath := filepath.Join(dbPath, dbName)
|
||||||
|
dsn := fmt.Sprintf("%s?%s", fullPath, dbArgs)
|
||||||
viper.Set("database.dsn", dsn)
|
viper.Set("database.dsn", dsn)
|
||||||
|
|
||||||
// 解码到结构体
|
// 解码到结构体
|
||||||
@@ -104,57 +181,23 @@ func LoadConfig() (*Config, error) {
|
|||||||
return GlobalConfig, nil
|
return GlobalConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitDirs 创建所有必需目录
|
// ==============================================================
|
||||||
func InitDirs() error {
|
// SaveConfig - 保存全局配置到文件、运行时配置
|
||||||
dirs := []string{
|
// ==============================================================
|
||||||
BaseDir,
|
func SaveConfig() error {
|
||||||
TmplDir,
|
configMutex.RLock()
|
||||||
LogDir,
|
defer configMutex.RUnlock()
|
||||||
}
|
|
||||||
for _, d := range dirs {
|
|
||||||
if err := os.MkdirAll(d, 0755); err != nil {
|
|
||||||
return fmt.Errorf("创建目录 %s 失败: %w", d, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) WriteDefaultConfig(path string) error {
|
if GlobalConfig == nil {
|
||||||
// 确保目录存在
|
return fmt.Errorf("全局配置为空")
|
||||||
dir := filepath.Dir(path)
|
|
||||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
||||||
return fmt.Errorf("创建目录失败: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成默认配置
|
configPath := filepath.Join(BaseDir, "config.yaml")
|
||||||
cfg := DefaultConfig(path)
|
data, err := yaml.Marshal(GlobalConfig)
|
||||||
|
|
||||||
// 序列化为 YAML
|
|
||||||
data, err := yaml.Marshal(cfg)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("序列化配置失败: %w", err)
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
// 写入文件(0644 权限)
|
|
||||||
return os.WriteFile(path, data, 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
func DefaultConfig(path string) *Config {
|
|
||||||
return &Config{
|
|
||||||
Database: DatabaseConfig{
|
|
||||||
DSN: fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000",
|
|
||||||
filepath.Join(filepath.Dir(path), defaultDBName)),
|
|
||||||
Path: filepath.Dir(path),
|
|
||||||
Name: defaultDBName,
|
|
||||||
},
|
|
||||||
Log: LogConfig{
|
|
||||||
Level: "info",
|
|
||||||
Format: "text",
|
|
||||||
Output: "stdout",
|
|
||||||
LogFile: filepath.Join(filepath.Dir(path), "sunhpc.log"),
|
|
||||||
Verbose: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
return os.WriteFile(configPath, data, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetConfig 重置全局配置为默认值
|
// ResetConfig 重置全局配置为默认值
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@@ -16,65 +15,57 @@ import (
|
|||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DB struct {
|
// =========================================================
|
||||||
db *sql.DB
|
// 全局变量
|
||||||
logger logger.Logger
|
// =========================================================
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dbInstance *DB
|
dbInstance *sql.DB
|
||||||
dbOnce sync.Once
|
dbOnce sync.Once
|
||||||
|
dbMutex sync.RWMutex
|
||||||
dbErr error
|
dbErr error
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetInstance(dbConfig *config.DatabaseConfig, log logger.Logger) (*DB, error) {
|
// =========================================================
|
||||||
|
// GetDB - 获取数据库连接(单例模式)
|
||||||
|
// =========================================================
|
||||||
|
func GetDB() (*sql.DB, error) {
|
||||||
dbOnce.Do(func() {
|
dbOnce.Do(func() {
|
||||||
// 兜底: 未注入则使用全局默认日志实例
|
if dbInstance != nil {
|
||||||
if log == nil {
|
|
||||||
log = logger.DefaultLogger
|
|
||||||
}
|
|
||||||
log.Debugf("开始初始化数据库,路径: %s", dbConfig.Path)
|
|
||||||
|
|
||||||
// 确认数据库目录存在
|
|
||||||
if err := os.MkdirAll(dbConfig.Path, 0755); err != nil {
|
|
||||||
log.Errorf("创建数据库目录失败: %v", err)
|
|
||||||
dbErr = err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := filepath.Join(dbConfig.Path, dbConfig.Name)
|
// 确保配置已加载
|
||||||
log.Debugf("数据库路径: %s", fullPath)
|
cfg, err := config.LoadConfig()
|
||||||
|
if err != nil {
|
||||||
|
dbErr = fmt.Errorf("加载配置失败: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// 构建DSN
|
// 构建DSN
|
||||||
dsn := fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000&cache=shared",
|
logger.Debugf("DSN: %s", cfg.Database.DSN)
|
||||||
fullPath)
|
|
||||||
log.Debugf("DSN: %s", dsn)
|
|
||||||
|
|
||||||
// 打开SQLite 连接
|
// 打开SQLite 连接
|
||||||
sqlDB, err := sql.Open("sqlite3", dsn)
|
sqlDB, err := sql.Open("sqlite3", cfg.Database.DSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("数据库打开失败: %v", err)
|
dbErr = fmt.Errorf("数据库打开失败: %w", err)
|
||||||
dbErr = err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置连接池参数
|
// 设置连接池参数
|
||||||
sqlDB.SetMaxOpenConns(1) // SQLite 只支持单连接
|
sqlDB.SetMaxOpenConns(10) // 最大打开连接数
|
||||||
sqlDB.SetMaxIdleConns(1) // 保持一个空闲连接
|
sqlDB.SetMaxIdleConns(5) // 保持空闲连接
|
||||||
sqlDB.SetConnMaxLifetime(0) // 禁用连接生命周期超时
|
sqlDB.SetConnMaxLifetime(0) // 禁用连接生命周期超时
|
||||||
sqlDB.SetConnMaxIdleTime(0) // 禁用空闲连接超时
|
sqlDB.SetConnMaxIdleTime(0) // 禁用空闲连接超时
|
||||||
|
|
||||||
// 测试数据库连接
|
// 测试数据库连接
|
||||||
if err := sqlDB.Ping(); err != nil {
|
if err := sqlDB.Ping(); err != nil {
|
||||||
sqlDB.Close()
|
sqlDB.Close()
|
||||||
log.Errorf("数据库连接失败: %v", err)
|
dbErr = fmt.Errorf("数据库连接失败: %w", err)
|
||||||
dbErr = err
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 赋值 *DB 类型的单例(而非直接复制 *sql.DB)
|
logger.Debug("数据库连接成功")
|
||||||
log.Info("数据库连接成功")
|
dbInstance = sqlDB
|
||||||
dbInstance = &DB{sqlDB, log}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
if dbErr != nil {
|
if dbErr != nil {
|
||||||
@@ -84,21 +75,6 @@ func GetInstance(dbConfig *config.DatabaseConfig, log logger.Logger) (*DB, error
|
|||||||
return dbInstance, nil
|
return dbInstance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close 关闭数据库连接
|
|
||||||
func (d *DB) Close() error {
|
|
||||||
if d.db != nil {
|
|
||||||
d.logger.Debug("关闭数据库连接")
|
|
||||||
err := d.db.Close()
|
|
||||||
if err != nil {
|
|
||||||
d.logger.Errorf("数据库连接关闭失败: %v", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.logger.Info("数据库连接关闭成功")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func confirmAction(prompt string) bool {
|
func confirmAction(prompt string) bool {
|
||||||
reader := bufio.NewReader(os.Stdin)
|
reader := bufio.NewReader(os.Stdin)
|
||||||
|
|
||||||
@@ -112,38 +88,38 @@ func confirmAction(prompt string) bool {
|
|||||||
return response == "y" || response == "yes"
|
return response == "y" || response == "yes"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DB) InitTables(force bool) error {
|
func InitTables(db *sql.DB, force bool) error {
|
||||||
d.logger.Info("开始初始化数据库表...")
|
|
||||||
|
|
||||||
if force {
|
if force {
|
||||||
// 确认是否强制删除
|
// 确认是否强制删除
|
||||||
if !confirmAction("确认强制删除所有表和触发器?") {
|
if !confirmAction("确认强制删除所有表和触发器?") {
|
||||||
d.logger.Info("操作已取消")
|
logger.Info("操作已取消")
|
||||||
|
db.Close()
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 强制删除所有表和触发器
|
// 强制删除所有表和触发器
|
||||||
d.logger.Debug("强制删除所有表和触发器...")
|
logger.Debug("强制删除所有表和触发器...")
|
||||||
if err := dropTables(d.db); err != nil {
|
if err := dropTables(db); err != nil {
|
||||||
return fmt.Errorf("删除表失败: %w", err)
|
return fmt.Errorf("删除表失败: %w", err)
|
||||||
}
|
}
|
||||||
d.logger.Debug("删除所有表和触发器成功")
|
logger.Debug("删除所有表和触发器成功")
|
||||||
|
|
||||||
if err := dropTriggers(d.db); err != nil {
|
if err := dropTriggers(db); err != nil {
|
||||||
return fmt.Errorf("删除触发器失败: %w", err)
|
return fmt.Errorf("删除触发器失败: %w", err)
|
||||||
}
|
}
|
||||||
d.logger.Debug("删除所有触发器成功")
|
logger.Debug("删除所有触发器成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ✅ 调用 schema.go 中的函数
|
// ✅ 调用 schema.go 中的函数
|
||||||
for _, ddl := range CreateTableStatements() {
|
for _, ddl := range CreateTableStatements() {
|
||||||
d.logger.Debugf("执行: %s", ddl)
|
logger.Debugf("执行: %s", ddl)
|
||||||
if _, err := d.db.Exec(ddl); err != nil {
|
if _, err := db.Exec(ddl); err != nil {
|
||||||
return fmt.Errorf("数据表创建失败: %w", err)
|
return fmt.Errorf("数据表创建失败: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
d.logger.Info("数据库表创建成功")
|
logger.Info("数据库表创建成功")
|
||||||
/*
|
/*
|
||||||
使用sqlite3命令 测试数据库是否存在表
|
使用sqlite3命令 测试数据库是否存在表
|
||||||
✅ 查询所有表
|
✅ 查询所有表
|
||||||
@@ -174,3 +150,65 @@ func dropTriggers(db *sql.DB) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CloseDB() error {
|
||||||
|
dbMutex.Lock()
|
||||||
|
defer dbMutex.Unlock()
|
||||||
|
|
||||||
|
if dbInstance == nil {
|
||||||
|
if err := dbInstance.Close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dbInstance = nil
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用事务回滚测试
|
||||||
|
func RunTestWithRollback(db *sql.DB, testFunc func(*sql.Tx) error) error {
|
||||||
|
tx, err := db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行测试
|
||||||
|
if err := testFunc(tx); err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回滚事务,所有更改(包括 ID 递增)都会撤销
|
||||||
|
return tx.Rollback()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用示例
|
||||||
|
func TestNodeInsert(db *sql.DB) error {
|
||||||
|
logger.Debug("测试数据插入...")
|
||||||
|
return RunTestWithRollback(db, func(tx *sql.Tx) error {
|
||||||
|
// 插入测试数据
|
||||||
|
logger.Debug("执行插入测试数据...")
|
||||||
|
|
||||||
|
_, err := tx.Exec(`
|
||||||
|
INSERT INTO nodes (name, cpus, rack, rank)
|
||||||
|
VALUES (?, ?, ?, ?)
|
||||||
|
`, "test-node", 64, 1, 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证插入
|
||||||
|
var count int
|
||||||
|
logger.Debug("执行查询测试数据...")
|
||||||
|
err = tx.QueryRow(`
|
||||||
|
SELECT COUNT(*) FROM nodes WHERE name = ?
|
||||||
|
`, "test-node").Scan(&count)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger.Infof("测试数据插入成功,共 %d 条", count)
|
||||||
|
|
||||||
|
// 不需要手动删除,回滚会自动撤销
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -67,21 +67,25 @@ var (
|
|||||||
// DefaultLogger 全局默认日志实例(所有模块可直接用,也可注入自定义实现)
|
// DefaultLogger 全局默认日志实例(所有模块可直接用,也可注入自定义实现)
|
||||||
DefaultLogger Logger
|
DefaultLogger Logger
|
||||||
// once 保证日志只初始化一次
|
// once 保证日志只初始化一次
|
||||||
once sync.Once
|
initOnce sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogConfig 日志配置结构体(和项目的config包对齐)
|
// LogConfig 日志配置结构体(和项目的config包对齐)
|
||||||
type LogConfig struct {
|
type LogConfig struct {
|
||||||
Verbose bool // 是否开启详细模式(Debug级别)
|
Level string `mapstructure:"level" yaml:"level"`
|
||||||
Level string // 日志级别:debug/info/warn/error
|
Format string `mapstructure:"format" yaml:"format"`
|
||||||
ShowColor bool // 是否显示彩色输出
|
Output string `mapstructure:"output" yaml:"output"`
|
||||||
LogFile string // 日志文件路径(可选,空则只输出到控制台)
|
Verbose bool `mapstructure:"verbose" yaml:"verbose"`
|
||||||
|
LogFile string `mapstructure:"log_file" yaml:"log_file"`
|
||||||
|
ShowColor bool `mapstructure:"show_color" yaml:"show_color"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 默认配置
|
// 默认配置
|
||||||
var defaultConfig = LogConfig{
|
var defaultConfig = LogConfig{
|
||||||
Verbose: false,
|
|
||||||
Level: "info",
|
Level: "info",
|
||||||
|
Format: "text",
|
||||||
|
Output: "stdout",
|
||||||
|
Verbose: false,
|
||||||
ShowColor: true,
|
ShowColor: true,
|
||||||
LogFile: "/var/log/sunhpc/sunhpc.log",
|
LogFile: "/var/log/sunhpc/sunhpc.log",
|
||||||
}
|
}
|
||||||
@@ -113,6 +117,9 @@ func (f *CustomFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
|||||||
colorReset = ""
|
colorReset = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug,打印原始字节,用于调试
|
||||||
|
// fmt.Printf("%q\n", entry.Message)
|
||||||
|
|
||||||
// 拼接格式:
|
// 拼接格式:
|
||||||
// 灰色日期 + 空格 + 带颜色的[级别] + 空格 + 日志内容 + 空格 + 灰色文件行号 + 重置
|
// 灰色日期 + 空格 + 带颜色的[级别] + 空格 + 日志内容 + 空格 + 灰色文件行号 + 重置
|
||||||
fmt.Fprintf(&buf, "%s%s%s %s[%s]%s %s %s%s:%d%s\n",
|
fmt.Fprintf(&buf, "%s%s%s %s[%s]%s %s %s%s:%d%s\n",
|
||||||
@@ -150,27 +157,6 @@ func getLevelInfo(level logrus.Level) (string, string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCallerInfo 获取调用日志的文件和行号(跳过logrus内部调用)
|
|
||||||
func _getCallerInfo() (string, int) {
|
|
||||||
// 跳过的调用栈深度:根据实际情况调整(这里跳过logrus和logger包的调用)
|
|
||||||
skip := 6
|
|
||||||
pc, file, line, ok := runtime.Caller(skip)
|
|
||||||
if !ok {
|
|
||||||
return "unknown.go", 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// 只保留文件名(如 db.go),去掉完整路径
|
|
||||||
fileName := filepath.Base(file)
|
|
||||||
|
|
||||||
// 过滤logrus内部调用(可选)
|
|
||||||
funcName := runtime.FuncForPC(pc).Name()
|
|
||||||
if funcName == "" || filepath.Base(funcName) == "logrus" {
|
|
||||||
return getCallerInfoWithSkip(skip + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileName, line
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCallerInfo() (string, int) {
|
func getCallerInfo() (string, int) {
|
||||||
// 从当前调用开始,逐层向上查找
|
// 从当前调用开始,逐层向上查找
|
||||||
for i := 2; i < 15; i++ { // i从2开始(跳过getCallerInfo自身)
|
for i := 2; i < 15; i++ { // i从2开始(跳过getCallerInfo自身)
|
||||||
@@ -203,8 +189,7 @@ func shouldSkipPackage(funcName, file string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 跳过logger包(你自己的包装包)
|
// 跳过logger包(你自己的包装包)
|
||||||
if strings.Contains(funcName, "your/package/logger") ||
|
if strings.Contains(file, "/logger/") {
|
||||||
strings.Contains(file, "logger") {
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,21 +212,14 @@ func getCallerInfoWithSkip(skip int) (string, int) {
|
|||||||
|
|
||||||
// Init 初始化全局默认日志实例(全局只执行一次)
|
// Init 初始化全局默认日志实例(全局只执行一次)
|
||||||
func Init(cfg LogConfig) {
|
func Init(cfg LogConfig) {
|
||||||
once.Do(func() {
|
initOnce.Do(func() {
|
||||||
// 合并配置:传入的配置为空则用默认值
|
|
||||||
if cfg.Level == "" {
|
|
||||||
cfg.Level = defaultConfig.Level
|
|
||||||
}
|
|
||||||
if cfg.LogFile == "" {
|
|
||||||
cfg.LogFile = defaultConfig.LogFile
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 创建logrus实例
|
// 1. 创建logrus实例
|
||||||
logrusInst := logrus.New()
|
logrusInst := logrus.New()
|
||||||
|
|
||||||
// 2. 配置输出(控制台 + 文件,可选)
|
// 2. 配置输出(控制台 + 文件,可选)
|
||||||
var outputs []io.Writer
|
var outputs []io.Writer
|
||||||
outputs = append(outputs, os.Stdout) // 控制台输出
|
outputs = append(outputs, os.Stdout) // 控制台输出
|
||||||
|
|
||||||
// 如果配置了日志文件,添加文件输出
|
// 如果配置了日志文件,添加文件输出
|
||||||
if cfg.LogFile != "" {
|
if cfg.LogFile != "" {
|
||||||
// 确保日志目录存在
|
// 确保日志目录存在
|
||||||
|
|||||||
@@ -36,3 +36,18 @@ func GetTimestamp() string {
|
|||||||
const (
|
const (
|
||||||
NoAvailableNetworkInterfaces = "No available network interfaces"
|
NoAvailableNetworkInterfaces = "No available network interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 定义目录
|
||||||
|
const (
|
||||||
|
DefaultBaseDir string = "/etc/sunhpc"
|
||||||
|
DefaultTmplDir string = DefaultBaseDir + "/tmpl.d"
|
||||||
|
DefaultLogDir string = "/var/log/sunhpc"
|
||||||
|
DefaultLogFile string = DefaultLogDir + "/sunhpc.log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 定义数据库
|
||||||
|
const (
|
||||||
|
DefaultDBName string = "sunhpc.db"
|
||||||
|
DefaultDBPath string = "/var/lib/sunhpc"
|
||||||
|
DefaultDBArgs string = "_foreign_keys=on&_journal_mode=WAL&_timeout=5000"
|
||||||
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user