feat(database): add CRUD operations for parsed sessions and update session name functionality
This commit is contained in:
@@ -4,15 +4,18 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"equipment-analyzer/internal/capture"
|
||||
"equipment-analyzer/internal/config"
|
||||
"equipment-analyzer/internal/model"
|
||||
"equipment-analyzer/internal/utils"
|
||||
"github.com/wailsapp/wails/v2/pkg/runtime"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
ctx context.Context
|
||||
config *config.Config
|
||||
logger *utils.Logger
|
||||
captureService *CaptureService
|
||||
@@ -22,217 +25,228 @@ type App struct {
|
||||
}
|
||||
|
||||
func NewApp(cfg *config.Config, logger *utils.Logger) *App {
|
||||
// 初始化数据库
|
||||
// init database
|
||||
database, err := model.NewDatabase()
|
||||
if err != nil {
|
||||
logger.Error("初始化数据库失败", "error", err)
|
||||
// 如果数据库初始化失败,仍然创建应用,但数据库功能不可用
|
||||
logger.Error("database init failed", "error", err)
|
||||
// allow app to run without db features
|
||||
parserService := NewParserService(cfg, logger)
|
||||
return &App{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
captureService: NewCaptureService(cfg, logger),
|
||||
parserService: NewParserService(cfg, logger),
|
||||
captureService: NewCaptureService(cfg, logger, parserService),
|
||||
parserService: parserService,
|
||||
}
|
||||
}
|
||||
|
||||
databaseService := NewDatabaseService(database, logger)
|
||||
|
||||
parserService := NewParserService(cfg, logger)
|
||||
return &App{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
captureService: NewCaptureService(cfg, logger),
|
||||
parserService: NewParserService(cfg, logger),
|
||||
captureService: NewCaptureService(cfg, logger, parserService),
|
||||
parserService: parserService,
|
||||
database: database,
|
||||
databaseService: databaseService,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) Startup(ctx context.Context) {
|
||||
a.logger.Info("应用启动")
|
||||
a.ctx = ctx
|
||||
a.captureService.SetBeforeRemote(func() {
|
||||
if a.ctx != nil {
|
||||
runtime.EventsEmit(a.ctx, "capture:ready_to_parse")
|
||||
}
|
||||
})
|
||||
a.logger.Info("app startup")
|
||||
}
|
||||
|
||||
func (a *App) DomReady(ctx context.Context) {
|
||||
a.logger.Info("DOM准备就绪")
|
||||
a.logger.Info("dom ready")
|
||||
}
|
||||
|
||||
func (a *App) BeforeClose(ctx context.Context) (prevent bool) {
|
||||
a.logger.Info("应用即将关闭")
|
||||
a.logger.Info("app closing")
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *App) Shutdown(ctx context.Context) {
|
||||
a.logger.Info("应用关闭")
|
||||
a.logger.Info("app shutdown")
|
||||
|
||||
// 关闭数据库连接
|
||||
if a.database != nil {
|
||||
if err := a.database.Close(); err != nil {
|
||||
a.logger.Error("关闭数据库连接失败", "error", err)
|
||||
a.logger.Error("failed to close database connection", "error", err)
|
||||
} else {
|
||||
a.logger.Info("数据库连接已关闭")
|
||||
a.logger.Info("database connection closed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetNetworkInterfaces 获取网络接口列表
|
||||
// GetNetworkInterfaces returns available network interfaces.
|
||||
func (a *App) GetNetworkInterfaces() ([]model.NetworkInterface, error) {
|
||||
interfaces, err := capture.GetNetworkInterfaces()
|
||||
if err != nil {
|
||||
a.logger.Error("获取网络接口失败", "error", err)
|
||||
a.logger.Error("get network interfaces failed", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// StartCapture 开始抓包
|
||||
// StartCapture starts capture on the given interface.
|
||||
func (a *App) StartCapture(interfaceName string) error {
|
||||
if a.captureService.IsCapturing() {
|
||||
return fmt.Errorf("抓包已在进行中")
|
||||
return fmt.Errorf("capture already running")
|
||||
}
|
||||
|
||||
timeoutMs := a.config.Capture.DefaultTimeout
|
||||
if timeoutMs > 500 {
|
||||
log.Printf("[service] capture timeout too high (%dms), clamp to 500ms for responsive stop", timeoutMs)
|
||||
timeoutMs = 500
|
||||
}
|
||||
|
||||
config := capture.Config{
|
||||
InterfaceName: interfaceName,
|
||||
Filter: a.config.Capture.DefaultFilter,
|
||||
Timeout: time.Duration(a.config.Capture.DefaultTimeout) * time.Millisecond,
|
||||
Timeout: time.Duration(timeoutMs) * time.Millisecond,
|
||||
BufferSize: a.config.Capture.BufferSize,
|
||||
}
|
||||
|
||||
err := a.captureService.StartCapture(context.Background(), config)
|
||||
err := a.captureService.StartCaptureAsync(context.Background(), config, func() {
|
||||
if a.ctx != nil {
|
||||
runtime.EventsEmit(a.ctx, "capture:started")
|
||||
}
|
||||
}, func(err error) {
|
||||
a.logger.Error("start capture failed", "error", err)
|
||||
if a.ctx != nil {
|
||||
runtime.EventsEmit(a.ctx, "capture:start_failed", err.Error())
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
a.logger.Error("开始抓包失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
a.logger.Info("抓包开始", "interface", interfaceName)
|
||||
a.logger.Info("capture start requested", "interface", interfaceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopCapture 停止抓包
|
||||
// StopCapture stops capture.
|
||||
func (a *App) StopCapture() error {
|
||||
if !a.captureService.IsCapturing() {
|
||||
return fmt.Errorf("没有正在进行的抓包")
|
||||
return fmt.Errorf("capture not running")
|
||||
}
|
||||
|
||||
err := a.captureService.StopCapture()
|
||||
if err != nil {
|
||||
a.logger.Error("停止抓包失败", "error", err)
|
||||
a.logger.Error("stop capture failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 处理所有收集的数据
|
||||
a.captureService.ProcessAllData()
|
||||
|
||||
a.logger.Info("抓包停止")
|
||||
a.logger.Info("capture stopped")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCapturedData 获取抓包数据
|
||||
// GetCapturedData returns raw captured data.
|
||||
func (a *App) GetCapturedData() ([]string, error) {
|
||||
return a.captureService.GetCapturedData(), nil
|
||||
}
|
||||
|
||||
// ParseData 解析数据为JSON
|
||||
// ParseData parses captured data to JSON (remote parser).
|
||||
func (a *App) ParseData(hexDataList []string) (string, error) {
|
||||
_, rawJson, err := a.parserService.ParseHexData(hexDataList)
|
||||
if err != nil {
|
||||
a.logger.Error("解析数据失败", "error", err)
|
||||
a.logger.Error("parse data failed", "error", err)
|
||||
return "", err
|
||||
}
|
||||
return rawJson, nil
|
||||
}
|
||||
|
||||
// ExportData 导出数据到文件
|
||||
// ExportData exports data to a file.
|
||||
func (a *App) ExportData(hexDataList []string, filename string) error {
|
||||
result, rawJson, err := a.parserService.ParseHexData(hexDataList)
|
||||
if err != nil {
|
||||
a.logger.Error("解析数据失败", "error", err)
|
||||
a.logger.Error("parse data failed", "error", err)
|
||||
return err
|
||||
}
|
||||
// 这里可以添加文件写入逻辑
|
||||
a.logger.Info("导出数据", "filename", filename, "count", len(result.Items))
|
||||
// 简单示例:写入到当前目录
|
||||
|
||||
a.logger.Info("export data", "filename", filename, "count", len(result.Items))
|
||||
err = utils.WriteFile(filename, []byte(rawJson))
|
||||
if err != nil {
|
||||
a.logger.Error("写入文件失败", "error", err)
|
||||
a.logger.Error("write file failed", "error", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExportCurrentData 导出当前数据库中的数据到文件
|
||||
// ExportCurrentData exports latest data from database to a file.
|
||||
func (a *App) ExportCurrentData(filename string) error {
|
||||
if a.databaseService == nil {
|
||||
return fmt.Errorf("数据库服务未初始化")
|
||||
return fmt.Errorf("database service not initialized")
|
||||
}
|
||||
|
||||
// 从数据库获取最新数据
|
||||
|
||||
parsedResult, err := a.GetLatestParsedDataFromDatabase()
|
||||
if err != nil {
|
||||
a.logger.Error("获取数据库数据失败", "error", err)
|
||||
a.logger.Error("failed to load data from database", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
if parsedResult == nil || (len(parsedResult.Items) == 0 && len(parsedResult.Heroes) == 0) {
|
||||
return fmt.Errorf("没有数据可导出")
|
||||
return fmt.Errorf("no data to export")
|
||||
}
|
||||
|
||||
// 创建导出数据格式
|
||||
|
||||
exportData := map[string]interface{}{
|
||||
"items": parsedResult.Items,
|
||||
"heroes": parsedResult.Heroes,
|
||||
}
|
||||
|
||||
// 序列化为JSON
|
||||
|
||||
jsonData, err := json.MarshalIndent(exportData, "", " ")
|
||||
if err != nil {
|
||||
a.logger.Error("序列化数据失败", "error", err)
|
||||
a.logger.Error("failed to marshal data", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
|
||||
err = utils.WriteFile(filename, jsonData)
|
||||
if err != nil {
|
||||
a.logger.Error("写入文件失败", "error", err)
|
||||
a.logger.Error("write file failed", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
a.logger.Info("数据导出成功", "filename", filename, "items_count", len(parsedResult.Items), "heroes_count", len(parsedResult.Heroes))
|
||||
|
||||
a.logger.Info("export current data succeeded", "filename", filename, "items_count", len(parsedResult.Items), "heroes_count", len(parsedResult.Heroes))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCurrentDataForExport 获取当前数据库中的数据,供前端导出使用
|
||||
// GetCurrentDataForExport returns latest data from database as JSON string.
|
||||
func (a *App) GetCurrentDataForExport() (string, error) {
|
||||
if a.databaseService == nil {
|
||||
return "", fmt.Errorf("数据库服务未初始化")
|
||||
return "", fmt.Errorf("database service not initialized")
|
||||
}
|
||||
|
||||
// 从数据库获取最新数据
|
||||
|
||||
parsedResult, err := a.GetLatestParsedDataFromDatabase()
|
||||
if err != nil {
|
||||
a.logger.Error("获取数据库数据失败", "error", err)
|
||||
a.logger.Error("failed to load data from database", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
if parsedResult == nil || (len(parsedResult.Items) == 0 && len(parsedResult.Heroes) == 0) {
|
||||
return "", fmt.Errorf("没有数据可导出")
|
||||
return "", fmt.Errorf("no data to export")
|
||||
}
|
||||
|
||||
// 创建导出数据格式
|
||||
|
||||
exportData := map[string]interface{}{
|
||||
"items": parsedResult.Items,
|
||||
"heroes": parsedResult.Heroes,
|
||||
}
|
||||
|
||||
// 序列化为JSON
|
||||
|
||||
jsonData, err := json.MarshalIndent(exportData, "", " ")
|
||||
if err != nil {
|
||||
a.logger.Error("序列化数据失败", "error", err)
|
||||
a.logger.Error("failed to marshal data", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
|
||||
return string(jsonData), nil
|
||||
}
|
||||
|
||||
// GetCaptureStatus 获取抓包状态
|
||||
// GetCaptureStatus returns capture status.
|
||||
func (a *App) GetCaptureStatus() model.CaptureStatus {
|
||||
return model.CaptureStatus{
|
||||
IsCapturing: a.captureService.IsCapturing(),
|
||||
@@ -242,27 +256,29 @@ func (a *App) GetCaptureStatus() model.CaptureStatus {
|
||||
|
||||
func (a *App) getStatusMessage() string {
|
||||
if a.captureService.IsCapturing() {
|
||||
return "正在抓包..."
|
||||
return "capturing"
|
||||
}
|
||||
return "准备就绪"
|
||||
if a.captureService.IsStarting() {
|
||||
return "starting"
|
||||
}
|
||||
return "ready"
|
||||
}
|
||||
|
||||
// ReadRawJsonFile 已废弃,请使用GetLatestParsedDataFromDatabase从数据库获取数据
|
||||
// ReadRawJsonFile is deprecated; use GetLatestParsedDataFromDatabase.
|
||||
func (a *App) ReadRawJsonFile() (*model.ParsedResult, error) {
|
||||
return a.GetLatestParsedDataFromDatabase()
|
||||
}
|
||||
|
||||
// StopAndParseCapture 停止抓包并解析数据,供前端调用
|
||||
// StopAndParseCapture stops capture and parses data.
|
||||
func (a *App) StopAndParseCapture() (*model.ParsedResult, error) {
|
||||
result, err := a.captureService.StopAndParseCapture(a.parserService)
|
||||
log.Printf("[service] StopAndParseCapture entry")
|
||||
result, err := a.captureService.StopAndParseCapture()
|
||||
if err != nil {
|
||||
a.logger.Error("停止抓包并解析数据失败", "error", err)
|
||||
a.logger.Error("stop and parse capture failed", "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 {
|
||||
@@ -270,7 +286,6 @@ func (a *App) StopAndParseCapture() (*model.ParsedResult, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 序列化英雄数据
|
||||
heroesJSON := "[]"
|
||||
if result.Heroes != nil {
|
||||
if jsonData, err := json.Marshal(result.Heroes); err == nil {
|
||||
@@ -278,33 +293,29 @@ func (a *App) StopAndParseCapture() (*model.ParsedResult, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
sessionName := fmt.Sprintf("capture_%d", time.Now().Unix())
|
||||
if err := a.databaseService.SaveParsedDataToDatabase(sessionName, itemsJSON, heroesJSON); err != nil {
|
||||
a.logger.Error("保存解析数据到数据库失败", "error", err)
|
||||
// 不返回错误,因为解析成功了,只是保存失败
|
||||
a.logger.Error("save parsed data failed", "error", err)
|
||||
} else {
|
||||
a.logger.Info("解析数据已保存到数据库", "session_name", sessionName)
|
||||
a.logger.Info("parsed data saved", "session_name", sessionName)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ========== 数据库相关API ==========
|
||||
|
||||
// SaveParsedDataToDatabase 保存解析后的数据到数据库
|
||||
// SaveParsedDataToDatabase saves parsed data.
|
||||
func (a *App) SaveParsedDataToDatabase(sessionName string, itemsJSON, heroesJSON string) error {
|
||||
if a.databaseService == nil {
|
||||
return fmt.Errorf("数据库服务未初始化")
|
||||
return fmt.Errorf("database service not initialized")
|
||||
}
|
||||
return a.databaseService.SaveParsedDataToDatabase(sessionName, itemsJSON, heroesJSON)
|
||||
}
|
||||
|
||||
// GetLatestParsedDataFromDatabase 从数据库获取最新的解析数据
|
||||
// GetLatestParsedDataFromDatabase returns latest parsed data from database.
|
||||
func (a *App) GetLatestParsedDataFromDatabase() (*model.ParsedResult, error) {
|
||||
if a.databaseService == nil {
|
||||
return nil, fmt.Errorf("数据库服务未初始化")
|
||||
return nil, fmt.Errorf("database service not initialized")
|
||||
}
|
||||
|
||||
itemsJSON, heroesJSON, err := a.databaseService.GetLatestParsedDataFromDatabase()
|
||||
@@ -312,19 +323,17 @@ func (a *App) GetLatestParsedDataFromDatabase() (*model.ParsedResult, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解析装备数据
|
||||
var items []interface{}
|
||||
if itemsJSON != "" {
|
||||
if err := json.Unmarshal([]byte(itemsJSON), &items); err != nil {
|
||||
return nil, fmt.Errorf("解析装备数据失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal items: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 解析英雄数据
|
||||
var heroes []interface{}
|
||||
if heroesJSON != "" {
|
||||
if err := json.Unmarshal([]byte(heroesJSON), &heroes); err != nil {
|
||||
return nil, fmt.Errorf("解析英雄数据失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to unmarshal heroes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,26 +343,125 @@ func (a *App) GetLatestParsedDataFromDatabase() (*model.ParsedResult, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SaveAppSetting 保存应用设置
|
||||
// GetParsedSessions returns all parsed sessions.
|
||||
func (a *App) GetParsedSessions() ([]model.ParsedSession, error) {
|
||||
if a.databaseService == nil {
|
||||
return nil, fmt.Errorf("database service not initialized")
|
||||
}
|
||||
return a.databaseService.GetParsedSessions()
|
||||
}
|
||||
|
||||
// GetParsedDataByID returns parsed data by session id.
|
||||
func (a *App) GetParsedDataByID(id int64) (*model.ParsedResult, error) {
|
||||
if a.databaseService == nil {
|
||||
return nil, fmt.Errorf("database service not initialized")
|
||||
}
|
||||
itemsJSON, heroesJSON, err := a.databaseService.GetParsedDataByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var items []interface{}
|
||||
if itemsJSON != "" {
|
||||
if err := json.Unmarshal([]byte(itemsJSON), &items); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal items: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var heroes []interface{}
|
||||
if heroesJSON != "" {
|
||||
if err := json.Unmarshal([]byte(heroesJSON), &heroes); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal heroes: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return &model.ParsedResult{
|
||||
Items: items,
|
||||
Heroes: heroes,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UpdateParsedSessionName updates session name.
|
||||
func (a *App) UpdateParsedSessionName(id int64, name string) error {
|
||||
if a.databaseService == nil {
|
||||
return fmt.Errorf("database service not initialized")
|
||||
}
|
||||
if name == "" {
|
||||
return fmt.Errorf("session name cannot be empty")
|
||||
}
|
||||
return a.databaseService.UpdateParsedSessionName(id, name)
|
||||
}
|
||||
|
||||
// DeleteParsedSession deletes a parsed session by id.
|
||||
func (a *App) DeleteParsedSession(id int64) error {
|
||||
if a.databaseService == nil {
|
||||
return fmt.Errorf("database service not initialized")
|
||||
}
|
||||
return a.databaseService.DeleteParsedSession(id)
|
||||
}
|
||||
|
||||
// SaveAppSetting saves app setting.
|
||||
func (a *App) SaveAppSetting(key, value string) error {
|
||||
if a.databaseService == nil {
|
||||
return fmt.Errorf("数据库服务未初始化")
|
||||
return fmt.Errorf("database service not initialized")
|
||||
}
|
||||
return a.databaseService.SaveAppSetting(key, value)
|
||||
}
|
||||
|
||||
// GetAppSetting 获取应用设置
|
||||
// GetAppSetting gets app setting.
|
||||
func (a *App) GetAppSetting(key string) (string, error) {
|
||||
if a.databaseService == nil {
|
||||
return "", fmt.Errorf("数据库服务未初始化")
|
||||
return "", fmt.Errorf("database service not initialized")
|
||||
}
|
||||
return a.databaseService.GetAppSetting(key)
|
||||
}
|
||||
|
||||
// GetAllAppSettings 获取所有应用设置
|
||||
// GetAllAppSettings gets all app settings.
|
||||
func (a *App) GetAllAppSettings() (map[string]string, error) {
|
||||
if a.databaseService == nil {
|
||||
return nil, fmt.Errorf("数据库服务未初始化")
|
||||
return nil, fmt.Errorf("database service not initialized")
|
||||
}
|
||||
return a.databaseService.GetAllAppSettings()
|
||||
}
|
||||
|
||||
// StartCaptureWithFilter allows frontend to provide a custom BPF filter.
|
||||
func (a *App) StartCaptureWithFilter(interfaceName string, filter string) error {
|
||||
a.logger.Info("StartCaptureWithFilter requested", "interface", interfaceName, "filter", filter)
|
||||
if a.captureService.IsCapturing() {
|
||||
return fmt.Errorf("capture already running")
|
||||
}
|
||||
|
||||
useFilter := filter
|
||||
if useFilter == "" {
|
||||
useFilter = a.config.Capture.DefaultFilter
|
||||
}
|
||||
|
||||
timeoutMs := a.config.Capture.DefaultTimeout
|
||||
if timeoutMs > 500 {
|
||||
log.Printf("[service] capture timeout too high (%dms), clamp to 500ms for responsive stop", timeoutMs)
|
||||
timeoutMs = 500
|
||||
}
|
||||
|
||||
config := capture.Config{
|
||||
InterfaceName: interfaceName,
|
||||
Filter: useFilter,
|
||||
Timeout: time.Duration(timeoutMs) * time.Millisecond,
|
||||
BufferSize: a.config.Capture.BufferSize,
|
||||
}
|
||||
|
||||
err := a.captureService.StartCaptureAsync(context.Background(), config, func() {
|
||||
if a.ctx != nil {
|
||||
runtime.EventsEmit(a.ctx, "capture:started")
|
||||
}
|
||||
}, func(err error) {
|
||||
a.logger.Error("start capture failed", "error", err)
|
||||
if a.ctx != nil {
|
||||
runtime.EventsEmit(a.ctx, "capture:start_failed", err.Error())
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a.logger.Info("capture start requested", "interface", interfaceName, "filter", useFilter)
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user