init
This commit is contained in:
162
internal/capture/capture.go
Normal file
162
internal/capture/capture.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"equipment-analyzer/internal/model"
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/google/gopacket/pcap"
|
||||
)
|
||||
|
||||
type PacketCapture struct {
|
||||
handle *pcap.Handle
|
||||
isCapturing bool
|
||||
stopChan chan bool
|
||||
mutex sync.RWMutex
|
||||
tcpProcessor *TCPProcessor
|
||||
dataChan chan *model.TCPData
|
||||
errorChan chan error
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
InterfaceName string
|
||||
Filter string
|
||||
Timeout time.Duration
|
||||
BufferSize int
|
||||
}
|
||||
|
||||
func NewPacketCapture() *PacketCapture {
|
||||
return &PacketCapture{
|
||||
stopChan: make(chan bool),
|
||||
tcpProcessor: NewTCPProcessor(),
|
||||
dataChan: make(chan *model.TCPData, 1000),
|
||||
errorChan: make(chan error, 100),
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) Start(config Config) error {
|
||||
pc.mutex.Lock()
|
||||
defer pc.mutex.Unlock()
|
||||
|
||||
if pc.isCapturing {
|
||||
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)
|
||||
}
|
||||
|
||||
// 设置过滤器
|
||||
if err := handle.SetBPFFilter(config.Filter); err != nil {
|
||||
handle.Close()
|
||||
return fmt.Errorf("failed to set filter: %v", err)
|
||||
}
|
||||
|
||||
pc.handle = handle
|
||||
pc.isCapturing = true
|
||||
|
||||
// 启动抓包协程
|
||||
go pc.captureLoop()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) Stop() {
|
||||
pc.mutex.Lock()
|
||||
defer pc.mutex.Unlock()
|
||||
|
||||
if !pc.isCapturing {
|
||||
return
|
||||
}
|
||||
|
||||
pc.isCapturing = false
|
||||
close(pc.stopChan)
|
||||
|
||||
if pc.handle != nil {
|
||||
pc.handle.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) IsCapturing() bool {
|
||||
pc.mutex.RLock()
|
||||
defer pc.mutex.RUnlock()
|
||||
return pc.isCapturing
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) captureLoop() {
|
||||
packetSource := gopacket.NewPacketSource(pc.handle, pc.handle.LinkType())
|
||||
|
||||
log.Println("[抓包] 开始监听数据包...")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-pc.stopChan:
|
||||
return
|
||||
default:
|
||||
packet, err := packetSource.NextPacket()
|
||||
if err != nil {
|
||||
if err.Error() == "Timeout Expired" {
|
||||
// 静默跳过超时
|
||||
continue
|
||||
}
|
||||
log.Printf("Error reading packet: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 处理TCP包
|
||||
pc.processTCPPacket(packet)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) processTCPPacket(packet gopacket.Packet) {
|
||||
tcpLayer := packet.Layer(layers.LayerTypeTCP)
|
||||
if tcpLayer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
tcp, ok := tcpLayer.(*layers.TCP)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// 提取TCP负载
|
||||
if len(tcp.Payload) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建TCP数据包
|
||||
tcpData := &model.TCPData{
|
||||
Payload: tcp.Payload,
|
||||
Seq: uint32(tcp.Seq),
|
||||
Ack: uint32(tcp.Ack),
|
||||
SrcPort: uint16(tcp.SrcPort),
|
||||
DstPort: uint16(tcp.DstPort),
|
||||
}
|
||||
|
||||
// 发送给TCP处理器
|
||||
pc.tcpProcessor.ProcessPacket(tcpData)
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) GetCapturedData() []string {
|
||||
return pc.tcpProcessor.GetFinalBuffer()
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) ProcessAllData() {
|
||||
pc.tcpProcessor.ProcessAllData()
|
||||
}
|
||||
|
||||
func (pc *PacketCapture) Clear() {
|
||||
pc.tcpProcessor.Clear()
|
||||
}
|
||||
73
internal/capture/interface.go
Normal file
73
internal/capture/interface.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"equipment-analyzer/internal/model"
|
||||
"fmt"
|
||||
"github.com/google/gopacket/pcap"
|
||||
)
|
||||
|
||||
// GetNetworkInterfaces 获取网络接口列表
|
||||
func GetNetworkInterfaces() ([]model.NetworkInterface, error) {
|
||||
devices, err := pcap.FindAllDevs()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to find network devices: %v", err)
|
||||
}
|
||||
|
||||
var interfaces []model.NetworkInterface
|
||||
for _, device := range devices {
|
||||
// 跳过回环接口
|
||||
if device.Name == "lo" || device.Name == "loopback" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 提取IP地址
|
||||
var addresses []string
|
||||
for _, address := range device.Addresses {
|
||||
addresses = append(addresses, address.IP.String())
|
||||
}
|
||||
|
||||
interfaceInfo := model.NetworkInterface{
|
||||
Name: device.Name,
|
||||
Description: device.Description,
|
||||
Addresses: addresses,
|
||||
IsLoopback: device.Name == "lo" || device.Name == "loopback",
|
||||
}
|
||||
|
||||
interfaces = append(interfaces, interfaceInfo)
|
||||
}
|
||||
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// GetDefaultInterface 获取默认网络接口
|
||||
func GetDefaultInterface() (*model.NetworkInterface, error) {
|
||||
interfaces, err := GetNetworkInterfaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 查找第一个非回环接口
|
||||
for _, iface := range interfaces {
|
||||
if !iface.IsLoopback && len(iface.Addresses) > 0 {
|
||||
return &iface, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no suitable network interface found")
|
||||
}
|
||||
|
||||
// ValidateInterface 验证网络接口是否可用
|
||||
func ValidateInterface(interfaceName string) error {
|
||||
devices, err := pcap.FindAllDevs()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find network devices: %v", err)
|
||||
}
|
||||
|
||||
for _, device := range devices {
|
||||
if device.Name == interfaceName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("interface %s not found", interfaceName)
|
||||
}
|
||||
96
internal/capture/processor.go
Normal file
96
internal/capture/processor.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package capture
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"equipment-analyzer/internal/model"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type TCPProcessor struct {
|
||||
mutex sync.RWMutex
|
||||
ackData map[uint32][]*model.TCPData
|
||||
finalBuffer []string
|
||||
loads map[string]bool // 去重用
|
||||
}
|
||||
|
||||
func NewTCPProcessor() *TCPProcessor {
|
||||
return &TCPProcessor{
|
||||
ackData: make(map[uint32][]*model.TCPData),
|
||||
loads: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TCPProcessor) ProcessPacket(tcpData *model.TCPData) {
|
||||
tp.mutex.Lock()
|
||||
defer tp.mutex.Unlock()
|
||||
|
||||
// 生成数据哈希用于去重
|
||||
hash := tp.generateHash(tcpData.Payload)
|
||||
if tp.loads[hash] {
|
||||
return // 跳过重复数据
|
||||
}
|
||||
tp.loads[hash] = true
|
||||
|
||||
// 按ACK号分组存储
|
||||
ack := uint32(tcpData.Ack)
|
||||
if tp.ackData[ack] == nil {
|
||||
tp.ackData[ack] = make([]*model.TCPData, 0)
|
||||
}
|
||||
tp.ackData[ack] = append(tp.ackData[ack], tcpData)
|
||||
}
|
||||
|
||||
func (tp *TCPProcessor) generateHash(data []byte) string {
|
||||
hash := md5.Sum(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
func (tp *TCPProcessor) ProcessAllData() {
|
||||
tp.mutex.Lock()
|
||||
defer tp.mutex.Unlock()
|
||||
|
||||
tp.finalBuffer = make([]string, 0)
|
||||
|
||||
for ack, dataList := range tp.ackData {
|
||||
tp.tryBuffer(ack, dataList)
|
||||
}
|
||||
}
|
||||
|
||||
func (tp *TCPProcessor) tryBuffer(ack uint32, dataList []*model.TCPData) {
|
||||
// 按序列号排序
|
||||
sort.Slice(dataList, func(i, j int) bool {
|
||||
return dataList[i].Seq < dataList[j].Seq
|
||||
})
|
||||
|
||||
// 合并所有分片数据
|
||||
var buffer bytes.Buffer
|
||||
for _, data := range dataList {
|
||||
buffer.Write(data.Payload)
|
||||
}
|
||||
|
||||
// 转换为十六进制字符串
|
||||
hexStr := hex.EncodeToString(buffer.Bytes())
|
||||
tp.finalBuffer = append(tp.finalBuffer, hexStr)
|
||||
}
|
||||
|
||||
func (tp *TCPProcessor) GetFinalBuffer() []string {
|
||||
tp.mutex.RLock()
|
||||
defer tp.mutex.RUnlock()
|
||||
|
||||
result := make([]string, len(tp.finalBuffer))
|
||||
copy(result, tp.finalBuffer)
|
||||
//// 输出每条finalBuffer的16进制字符串到控制台
|
||||
//fmt.Println("抓取16进制字符串")
|
||||
//fmt.Println(result)
|
||||
return result
|
||||
}
|
||||
func (tp *TCPProcessor) Clear() {
|
||||
tp.mutex.Lock()
|
||||
defer tp.mutex.Unlock()
|
||||
|
||||
tp.ackData = make(map[uint32][]*model.TCPData)
|
||||
tp.finalBuffer = make([]string, 0)
|
||||
tp.loads = make(map[string]bool)
|
||||
}
|
||||
119
internal/config/config.go
Normal file
119
internal/config/config.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
App AppConfig `json:"app"`
|
||||
Capture CaptureConfig `json:"capture"`
|
||||
Parser ParserConfig `json:"parser"`
|
||||
Log LogConfig `json:"log"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Debug bool `json:"debug"`
|
||||
}
|
||||
|
||||
type CaptureConfig struct {
|
||||
DefaultFilter string `json:"default_filter"`
|
||||
DefaultTimeout int `json:"default_timeout"`
|
||||
BufferSize int `json:"buffer_size"`
|
||||
MaxPacketSize int `json:"max_packet_size"`
|
||||
}
|
||||
|
||||
type ParserConfig struct {
|
||||
MaxDataSize int `json:"max_data_size"`
|
||||
EnableValidation bool `json:"enable_validation"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `json:"level"`
|
||||
File string `json:"file"`
|
||||
MaxSize int `json:"max_size"`
|
||||
MaxBackups int `json:"max_backups"`
|
||||
MaxAge int `json:"max_age"`
|
||||
Compress bool `json:"compress"`
|
||||
}
|
||||
|
||||
// Load 加载配置文件
|
||||
func Load() (*Config, error) {
|
||||
configPath := getConfigPath()
|
||||
|
||||
// 如果配置文件不存在,创建默认配置
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
if err := createDefaultConfig(configPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
file, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var config Config
|
||||
if err := json.NewDecoder(file).Decode(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func getConfigPath() string {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "config.json"
|
||||
}
|
||||
return filepath.Join(homeDir, ".equipment-analyzer", "config.json")
|
||||
}
|
||||
|
||||
func createDefaultConfig(path string) error {
|
||||
config := &Config{
|
||||
App: AppConfig{
|
||||
Name: "equipment-analyzer",
|
||||
Version: "1.0.0",
|
||||
Debug: false,
|
||||
},
|
||||
Capture: CaptureConfig{
|
||||
DefaultFilter: "tcp and ( port 5222 or port 3333 )",
|
||||
DefaultTimeout: 3000,
|
||||
BufferSize: 1600,
|
||||
MaxPacketSize: 65535,
|
||||
},
|
||||
Parser: ParserConfig{
|
||||
MaxDataSize: 1024 * 1024, // 1MB
|
||||
EnableValidation: true,
|
||||
},
|
||||
Log: LogConfig{
|
||||
Level: "info",
|
||||
File: "equipment-analyzer.log",
|
||||
MaxSize: 100, // MB
|
||||
MaxBackups: 3,
|
||||
MaxAge: 28, // days
|
||||
Compress: true,
|
||||
},
|
||||
}
|
||||
|
||||
// 创建目录
|
||||
dir := filepath.Dir(path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入配置文件
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
encoder := json.NewEncoder(file)
|
||||
encoder.SetIndent("", " ")
|
||||
return encoder.Encode(config)
|
||||
}
|
||||
68
internal/model/equipment.go
Normal file
68
internal/model/equipment.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Equipment 装备模型
|
||||
type Equipment struct {
|
||||
ID string `json:"id" db:"id"`
|
||||
Code string `json:"code" db:"code"`
|
||||
Ct time.Time `json:"ct" db:"created_time"`
|
||||
E int `json:"e" db:"experience"`
|
||||
G int `json:"g" db:"grade"`
|
||||
L bool `json:"l" db:"locked"`
|
||||
Mg int `json:"mg" db:"magic"`
|
||||
Op []Operation `json:"op" db:"operations"`
|
||||
P int `json:"p" db:"power"`
|
||||
S string `json:"s" db:"string_value"`
|
||||
Sk int `json:"sk" db:"skill"`
|
||||
CreatedAt time.Time `json:"created_at" db:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
|
||||
}
|
||||
|
||||
// Operation 操作属性
|
||||
type Operation struct {
|
||||
Type string `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
// MarshalJSON 自定义JSON序列化
|
||||
func (e *Equipment) MarshalJSON() ([]byte, error) {
|
||||
type Alias Equipment
|
||||
return json.Marshal(&struct {
|
||||
*Alias
|
||||
Ct int64 `json:"ct"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}{
|
||||
Alias: (*Alias)(e),
|
||||
Ct: e.Ct.Unix(),
|
||||
CreatedAt: e.CreatedAt.Unix(),
|
||||
UpdatedAt: e.UpdatedAt.Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
// UnmarshalJSON 自定义JSON反序列化
|
||||
func (e *Equipment) UnmarshalJSON(data []byte) error {
|
||||
type Alias Equipment
|
||||
aux := &struct {
|
||||
*Alias
|
||||
Ct int64 `json:"ct"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}{
|
||||
Alias: (*Alias)(e),
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
e.Ct = time.Unix(aux.Ct, 0)
|
||||
e.CreatedAt = time.Unix(aux.CreatedAt, 0)
|
||||
e.UpdatedAt = time.Unix(aux.UpdatedAt, 0)
|
||||
|
||||
return nil
|
||||
}
|
||||
31
internal/model/packet.go
Normal file
31
internal/model/packet.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package model
|
||||
|
||||
// TCPData TCP数据包模型
|
||||
type TCPData struct {
|
||||
Payload []byte
|
||||
Seq uint32
|
||||
Ack uint32
|
||||
SrcPort uint16
|
||||
DstPort uint16
|
||||
}
|
||||
|
||||
// CaptureResult 抓包结果
|
||||
type CaptureResult struct {
|
||||
Data []Equipment `json:"data"`
|
||||
Units []interface{} `json:"units"`
|
||||
}
|
||||
|
||||
// NetworkInterface 网络接口信息
|
||||
type NetworkInterface struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Addresses []string `json:"addresses"`
|
||||
IsLoopback bool `json:"is_loopback"`
|
||||
}
|
||||
|
||||
// CaptureStatus 抓包状态
|
||||
type CaptureStatus struct {
|
||||
IsCapturing bool `json:"is_capturing"`
|
||||
Status string `json:"status"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
206
internal/parser/hex_parser.go
Normal file
206
internal/parser/hex_parser.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"equipment-analyzer/internal/model"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
type HexParser struct{}
|
||||
|
||||
func NewHexParser() *HexParser {
|
||||
return &HexParser{}
|
||||
}
|
||||
|
||||
func (hp *HexParser) ParseHexData(hexDataList []string) (*model.CaptureResult, error) {
|
||||
result := &model.CaptureResult{
|
||||
Data: make([]model.Equipment, 0),
|
||||
Units: make([]interface{}, 0),
|
||||
}
|
||||
|
||||
for _, hexData := range hexDataList {
|
||||
equipment, err := hp.parseSingleEquipment(hexData)
|
||||
if err != nil {
|
||||
continue // 跳过解析失败的数据
|
||||
}
|
||||
result.Data = append(result.Data, *equipment)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (hp *HexParser) parseSingleEquipment(hexData string) (*model.Equipment, error) {
|
||||
// 将十六进制字符串转换为字节数组
|
||||
bytes, err := hex.DecodeString(hexData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid hex string: %v", err)
|
||||
}
|
||||
|
||||
if len(bytes) < 47 { // 最小长度检查
|
||||
return nil, fmt.Errorf("data too short")
|
||||
}
|
||||
|
||||
equipment := &model.Equipment{
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// 解析各个字段(基于之前的分析逻辑)
|
||||
equipment.Code = hp.extractCode(bytes)
|
||||
equipment.Ct = hp.extractTimestamp(bytes)
|
||||
equipment.E = hp.extractExperience(bytes)
|
||||
equipment.G = hp.extractGrade(bytes)
|
||||
//equipment.ID = hp.extractID(bytes)
|
||||
equipment.L = hp.extractLocked(bytes)
|
||||
equipment.Mg = hp.extractMagic(bytes)
|
||||
equipment.Op = hp.extractOperations(bytes)
|
||||
equipment.P = hp.extractPower(bytes)
|
||||
equipment.S = hp.extractString(bytes)
|
||||
equipment.Sk = hp.extractSkill(bytes)
|
||||
|
||||
return equipment, nil
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractCode(bytes []byte) string {
|
||||
if len(bytes) < 4 {
|
||||
return "efm00"
|
||||
}
|
||||
codeValue := hp.readInt(bytes, 0)
|
||||
return fmt.Sprintf("efm%02d", codeValue%100)
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractTimestamp(bytes []byte) time.Time {
|
||||
if len(bytes) < 8 {
|
||||
return time.Now()
|
||||
}
|
||||
timestamp := hp.readInt(bytes, 4)
|
||||
return time.Unix(int64(timestamp), 0)
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractExperience(bytes []byte) int {
|
||||
if len(bytes) < 12 {
|
||||
return 0
|
||||
}
|
||||
return hp.readInt(bytes, 8)
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractGrade(bytes []byte) int {
|
||||
if len(bytes) < 13 {
|
||||
return 0
|
||||
}
|
||||
return int(bytes[12])
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractID(bytes []byte) int {
|
||||
if len(bytes) < 17 {
|
||||
return 0
|
||||
}
|
||||
return hp.readInt(bytes, 13)
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractLocked(bytes []byte) bool {
|
||||
if len(bytes) < 18 {
|
||||
return false
|
||||
}
|
||||
return (bytes[17] & 0x01) != 0
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractMagic(bytes []byte) int {
|
||||
if len(bytes) < 20 {
|
||||
return 0
|
||||
}
|
||||
return hp.readShort(bytes, 18)
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractOperations(bytes []byte) []model.Operation {
|
||||
operations := make([]model.Operation, 0)
|
||||
|
||||
if len(bytes) < 21 {
|
||||
return operations
|
||||
}
|
||||
|
||||
offset := 20
|
||||
opCount := int(bytes[offset])
|
||||
offset++
|
||||
|
||||
for i := 0; i < opCount && offset+3 < len(bytes); i++ {
|
||||
attrType := int(bytes[offset])
|
||||
offset++
|
||||
|
||||
attrValue := hp.readShort(bytes, offset)
|
||||
offset += 2
|
||||
|
||||
attrName := hp.getAttributeName(attrType)
|
||||
|
||||
op := model.Operation{
|
||||
Type: attrName,
|
||||
Value: attrValue,
|
||||
}
|
||||
operations = append(operations, op)
|
||||
}
|
||||
|
||||
return operations
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractPower(bytes []byte) int {
|
||||
if len(bytes) < 44 {
|
||||
return 0
|
||||
}
|
||||
return hp.readInt(bytes, 40)
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractString(bytes []byte) string {
|
||||
if len(bytes) < 46 {
|
||||
return "0000"
|
||||
}
|
||||
stringValue := hp.readShort(bytes, 44)
|
||||
return fmt.Sprintf("%04x", stringValue)
|
||||
}
|
||||
|
||||
func (hp *HexParser) extractSkill(bytes []byte) int {
|
||||
if len(bytes) < 47 {
|
||||
return 0
|
||||
}
|
||||
return int(bytes[46])
|
||||
}
|
||||
|
||||
func (hp *HexParser) readInt(bytes []byte, offset int) int {
|
||||
if offset+3 >= len(bytes) {
|
||||
return 0
|
||||
}
|
||||
return int(bytes[offset])<<24 | int(bytes[offset+1])<<16 |
|
||||
int(bytes[offset+2])<<8 | int(bytes[offset+3])
|
||||
}
|
||||
|
||||
func (hp *HexParser) readShort(bytes []byte, offset int) int {
|
||||
if offset+1 >= len(bytes) {
|
||||
return 0
|
||||
}
|
||||
return int(bytes[offset])<<8 | int(bytes[offset+1])
|
||||
}
|
||||
|
||||
func (hp *HexParser) getAttributeName(attrType int) string {
|
||||
switch attrType {
|
||||
case 1:
|
||||
return "att"
|
||||
case 2:
|
||||
return "def"
|
||||
case 3:
|
||||
return "hp"
|
||||
case 4:
|
||||
return "max_hp"
|
||||
case 5:
|
||||
return "speed"
|
||||
case 6:
|
||||
return "crit"
|
||||
case 7:
|
||||
return "crit_dmg"
|
||||
case 8:
|
||||
return "effectiveness"
|
||||
case 9:
|
||||
return "effect_resistance"
|
||||
default:
|
||||
return fmt.Sprintf("unknown_%d", attrType)
|
||||
}
|
||||
}
|
||||
103
internal/service/capture_service.go
Normal file
103
internal/service/capture_service.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"equipment-analyzer/internal/capture"
|
||||
"equipment-analyzer/internal/config"
|
||||
"equipment-analyzer/internal/model"
|
||||
"equipment-analyzer/internal/utils"
|
||||
)
|
||||
|
||||
type CaptureService struct {
|
||||
config *config.Config
|
||||
logger *utils.Logger
|
||||
packetCapture *capture.PacketCapture
|
||||
processor *capture.TCPProcessor
|
||||
mutex sync.RWMutex
|
||||
isCapturing bool
|
||||
dataChan chan *model.CaptureResult
|
||||
errorChan chan error
|
||||
}
|
||||
|
||||
func NewCaptureService(cfg *config.Config, logger *utils.Logger) *CaptureService {
|
||||
return &CaptureService{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
packetCapture: capture.NewPacketCapture(),
|
||||
processor: capture.NewTCPProcessor(),
|
||||
dataChan: make(chan *model.CaptureResult, 100),
|
||||
errorChan: make(chan error, 100),
|
||||
}
|
||||
}
|
||||
|
||||
// StartCapture 开始抓包
|
||||
func (cs *CaptureService) StartCapture(ctx context.Context, config capture.Config) error {
|
||||
cs.mutex.Lock()
|
||||
defer cs.mutex.Unlock()
|
||||
|
||||
if cs.isCapturing {
|
||||
return fmt.Errorf("capture already running")
|
||||
}
|
||||
|
||||
if err := cs.packetCapture.Start(config); err != nil {
|
||||
return fmt.Errorf("failed to start capture: %w", err)
|
||||
}
|
||||
|
||||
cs.isCapturing = true
|
||||
cs.logger.Info("Packet capture started", "interface", config.InterfaceName)
|
||||
|
||||
// 启动数据处理协程
|
||||
go cs.processData(ctx)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopCapture 停止抓包
|
||||
func (cs *CaptureService) StopCapture() error {
|
||||
cs.mutex.Lock()
|
||||
defer cs.mutex.Unlock()
|
||||
|
||||
if !cs.isCapturing {
|
||||
return fmt.Errorf("capture not running")
|
||||
}
|
||||
|
||||
cs.packetCapture.Stop()
|
||||
cs.isCapturing = false
|
||||
cs.logger.Info("Packet capture stopped")
|
||||
|
||||
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) processData(ctx context.Context) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
// 这里可以添加实时数据处理逻辑
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
168
internal/service/main_service.go
Normal file
168
internal/service/main_service.go
Normal file
@@ -0,0 +1,168 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"equipment-analyzer/internal/capture"
|
||||
"equipment-analyzer/internal/config"
|
||||
"equipment-analyzer/internal/model"
|
||||
"equipment-analyzer/internal/utils"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
config *config.Config
|
||||
logger *utils.Logger
|
||||
captureService *CaptureService
|
||||
parserService *ParserService
|
||||
}
|
||||
|
||||
func NewApp(cfg *config.Config, logger *utils.Logger) *App {
|
||||
return &App{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
captureService: NewCaptureService(cfg, logger),
|
||||
parserService: NewParserService(cfg, logger),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) Startup(ctx context.Context) {
|
||||
a.logger.Info("应用启动")
|
||||
}
|
||||
|
||||
func (a *App) DomReady(ctx context.Context) {
|
||||
a.logger.Info("DOM准备就绪")
|
||||
}
|
||||
|
||||
func (a *App) BeforeClose(ctx context.Context) (prevent bool) {
|
||||
a.logger.Info("应用即将关闭")
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *App) Shutdown(ctx context.Context) {
|
||||
a.logger.Info("应用关闭")
|
||||
}
|
||||
|
||||
// GetNetworkInterfaces 获取网络接口列表
|
||||
func (a *App) GetNetworkInterfaces() ([]model.NetworkInterface, error) {
|
||||
interfaces, err := capture.GetNetworkInterfaces()
|
||||
if err != nil {
|
||||
a.logger.Error("获取网络接口失败", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
return interfaces, nil
|
||||
}
|
||||
|
||||
// StartCapture 开始抓包
|
||||
func (a *App) StartCapture(interfaceName string) error {
|
||||
if a.captureService.IsCapturing() {
|
||||
return fmt.Errorf("抓包已在进行中")
|
||||
}
|
||||
|
||||
config := capture.Config{
|
||||
InterfaceName: interfaceName,
|
||||
Filter: a.config.Capture.DefaultFilter,
|
||||
Timeout: time.Duration(a.config.Capture.DefaultTimeout) * time.Millisecond,
|
||||
BufferSize: a.config.Capture.BufferSize,
|
||||
}
|
||||
|
||||
err := a.captureService.StartCapture(context.Background(), config)
|
||||
if err != nil {
|
||||
a.logger.Error("开始抓包失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
a.logger.Info("抓包开始", "interface", interfaceName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopCapture 停止抓包
|
||||
func (a *App) StopCapture() error {
|
||||
if !a.captureService.IsCapturing() {
|
||||
return fmt.Errorf("没有正在进行的抓包")
|
||||
}
|
||||
|
||||
err := a.captureService.StopCapture()
|
||||
if err != nil {
|
||||
a.logger.Error("停止抓包失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 处理所有收集的数据
|
||||
a.captureService.ProcessAllData()
|
||||
|
||||
a.logger.Info("抓包停止")
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCapturedData 获取抓包数据
|
||||
func (a *App) GetCapturedData() ([]string, error) {
|
||||
return a.captureService.GetCapturedData(), nil
|
||||
}
|
||||
|
||||
// ParseData 解析数据为JSON
|
||||
func (a *App) ParseData(hexDataList []string) (string, error) {
|
||||
result, err := a.parserService.ParseHexData(hexDataList)
|
||||
if err != nil {
|
||||
a.logger.Error("解析数据失败", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
a.logger.Error("JSON序列化失败", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
a.logger.Info("数据解析完成", "count", len(result.Data))
|
||||
return string(jsonData), nil
|
||||
}
|
||||
|
||||
// ExportData 导出数据到文件
|
||||
func (a *App) ExportData(hexDataList []string, filename string) error {
|
||||
result, err := a.parserService.ParseHexData(hexDataList)
|
||||
if err != nil {
|
||||
a.logger.Error("解析数据失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
jsonData, err := json.MarshalIndent(result, "", " ")
|
||||
if err != nil {
|
||||
a.logger.Error("JSON序列化失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 这里可以添加文件写入逻辑
|
||||
a.logger.Info("导出数据", "filename", filename, "count", len(result.Data))
|
||||
|
||||
// 简单示例:写入到当前目录
|
||||
err = utils.WriteFile(filename, jsonData)
|
||||
if err != nil {
|
||||
a.logger.Error("写入文件失败", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCaptureStatus 获取抓包状态
|
||||
func (a *App) GetCaptureStatus() model.CaptureStatus {
|
||||
return model.CaptureStatus{
|
||||
IsCapturing: a.captureService.IsCapturing(),
|
||||
Status: a.getStatusMessage(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *App) getStatusMessage() string {
|
||||
if a.captureService.IsCapturing() {
|
||||
return "正在抓包..."
|
||||
}
|
||||
return "准备就绪"
|
||||
}
|
||||
|
||||
// ReadRawJsonFile 供前端调用,读取output_raw.json内容
|
||||
func (a *App) ReadRawJsonFile() (string, error) {
|
||||
return a.parserService.ReadRawJsonFile()
|
||||
}
|
||||
95152
internal/service/output_raw.json
Normal file
95152
internal/service/output_raw.json
Normal file
File diff suppressed because it is too large
Load Diff
516
internal/service/parser_service.go
Normal file
516
internal/service/parser_service.go
Normal file
@@ -0,0 +1,516 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"equipment-analyzer/internal/config"
|
||||
"equipment-analyzer/internal/model"
|
||||
"equipment-analyzer/internal/parser"
|
||||
"equipment-analyzer/internal/utils"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ParserService struct {
|
||||
config *config.Config
|
||||
logger *utils.Logger
|
||||
hexParser *parser.HexParser
|
||||
}
|
||||
|
||||
func NewParserService(cfg *config.Config, logger *utils.Logger) *ParserService {
|
||||
return &ParserService{
|
||||
config: cfg,
|
||||
logger: logger,
|
||||
hexParser: parser.NewHexParser(),
|
||||
}
|
||||
}
|
||||
|
||||
// ParseHexData 解析十六进制数据
|
||||
func (ps *ParserService) ParseHexData(hexDataList []string) (*model.CaptureResult, error) {
|
||||
if len(hexDataList) == 0 {
|
||||
ps.logger.Warn("没有数据需要解析")
|
||||
return &model.CaptureResult{
|
||||
Data: make([]model.Equipment, 0),
|
||||
Units: make([]interface{}, 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
ps.logger.Info("开始远程解析数据", "count", len(hexDataList))
|
||||
|
||||
// 远程接口解析
|
||||
url := "https://krivpfvxi0.execute-api.us-west-2.amazonaws.com/dev/getItems"
|
||||
reqBody := map[string]interface{}{
|
||||
"data": hexDataList,
|
||||
}
|
||||
jsonBytes, _ := json.Marshal(reqBody)
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBytes))
|
||||
if err == nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
resp, err := client.Do(req)
|
||||
if err == nil && resp.StatusCode == 200 {
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
// 直接写入本地文件
|
||||
fileErr := ioutil.WriteFile("output_raw.json", body, 0644)
|
||||
if fileErr != nil {
|
||||
ps.logger.Error("写入原始json文件失败", "error", fileErr)
|
||||
}
|
||||
|
||||
ps.logger.Info("远程原始数据已写入output_raw.json")
|
||||
|
||||
// 返回空集合,保证前端不报错
|
||||
return &model.CaptureResult{
|
||||
Data: make([]model.Equipment, 0),
|
||||
Units: make([]interface{}, 0),
|
||||
}, nil
|
||||
} else if err != nil {
|
||||
ps.logger.Error("远程解析请求失败", "error", err)
|
||||
return nil, fmt.Errorf("远程解析请求失败: %v", err)
|
||||
} else {
|
||||
ps.logger.Error("远程解析响应码异常", "status", resp.StatusCode)
|
||||
return nil, fmt.Errorf("远程解析响应码异常: %d", resp.StatusCode)
|
||||
}
|
||||
} else {
|
||||
ps.logger.Error("远程解析请求构建失败", "error", err)
|
||||
return nil, fmt.Errorf("远程解析请求构建失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// ReadRawJsonFile 读取output_raw.json文件内容并进行数据转换
|
||||
func (ps *ParserService) ReadRawJsonFile() (string, error) {
|
||||
data, err := ioutil.ReadFile("output_raw.json")
|
||||
if err != nil {
|
||||
ps.logger.Error("读取output_raw.json失败", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 解析原始JSON数据
|
||||
var rawData map[string]interface{}
|
||||
if err := json.Unmarshal(data, &rawData); err != nil {
|
||||
ps.logger.Error("解析JSON失败", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 提取装备和英雄数据
|
||||
equips, _ := rawData["data"].([]interface{})
|
||||
// 修正:units 取最大长度的那组
|
||||
var rawUnits []interface{}
|
||||
if unitsRaw, ok := rawData["units"].([]interface{}); ok && len(unitsRaw) > 0 {
|
||||
maxLen := 0
|
||||
for _, u := range unitsRaw {
|
||||
if arr, ok := u.([]interface{}); ok && len(arr) > maxLen {
|
||||
maxLen = len(arr)
|
||||
rawUnits = arr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1. 原始装备总数
|
||||
fmt.Println("原始装备总数:", len(equips))
|
||||
|
||||
// 过滤有效装备 (x => !!x.f)
|
||||
var validEquips []interface{}
|
||||
for _, equip := range equips {
|
||||
if equipMap, ok := equip.(map[string]interface{}); ok {
|
||||
if f, exists := equipMap["f"]; exists && f != nil && f != "" {
|
||||
validEquips = append(validEquips, equip)
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("过滤f字段后装备数:", len(validEquips))
|
||||
|
||||
// 转换装备数据
|
||||
convertedItems := ps.convertItemsAllWithLog(validEquips)
|
||||
fmt.Println("转换后装备数:", len(convertedItems))
|
||||
|
||||
// 转换英雄数据(只对最大组)
|
||||
convertedHeroes := ps.convertUnits(rawUnits)
|
||||
|
||||
// 构建最终结果
|
||||
result := map[string]interface{}{
|
||||
"items": convertedItems,
|
||||
"heroes": convertedHeroes,
|
||||
}
|
||||
|
||||
// 序列化为JSON字符串
|
||||
resultJSON, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
ps.logger.Error("序列化结果失败", "error", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(resultJSON), nil
|
||||
}
|
||||
|
||||
// convertItems 转换装备数据
|
||||
func (ps *ParserService) convertItems(rawItems []interface{}) []map[string]interface{} {
|
||||
var convertedItems []map[string]interface{}
|
||||
|
||||
for _, rawItem := range rawItems {
|
||||
if itemMap, ok := rawItem.(map[string]interface{}); ok {
|
||||
convertedItem := ps.convertSingleItem(itemMap)
|
||||
if convertedItem != nil {
|
||||
convertedItems = append(convertedItems, convertedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var filteredItems []map[string]interface{}
|
||||
for _, item := range convertedItems {
|
||||
filteredItems = append(filteredItems, item)
|
||||
}
|
||||
|
||||
return filteredItems
|
||||
}
|
||||
|
||||
// convertSingleItem 转换单个装备
|
||||
func (ps *ParserService) convertSingleItem(item map[string]interface{}) map[string]interface{} {
|
||||
converted := make(map[string]interface{})
|
||||
|
||||
// 复制基本字段
|
||||
for key, value := range item {
|
||||
converted[key] = value
|
||||
}
|
||||
|
||||
// 转换装备类型
|
||||
ps.convertGear(converted)
|
||||
|
||||
// 转换等级
|
||||
ps.convertRank(converted)
|
||||
|
||||
// 转换套装
|
||||
ps.convertSet(converted)
|
||||
|
||||
// 转换名称
|
||||
ps.convertName(converted)
|
||||
|
||||
// 转换等级
|
||||
ps.convertLevel(converted)
|
||||
|
||||
// 转换增强
|
||||
ps.convertEnhance(converted)
|
||||
|
||||
// 转换主属性
|
||||
ps.convertMainStat(converted)
|
||||
|
||||
// 转换副属性
|
||||
ps.convertSubStats(converted)
|
||||
|
||||
// 转换ID
|
||||
ps.convertId(converted)
|
||||
|
||||
// 转换装备ID
|
||||
ps.convertEquippedId(converted)
|
||||
|
||||
return converted
|
||||
}
|
||||
|
||||
// convertUnits 转换英雄数据
|
||||
func (ps *ParserService) convertUnits(rawUnits []interface{}) []map[string]interface{} {
|
||||
var convertedUnits []map[string]interface{}
|
||||
|
||||
for _, rawUnit := range rawUnits {
|
||||
if unitMap, ok := rawUnit.(map[string]interface{}); ok {
|
||||
if name, exists := unitMap["name"]; exists && name != nil && name != "" {
|
||||
if id, exists := unitMap["id"]; exists && id != nil {
|
||||
convertedUnit := make(map[string]interface{})
|
||||
for key, value := range unitMap {
|
||||
convertedUnit[key] = value
|
||||
}
|
||||
|
||||
// 转换星星和觉醒
|
||||
if g, exists := unitMap["g"]; exists {
|
||||
convertedUnit["stars"] = g
|
||||
}
|
||||
if z, exists := unitMap["z"]; exists {
|
||||
convertedUnit["awaken"] = z
|
||||
}
|
||||
|
||||
convertedUnits = append(convertedUnits, convertedUnit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return convertedUnits
|
||||
}
|
||||
|
||||
// 转换函数实现
|
||||
func (ps *ParserService) convertGear(item map[string]interface{}) {
|
||||
if _, exists := item["type"]; !exists {
|
||||
if code, exists := item["code"].(string); exists {
|
||||
baseCode := code
|
||||
if idx := len(baseCode) - 1; idx >= 0 {
|
||||
gearLetter := string(baseCode[idx])
|
||||
item["gear"] = gearByGearLetter[gearLetter]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if itemType, exists := item["type"].(string); exists {
|
||||
item["gear"] = gearByIngameType[itemType]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertRank(item map[string]interface{}) {
|
||||
if g, exists := item["g"].(float64); exists {
|
||||
rankIndex := int(g)
|
||||
if rankIndex >= 0 && rankIndex < len(rankByIngameGrade) {
|
||||
item["rank"] = rankByIngameGrade[rankIndex]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertSet(item map[string]interface{}) {
|
||||
if f, exists := item["f"].(string); exists {
|
||||
item["set"] = setsByIngameSet[f]
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertName(item map[string]interface{}) {
|
||||
if _, exists := item["name"]; !exists {
|
||||
item["name"] = "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertLevel(item map[string]interface{}) {
|
||||
if _, exists := item["level"]; !exists {
|
||||
item["level"] = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertEnhance(item map[string]interface{}) {
|
||||
rank, rankExists := item["rank"].(string)
|
||||
op, opExists := item["op"].([]interface{})
|
||||
|
||||
if rankExists && opExists {
|
||||
countByRank := map[string]int{
|
||||
"Normal": 5,
|
||||
"Good": 6,
|
||||
"Rare": 7,
|
||||
"Heroic": 8,
|
||||
"Epic": 9,
|
||||
}
|
||||
offsetByRank := map[string]int{
|
||||
"Normal": 0,
|
||||
"Good": 1,
|
||||
"Rare": 2,
|
||||
"Heroic": 3,
|
||||
"Epic": 4,
|
||||
}
|
||||
|
||||
count := countByRank[rank]
|
||||
offset := offsetByRank[rank]
|
||||
subsCount := len(op) - 1
|
||||
if subsCount > count {
|
||||
subsCount = count
|
||||
}
|
||||
|
||||
enhance := (subsCount - offset) * 3
|
||||
if enhance < 0 {
|
||||
enhance = 0
|
||||
}
|
||||
item["enhance"] = enhance
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertMainStat(item map[string]interface{}) {
|
||||
op, opExists := item["op"].([]interface{})
|
||||
mainStatValue, mainStatExists := item["mainStatValue"].(float64)
|
||||
|
||||
if opExists && len(op) > 0 && mainStatExists {
|
||||
if mainOp, ok := op[0].([]interface{}); ok && len(mainOp) > 0 {
|
||||
if mainOpType, ok := mainOp[0].(string); ok {
|
||||
mainType := statByIngameStat[mainOpType]
|
||||
var mainValue float64
|
||||
|
||||
if ps.isFlat(mainOpType) {
|
||||
mainValue = mainStatValue
|
||||
} else {
|
||||
mainValue = mainStatValue * 100
|
||||
}
|
||||
|
||||
if mainValue == 0 || mainValue != mainValue { // NaN check
|
||||
mainValue = 0
|
||||
}
|
||||
|
||||
item["main"] = map[string]interface{}{
|
||||
"type": mainType,
|
||||
"value": mainValue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertSubStats(item map[string]interface{}) {
|
||||
op, opExists := item["op"].([]interface{})
|
||||
|
||||
if !opExists || len(op) <= 1 {
|
||||
item["substats"] = []interface{}{}
|
||||
return
|
||||
}
|
||||
|
||||
statAcc := make(map[string]map[string]interface{})
|
||||
|
||||
// 处理副属性 (从索引1开始)
|
||||
for i := 1; i < len(op); i++ {
|
||||
if opItem, ok := op[i].([]interface{}); ok && len(opItem) >= 2 {
|
||||
opType, _ := opItem[0].(string)
|
||||
opValue, _ := opItem[1].(float64)
|
||||
|
||||
annotation := ""
|
||||
if len(opItem) > 2 {
|
||||
annotation, _ = opItem[2].(string)
|
||||
}
|
||||
|
||||
statType := statByIngameStat[opType]
|
||||
var value float64
|
||||
|
||||
if ps.isFlat(opType) {
|
||||
value = opValue
|
||||
} else {
|
||||
value = opValue * 100
|
||||
}
|
||||
|
||||
if existingStat, exists := statAcc[statType]; exists {
|
||||
existingStat["value"] = existingStat["value"].(float64) + value
|
||||
|
||||
if annotation == "c" {
|
||||
existingStat["modified"] = true
|
||||
} else if annotation != "u" {
|
||||
rolls := existingStat["rolls"].(int) + 1
|
||||
existingStat["rolls"] = rolls
|
||||
existingStat["ingameRolls"] = rolls
|
||||
}
|
||||
} else {
|
||||
rolls := 1
|
||||
if annotation == "u" {
|
||||
rolls = 0
|
||||
}
|
||||
|
||||
statAcc[statType] = map[string]interface{}{
|
||||
"value": value,
|
||||
"rolls": rolls,
|
||||
"ingameRolls": rolls,
|
||||
"modified": annotation == "c",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为最终格式
|
||||
var substats []interface{}
|
||||
for statType, statData := range statAcc {
|
||||
substat := map[string]interface{}{
|
||||
"type": statType,
|
||||
"value": statData["value"],
|
||||
"rolls": statData["rolls"],
|
||||
"ingameRolls": statData["ingameRolls"],
|
||||
"modified": statData["modified"],
|
||||
}
|
||||
substats = append(substats, substat)
|
||||
}
|
||||
|
||||
item["substats"] = substats
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertId(item map[string]interface{}) {
|
||||
if id, exists := item["id"]; exists {
|
||||
item["ingameId"] = id
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) convertEquippedId(item map[string]interface{}) {
|
||||
if p, exists := item["p"]; exists {
|
||||
item["ingameEquippedId"] = fmt.Sprintf("%v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func (ps *ParserService) isFlat(text string) bool {
|
||||
return text == "max_hp" || text == "speed" || text == "att" || text == "def"
|
||||
}
|
||||
|
||||
// 映射常量
|
||||
var (
|
||||
rankByIngameGrade = []string{
|
||||
"Unknown",
|
||||
"Normal",
|
||||
"Good",
|
||||
"Rare",
|
||||
"Heroic",
|
||||
"Epic",
|
||||
}
|
||||
|
||||
gearByIngameType = map[string]string{
|
||||
"weapon": "Weapon",
|
||||
"helm": "Helmet",
|
||||
"armor": "Armor",
|
||||
"neck": "Necklace",
|
||||
"ring": "Ring",
|
||||
"boot": "Boots",
|
||||
}
|
||||
|
||||
gearByGearLetter = map[string]string{
|
||||
"w": "Weapon",
|
||||
"h": "Helmet",
|
||||
"a": "Armor",
|
||||
"n": "Necklace",
|
||||
"r": "Ring",
|
||||
"b": "Boots",
|
||||
}
|
||||
|
||||
setsByIngameSet = map[string]string{
|
||||
"set_acc": "HitSet",
|
||||
"set_att": "AttackSet",
|
||||
"set_coop": "UnitySet",
|
||||
"set_counter": "CounterSet",
|
||||
"set_cri_dmg": "DestructionSet",
|
||||
"set_cri": "CriticalSet",
|
||||
"set_def": "DefenseSet",
|
||||
"set_immune": "ImmunitySet",
|
||||
"set_max_hp": "HealthSet",
|
||||
"set_penetrate": "PenetrationSet",
|
||||
"set_rage": "RageSet",
|
||||
"set_res": "ResistSet",
|
||||
"set_revenge": "RevengeSet",
|
||||
"set_scar": "InjurySet",
|
||||
"set_speed": "SpeedSet",
|
||||
"set_vampire": "LifestealSet",
|
||||
"set_shield": "ProtectionSet",
|
||||
"set_torrent": "TorrentSet",
|
||||
}
|
||||
|
||||
statByIngameStat = map[string]string{
|
||||
"att_rate": "AttackPercent",
|
||||
"max_hp_rate": "HealthPercent",
|
||||
"def_rate": "DefensePercent",
|
||||
"att": "Attack",
|
||||
"max_hp": "Health",
|
||||
"def": "Defense",
|
||||
"speed": "Speed",
|
||||
"res": "EffectResistancePercent",
|
||||
"cri": "CriticalHitChancePercent",
|
||||
"cri_dmg": "CriticalHitDamagePercent",
|
||||
"acc": "EffectivenessPercent",
|
||||
"coop": "DualAttackChancePercent",
|
||||
}
|
||||
)
|
||||
|
||||
// 新增:不做enhance过滤的convertItems
|
||||
func (ps *ParserService) convertItemsAllWithLog(rawItems []interface{}) []map[string]interface{} {
|
||||
var convertedItems []map[string]interface{}
|
||||
for _, rawItem := range rawItems {
|
||||
if itemMap, ok := rawItem.(map[string]interface{}); ok {
|
||||
convertedItem := ps.convertSingleItem(itemMap)
|
||||
if convertedItem != nil {
|
||||
convertedItems = append(convertedItems, convertedItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
return convertedItems
|
||||
}
|
||||
85
internal/service/parser_service_test.go
Normal file
85
internal/service/parser_service_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
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
|
||||
}
|
||||
34
internal/utils/file.go
Normal file
34
internal/utils/file.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// WriteFile 写入文件
|
||||
func WriteFile(filename string, data []byte) error {
|
||||
// 确保目录存在
|
||||
dir := filepath.Dir(filename)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
return os.WriteFile(filename, data, 0644)
|
||||
}
|
||||
|
||||
// ReadFile 读取文件
|
||||
func ReadFile(filename string) ([]byte, error) {
|
||||
return os.ReadFile(filename)
|
||||
}
|
||||
|
||||
// FileExists 检查文件是否存在
|
||||
func FileExists(filename string) bool {
|
||||
_, err := os.Stat(filename)
|
||||
return !os.IsNotExist(err)
|
||||
}
|
||||
|
||||
// CreateDir 创建目录
|
||||
func CreateDir(dir string) error {
|
||||
return os.MkdirAll(dir, 0755)
|
||||
}
|
||||
41
internal/utils/logger.go
Normal file
41
internal/utils/logger.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
type Logger struct {
|
||||
*zap.SugaredLogger
|
||||
}
|
||||
|
||||
func NewLogger() *Logger {
|
||||
// 配置日志轮转
|
||||
lumberJackLogger := &lumberjack.Logger{
|
||||
Filename: "logs/equipment-analyzer.log",
|
||||
MaxSize: 100, // MB
|
||||
MaxBackups: 3,
|
||||
MaxAge: 28, // days
|
||||
Compress: true,
|
||||
}
|
||||
|
||||
// 配置编码器
|
||||
encoderConfig := zap.NewProductionEncoderConfig()
|
||||
encoderConfig.TimeKey = "timestamp"
|
||||
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
|
||||
// 创建核心
|
||||
core := zapcore.NewCore(
|
||||
zapcore.NewJSONEncoder(encoderConfig),
|
||||
zapcore.AddSync(lumberJackLogger),
|
||||
zap.NewAtomicLevelAt(zap.InfoLevel),
|
||||
)
|
||||
|
||||
// 创建logger
|
||||
logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zapcore.ErrorLevel))
|
||||
sugar := logger.Sugar()
|
||||
|
||||
return &Logger{sugar}
|
||||
}
|
||||
Reference in New Issue
Block a user