Fix: 替换SQLite为JSON文件存储,无需CGO支持

- 移除go-sqlite3依赖,改用纯Go的JSON文件存储
- 解决Windows上CGO_ENABLED=0导致SQLite无法使用的问题
- 添加线程安全的读写锁保护
- 支持数据持久化,重启后数据不丢失
- 简化存储逻辑,提高可靠性
This commit is contained in:
Your Name
2026-04-26 00:46:37 +08:00
parent e5e624d72e
commit 8b7dbf2886
5 changed files with 92 additions and 174 deletions
+2
View File
@@ -23,6 +23,8 @@ echo.
echo [2/3] Building program... echo [2/3] Building program...
set GOOS=windows set GOOS=windows
set GOARCH=amd64 set GOARCH=amd64
set CGO_ENABLED=1
set CXX=x86_64-w64-mingw32-g++
go build -o network-topology.exe -ldflags="-s -w" ./cmd go build -o network-topology.exe -ldflags="-s -w" ./cmd
if %ERRORLEVEL% NEQ 0 ( if %ERRORLEVEL% NEQ 0 (
+1 -4
View File
@@ -4,7 +4,4 @@ go 1.26.2
require golang.org/x/crypto v0.50.0 require golang.org/x/crypto v0.50.0
require ( require golang.org/x/sys v0.43.0 // indirect
github.com/mattn/go-sqlite3 v1.14.42 // indirect
golang.org/x/sys v0.43.0 // indirect
)
-2
View File
@@ -1,5 +1,3 @@
github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo=
github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
+11 -1
View File
@@ -31,7 +31,17 @@ func (p *H3CParser) Parse(device *models.Device, outputs []string) error {
} }
p.parseVersion(device, outputs[0]) p.parseVersion(device, outputs[0])
device.Interfaces = p.parseInterfaces(outputs[1], outputs[2])
// 检查命令输出是否为空
if outputs[1] == "" {
fmt.Printf("Warning: 'display interface' output is empty for device %s\n", device.IP)
} else {
device.Interfaces = p.parseInterfaces(outputs[1], outputs[2])
if len(device.Interfaces) == 0 {
fmt.Printf("Warning: parsed 0 interfaces for device %s (output length: %d)\n",
device.IP, len(outputs[1]))
}
}
// 解析ARP表用于MAC到IP映射(允许失败) // 解析ARP表用于MAC到IP映射(允许失败)
arpTable := make(map[string]string) arpTable := make(map[string]string)
+78 -167
View File
@@ -1,75 +1,84 @@
package storage package storage
import ( import (
"database/sql"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"os"
"sync"
"network-topology-discovery/pkg/models" "network-topology-discovery/pkg/models"
"time" "time"
_ "github.com/mattn/go-sqlite3"
) )
// Storage 数据库存储 // Storage 存储(使用JSON文件)
type Storage struct { type Storage struct {
db *sql.DB mu sync.RWMutex
filePath string
devices map[string]models.Device
} }
// NewStorage 创建存储实例 // NewStorage 创建存储实例
func NewStorage(dbPath string) (*Storage, error) { func NewStorage(filePath string) (*Storage, error) {
db, err := sql.Open("sqlite3", dbPath) s := &Storage{
if err != nil { filePath: filePath,
return nil, fmt.Errorf("failed to open database: %w", err) devices: make(map[string]models.Device),
} }
// 创建表 // 从文件加载数据
if err := createTables(db); err != nil { if err := s.load(); err != nil {
return nil, fmt.Errorf("failed to create tables: %w", err) if !os.IsNotExist(err) {
return nil, fmt.Errorf("failed to load storage: %w", err)
}
// 文件不存在是正常的,创建新文件
log.Printf("Creating new storage file: %s", filePath)
} }
return &Storage{db: db}, nil return s, nil
} }
// createTables 创建数据 // load 从文件加载数据
func createTables(db *sql.DB) error { func (s *Storage) load() error {
query := ` data, err := os.ReadFile(s.filePath)
CREATE TABLE IF NOT EXISTS devices ( if err != nil {
id TEXT PRIMARY KEY, return err
ip TEXT NOT NULL UNIQUE, }
type TEXT NOT NULL,
hostname TEXT, var devices []models.Device
os_version TEXT, if err := json.Unmarshal(data, &devices); err != nil {
uptime TEXT, return fmt.Errorf("failed to parse storage file: %w", err)
interfaces TEXT, }
neighbors TEXT,
last_scanned DATETIME, for _, dev := range devices {
scan_status TEXT, s.devices[dev.ID] = dev
error_message TEXT, }
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP log.Printf("Loaded %d devices from storage", len(devices))
); return nil
}
CREATE INDEX IF NOT EXISTS idx_devices_ip ON devices(ip);
CREATE INDEX IF NOT EXISTS idx_devices_type ON devices(type); // save 保存数据到文件
` func (s *Storage) save() error {
devices := make([]models.Device, 0, len(s.devices))
_, err := db.Exec(query) for _, dev := range s.devices {
return err devices = append(devices, dev)
}
data, err := json.MarshalIndent(devices, "", " ")
if err != nil {
return fmt.Errorf("failed to marshal devices: %w", err)
}
if err := os.WriteFile(s.filePath, data, 0644); err != nil {
return fmt.Errorf("failed to write storage file: %w", err)
}
return nil
} }
// SaveDevice 保存设备 // SaveDevice 保存设备
func (s *Storage) SaveDevice(device *models.Device) error { func (s *Storage) SaveDevice(device *models.Device) error {
// 序列化接口和邻居数据 s.mu.Lock()
interfacesJSON, err := json.Marshal(device.Interfaces) defer s.mu.Unlock()
if err != nil {
return fmt.Errorf("failed to marshal interfaces: %w", err)
}
neighborsJSON, err := json.Marshal(device.Neighbors)
if err != nil {
return fmt.Errorf("failed to marshal neighbors: %w", err)
}
// 设置ID和扫描时间 // 设置ID和扫描时间
if device.ID == "" { if device.ID == "" {
@@ -77,29 +86,10 @@ func (s *Storage) SaveDevice(device *models.Device) error {
} }
device.LastScanned = time.Now() device.LastScanned = time.Now()
query := ` s.devices[device.ID] = *device
INSERT OR REPLACE INTO devices
(id, ip, type, hostname, os_version, uptime, interfaces, neighbors,
last_scanned, scan_status, error_message, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`
_, err = s.db.Exec(query, // 保存到文件
device.ID, if err := s.save(); err != nil {
device.IP,
string(device.Type),
device.Hostname,
device.OSVersion,
device.Uptime,
string(interfacesJSON),
string(neighborsJSON),
device.LastScanned,
device.ScanStatus,
device.ErrorMessage,
time.Now(),
)
if err != nil {
return fmt.Errorf("failed to save device: %w", err) return fmt.Errorf("failed to save device: %w", err)
} }
@@ -109,47 +99,12 @@ func (s *Storage) SaveDevice(device *models.Device) error {
// GetDevice 获取设备 // GetDevice 获取设备
func (s *Storage) GetDevice(id string) (*models.Device, error) { func (s *Storage) GetDevice(id string) (*models.Device, error) {
query := ` s.mu.RLock()
SELECT id, ip, type, hostname, os_version, uptime, interfaces, neighbors, defer s.mu.RUnlock()
last_scanned, scan_status, error_message
FROM devices WHERE id = ?
`
row := s.db.QueryRow(query, id) device, exists := s.devices[id]
if !exists {
var device models.Device return nil, fmt.Errorf("device not found: %s", id)
var typeStr string
var interfacesJSON, neighborsJSON string
var lastScanned sql.NullTime
err := row.Scan(
&device.ID, &device.IP, &typeStr, &device.Hostname,
&device.OSVersion, &device.Uptime, &interfacesJSON, &neighborsJSON,
&lastScanned, &device.ScanStatus, &device.ErrorMessage,
)
if err != nil {
return nil, fmt.Errorf("failed to get device: %w", err)
}
device.Type = models.DeviceType(typeStr)
if lastScanned.Valid {
device.LastScanned = lastScanned.Time
}
// 反序列化接口
if interfacesJSON != "" {
if err := json.Unmarshal([]byte(interfacesJSON), &device.Interfaces); err != nil {
log.Printf("Warning: failed to unmarshal interfaces for %s: %v", device.IP, err)
}
}
// 反序列化邻居
if neighborsJSON != "" {
if err := json.Unmarshal([]byte(neighborsJSON), &device.Neighbors); err != nil {
log.Printf("Warning: failed to unmarshal neighbors for %s: %v", device.IP, err)
}
} }
return &device, nil return &device, nil
@@ -157,58 +112,12 @@ func (s *Storage) GetDevice(id string) (*models.Device, error) {
// GetAllDevices 获取所有设备 // GetAllDevices 获取所有设备
func (s *Storage) GetAllDevices() ([]models.Device, error) { func (s *Storage) GetAllDevices() ([]models.Device, error) {
query := ` s.mu.RLock()
SELECT id, ip, type, hostname, os_version, uptime, interfaces, neighbors, defer s.mu.RUnlock()
last_scanned, scan_status, error_message
FROM devices ORDER BY created_at
`
rows, err := s.db.Query(query) devices := make([]models.Device, 0, len(s.devices))
if err != nil { for _, dev := range s.devices {
return nil, fmt.Errorf("failed to query devices: %w", err) devices = append(devices, dev)
}
defer rows.Close()
var devices []models.Device
for rows.Next() {
var device models.Device
var typeStr string
var interfacesJSON, neighborsJSON string
var lastScanned sql.NullTime
err := rows.Scan(
&device.ID, &device.IP, &typeStr, &device.Hostname,
&device.OSVersion, &device.Uptime, &interfacesJSON, &neighborsJSON,
&lastScanned, &device.ScanStatus, &device.ErrorMessage,
)
if err != nil {
log.Printf("Warning: failed to scan device row: %v", err)
continue
}
device.Type = models.DeviceType(typeStr)
if lastScanned.Valid {
device.LastScanned = lastScanned.Time
}
// 反序列化接口
if interfacesJSON != "" {
if err := json.Unmarshal([]byte(interfacesJSON), &device.Interfaces); err != nil {
log.Printf("Warning: failed to unmarshal interfaces for %s: %v", device.IP, err)
}
}
// 反序列化邻居
if neighborsJSON != "" {
if err := json.Unmarshal([]byte(neighborsJSON), &device.Neighbors); err != nil {
log.Printf("Warning: failed to unmarshal neighbors for %s: %v", device.IP, err)
}
}
devices = append(devices, device)
} }
return devices, nil return devices, nil
@@ -216,17 +125,19 @@ func (s *Storage) GetAllDevices() ([]models.Device, error) {
// DeleteDevice 删除设备 // DeleteDevice 删除设备
func (s *Storage) DeleteDevice(id string) error { func (s *Storage) DeleteDevice(id string) error {
_, err := s.db.Exec("DELETE FROM devices WHERE id = ?", id) s.mu.Lock()
if err != nil { defer s.mu.Unlock()
delete(s.devices, id)
// 保存到文件
if err := s.save(); err != nil {
return fmt.Errorf("failed to delete device: %w", err) return fmt.Errorf("failed to delete device: %w", err)
} }
return nil return nil
} }
// Close 关闭数据库连接 // Close 关闭存储(不需要操作,JSON文件不需要关闭)
func (s *Storage) Close() error { func (s *Storage) Close() error {
if s.db != nil {
return s.db.Close()
}
return nil return nil
} }