数据库模块完成-1

This commit is contained in:
2026-02-18 17:09:52 +08:00
parent 8a7bf8a39c
commit bf1df59bc8
7 changed files with 609 additions and 127 deletions

View File

@@ -29,9 +29,6 @@ func NewDatabaseCmd() *cobra.Command {
}
log.Info("初始化数据库...")
if force {
log.Warn("⚠️ 警告:强制重新初始化将清空数据库中的所有数据!")
}
dbInst := db.MustGetDB() // panic if fail (ok for CLI tool)
if err := dbInst.InitSchema(force); err != nil {

View File

@@ -1,13 +1,11 @@
package cmd
import (
"os"
"strings"
initcmd "sunhpc/cmd/init"
"sunhpc/cmd/soft"
"sunhpc/cmd/tmpl"
"sunhpc/internal/auth"
"sunhpc/internal/config"
"sunhpc/internal/db"
"sunhpc/internal/log"
"github.com/spf13/cobra"
@@ -19,43 +17,6 @@ var (
noColor bool
)
func checkDB() error {
cfg, err := config.LoadConfig()
if err != nil {
log.Fatal("加载配置失败: ", err)
}
// 统一转为小写,避免用户输入错误
dbType := strings.ToLower(cfg.DB.Type)
// 打印配置(调试用)
log.Debugf("数据库类型: %s", dbType)
log.Debugf("数据库名称: %s", cfg.DB.Name)
log.Debugf("数据库路径: %s", cfg.DB.Path)
log.Debugf("数据库用户: %s", cfg.DB.User)
log.Debugf("数据库主机: %s", cfg.DB.Host)
log.Debugf("数据库套接字: %s", cfg.DB.Socket)
log.Debugf("数据库详细日志: %v", cfg.DB.Verbose)
// 支持 sqlitemysql的常见别名
isSQLite := dbType == "sqlite" || dbType == "sqlite3"
isMySQL := dbType == "mysql"
// 检查数据库类型,只允许 sqlite 和 mysql
if !isSQLite && !isMySQL {
log.Fatalf("不支持的数据库类型: %s(仅支持 sqlite、sqlite3、mysql)", dbType)
}
// 检查数据库路径是否存在
if isSQLite {
if _, err := os.Stat(cfg.DB.Path); os.IsNotExist(err) {
log.Warnf("SQLite 数据库路径 %s 不存在", cfg.DB.Path)
log.Fatalf("必须先执行 'sunhpc init database' 初始化数据库")
}
}
return nil
}
var rootCmd = &cobra.Command{
Use: "sunhpc",
Short: "SunHPC - HPC集群一体化运维工具",
@@ -68,15 +29,9 @@ var rootCmd = &cobra.Command{
log.Debugf("当前命令 Annotations: %+v", cmd.Annotations)
// 检查当前命令是否标记为跳过 DB 检查
if cmd.Annotations["skip-db-check"] == "true" {
log.Debugf("当前命令 %s 标记为跳过 DB 检查", cmd.Name())
return
} else {
// 检查数据库
if err := checkDB(); err != nil {
log.Fatalf("数据库检查失败: %v", err)
}
_, err := db.CheckDB()
if err != nil {
log.Warnf("数据库检查失败: %v", err)
}
// 需要 root 权限

View File

@@ -1,10 +1,12 @@
package db
import (
"bufio"
"database/sql"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
_ "github.com/go-sql-driver/mysql"
@@ -17,31 +19,127 @@ import (
// DB wraps the sql.DB connection pool.
type DB struct {
engine *sql.DB
config *config.DBConfig // 保存配置
}
/*
// Engine returns the underlying *sql.DB.
func (d *DB) Engine() *sql.DB {
return d.engine
}
*/
func ConfirmWithRetry(prompt string, maxAttempts int) bool {
reader := bufio.NewReader(os.Stdin)
for attempt := 1; attempt <= maxAttempts; attempt++ {
log.Infof("%s [y/n]", prompt)
response, err := reader.ReadString('\n')
if err != nil {
continue
}
response = strings.ToLower(strings.TrimSpace(response))
switch response {
case "y", "yes":
return true
case "n", "no", "":
return false
default:
if attempt < maxAttempts {
log.Warnf(
"⚠️ 无效输入、请输入 'y' 或 'n'(剩余尝试次数: %d)",
maxAttempts-attempt)
}
}
}
log.Warn("⚠️ 警告:尝试次数过多、操作已取消")
return false
}
// InitSchema initializes the database schema.
// If force is true, drops existing tables before recreating them.
func (d *DB) InitSchema(force bool) error {
db := d.engine
fullPath := filepath.Join(d.config.Path, d.config.Name)
if force {
if err := dropTables(db); err != nil {
return fmt.Errorf("failed to drop tables: %w", err)
// 检查文件是否存在
_, err := os.Stat(fullPath)
fileExists := err == nil
// 处理不同的场景
switch {
case !fileExists:
// 场景1文件不存在连接并创建(allowCreate = true).
log.Infof("数据库文件不存在,将创建: %s", fullPath)
if err := d.Connect(true); err != nil {
return err
}
return createTables(d.engine)
case fileExists && !force:
// 场景2文件存在、无 force 参数、提示友好退出.
log.Warnf("数据库文件已存在: %s", fullPath)
log.Warn("如果需要强制重新初始化,请添加 --force 参数")
log.Warn("数据库已存在、退出初始化操作.")
os.Exit(1)
case fileExists && force:
// 场景3文件存在、force 参数 -> 需要用户确认并重建.
log.Warn("警告:强制重新初始化将清空数据库中的所有数据!")
if !ConfirmWithRetry("是否继续?", 3) {
return fmt.Errorf("用户取消操作")
}
// 连接现有数据库(allowCreate = true, 因为文件已经存在)
if err := d.Connect(true); err != nil {
return err
}
// 清空现有数据.
if err := dropTables(d.engine); err != nil {
return err
}
// 清空表
if err := dropTriggers(d.engine); err != nil {
return err
}
log.Info("已清空现有数据库触发器")
return createTables(d.engine)
}
log.Info("数据库创建成功")
return nil
}
// 辅助函数:检查文件是否存在
func fileExists(path string) bool {
_, err := os.Stat(path)
if err == nil {
return true
}
if os.IsNotExist(err) {
return false
}
// 其他问题(如权限问题)也视为文件不存在,但应该记录日志
log.Debugf("检查数据库文件状态失败: %v", err)
return false
}
// 辅助函数: 创建数据库表
func createTables(db *sql.DB) error {
// ✅ 调用 schema.go 中的函数
for _, ddl := range CreateTableStatements() {
log.Debugf("执行: %s", ddl)
if _, err := db.Exec(ddl); err != nil {
return fmt.Errorf("failed to create table: %w", err)
return fmt.Errorf("数据表创建失败: %w", err)
}
}
log.Info("数据库表创建成功")
return nil
}
@@ -55,6 +153,16 @@ func dropTables(db *sql.DB) error {
return nil
}
func dropTriggers(db *sql.DB) error {
// ✅ 调用 schema.go 中的函数
for _, trigger := range DropTriggerStatements() {
if _, err := db.Exec(fmt.Sprintf("DROP TRIGGER IF EXISTS `%s`", trigger)); err != nil {
return err
}
}
return nil
}
// --- Singleton DB Instance ---
var (
globalDB *DB
@@ -70,52 +178,77 @@ func GetDB() (*DB, error) {
return
}
if _, err := os.Stat(cfg.DB.Path); err != nil {
// 创建数据库目录
if err := os.MkdirAll(cfg.DB.Path, 0755); err != nil {
log.Fatalf("创建数据库目录失败: %v", err)
globalDB = &DB{
config: &cfg.DB,
}
log.Infof("数据库目录创建成功: %s", cfg.DB.Path)
})
return globalDB, initErr
}
func (d *DB) Connect(allowCreate bool) error {
// 如果已经连接,直接返回
if d.engine != nil {
return nil
}
var dsn string
var driver string
switch cfg.DB.Type {
switch d.config.Type {
case "sqlite":
driver = "sqlite3"
fullPath := filepath.Join(cfg.DB.Path, cfg.DB.Name)
dsn = fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000", fullPath)
case "mysql":
driver = "mysql"
if cfg.DB.Socket != "" {
dsn = fmt.Sprintf("%s:%s@unix(%s)/%s?parseTime=true&loc=Local",
cfg.DB.User, cfg.DB.Password, cfg.DB.Socket, cfg.DB.Name)
} else {
dsn = fmt.Sprintf("%s:%s@tcp(%s)/%s?parseTime=true&loc=Local",
cfg.DB.User, cfg.DB.Password, cfg.DB.Host, cfg.DB.Name)
}
default:
initErr = fmt.Errorf("unsupported database type: %s", cfg.DB.Type)
return
fullPath := filepath.Join(d.config.Path, d.config.Name)
// 检查文件是否存在
_, err := os.Stat(fullPath)
fileExists := err == nil
// 如果文件不存在且不允许创建,返回错误
if !fileExists && !allowCreate {
return fmt.Errorf("数据库文件不存在: %s, 请先初始化.", fullPath)
}
engine, err := sql.Open(driver, dsn)
// 确保目录存在
if err := os.MkdirAll(d.config.Path, 0755); err != nil {
return fmt.Errorf("创建数据库目录失败: %w", err)
}
// 连接参数
dsn := fmt.Sprintf("%s?_foreign_keys=on&_journal_mode=WAL&_timeout=5000",
fullPath)
engine, err := sql.Open("sqlite3", dsn)
if err != nil {
initErr = fmt.Errorf("failed to open database: %w", err)
return
return fmt.Errorf("数据库打开失败: %w", err)
}
if err := engine.Ping(); err != nil {
engine.Close()
initErr = fmt.Errorf("failed to ping database: %w", err)
return
return fmt.Errorf("数据库连接失败: %w", err)
}
globalDB = &DB{engine: engine}
})
d.engine = engine
case "mysql":
// TODO: 实现 MySQL 连接逻辑
return fmt.Errorf("mysql 数据库连接未实现")
}
return globalDB, initErr
return nil
}
// Close 关闭数据库连接
func (d *DB) Close() error {
if d.engine != nil {
return d.engine.Close()
}
return nil
}
// GetEngine 获取数据库引擎(自动连接)
func (d *DB) GetEngine() (*sql.DB, error) {
// 如果还没有连接,自动连接(但不创建新文件)
if d.engine == nil {
if err := d.Connect(false); err != nil {
return nil, err
}
}
return d.engine, nil
}
// MustGetDB is a helper that panics on error (use in main/init only).
@@ -126,3 +259,48 @@ func MustGetDB() *DB {
}
return db
}
func GetDBConfig() (*config.DBConfig, error) {
cfg, err := config.LoadConfig()
if err != nil {
return nil, fmt.Errorf("数据库配置文件加载失败: %w", err)
}
return &cfg.DB, nil
}
func CheckDB() (*config.Config, error) {
cfg, err := config.LoadConfig()
if err != nil {
log.Warnf("加载配置失败: %v", err)
}
// 统一转为小写,避免用户输入错误
dbType := strings.ToLower(cfg.DB.Type)
// 打印配置(调试用)
log.Debugf("数据库类型: %s", dbType)
log.Debugf("数据库名称: %s", cfg.DB.Name)
log.Debugf("数据库路径: %s", cfg.DB.Path)
log.Debugf("数据库用户: %s", cfg.DB.User)
log.Debugf("数据库主机: %s", cfg.DB.Host)
log.Debugf("数据库套接字: %s", cfg.DB.Socket)
log.Debugf("数据库详细日志: %v", cfg.DB.Verbose)
// 支持 sqlitemysql的常见别名
isSQLite := dbType == "sqlite" || dbType == "sqlite3"
isMySQL := dbType == "mysql"
// 检查数据库类型,只允许 sqlite 和 mysql
if !isSQLite && !isMySQL {
log.Fatalf("不支持的数据库类型: %s(仅支持 sqlite、sqlite3、mysql)", dbType)
}
// 检查数据库路径是否存在
if isSQLite {
if _, err := os.Stat(cfg.DB.Path); os.IsNotExist(err) {
log.Warnf("SQLite 数据库路径 %s 不存在", cfg.DB.Path)
log.Warn("必须先执行 'sunhpc init database' 初始化数据库")
}
}
return cfg, nil
}

View File

@@ -9,31 +9,56 @@ func CurrentSchemaVersion() int {
// CreateTableStatements returns a list of CREATE TABLE statements.
func CreateTableStatements() []string {
return []string{
createNodesTable(),
createAliasesTable(),
createAttributesTable(),
createBootactionTable(),
createDistributionsTable(),
createFirewallsTable(),
createNetworksTable(),
createSubnetsTable(),
createPartitionsTable(),
createPublicKeysTable(),
createSoftwareTable(),
createNodesTable(),
createSubnetsTable(),
createTrg_nodes_before_delete(),
}
}
// DropTableOrder returns table names in reverse dependency order for safe DROP.
func DropTableOrder() []string {
return []string{"software", "attributes", "nodes", "subnets", "networks"}
return []string{
"aliases",
"attributes",
"bootactions",
"distributions",
"firewalls",
"networks",
"partitions",
"publickeys",
"software",
"nodes",
"subnets",
}
}
func DropTriggerStatements() []string {
return []string{
"trg_nodes_before_delete",
}
}
// --- Private DDL Functions ---
func createNodesTable() string {
func createAliasesTable() string {
return `
CREATE TABLE IF NOT EXISTS nodes (
CREATE TABLE IF NOT EXISTS aliases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hostname TEXT NOT NULL UNIQUE,
ip TEXT,
status TEXT DEFAULT 'active',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`
node_id INTEGER NOT NULL,
alias TEXT NOT NULL,
CONSTRAINT fk_aliases_node FOREIGN KEY(node_id) REFERENCES nodes(id),
UNIQUE(node_id, alias)
);
create index if not exists idx_aliases_node on aliases (node_id);
`
}
func createAttributesTable() string {
@@ -41,33 +66,149 @@ func createAttributesTable() string {
CREATE TABLE IF NOT EXISTS attributes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
FOREIGN KEY(node_id) REFERENCES nodes(id) ON DELETE CASCADE,
UNIQUE(node_id, key)
);`
attr TEXT NOT NULL,
value TEXT,
shadow TEXT,
CONSTRAINT fk_attributes_node FOREIGN KEY(node_id) REFERENCES nodes(id)
);
create index if not exists idx_attributes_node on attributes (node_id);
`
}
func createBootactionTable() string {
return `
CREATE TABLE IF NOT EXISTS bootactions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER NOT NULL,
action TEXT,
kernel TEXT,
initrd TEXT,
cmdline TEXT,
CONSTRAINT fk_bootactions_node FOREIGN KEY(node_id) REFERENCES nodes(id),
UNIQUE(node_id)
);
create index if not exists idx_bootactions_node on bootactions (node_id);
`
}
func createDistributionsTable() string {
return `
CREATE TABLE IF NOT EXISTS distributions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER,
name TEXT NOT NULL,
version TEXT,
lang TEXT,
os_release TEXT,
constraint distributions_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id)
);
create index if not exists idx_distributions_node on distributions (node_id);
`
}
func createFirewallsTable() string {
return `
CREATE TABLE IF NOT EXISTS firewalls (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER,
rulename TEXT NOT NULL,
rulesrc TEXT NOT NULL,
insubnet INTEGER,
outsubnet INTEGER,
service TEXT,
protocol TEXT,
action TEXT,
chain TEXT,
flags TEXT,
comment TEXT,
constraint firewalls_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id)
);
create index if not exists idx_firewalls_node on firewalls (node_id);
`
}
func createNetworksTable() string {
return `
CREATE TABLE IF NOT EXISTS networks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
node_id INTEGER,
subnet_id INTEGER,
mac TEXT,
ip TEXT,
name TEXT,
device TEXT,
module TEXT,
vlanid INTEGER,
options TEXT,
channel TEXT,
disable_kvm INTEGER NOT NULL DEFAULT 0 CHECK (disable_kvm IN (0, 1)),
constraint networks_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id),
constraint networks_subnets_fk FOREIGN KEY(subnet_id) REFERENCES subnets(id)
);
create index if not exists idx_networks_node on networks (node_id);
`
}
func createNodesTable() string {
return `
CREATE TABLE IF NOT EXISTS nodes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
cpus INTEGER NOT NULL,
rack INTEGER NOT NULL,
rank INTEGER NOT NULL,
arch TEXT,
os TEXT,
runaction TEXT,
installaction TEXT
);
create index if not exists idx_nodes_name on nodes (name);
`
}
func createPartitionsTable() string {
return `
CREATE TABLE IF NOT EXISTS partitions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER,
device TEXT NOT NULL,
formatflags TEXT NOT NULL,
fstype TEXT NOT NULL,
mountpoint TEXT NOT NULL,
partitionflags TEXT NOT NULL,
partitionid TEXT NOT NULL,
partitionsize TEXT NOT NULL,
sectorstart TEXT NOT NULL,
constraint partitions_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id)
);
create index if not exists idx_partitions_node on partitions (node_id);
`
}
func createPublicKeysTable() string {
return `
CREATE TABLE IF NOT EXISTS publickeys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
node_id INTEGER,
publickey TEXT NOT NULL,
description TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);`
constraint publickeys_nodes_fk FOREIGN KEY(node_id) REFERENCES nodes(id)
);
create index if not exists idx_publickeys_node on publickeys (node_id);
`
}
func createSubnetsTable() string {
return `
CREATE TABLE IF NOT EXISTS subnets (
id INTEGER PRIMARY KEY AUTOINCREMENT,
network_id INTEGER NOT NULL,
cidr TEXT NOT NULL,
gateway TEXT,
vlan INTEGER,
FOREIGN KEY(network_id) REFERENCES networks(id) ON DELETE CASCADE,
UNIQUE(network_id, cidr)
name TEXT NOT NULL,
dnszone TEXT NOT NULL,
subnet TEXT NOT NULL,
netmask TEXT NOT NULL,
mtu INTEGER NOT NULL DEFAULT 1500,
servedns INTEGER NOT NULL DEFAULT 0 CHECK (servedns IN (0, 1)),
UNIQUE(name, dnszone)
);`
}
@@ -76,9 +217,78 @@ func createSoftwareTable() string {
CREATE TABLE IF NOT EXISTS software (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
version TEXT NOT NULL,
installed_on INTEGER,
FOREIGN KEY(installed_on) REFERENCES nodes(id) ON DELETE SET NULL,
UNIQUE(name, version, installed_on)
);`
pathversion TEXT,
fullversion TEXT,
description TEXT,
website TEXT,
license TEXT,
install_method TEXT NOT NULL CHECK (install_method IN (
'source',
'binary',
'rpm',
'docker',
'apptainer',
'conda',
'mamba',
'spack',
'tarball',
'zipball',
'pip',
'npm',
'custom'
)),
-- 源码编译相关参数
source_url TEXT, -- 源码下载地址
source_checksum TEXT, -- 源码校验和
source_checksum_type TEXT NOT NULL CHECK (source_checksum_type IN (
'md5',
'sha1',
'sha256',
'sha512'
)),
build_dependencies TEXT, -- 编译依赖(JSON格式)
configure_params TEXT, -- 配置参数(JSON格式)
make_params TEXT, -- make参数(JSON格式)
make_install_params TEXT, -- make install参数(JSON格式)
-- 安装路径参数
install_path TEXT NOT NULL, -- 安装路径
env_vars TEXT, -- 环境变量(JSON格式)
-- 状态信息
is_installed INTEGER NOT NULL DEFAULT 0 CHECK (is_installed IN (0, 1)), -- 是否安装
install_date TEXT, -- 安装日期
updated_date TEXT, -- 更新日期
install_user TEXT, -- 安装用户
notes TEXT, -- 安装备注
-- 元数据
created_at DATETIME DEFAULT CURRENT_TIMESTAMP, -- 创建时间
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, -- 更新时间
UNIQUE(name)
);
create index if not exists idx_software_name on software (name);
create index if not exists idx_software_install_method on software (install_method);
create index if not exists idx_software_is_installed on software (is_installed);
`
}
func createTrg_nodes_before_delete() string {
return `
CREATE TRIGGER IF NOT EXISTS trg_nodes_before_delete
BEFORE DELETE ON nodes
FOR EACH ROW
BEGIN
-- 先删除子表的关联记录
DELETE FROM aliases WHERE node_id = OLD.id;
DELETE FROM attributes WHERE node_id = OLD.id;
DELETE FROM bootactions WHERE node_id = OLD.id;
DELETE FROM distributions WHERE node_id = OLD.id;
DELETE FROM firewalls WHERE node_id = OLD.id;
DELETE FROM networks WHERE node_id = OLD.id;
DELETE FROM partitions WHERE node_id = OLD.id;
DELETE FROM publickeys WHERE node_id = OLD.id;
END;
`
}

