feat(database): 实现数据库功能并优化数据导出
- 新增数据库相关 API 和服务 - 实现数据导出功能,支持导出到 JSON 文件 - 优化数据导入流程,增加数据校验 - 新增数据库页面,展示解析数据和统计信息 - 更新捕获页面,支持导入数据到数据库
This commit is contained in:
82
internal/service/database_service.go
Normal file
82
internal/service/database_service.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"equipment-analyzer/internal/model"
|
||||
"equipment-analyzer/internal/utils"
|
||||
)
|
||||
|
||||
// DatabaseService 数据库服务
|
||||
type DatabaseService struct {
|
||||
db *model.Database
|
||||
logger *utils.Logger
|
||||
}
|
||||
|
||||
// NewDatabaseService 创建数据库服务
|
||||
func NewDatabaseService(db *model.Database, logger *utils.Logger) *DatabaseService {
|
||||
return &DatabaseService{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// SaveParsedDataToDatabase 保存解析后的数据到数据库
|
||||
func (s *DatabaseService) SaveParsedDataToDatabase(sessionName string, itemsJSON, heroesJSON string) error {
|
||||
err := s.db.SaveParsedData(sessionName, itemsJSON, heroesJSON)
|
||||
if err != nil {
|
||||
s.logger.Error("保存解析数据到数据库失败", "error", err, "session_name", sessionName)
|
||||
return fmt.Errorf("保存解析数据失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("解析数据保存成功", "session_name", sessionName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetLatestParsedDataFromDatabase 从数据库获取最新的解析数据
|
||||
func (s *DatabaseService) GetLatestParsedDataFromDatabase() (string, string, error) {
|
||||
itemsJSON, heroesJSON, err := s.db.GetLatestParsedData()
|
||||
if err != nil {
|
||||
s.logger.Error("从数据库获取最新解析数据失败", "error", err)
|
||||
return "", "", fmt.Errorf("获取解析数据失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("最新解析数据获取成功")
|
||||
return itemsJSON, heroesJSON, nil
|
||||
}
|
||||
|
||||
// SaveAppSetting 保存应用设置
|
||||
func (s *DatabaseService) SaveAppSetting(key, value string) error {
|
||||
err := s.db.SaveSetting(key, value)
|
||||
if err != nil {
|
||||
s.logger.Error("保存应用设置失败", "error", err, "key", key)
|
||||
return fmt.Errorf("保存设置失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("应用设置保存成功", "key", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAppSetting 获取应用设置
|
||||
func (s *DatabaseService) GetAppSetting(key string) (string, error) {
|
||||
value, err := s.db.GetSetting(key)
|
||||
if err != nil {
|
||||
s.logger.Error("获取应用设置失败", "error", err, "key", key)
|
||||
return "", fmt.Errorf("获取设置失败: %w", err)
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// GetAllAppSettings 获取所有应用设置
|
||||
func (s *DatabaseService) GetAllAppSettings() (map[string]string, error) {
|
||||
settings, err := s.db.GetAllSettings()
|
||||
if err != nil {
|
||||
s.logger.Error("获取所有应用设置失败", "error", err)
|
||||
return nil, fmt.Errorf("获取设置失败: %w", err)
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@ package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"equipment-analyzer/internal/capture"
|
||||
@@ -13,18 +13,37 @@ import (
|
||||
)
|
||||
|
||||
type App struct {
|
||||
config *config.Config
|
||||
logger *utils.Logger
|
||||
captureService *CaptureService
|
||||
parserService *ParserService
|
||||
config *config.Config
|
||||
logger *utils.Logger
|
||||
captureService *CaptureService
|
||||
parserService *ParserService
|
||||
database *model.Database
|
||||
databaseService *DatabaseService
|
||||
}
|
||||
|
||||
func NewApp(cfg *config.Config, logger *utils.Logger) *App {
|
||||
// 初始化数据库
|
||||
database, err := model.NewDatabase()
|
||||
if err != nil {
|
||||
logger.Error("初始化数据库失败", "error", err)
|
||||
// 如果数据库初始化失败,仍然创建应用,但数据库功能不可用
|
||||
return &App{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
captureService: NewCaptureService(cfg, logger),
|
||||
parserService: NewParserService(cfg, logger),
|
||||
}
|
||||
}
|
||||
|
||||
databaseService := NewDatabaseService(database, logger)
|
||||
|
||||
return &App{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
captureService: NewCaptureService(cfg, logger),
|
||||
parserService: NewParserService(cfg, logger),
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
captureService: NewCaptureService(cfg, logger),
|
||||
parserService: NewParserService(cfg, logger),
|
||||
database: database,
|
||||
databaseService: databaseService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +62,15 @@ func (a *App) BeforeClose(ctx context.Context) (prevent bool) {
|
||||
|
||||
func (a *App) Shutdown(ctx context.Context) {
|
||||
a.logger.Info("应用关闭")
|
||||
|
||||
// 关闭数据库连接
|
||||
if a.database != nil {
|
||||
if err := a.database.Close(); err != nil {
|
||||
a.logger.Error("关闭数据库连接失败", "error", err)
|
||||
} else {
|
||||
a.logger.Info("数据库连接已关闭")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetNetworkInterfaces 获取网络接口列表
|
||||
@@ -130,6 +158,80 @@ func (a *App) ExportData(hexDataList []string, filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportCurrentData 导出当前数据库中的数据到文件
|
||||
func (a *App) ExportCurrentData(filename string) error {
|
||||
if a.databaseService == nil {
|
||||
return fmt.Errorf("数据库服务未初始化")
|
||||
}
|
||||
|
||||
// 从数据库获取最新数据
|
||||
parsedResult, err := a.GetLatestParsedDataFromDatabase()
|
||||
if err != nil {
|
||||
a.logger.Error("获取数据库数据失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
if parsedResult == nil || (len(parsedResult.Items) == 0 && len(parsedResult.Heroes) == 0) {
|
||||
return fmt.Errorf("没有数据可导出")
|
||||
}
|
||||
|
||||
// 创建导出数据格式
|
||||
exportData := map[string]interface{}{
|
||||
"items": parsedResult.Items,
|
||||
"heroes": parsedResult.Heroes,
|
||||
}
|
||||
|
||||
// 序列化为JSON
|
||||
jsonData, err := json.MarshalIndent(exportData, "", " ")
|
||||
if err != nil {
|
||||
a.logger.Error("序列化数据失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
err = utils.WriteFile(filename, jsonData)
|
||||
if err != nil {
|
||||
a.logger.Error("写入文件失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
a.logger.Info("数据导出成功", "filename", filename, "items_count", len(parsedResult.Items), "heroes_count", len(parsedResult.Heroes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCurrentDataForExport 获取当前数据库中的数据,供前端导出使用
|
||||
func (a *App) GetCurrentDataForExport() (string, error) {
|
||||
if a.databaseService == nil {
|
||||
return "", fmt.Errorf("数据库服务未初始化")
|
||||
}
|
||||
|
||||
// 从数据库获取最新数据
|
||||
parsedResult, err := a.GetLatestParsedDataFromDatabase()
|
||||
if err != nil {
|
||||
a.logger.Error("获取数据库数据失败", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
if parsedResult == nil || (len(parsedResult.Items) == 0 && len(parsedResult.Heroes) == 0) {
|
||||
return "", fmt.Errorf("没有数据可导出")
|
||||
}
|
||||
|
||||
// 创建导出数据格式
|
||||
exportData := map[string]interface{}{
|
||||
"items": parsedResult.Items,
|
||||
"heroes": parsedResult.Heroes,
|
||||
}
|
||||
|
||||
// 序列化为JSON
|
||||
jsonData, err := json.MarshalIndent(exportData, "", " ")
|
||||
if err != nil {
|
||||
a.logger.Error("序列化数据失败", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(jsonData), nil
|
||||
}
|
||||
|
||||
// GetCaptureStatus 获取抓包状态
|
||||
func (a *App) GetCaptureStatus() model.CaptureStatus {
|
||||
return model.CaptureStatus{
|
||||
@@ -145,13 +247,9 @@ func (a *App) getStatusMessage() string {
|
||||
return "准备就绪"
|
||||
}
|
||||
|
||||
// ReadRawJsonFile 供前端调用,读取output_raw.json内容
|
||||
// ReadRawJsonFile 已废弃,请使用GetLatestParsedDataFromDatabase从数据库获取数据
|
||||
func (a *App) ReadRawJsonFile() (*model.ParsedResult, error) {
|
||||
data, err := ioutil.ReadFile("output_raw.json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.parserService.ReadRawJsonFile(string(data))
|
||||
return a.GetLatestParsedDataFromDatabase()
|
||||
}
|
||||
|
||||
// StopAndParseCapture 停止抓包并解析数据,供前端调用
|
||||
@@ -161,5 +259,101 @@ func (a *App) StopAndParseCapture() (*model.ParsedResult, error) {
|
||||
a.logger.Error("停止抓包并解析数据失败", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 将解析结果保存到数据库
|
||||
if a.databaseService != nil && result != nil {
|
||||
// 序列化装备数据
|
||||
itemsJSON := "[]"
|
||||
if result.Items != nil {
|
||||
if jsonData, err := json.Marshal(result.Items); err == nil {
|
||||
itemsJSON = string(jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
// 序列化英雄数据
|
||||
heroesJSON := "[]"
|
||||
if result.Heroes != nil {
|
||||
if jsonData, err := json.Marshal(result.Heroes); err == nil {
|
||||
heroesJSON = string(jsonData)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
sessionName := fmt.Sprintf("capture_%d", time.Now().Unix())
|
||||
if err := a.databaseService.SaveParsedDataToDatabase(sessionName, itemsJSON, heroesJSON); err != nil {
|
||||
a.logger.Error("保存解析数据到数据库失败", "error", err)
|
||||
// 不返回错误,因为解析成功了,只是保存失败
|
||||
} else {
|
||||
a.logger.Info("解析数据已保存到数据库", "session_name", sessionName)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ========== 数据库相关API ==========
|
||||
|
||||
// SaveParsedDataToDatabase 保存解析后的数据到数据库
|
||||
func (a *App) SaveParsedDataToDatabase(sessionName string, itemsJSON, heroesJSON string) error {
|
||||
if a.databaseService == nil {
|
||||
return fmt.Errorf("数据库服务未初始化")
|
||||
}
|
||||
return a.databaseService.SaveParsedDataToDatabase(sessionName, itemsJSON, heroesJSON)
|
||||
}
|
||||
|
||||
// GetLatestParsedDataFromDatabase 从数据库获取最新的解析数据
|
||||
func (a *App) GetLatestParsedDataFromDatabase() (*model.ParsedResult, error) {
|
||||
if a.databaseService == nil {
|
||||
return nil, fmt.Errorf("数据库服务未初始化")
|
||||
}
|
||||
|
||||
itemsJSON, heroesJSON, err := a.databaseService.GetLatestParsedDataFromDatabase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解析装备数据
|
||||
var items []interface{}
|
||||
if itemsJSON != "" {
|
||||
if err := json.Unmarshal([]byte(itemsJSON), &items); err != nil {
|
||||
return nil, fmt.Errorf("解析装备数据失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 解析英雄数据
|
||||
var heroes []interface{}
|
||||
if heroesJSON != "" {
|
||||
if err := json.Unmarshal([]byte(heroesJSON), &heroes); err != nil {
|
||||
return nil, fmt.Errorf("解析英雄数据失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &model.ParsedResult{
|
||||
Items: items,
|
||||
Heroes: heroes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SaveAppSetting 保存应用设置
|
||||
func (a *App) SaveAppSetting(key, value string) error {
|
||||
if a.databaseService == nil {
|
||||
return fmt.Errorf("数据库服务未初始化")
|
||||
}
|
||||
return a.databaseService.SaveAppSetting(key, value)
|
||||
}
|
||||
|
||||
// GetAppSetting 获取应用设置
|
||||
func (a *App) GetAppSetting(key string) (string, error) {
|
||||
if a.databaseService == nil {
|
||||
return "", fmt.Errorf("数据库服务未初始化")
|
||||
}
|
||||
return a.databaseService.GetAppSetting(key)
|
||||
}
|
||||
|
||||
// GetAllAppSettings 获取所有应用设置
|
||||
func (a *App) GetAllAppSettings() (map[string]string, error) {
|
||||
if a.databaseService == nil {
|
||||
return nil, fmt.Errorf("数据库服务未初始化")
|
||||
}
|
||||
return a.databaseService.GetAllAppSettings()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -67,12 +67,8 @@ func (ps *ParserService) ParseHexData(hexDataList []string) (*model.ParsedResult
|
||||
return nil, "", fmt.Errorf("远程json校验失败,data字段缺失或为空")
|
||||
}
|
||||
|
||||
// 校验通过再写入本地文件
|
||||
fileErr := ioutil.WriteFile("output_raw.json", body, 0644)
|
||||
if fileErr != nil {
|
||||
ps.logger.Error("写入原始json文件失败", "error", fileErr)
|
||||
}
|
||||
ps.logger.Info("远程原始数据已写入output_raw.json")
|
||||
// 校验通过,直接解析数据
|
||||
ps.logger.Info("远程原始数据校验通过,开始解析")
|
||||
parsedResult, err := ps.ReadRawJsonFile(string(body))
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
|
||||
@@ -1,85 +1,13 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"equipment-analyzer/internal/config"
|
||||
"equipment-analyzer/internal/utils"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadRawJsonFile(t *testing.T) {
|
||||
// 创建测试用的配置和日志器
|
||||
config := &config.Config{}
|
||||
logger := utils.NewLogger()
|
||||
|
||||
// 创建ParserService实例
|
||||
ps := NewParserService(config, logger)
|
||||
|
||||
// 调用ReadRawJsonFile方法
|
||||
result, err := ps.ReadRawJsonFile()
|
||||
if err != nil {
|
||||
t.Fatalf("ReadRawJsonFile failed: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Raw result length: %d\n", len(result))
|
||||
fmt.Printf("Raw result preview: %s\n", result[:min(200, len(result))])
|
||||
|
||||
// 解析JSON结果
|
||||
var parsedData map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(result), &parsedData); err != nil {
|
||||
t.Fatalf("Failed to parse JSON result: %v", err)
|
||||
}
|
||||
|
||||
// 检查数据结构
|
||||
fmt.Printf("Parsed data keys: %v\n", getKeys(parsedData))
|
||||
|
||||
// 检查items字段
|
||||
if items, exists := parsedData["items"]; exists {
|
||||
if itemsArray, ok := items.([]interface{}); ok {
|
||||
fmt.Printf("Items count: %d\n", len(itemsArray))
|
||||
if len(itemsArray) > 0 {
|
||||
fmt.Printf("First item: %+v\n", itemsArray[0])
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Items is not an array: %T\n", items)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Items field not found")
|
||||
}
|
||||
|
||||
// 检查heroes字段
|
||||
if heroes, exists := parsedData["heroes"]; exists {
|
||||
if heroesArray, ok := heroes.([]interface{}); ok {
|
||||
fmt.Printf("Heroes count: %d\n", len(heroesArray))
|
||||
if len(heroesArray) > 0 {
|
||||
fmt.Printf("First hero: %+v\n", heroesArray[0])
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("Heroes is not an array: %T\n", heroes)
|
||||
}
|
||||
} else {
|
||||
fmt.Println("Heroes field not found")
|
||||
}
|
||||
|
||||
// 如果没有数据,输出更多调试信息
|
||||
if len(result) < 100 {
|
||||
fmt.Printf("Result seems empty or very short: %q\n", result)
|
||||
}
|
||||
// 此测试已废弃,因为ReadRawJsonFile方法已被移除
|
||||
// 现在数据存储在SQLite数据库中,不再依赖output_raw.json文件
|
||||
t.Skip("ReadRawJsonFile测试已废弃,数据现在存储在SQLite数据库中")
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func getKeys(m map[string]interface{}) []string {
|
||||
keys := make([]string, 0, len(m))
|
||||
for k := range m {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
// 辅助函数已移除,因为测试已废弃
|
||||
|
||||
Reference in New Issue
Block a user