feat(database): add CRUD operations for parsed sessions and update session name functionality
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"equipment-analyzer/internal/model"
|
||||
@@ -13,13 +17,28 @@ import (
|
||||
)
|
||||
|
||||
type PacketCapture struct {
|
||||
handle *pcap.Handle
|
||||
handles []*pcap.Handle
|
||||
isCapturing bool
|
||||
stopChan chan bool
|
||||
stopChan chan struct{}
|
||||
mutex sync.RWMutex
|
||||
tcpProcessor *TCPProcessor
|
||||
dataChan chan *model.TCPData
|
||||
errorChan chan error
|
||||
totalPackets uint64
|
||||
tcpPackets uint64
|
||||
payloads uint64
|
||||
portCounts map[uint16]uint64
|
||||
ipCounts map[string]uint64
|
||||
}
|
||||
|
||||
type CaptureStats struct {
|
||||
TotalPackets uint64
|
||||
TCPPackets uint64
|
||||
PayloadPackets uint64
|
||||
AckGroups int
|
||||
UniquePayloads int
|
||||
RawSegments int
|
||||
FinalBuffers int
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -31,10 +50,12 @@ type Config struct {
|
||||
|
||||
func NewPacketCapture() *PacketCapture {
|
||||
return &PacketCapture{
|
||||
stopChan: make(chan bool),
|
||||
stopChan: make(chan struct{}),
|
||||
tcpProcessor: NewTCPProcessor(),
|
||||
dataChan: make(chan *model.TCPData, 1000),
|
||||
errorChan: make(chan error, 100),
|
||||
portCounts: make(map[uint16]uint64),
|
||||
ipCounts: make(map[string]uint64),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,46 +67,93 @@ func (pc *PacketCapture) Start(config Config) error {
|
||||
return fmt.Errorf("capture already running")
|
||||
}
|
||||
|
||||
// 打开网络接口
|
||||
handle, err := pcap.OpenLive(
|
||||
config.InterfaceName,
|
||||
int32(config.BufferSize),
|
||||
true, // promiscuous
|
||||
config.Timeout,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open interface: %v", err)
|
||||
atomic.StoreUint64(&pc.totalPackets, 0)
|
||||
atomic.StoreUint64(&pc.tcpPackets, 0)
|
||||
atomic.StoreUint64(&pc.payloads, 0)
|
||||
pc.resetStats()
|
||||
|
||||
if pc.stopChan == nil {
|
||||
pc.stopChan = make(chan struct{})
|
||||
}
|
||||
|
||||
// 设置过滤器
|
||||
if err := handle.SetBPFFilter(config.Filter); err != nil {
|
||||
handle.Close()
|
||||
return fmt.Errorf("failed to set filter: %v", err)
|
||||
interfaces := []string{}
|
||||
if config.InterfaceName != "" {
|
||||
interfaces = []string{config.InterfaceName}
|
||||
} else {
|
||||
interfaces = findWorkingInterfaces()
|
||||
}
|
||||
|
||||
pc.handle = handle
|
||||
log.Printf("[capture] start: interfaces=%v filter=%q timeout=%s buffer_size=%d",
|
||||
interfaces, config.Filter, config.Timeout, config.BufferSize)
|
||||
|
||||
var opened []*pcap.Handle
|
||||
for _, iface := range interfaces {
|
||||
handle, err := pcap.OpenLive(
|
||||
iface,
|
||||
int32(config.BufferSize),
|
||||
true,
|
||||
config.Timeout,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("[capture] OpenLive failed: interface=%s err=%v", iface, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if config.Filter != "" {
|
||||
if err := handle.SetBPFFilter(config.Filter); err != nil {
|
||||
handle.Close()
|
||||
log.Printf("[capture] SetBPFFilter failed: interface=%s filter=%q err=%v", iface, config.Filter, err)
|
||||
continue
|
||||
}
|
||||
log.Printf("[capture] SetBPFFilter applied: interface=%s filter=%q", iface, config.Filter)
|
||||
} else {
|
||||
log.Printf("[capture] SetBPFFilter skipped: interface=%s filter=empty", iface)
|
||||
}
|
||||
opened = append(opened, handle)
|
||||
}
|
||||
|
||||
if len(opened) == 0 {
|
||||
return fmt.Errorf("failed to open any interfaces")
|
||||
}
|
||||
|
||||
pc.handles = opened
|
||||
pc.isCapturing = true
|
||||
|
||||
// 启动抓包协程
|
||||
go pc.captureLoop()
|
||||
for _, h := range pc.handles {
|
||||
go pc.captureLoop(h)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) Stop() {
|
||||
pc.mutex.Lock()
|
||||
defer pc.mutex.Unlock()
|
||||
|
||||
if !pc.isCapturing {
|
||||
pc.mutex.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
pc.isCapturing = false
|
||||
close(pc.stopChan)
|
||||
|
||||
if pc.handle != nil {
|
||||
pc.handle.Close()
|
||||
if pc.stopChan != nil {
|
||||
close(pc.stopChan)
|
||||
pc.stopChan = nil
|
||||
}
|
||||
|
||||
stats := pc.GetStats()
|
||||
portsSnapshot, ipsSnapshot := pc.snapshotStatsLocked()
|
||||
|
||||
for _, h := range pc.handles {
|
||||
if h != nil {
|
||||
h.Close()
|
||||
}
|
||||
}
|
||||
pc.handles = nil
|
||||
|
||||
pc.mutex.Unlock()
|
||||
|
||||
log.Printf("[capture] stop: total=%d tcp=%d payload=%d ack_groups=%d unique_payloads=%d raw_segments=%d final_buffers=%d",
|
||||
stats.TotalPackets, stats.TCPPackets, stats.PayloadPackets, stats.AckGroups, stats.UniquePayloads, stats.RawSegments, stats.FinalBuffers)
|
||||
pc.logTopStatsFromSnapshots(portsSnapshot, ipsSnapshot)
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) IsCapturing() bool {
|
||||
@@ -94,10 +162,9 @@ func (pc *PacketCapture) IsCapturing() bool {
|
||||
return pc.isCapturing
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) captureLoop() {
|
||||
packetSource := gopacket.NewPacketSource(pc.handle, pc.handle.LinkType())
|
||||
|
||||
log.Println("[抓包] 开始监听数据包...")
|
||||
func (pc *PacketCapture) captureLoop(handle *pcap.Handle) {
|
||||
packetSource := gopacket.NewPacketSource(handle, handle.LinkType())
|
||||
log.Println("[capture] packet loop started")
|
||||
|
||||
for {
|
||||
select {
|
||||
@@ -106,37 +173,73 @@ func (pc *PacketCapture) captureLoop() {
|
||||
default:
|
||||
packet, err := packetSource.NextPacket()
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) || err.Error() == "EOF" {
|
||||
return
|
||||
}
|
||||
if err.Error() == "Timeout Expired" {
|
||||
// 静默跳过超时
|
||||
continue
|
||||
}
|
||||
log.Printf("Error reading packet: %v", err)
|
||||
log.Printf("[capture] NextPacket error: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 处理TCP包
|
||||
atomic.AddUint64(&pc.totalPackets, 1)
|
||||
pc.processTCPPacket(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findWorkingInterfaces() []string {
|
||||
devs, err := pcap.FindAllDevs()
|
||||
if err != nil {
|
||||
log.Printf("[capture] FindAllDevs failed: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, dev := range devs {
|
||||
log.Printf("[capture] iface: name=%s desc=%q addrs=%v", dev.Name, dev.Description, dev.Addresses)
|
||||
}
|
||||
|
||||
all := make([]string, 0, len(devs))
|
||||
for _, dev := range devs {
|
||||
all = append(all, dev.Name)
|
||||
}
|
||||
return uniqueStrings(all)
|
||||
}
|
||||
|
||||
func uniqueStrings(in []string) []string {
|
||||
seen := make(map[string]struct{}, len(in))
|
||||
out := make([]string, 0, len(in))
|
||||
for _, v := range in {
|
||||
if _, ok := seen[v]; ok {
|
||||
continue
|
||||
}
|
||||
seen[v] = struct{}{}
|
||||
out = append(out, v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) processTCPPacket(packet gopacket.Packet) {
|
||||
ipLayer := packet.Layer(layers.LayerTypeIPv4)
|
||||
|
||||
tcpLayer := packet.Layer(layers.LayerTypeTCP)
|
||||
if tcpLayer == nil {
|
||||
return
|
||||
}
|
||||
atomic.AddUint64(&pc.tcpPackets, 1)
|
||||
|
||||
tcp, ok := tcpLayer.(*layers.TCP)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 提取TCP负载
|
||||
if len(tcp.Payload) == 0 {
|
||||
return
|
||||
}
|
||||
atomic.AddUint64(&pc.payloads, 1)
|
||||
pc.recordTCPStats(tcp, ipLayer)
|
||||
|
||||
// 创建TCP数据包
|
||||
tcpData := &model.TCPData{
|
||||
Payload: tcp.Payload,
|
||||
Seq: uint32(tcp.Seq),
|
||||
@@ -145,18 +248,101 @@ func (pc *PacketCapture) processTCPPacket(packet gopacket.Packet) {
|
||||
DstPort: uint16(tcp.DstPort),
|
||||
}
|
||||
|
||||
// 发送给TCP处理器
|
||||
pc.tcpProcessor.ProcessPacket(tcpData)
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) resetStats() {
|
||||
pc.portCounts = make(map[uint16]uint64)
|
||||
pc.ipCounts = make(map[string]uint64)
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) recordTCPStats(tcp *layers.TCP, ipLayer gopacket.Layer) {
|
||||
pc.mutex.Lock()
|
||||
defer pc.mutex.Unlock()
|
||||
|
||||
pc.portCounts[uint16(tcp.SrcPort)]++
|
||||
pc.portCounts[uint16(tcp.DstPort)]++
|
||||
|
||||
if ipLayer == nil {
|
||||
return
|
||||
}
|
||||
if ip, ok := ipLayer.(*layers.IPv4); ok {
|
||||
pc.ipCounts[ip.SrcIP.String()]++
|
||||
pc.ipCounts[ip.DstIP.String()]++
|
||||
}
|
||||
}
|
||||
|
||||
type kvU16 struct {
|
||||
Key uint16
|
||||
Value uint64
|
||||
}
|
||||
|
||||
type kvStr struct {
|
||||
Key string
|
||||
Value uint64
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) snapshotStatsLocked() ([]kvU16, []kvStr) {
|
||||
ports := make([]kvU16, 0, len(pc.portCounts))
|
||||
for k, v := range pc.portCounts {
|
||||
ports = append(ports, kvU16{Key: k, Value: v})
|
||||
}
|
||||
|
||||
ips := make([]kvStr, 0, len(pc.ipCounts))
|
||||
for k, v := range pc.ipCounts {
|
||||
ips = append(ips, kvStr{Key: k, Value: v})
|
||||
}
|
||||
|
||||
return ports, ips
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) logTopStatsFromSnapshots(ports []kvU16, ips []kvStr) {
|
||||
sort.Slice(ports, func(i, j int) bool { return ports[i].Value > ports[j].Value })
|
||||
sort.Slice(ips, func(i, j int) bool { return ips[i].Value > ips[j].Value })
|
||||
|
||||
maxPorts := 10
|
||||
if len(ports) < maxPorts {
|
||||
maxPorts = len(ports)
|
||||
}
|
||||
maxIPs := 10
|
||||
if len(ips) < maxIPs {
|
||||
maxIPs = len(ips)
|
||||
}
|
||||
|
||||
log.Printf("[capture] top tcp ports: %v", ports[:maxPorts])
|
||||
log.Printf("[capture] top tcp ips: %v", ips[:maxIPs])
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) GetCapturedData() []string {
|
||||
return pc.tcpProcessor.GetFinalBuffer()
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) GetPortCount(port uint16) uint64 {
|
||||
pc.mutex.RLock()
|
||||
defer pc.mutex.RUnlock()
|
||||
return pc.portCounts[port]
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) ProcessAllData() {
|
||||
start := time.Now()
|
||||
log.Printf("[capture] ProcessAllData started")
|
||||
pc.tcpProcessor.ProcessAllData()
|
||||
log.Printf("[capture] ProcessAllData finished in %s", time.Since(start))
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) Clear() {
|
||||
pc.tcpProcessor.Clear()
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) GetStats() CaptureStats {
|
||||
processorStats := pc.tcpProcessor.Stats()
|
||||
return CaptureStats{
|
||||
TotalPackets: atomic.LoadUint64(&pc.totalPackets),
|
||||
TCPPackets: atomic.LoadUint64(&pc.tcpPackets),
|
||||
PayloadPackets: atomic.LoadUint64(&pc.payloads),
|
||||
AckGroups: processorStats.AckGroups,
|
||||
UniquePayloads: processorStats.UniquePayloads,
|
||||
RawSegments: processorStats.RawSegmentCount,
|
||||
FinalBuffers: processorStats.FinalBufferSize,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,13 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type ProcessorStats struct {
|
||||
AckGroups int
|
||||
UniquePayloads int
|
||||
RawSegmentCount int
|
||||
FinalBufferSize int
|
||||
}
|
||||
|
||||
type TCPProcessor struct {
|
||||
mutex sync.RWMutex
|
||||
ackData map[uint32][]*model.TCPData
|
||||
@@ -94,3 +101,20 @@ func (tp *TCPProcessor) Clear() {
|
||||
tp.finalBuffer = make([]string, 0)
|
||||
tp.loads = make(map[string]bool)
|
||||
}
|
||||
|
||||
func (tp *TCPProcessor) Stats() ProcessorStats {
|
||||
tp.mutex.RLock()
|
||||
defer tp.mutex.RUnlock()
|
||||
|
||||
rawSegmentCount := 0
|
||||
for _, dataList := range tp.ackData {
|
||||
rawSegmentCount += len(dataList)
|
||||
}
|
||||
|
||||
return ProcessorStats{
|
||||
AckGroups: len(tp.ackData),
|
||||
UniquePayloads: len(tp.loads),
|
||||
RawSegmentCount: rawSegmentCount,
|
||||
FinalBufferSize: len(tp.finalBuffer),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@ package model
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// Database 数据库管理器
|
||||
@@ -18,31 +19,37 @@ type Database struct {
|
||||
// NewDatabase 创建新的数据库连接
|
||||
func NewDatabase() (*Database, error) {
|
||||
dbPath := getDatabasePath()
|
||||
|
||||
log.Printf("[db] init: path=%s", dbPath)
|
||||
|
||||
// 确保目录存在
|
||||
dir := filepath.Dir(dbPath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
log.Printf("[db] mkdir failed: dir=%s err=%v", dir, err)
|
||||
return nil, fmt.Errorf("创建数据库目录失败: %w", err)
|
||||
}
|
||||
|
||||
// 连接数据库
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
db, err := sql.Open("sqlite", dbPath)
|
||||
if err != nil {
|
||||
log.Printf("[db] open failed: path=%s err=%v", dbPath, err)
|
||||
return nil, fmt.Errorf("连接数据库失败: %w", err)
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
if err := db.Ping(); err != nil {
|
||||
log.Printf("[db] ping failed: err=%v", err)
|
||||
return nil, fmt.Errorf("数据库连接测试失败: %w", err)
|
||||
}
|
||||
|
||||
database := &Database{db: db}
|
||||
|
||||
|
||||
// 初始化表结构
|
||||
if err := database.initTables(); err != nil {
|
||||
log.Printf("[db] init tables failed: err=%v", err)
|
||||
return nil, fmt.Errorf("初始化数据库表失败: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("[db] init ok")
|
||||
return database, nil
|
||||
}
|
||||
|
||||
@@ -115,13 +122,76 @@ func (d *Database) GetLatestParsedData() (string, string, error) {
|
||||
var itemsJSON, heroesJSON string
|
||||
err := d.db.QueryRow(stmt).Scan(&itemsJSON, &heroesJSON)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return "", "", nil
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return itemsJSON, heroesJSON, nil
|
||||
}
|
||||
|
||||
// GetParsedSessions 获取所有解析会话
|
||||
func (d *Database) GetParsedSessions() ([]ParsedSession, error) {
|
||||
stmt := `
|
||||
SELECT id, session_name, created_at
|
||||
FROM parsed_data
|
||||
ORDER BY created_at DESC`
|
||||
|
||||
rows, err := d.db.Query(stmt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
sessions := make([]ParsedSession, 0)
|
||||
for rows.Next() {
|
||||
var s ParsedSession
|
||||
if err := rows.Scan(&s.ID, &s.SessionName, &s.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sessions = append(sessions, s)
|
||||
}
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
// GetParsedDataByID 获取指定会话的数据
|
||||
func (d *Database) GetParsedDataByID(id int64) (string, string, error) {
|
||||
stmt := `
|
||||
SELECT items_json, heroes_json
|
||||
FROM parsed_data
|
||||
WHERE id = ?
|
||||
LIMIT 1`
|
||||
|
||||
var itemsJSON, heroesJSON string
|
||||
err := d.db.QueryRow(stmt, id).Scan(&itemsJSON, &heroesJSON)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return "", "", nil
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
return itemsJSON, heroesJSON, nil
|
||||
}
|
||||
|
||||
// UpdateParsedSessionName 更新解析会话名称
|
||||
func (d *Database) UpdateParsedSessionName(id int64, name string) error {
|
||||
stmt := `
|
||||
UPDATE parsed_data
|
||||
SET session_name = ?
|
||||
WHERE id = ?`
|
||||
_, err := d.db.Exec(stmt, name, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteParsedSession 删除解析会话
|
||||
func (d *Database) DeleteParsedSession(id int64) error {
|
||||
stmt := `
|
||||
DELETE FROM parsed_data
|
||||
WHERE id = ?`
|
||||
_, err := d.db.Exec(stmt, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// SaveSetting 保存应用设置
|
||||
func (d *Database) SaveSetting(key, value string) error {
|
||||
@@ -160,4 +230,4 @@ func (d *Database) GetAllSettings() (map[string]string, error) {
|
||||
}
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,3 +35,10 @@ type ParsedResult struct {
|
||||
Items []interface{} `json:"items"`
|
||||
Heroes []interface{} `json:"heroes"`
|
||||
}
|
||||
|
||||
// ParsedSession 解析数据会话信息
|
||||
type ParsedSession struct {
|
||||
ID int64 `json:"id"`
|
||||
SessionName string `json:"session_name"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -17,54 +18,110 @@ type CaptureService struct {
|
||||
logger *utils.Logger
|
||||
packetCapture *capture.PacketCapture
|
||||
processor *capture.TCPProcessor
|
||||
parser *ParserService
|
||||
beforeRemote func()
|
||||
mutex sync.RWMutex
|
||||
isCapturing bool
|
||||
isStarting bool
|
||||
pendingStop bool
|
||||
dataChan chan *model.CaptureResult
|
||||
errorChan chan error
|
||||
}
|
||||
|
||||
func NewCaptureService(cfg *config.Config, logger *utils.Logger) *CaptureService {
|
||||
func NewCaptureService(cfg *config.Config, logger *utils.Logger, parser *ParserService) *CaptureService {
|
||||
return &CaptureService{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
packetCapture: capture.NewPacketCapture(),
|
||||
processor: capture.NewTCPProcessor(),
|
||||
parser: parser,
|
||||
dataChan: make(chan *model.CaptureResult, 100),
|
||||
errorChan: make(chan error, 100),
|
||||
}
|
||||
}
|
||||
|
||||
// StartCapture 开始抓包
|
||||
func (cs *CaptureService) StartCapture(ctx context.Context, config capture.Config) error {
|
||||
func (cs *CaptureService) SetBeforeRemote(fn func()) {
|
||||
cs.mutex.Lock()
|
||||
defer cs.mutex.Unlock()
|
||||
cs.beforeRemote = fn
|
||||
}
|
||||
|
||||
func (cs *CaptureService) StartCaptureAsync(ctx context.Context, config capture.Config, onStarted func(), onError func(error)) error {
|
||||
cs.mutex.Lock()
|
||||
defer cs.mutex.Unlock()
|
||||
|
||||
if cs.isCapturing {
|
||||
if cs.isCapturing || cs.isStarting {
|
||||
return fmt.Errorf("capture already running")
|
||||
}
|
||||
|
||||
if err := cs.packetCapture.Start(config); err != nil {
|
||||
return fmt.Errorf("failed to start capture: %w", err)
|
||||
}
|
||||
cs.isStarting = true
|
||||
cs.pendingStop = false
|
||||
|
||||
cs.isCapturing = true
|
||||
cs.logger.Info("Packet capture started", "interface", config.InterfaceName)
|
||||
cs.logger.Info("StartCapture requested",
|
||||
"interface", config.InterfaceName,
|
||||
"filter", config.Filter,
|
||||
"timeout_ms", config.Timeout.Milliseconds(),
|
||||
"buffer_size", config.BufferSize,
|
||||
)
|
||||
|
||||
go func() {
|
||||
if err := cs.packetCapture.Start(config); err != nil {
|
||||
cs.logger.Error("Packet capture start failed", "error", err)
|
||||
cs.mutex.Lock()
|
||||
cs.isStarting = false
|
||||
cs.mutex.Unlock()
|
||||
if onError != nil {
|
||||
onError(fmt.Errorf("failed to start capture: %w", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var shouldStop bool
|
||||
cs.mutex.Lock()
|
||||
cs.isStarting = false
|
||||
cs.isCapturing = true
|
||||
shouldStop = cs.pendingStop
|
||||
cs.pendingStop = false
|
||||
cs.mutex.Unlock()
|
||||
|
||||
cs.logger.Info("Packet capture started", "interface", config.InterfaceName)
|
||||
if onStarted != nil {
|
||||
onStarted()
|
||||
}
|
||||
if shouldStop {
|
||||
_, _ = cs.StopAndParseCapture()
|
||||
}
|
||||
}()
|
||||
|
||||
// 启动数据处理协程
|
||||
go cs.processData(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopCapture 停止抓包
|
||||
func (cs *CaptureService) StopCapture() error {
|
||||
cs.mutex.Lock()
|
||||
defer cs.mutex.Unlock()
|
||||
|
||||
if cs.isStarting {
|
||||
cs.pendingStop = true
|
||||
cs.logger.Info("StopCapture queued while starting")
|
||||
return nil
|
||||
}
|
||||
|
||||
if !cs.isCapturing {
|
||||
return fmt.Errorf("capture not running")
|
||||
}
|
||||
|
||||
beforeStats := cs.packetCapture.GetStats()
|
||||
cs.logger.Info("StopCapture requested",
|
||||
"total_packets", beforeStats.TotalPackets,
|
||||
"tcp_packets", beforeStats.TCPPackets,
|
||||
"payload_packets", beforeStats.PayloadPackets,
|
||||
"ack_groups", beforeStats.AckGroups,
|
||||
"unique_payloads", beforeStats.UniquePayloads,
|
||||
"raw_segments", beforeStats.RawSegments,
|
||||
"final_buffers", beforeStats.FinalBuffers,
|
||||
)
|
||||
|
||||
cs.packetCapture.Stop()
|
||||
cs.isCapturing = false
|
||||
cs.logger.Info("Packet capture stopped")
|
||||
@@ -72,62 +129,139 @@ func (cs *CaptureService) StopCapture() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCapturedData 获取抓包数据
|
||||
func (cs *CaptureService) GetCapturedData() []string {
|
||||
return cs.packetCapture.GetCapturedData()
|
||||
}
|
||||
|
||||
// ProcessAllData 处理所有数据
|
||||
func (cs *CaptureService) ProcessAllData() {
|
||||
cs.packetCapture.ProcessAllData()
|
||||
}
|
||||
|
||||
// IsCapturing 检查是否正在抓包
|
||||
func (cs *CaptureService) IsCapturing() bool {
|
||||
cs.mutex.RLock()
|
||||
defer cs.mutex.RUnlock()
|
||||
return cs.isCapturing
|
||||
}
|
||||
|
||||
// processData 处理抓包数据
|
||||
func (cs *CaptureService) IsStarting() bool {
|
||||
cs.mutex.RLock()
|
||||
defer cs.mutex.RUnlock()
|
||||
return cs.isStarting
|
||||
}
|
||||
|
||||
func (cs *CaptureService) processData(ctx context.Context) {
|
||||
ticker := time.NewTicker(3 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
// 这里可以添加实时数据处理逻辑
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
case <-ticker.C:
|
||||
if !cs.IsCapturing() {
|
||||
continue
|
||||
}
|
||||
stats := cs.packetCapture.GetStats()
|
||||
cs.logger.Info("Capture heartbeat",
|
||||
"total_packets", stats.TotalPackets,
|
||||
"tcp_packets", stats.TCPPackets,
|
||||
"payload_packets", stats.PayloadPackets,
|
||||
"ack_groups", stats.AckGroups,
|
||||
"unique_payloads", stats.UniquePayloads,
|
||||
"raw_segments", stats.RawSegments,
|
||||
"final_buffers", stats.FinalBuffers,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// StopAndParseCapture 停止抓包并解析数据
|
||||
func (cs *CaptureService) StopAndParseCapture(parser *ParserService) (*model.ParsedResult, error) {
|
||||
func (cs *CaptureService) StopAndParseCapture() (*model.ParsedResult, error) {
|
||||
log.Printf("[service] StopAndParseCapture enter")
|
||||
cs.mutex.Lock()
|
||||
defer cs.mutex.Unlock()
|
||||
|
||||
if cs.isStarting {
|
||||
cs.pendingStop = true
|
||||
cs.mutex.Unlock()
|
||||
log.Printf("[service] StopAndParseCapture queued while starting")
|
||||
return nil, fmt.Errorf("capture starting")
|
||||
}
|
||||
if !cs.isCapturing {
|
||||
cs.mutex.Unlock()
|
||||
log.Printf("[service] StopAndParseCapture exit: not running")
|
||||
return nil, fmt.Errorf("capture not running")
|
||||
}
|
||||
cs.mutex.Unlock()
|
||||
|
||||
beforeStop := cs.packetCapture.GetStats()
|
||||
cs.logger.Info("StopAndParseCapture requested",
|
||||
"total_packets", beforeStop.TotalPackets,
|
||||
"tcp_packets", beforeStop.TCPPackets,
|
||||
"payload_packets", beforeStop.PayloadPackets,
|
||||
"ack_groups", beforeStop.AckGroups,
|
||||
"unique_payloads", beforeStop.UniquePayloads,
|
||||
"raw_segments", beforeStop.RawSegments,
|
||||
"final_buffers", beforeStop.FinalBuffers,
|
||||
)
|
||||
|
||||
log.Printf("[service] StopAndParseCapture stopping packet capture")
|
||||
cs.packetCapture.Stop()
|
||||
log.Printf("[service] StopAndParseCapture packet capture stopped")
|
||||
cs.isCapturing = false
|
||||
cs.logger.Info("Packet capture stopped (StopAndParseCapture)")
|
||||
|
||||
// 处理所有收集的数据
|
||||
cs.packetCapture.ProcessAllData()
|
||||
afterProcess := cs.packetCapture.GetStats()
|
||||
cs.logger.Info("ProcessAllData finished",
|
||||
"total_packets", afterProcess.TotalPackets,
|
||||
"tcp_packets", afterProcess.TCPPackets,
|
||||
"payload_packets", afterProcess.PayloadPackets,
|
||||
"ack_groups", afterProcess.AckGroups,
|
||||
"unique_payloads", afterProcess.UniquePayloads,
|
||||
"raw_segments", afterProcess.RawSegments,
|
||||
"final_buffers", afterProcess.FinalBuffers,
|
||||
)
|
||||
|
||||
// 获取抓包数据
|
||||
rawData := cs.packetCapture.GetCapturedData()
|
||||
if len(rawData) == 0 {
|
||||
port5222 := cs.packetCapture.GetPortCount(5222)
|
||||
port3333 := cs.packetCapture.GetPortCount(3333)
|
||||
if port5222 == 0 && port3333 == 0 {
|
||||
cs.logger.Warn("No target port data after processing",
|
||||
"port_5222", port5222,
|
||||
"port_3333", port3333,
|
||||
)
|
||||
return nil, fmt.Errorf("no captured data")
|
||||
}
|
||||
|
||||
// 解析数据
|
||||
result, _, err := parser.ParseHexData(rawData)
|
||||
if afterProcess.FinalBuffers == 0 {
|
||||
cs.logger.Warn("No captured data after processing", "hint", "check interface and bpf filter")
|
||||
return nil, fmt.Errorf("no captured data")
|
||||
}
|
||||
|
||||
rawData := cs.packetCapture.GetCapturedData()
|
||||
cs.logger.Info("Captured raw data snapshot", "hex_chunks", len(rawData))
|
||||
if len(rawData) == 0 {
|
||||
cs.logger.Warn("No captured data after stop", "hint", "check interface and bpf filter")
|
||||
return nil, fmt.Errorf("no captured data")
|
||||
}
|
||||
|
||||
parseStart := time.Now()
|
||||
cs.logger.Info("ParseHexData starting",
|
||||
"port_5222", port5222,
|
||||
"port_3333", port3333,
|
||||
"final_buffers", afterProcess.FinalBuffers,
|
||||
"hex_chunks", len(rawData),
|
||||
)
|
||||
cs.mutex.RLock()
|
||||
beforeRemote := cs.beforeRemote
|
||||
cs.mutex.RUnlock()
|
||||
if beforeRemote != nil {
|
||||
beforeRemote()
|
||||
}
|
||||
result, _, err := cs.parser.ParseHexData(rawData)
|
||||
cs.logger.Info("ParseHexData finished", "duration_ms", time.Since(parseStart).Milliseconds())
|
||||
if err != nil {
|
||||
cs.logger.Error("ParseHexData failed", "error", err, "hex_chunks", len(rawData))
|
||||
return nil, fmt.Errorf("解析数据失败: %v", err)
|
||||
}
|
||||
|
||||
cs.logger.Info("ParseHexData succeeded", "items", len(result.Items), "heroes", len(result.Heroes))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -45,6 +45,44 @@ func (s *DatabaseService) GetLatestParsedDataFromDatabase() (string, string, err
|
||||
return itemsJSON, heroesJSON, nil
|
||||
}
|
||||
|
||||
// GetParsedSessions 从数据库获取所有解析会话
|
||||
func (s *DatabaseService) GetParsedSessions() ([]model.ParsedSession, error) {
|
||||
sessions, err := s.db.GetParsedSessions()
|
||||
if err != nil {
|
||||
s.logger.Error("从数据库获取解析会话失败", "error", err)
|
||||
return nil, fmt.Errorf("获取解析会话失败: %w", err)
|
||||
}
|
||||
return sessions, nil
|
||||
}
|
||||
|
||||
// GetParsedDataByID 从数据库获取指定会话数据
|
||||
func (s *DatabaseService) GetParsedDataByID(id int64) (string, string, error) {
|
||||
itemsJSON, heroesJSON, err := s.db.GetParsedDataByID(id)
|
||||
if err != nil {
|
||||
s.logger.Error("从数据库获取解析数据失败", "error", err, "id", id)
|
||||
return "", "", fmt.Errorf("获取解析数据失败: %w", err)
|
||||
}
|
||||
return itemsJSON, heroesJSON, nil
|
||||
}
|
||||
|
||||
// UpdateParsedSessionName 更新解析会话名称
|
||||
func (s *DatabaseService) UpdateParsedSessionName(id int64, name string) error {
|
||||
if err := s.db.UpdateParsedSessionName(id, name); err != nil {
|
||||
s.logger.Error("更新解析会话名称失败", "error", err, "id", id)
|
||||
return fmt.Errorf("更新解析会话名称失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteParsedSession 删除解析会话
|
||||
func (s *DatabaseService) DeleteParsedSession(id int64) error {
|
||||
if err := s.db.DeleteParsedSession(id); err != nil {
|
||||
s.logger.Error("删除解析会话失败", "error", err, "id", id)
|
||||
return fmt.Errorf("删除解析会话失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveAppSetting 保存应用设置
|
||||
func (s *DatabaseService) SaveAppSetting(key, value string) error {
|
||||
err := s.db.SaveSetting(key, value)
|
||||
@@ -78,5 +116,3 @@ func (s *DatabaseService) GetAllAppSettings() (map[string]string, error) {
|
||||
|
||||
return settings, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -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