146
readme.md Normal file
View File

@@ -0,0 +1,146 @@
## 其他包使用数据库模块
```go
package repository
import (
"your-project/database"
"your-project/log"
)
type NodeRepository struct {
db *database.DB
}
// NewNodeRepository 创建仓库(延迟连接)
func NewNodeRepository() (*NodeRepository, error) {
db, err := database.GetDB()
if err != nil {
return nil, err
}
return &NodeRepository{db: db}, nil
}
// GetNode 获取节点(自动连接)
func (r *NodeRepository) GetNode(id int) (*Node, error) {
// 获取数据库引擎(会自动连接)
engine, err := r.db.GetEngine()
if err != nil {
return nil, fmt.Errorf("数据库连接失败: %w", err)
}
var node Node
err = engine.QueryRow("SELECT id, name FROM nodes WHERE id = ?", id).
Scan(&node.ID, &node.Name)
if err != nil {
return nil, err
}
return &node, nil
}
// CreateNode 创建节点
func (r *NodeRepository) CreateNode(name string) error {
engine, err := r.db.GetEngine()
if err != nil {
return err
}
_, err = engine.Exec("INSERT INTO nodes (name) VALUES (?)", name)
return err
}
```
## 服务层使用
```go
package service
import (
"your-project/repository"
"your-project/log"
)
type NodeService struct {
repo *repository.NodeRepository
}
func NewNodeService() (*NodeService, error) {
repo, err := repository.NewNodeRepository()
if err != nil {
return nil, err
}
return &NodeService{repo: repo}, nil
}
func (s *NodeService) ListNode() error {
// 自动连接数据库
nodes, err := s.repo.GetAllNodes()
if err != nil {
return err
}
for _, node := range nodes {
log.Infof("Node: %v", node)
}
return nil
}
```
## 命令处理
```go
package main
import (
"your-project/cmd"
"your-project/database"
"your-project/log"
"github.com/spf13/cobra"
)
func main() {
var rootCmd = &cobra.Command{Use: "sunhpc"}
// init database 命令
var initCmd = &cobra.Command{
Use: "init database",
Short: "初始化数据库",
Run: func(cmd *cobra.Command, args []string) {
force, _ := cmd.Flags().GetBool("force")
// 获取DB实例只加载配置
db, err := database.GetDB()
if err != nil {
log.Fatal(err)
}
// 初始化schema会根据force参数决定行为
if err := db.InitSchema(force); err != nil {
log.Fatal(err)
}
log.Info("数据库初始化成功")
},
}
initCmd.Flags().BoolP("force", "f", false, "强制重新初始化")
rootCmd.AddCommand(initCmd)
// node list 命令 - 自动连接已存在的数据库
var nodeCmd = &cobra.Command{
Use: "node list",
Short: "列出所有节点",
Run: func(cmd *cobra.Command, args []string) {
service, err := service.NewNodeService()
if err != nil {
log.Fatal(err) // 如果数据库不存在,这里会报错
}
if err := service.ListNode(); err != nil {
log.Fatal(err)
}
},
}
rootCmd.AddCommand(nodeCmd)
rootCmd.Execute()
}
```

BIN
sunhpc

Binary file not shown.

View File

@@ -1,4 +0,0 @@
db:
type: sqlite
name: sunhpc.db
path: /tmp/sunhpc