From 4ee292cc1f02450b0ccca9779066aca95ae9a1d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Thu, 14 May 2026 19:24:48 +0800 Subject: [PATCH] Delete storage directory --- storage/result_storage.go | 297 --------------------- storage/result_storage_test.go | 453 --------------------------------- 2 files changed, 750 deletions(-) delete mode 100644 storage/result_storage.go delete mode 100644 storage/result_storage_test.go diff --git a/storage/result_storage.go b/storage/result_storage.go deleted file mode 100644 index 85a8b7b3..00000000 --- a/storage/result_storage.go +++ /dev/null @@ -1,297 +0,0 @@ -package storage - -import ( - "encoding/json" - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - "sync" - "time" - - "go.uber.org/zap" -) - -// ResultStorage 结果存储接口 -type ResultStorage interface { - // SaveResult 保存工具执行结果 - SaveResult(executionID string, toolName string, result string) error - - // GetResult 获取完整结果 - GetResult(executionID string) (string, error) - - // GetResultPage 分页获取结果 - GetResultPage(executionID string, page int, limit int) (*ResultPage, error) - - // SearchResult 搜索结果 - // useRegex: 如果为 true,将 keyword 作为正则表达式使用;如果为 false,使用简单的字符串包含匹配 - SearchResult(executionID string, keyword string, useRegex bool) ([]string, error) - - // FilterResult 过滤结果 - // useRegex: 如果为 true,将 filter 作为正则表达式使用;如果为 false,使用简单的字符串包含匹配 - FilterResult(executionID string, filter string, useRegex bool) ([]string, error) - - // GetResultMetadata 获取结果元信息 - GetResultMetadata(executionID string) (*ResultMetadata, error) - - // GetResultPath 获取结果文件路径 - GetResultPath(executionID string) string - - // DeleteResult 删除结果 - DeleteResult(executionID string) error -} - -// ResultPage 分页结果 -type ResultPage struct { - Lines []string `json:"lines"` - Page int `json:"page"` - Limit int `json:"limit"` - TotalLines int `json:"total_lines"` - TotalPages int `json:"total_pages"` -} - -// ResultMetadata 结果元信息 -type ResultMetadata struct { - ExecutionID string `json:"execution_id"` - ToolName string `json:"tool_name"` - TotalSize int `json:"total_size"` - TotalLines int `json:"total_lines"` - CreatedAt time.Time `json:"created_at"` -} - -// FileResultStorage 基于文件的结果存储实现 -type FileResultStorage struct { - baseDir string - logger *zap.Logger - mu sync.RWMutex -} - -// NewFileResultStorage 创建新的文件结果存储 -func NewFileResultStorage(baseDir string, logger *zap.Logger) (*FileResultStorage, error) { - // 确保目录存在 - if err := os.MkdirAll(baseDir, 0755); err != nil { - return nil, fmt.Errorf("创建存储目录失败: %w", err) - } - - return &FileResultStorage{ - baseDir: baseDir, - logger: logger, - }, nil -} - -// getResultPath 获取结果文件路径 -func (s *FileResultStorage) getResultPath(executionID string) string { - return filepath.Join(s.baseDir, executionID+".txt") -} - -// getMetadataPath 获取元数据文件路径 -func (s *FileResultStorage) getMetadataPath(executionID string) string { - return filepath.Join(s.baseDir, executionID+".meta.json") -} - -// SaveResult 保存工具执行结果 -func (s *FileResultStorage) SaveResult(executionID string, toolName string, result string) error { - s.mu.Lock() - defer s.mu.Unlock() - - // 保存结果文件 - resultPath := s.getResultPath(executionID) - if err := os.WriteFile(resultPath, []byte(result), 0644); err != nil { - return fmt.Errorf("保存结果文件失败: %w", err) - } - - // 计算统计信息 - lines := strings.Split(result, "\n") - metadata := &ResultMetadata{ - ExecutionID: executionID, - ToolName: toolName, - TotalSize: len(result), - TotalLines: len(lines), - CreatedAt: time.Now(), - } - - // 保存元数据 - metadataPath := s.getMetadataPath(executionID) - metadataJSON, err := json.Marshal(metadata) - if err != nil { - return fmt.Errorf("序列化元数据失败: %w", err) - } - - if err := os.WriteFile(metadataPath, metadataJSON, 0644); err != nil { - return fmt.Errorf("保存元数据文件失败: %w", err) - } - - s.logger.Info("保存工具执行结果", - zap.String("executionID", executionID), - zap.String("toolName", toolName), - zap.Int("size", len(result)), - zap.Int("lines", len(lines)), - ) - - return nil -} - -// GetResult 获取完整结果 -func (s *FileResultStorage) GetResult(executionID string) (string, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - resultPath := s.getResultPath(executionID) - data, err := os.ReadFile(resultPath) - if err != nil { - if os.IsNotExist(err) { - return "", fmt.Errorf("结果不存在: %s", executionID) - } - return "", fmt.Errorf("读取结果文件失败: %w", err) - } - - return string(data), nil -} - -// GetResultMetadata 获取结果元信息 -func (s *FileResultStorage) GetResultMetadata(executionID string) (*ResultMetadata, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - metadataPath := s.getMetadataPath(executionID) - data, err := os.ReadFile(metadataPath) - if err != nil { - if os.IsNotExist(err) { - return nil, fmt.Errorf("结果不存在: %s", executionID) - } - return nil, fmt.Errorf("读取元数据文件失败: %w", err) - } - - var metadata ResultMetadata - if err := json.Unmarshal(data, &metadata); err != nil { - return nil, fmt.Errorf("解析元数据失败: %w", err) - } - - return &metadata, nil -} - -// GetResultPage 分页获取结果 -func (s *FileResultStorage) GetResultPage(executionID string, page int, limit int) (*ResultPage, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - // 获取完整结果 - result, err := s.GetResult(executionID) - if err != nil { - return nil, err - } - - // 分割为行 - lines := strings.Split(result, "\n") - totalLines := len(lines) - - // 计算分页 - totalPages := (totalLines + limit - 1) / limit - if page < 1 { - page = 1 - } - if page > totalPages && totalPages > 0 { - page = totalPages - } - - // 计算起始和结束索引 - start := (page - 1) * limit - end := start + limit - if end > totalLines { - end = totalLines - } - - // 提取指定页的行 - var pageLines []string - if start < totalLines { - pageLines = lines[start:end] - } else { - pageLines = []string{} - } - - return &ResultPage{ - Lines: pageLines, - Page: page, - Limit: limit, - TotalLines: totalLines, - TotalPages: totalPages, - }, nil -} - -// SearchResult 搜索结果 -func (s *FileResultStorage) SearchResult(executionID string, keyword string, useRegex bool) ([]string, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - // 获取完整结果 - result, err := s.GetResult(executionID) - if err != nil { - return nil, err - } - - // 如果使用正则表达式,先编译正则 - var regex *regexp.Regexp - if useRegex { - compiledRegex, err := regexp.Compile(keyword) - if err != nil { - return nil, fmt.Errorf("无效的正则表达式: %w", err) - } - regex = compiledRegex - } - - // 分割为行并搜索 - lines := strings.Split(result, "\n") - var matchedLines []string - - for _, line := range lines { - var matched bool - if useRegex { - matched = regex.MatchString(line) - } else { - matched = strings.Contains(line, keyword) - } - - if matched { - matchedLines = append(matchedLines, line) - } - } - - return matchedLines, nil -} - -// FilterResult 过滤结果 -func (s *FileResultStorage) FilterResult(executionID string, filter string, useRegex bool) ([]string, error) { - // 过滤和搜索逻辑相同,都是查找包含关键词的行 - return s.SearchResult(executionID, filter, useRegex) -} - -// GetResultPath 获取结果文件路径 -func (s *FileResultStorage) GetResultPath(executionID string) string { - return s.getResultPath(executionID) -} - -// DeleteResult 删除结果 -func (s *FileResultStorage) DeleteResult(executionID string) error { - s.mu.Lock() - defer s.mu.Unlock() - - resultPath := s.getResultPath(executionID) - metadataPath := s.getMetadataPath(executionID) - - // 删除结果文件 - if err := os.Remove(resultPath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("删除结果文件失败: %w", err) - } - - // 删除元数据文件 - if err := os.Remove(metadataPath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("删除元数据文件失败: %w", err) - } - - s.logger.Info("删除工具执行结果", - zap.String("executionID", executionID), - ) - - return nil -} diff --git a/storage/result_storage_test.go b/storage/result_storage_test.go deleted file mode 100644 index 51305c92..00000000 --- a/storage/result_storage_test.go +++ /dev/null @@ -1,453 +0,0 @@ -package storage - -import ( - "fmt" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "go.uber.org/zap" -) - -// setupTestStorage 创建测试用的存储实例 -func setupTestStorage(t *testing.T) (*FileResultStorage, string) { - tmpDir := filepath.Join(os.TempDir(), "test_result_storage_"+time.Now().Format("20060102_150405")) - logger := zap.NewNop() - - storage, err := NewFileResultStorage(tmpDir, logger) - if err != nil { - t.Fatalf("创建测试存储失败: %v", err) - } - - return storage, tmpDir -} - -// cleanupTestStorage 清理测试数据 -func cleanupTestStorage(t *testing.T, tmpDir string) { - if err := os.RemoveAll(tmpDir); err != nil { - t.Logf("清理测试目录失败: %v", err) - } -} - -func TestNewFileResultStorage(t *testing.T) { - tmpDir := filepath.Join(os.TempDir(), "test_new_storage_"+time.Now().Format("20060102_150405")) - defer cleanupTestStorage(t, tmpDir) - - logger := zap.NewNop() - storage, err := NewFileResultStorage(tmpDir, logger) - if err != nil { - t.Fatalf("创建存储失败: %v", err) - } - - if storage == nil { - t.Fatal("存储实例为nil") - } - - // 验证目录已创建 - if _, err := os.Stat(tmpDir); os.IsNotExist(err) { - t.Fatal("存储目录未创建") - } -} - -func TestFileResultStorage_SaveResult(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - executionID := "test_exec_001" - toolName := "nmap_scan" - result := "Line 1\nLine 2\nLine 3\nLine 4\nLine 5" - - err := storage.SaveResult(executionID, toolName, result) - if err != nil { - t.Fatalf("保存结果失败: %v", err) - } - - // 验证结果文件存在 - resultPath := filepath.Join(tmpDir, executionID+".txt") - if _, err := os.Stat(resultPath); os.IsNotExist(err) { - t.Fatal("结果文件未创建") - } - - // 验证元数据文件存在 - metadataPath := filepath.Join(tmpDir, executionID+".meta.json") - if _, err := os.Stat(metadataPath); os.IsNotExist(err) { - t.Fatal("元数据文件未创建") - } -} - -func TestFileResultStorage_GetResult(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - executionID := "test_exec_002" - toolName := "test_tool" - expectedResult := "Test result content\nLine 2\nLine 3" - - // 先保存结果 - err := storage.SaveResult(executionID, toolName, expectedResult) - if err != nil { - t.Fatalf("保存结果失败: %v", err) - } - - // 获取结果 - result, err := storage.GetResult(executionID) - if err != nil { - t.Fatalf("获取结果失败: %v", err) - } - - if result != expectedResult { - t.Errorf("结果不匹配。期望: %q, 实际: %q", expectedResult, result) - } - - // 测试不存在的执行ID - _, err = storage.GetResult("nonexistent_id") - if err == nil { - t.Fatal("应该返回错误") - } -} - -func TestFileResultStorage_GetResultMetadata(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - executionID := "test_exec_003" - toolName := "test_tool" - result := "Line 1\nLine 2\nLine 3" - - // 保存结果 - err := storage.SaveResult(executionID, toolName, result) - if err != nil { - t.Fatalf("保存结果失败: %v", err) - } - - // 获取元数据 - metadata, err := storage.GetResultMetadata(executionID) - if err != nil { - t.Fatalf("获取元数据失败: %v", err) - } - - if metadata.ExecutionID != executionID { - t.Errorf("执行ID不匹配。期望: %s, 实际: %s", executionID, metadata.ExecutionID) - } - - if metadata.ToolName != toolName { - t.Errorf("工具名称不匹配。期望: %s, 实际: %s", toolName, metadata.ToolName) - } - - if metadata.TotalSize != len(result) { - t.Errorf("总大小不匹配。期望: %d, 实际: %d", len(result), metadata.TotalSize) - } - - expectedLines := len(strings.Split(result, "\n")) - if metadata.TotalLines != expectedLines { - t.Errorf("总行数不匹配。期望: %d, 实际: %d", expectedLines, metadata.TotalLines) - } - - // 验证创建时间在合理范围内 - now := time.Now() - if metadata.CreatedAt.After(now) || metadata.CreatedAt.Before(now.Add(-time.Second)) { - t.Errorf("创建时间不在合理范围内: %v", metadata.CreatedAt) - } -} - -func TestFileResultStorage_GetResultPage(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - executionID := "test_exec_004" - toolName := "test_tool" - // 创建包含10行的结果 - lines := make([]string, 10) - for i := 0; i < 10; i++ { - lines[i] = fmt.Sprintf("Line %d", i+1) - } - result := strings.Join(lines, "\n") - - // 保存结果 - err := storage.SaveResult(executionID, toolName, result) - if err != nil { - t.Fatalf("保存结果失败: %v", err) - } - - // 测试第一页(每页3行) - page, err := storage.GetResultPage(executionID, 1, 3) - if err != nil { - t.Fatalf("获取第一页失败: %v", err) - } - - if page.Page != 1 { - t.Errorf("页码不匹配。期望: 1, 实际: %d", page.Page) - } - - if page.Limit != 3 { - t.Errorf("每页行数不匹配。期望: 3, 实际: %d", page.Limit) - } - - if page.TotalLines != 10 { - t.Errorf("总行数不匹配。期望: 10, 实际: %d", page.TotalLines) - } - - if page.TotalPages != 4 { - t.Errorf("总页数不匹配。期望: 4, 实际: %d", page.TotalPages) - } - - if len(page.Lines) != 3 { - t.Errorf("第一页行数不匹配。期望: 3, 实际: %d", len(page.Lines)) - } - - if page.Lines[0] != "Line 1" { - t.Errorf("第一行内容不匹配。期望: Line 1, 实际: %s", page.Lines[0]) - } - - // 测试第二页 - page2, err := storage.GetResultPage(executionID, 2, 3) - if err != nil { - t.Fatalf("获取第二页失败: %v", err) - } - - if len(page2.Lines) != 3 { - t.Errorf("第二页行数不匹配。期望: 3, 实际: %d", len(page2.Lines)) - } - - if page2.Lines[0] != "Line 4" { - t.Errorf("第二页第一行内容不匹配。期望: Line 4, 实际: %s", page2.Lines[0]) - } - - // 测试最后一页(可能不满一页) - page4, err := storage.GetResultPage(executionID, 4, 3) - if err != nil { - t.Fatalf("获取第四页失败: %v", err) - } - - if len(page4.Lines) != 1 { - t.Errorf("第四页行数不匹配。期望: 1, 实际: %d", len(page4.Lines)) - } - - // 测试超出范围的页码(应该返回最后一页) - page5, err := storage.GetResultPage(executionID, 5, 3) - if err != nil { - t.Fatalf("获取第五页失败: %v", err) - } - - // 超出范围的页码会被修正为最后一页,所以应该返回最后一页的内容 - if page5.Page != 4 { - t.Errorf("超出范围的页码应该被修正为最后一页。期望: 4, 实际: %d", page5.Page) - } - - // 最后一页应该只有1行 - if len(page5.Lines) != 1 { - t.Errorf("最后一页应该只有1行。实际: %d行", len(page5.Lines)) - } -} - -func TestFileResultStorage_SearchResult(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - executionID := "test_exec_005" - toolName := "test_tool" - result := "Line 1: error occurred\nLine 2: success\nLine 3: error again\nLine 4: ok" - - // 保存结果 - err := storage.SaveResult(executionID, toolName, result) - if err != nil { - t.Fatalf("保存结果失败: %v", err) - } - - // 搜索包含"error"的行(简单字符串匹配) - matchedLines, err := storage.SearchResult(executionID, "error", false) - if err != nil { - t.Fatalf("搜索失败: %v", err) - } - - if len(matchedLines) != 2 { - t.Errorf("搜索结果数量不匹配。期望: 2, 实际: %d", len(matchedLines)) - } - - // 验证搜索结果内容 - for i, line := range matchedLines { - if !strings.Contains(line, "error") { - t.Errorf("搜索结果第%d行不包含关键词: %s", i+1, line) - } - } - - // 测试搜索不存在的关键词 - noMatch, err := storage.SearchResult(executionID, "nonexistent", false) - if err != nil { - t.Fatalf("搜索失败: %v", err) - } - - if len(noMatch) != 0 { - t.Errorf("搜索不存在的关键词应该返回空结果。实际: %d行", len(noMatch)) - } - - // 测试正则表达式搜索 - regexMatched, err := storage.SearchResult(executionID, "error.*again", true) - if err != nil { - t.Fatalf("正则搜索失败: %v", err) - } - - if len(regexMatched) != 1 { - t.Errorf("正则搜索结果数量不匹配。期望: 1, 实际: %d", len(regexMatched)) - } -} - -func TestFileResultStorage_FilterResult(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - executionID := "test_exec_006" - toolName := "test_tool" - result := "Line 1: warning message\nLine 2: info message\nLine 3: warning again\nLine 4: debug message" - - // 保存结果 - err := storage.SaveResult(executionID, toolName, result) - if err != nil { - t.Fatalf("保存结果失败: %v", err) - } - - // 过滤包含"warning"的行(简单字符串匹配) - filteredLines, err := storage.FilterResult(executionID, "warning", false) - if err != nil { - t.Fatalf("过滤失败: %v", err) - } - - if len(filteredLines) != 2 { - t.Errorf("过滤结果数量不匹配。期望: 2, 实际: %d", len(filteredLines)) - } - - // 验证过滤结果内容 - for i, line := range filteredLines { - if !strings.Contains(line, "warning") { - t.Errorf("过滤结果第%d行不包含关键词: %s", i+1, line) - } - } -} - -func TestFileResultStorage_DeleteResult(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - executionID := "test_exec_007" - toolName := "test_tool" - result := "Test result" - - // 保存结果 - err := storage.SaveResult(executionID, toolName, result) - if err != nil { - t.Fatalf("保存结果失败: %v", err) - } - - // 验证文件存在 - resultPath := filepath.Join(tmpDir, executionID+".txt") - metadataPath := filepath.Join(tmpDir, executionID+".meta.json") - - if _, err := os.Stat(resultPath); os.IsNotExist(err) { - t.Fatal("结果文件不存在") - } - - if _, err := os.Stat(metadataPath); os.IsNotExist(err) { - t.Fatal("元数据文件不存在") - } - - // 删除结果 - err = storage.DeleteResult(executionID) - if err != nil { - t.Fatalf("删除结果失败: %v", err) - } - - // 验证文件已删除 - if _, err := os.Stat(resultPath); !os.IsNotExist(err) { - t.Fatal("结果文件未被删除") - } - - if _, err := os.Stat(metadataPath); !os.IsNotExist(err) { - t.Fatal("元数据文件未被删除") - } - - // 测试删除不存在的执行ID(应该不报错) - err = storage.DeleteResult("nonexistent_id") - if err != nil { - t.Errorf("删除不存在的执行ID不应该报错: %v", err) - } -} - -func TestFileResultStorage_ConcurrentAccess(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - // 并发保存多个结果 - done := make(chan bool, 10) - for i := 0; i < 10; i++ { - go func(id int) { - executionID := fmt.Sprintf("test_exec_%d", id) - toolName := "test_tool" - result := fmt.Sprintf("Result %d\nLine 2\nLine 3", id) - - err := storage.SaveResult(executionID, toolName, result) - if err != nil { - t.Errorf("并发保存失败 (ID: %s): %v", executionID, err) - } - - // 并发读取 - _, err = storage.GetResult(executionID) - if err != nil { - t.Errorf("并发读取失败 (ID: %s): %v", executionID, err) - } - - done <- true - }(i) - } - - // 等待所有goroutine完成 - for i := 0; i < 10; i++ { - <-done - } -} - -func TestFileResultStorage_LargeResult(t *testing.T) { - storage, tmpDir := setupTestStorage(t) - defer cleanupTestStorage(t, tmpDir) - - executionID := "test_exec_large" - toolName := "test_tool" - - // 创建大结果(1000行) - lines := make([]string, 1000) - for i := 0; i < 1000; i++ { - lines[i] = fmt.Sprintf("Line %d: This is a test line with some content", i+1) - } - result := strings.Join(lines, "\n") - - // 保存大结果 - err := storage.SaveResult(executionID, toolName, result) - if err != nil { - t.Fatalf("保存大结果失败: %v", err) - } - - // 验证元数据 - metadata, err := storage.GetResultMetadata(executionID) - if err != nil { - t.Fatalf("获取元数据失败: %v", err) - } - - if metadata.TotalLines != 1000 { - t.Errorf("总行数不匹配。期望: 1000, 实际: %d", metadata.TotalLines) - } - - // 测试分页查询大结果 - page, err := storage.GetResultPage(executionID, 1, 100) - if err != nil { - t.Fatalf("获取第一页失败: %v", err) - } - - if page.TotalPages != 10 { - t.Errorf("总页数不匹配。期望: 10, 实际: %d", page.TotalPages) - } - - if len(page.Lines) != 100 { - t.Errorf("第一页行数不匹配。期望: 100, 实际: %d", len(page.Lines)) - } -}