fscan/common/output/manager.go
ZacharyZcR a3177b28a6 fix: 修复插件系统逻辑Bug和架构问题
主要修复:
1. 修复时间显示Bug - StartTime初始化问题
2. 修复Web智能探测错误检测预定义端口而非用户指定端口
3. 修复本地插件被错误调用到端口扫描中的问题
4. 修复host:port格式双重处理导致的多余端口扫描
5. 统一插件过滤逻辑,消除接口不一致性
6. 优化Web检测缓存机制,减少重复HTTP请求

技术改进:
- 重构插件适用性检查逻辑,确保策略过滤器正确工作
- 区分Web检测的自动发现模式和用户指定端口模式
- 在解析阶段正确处理host:port格式,避免与默认端口冲突
- 完善缓存机制,提升性能

测试验证:
- ./fscan -h 127.0.0.1:3306 现在只检测3306端口
- 本地插件不再参与端口扫描
- Web检测只对指定端口进行协议检测
- 时间显示正确
2025-09-01 23:50:32 +00:00

257 lines
5.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package output
import (
"fmt"
"os"
"path/filepath"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
// Manager 输出管理器
type Manager struct {
mu sync.RWMutex
config *ManagerConfig
writer OutputWriter
reader OutputReader
statistics *Statistics
buffer []*ScanResult
bufferMutex sync.Mutex
flushTicker *time.Ticker
stopChan chan struct{}
initialized bool
closed bool
}
// NewManager 创建新的输出管理器
func NewManager(config *ManagerConfig) (*Manager, error) {
if config == nil {
return nil, fmt.Errorf(i18n.GetText("output_config_nil"))
}
// 验证输出格式
if err := validateFormat(config.Format); err != nil {
return nil, err
}
// 创建输出目录
if err := createOutputDir(config.OutputPath); err != nil {
return nil, err
}
manager := &Manager{
config: config,
statistics: NewStatistics(),
stopChan: make(chan struct{}),
}
// 初始化写入器
if err := manager.initializeWriter(); err != nil {
return nil, err
}
// 初始化读取器
manager.initializeReader()
// 如果启用缓冲,初始化缓冲区
if config.EnableBuffer {
manager.buffer = make([]*ScanResult, 0, config.BufferSize)
// 如果启用自动刷新,启动定时器
if config.AutoFlush {
manager.startAutoFlush()
}
}
manager.initialized = true
return manager, nil
}
// validateFormat 验证输出格式
func validateFormat(format OutputFormat) error {
switch format {
case FormatTXT, FormatJSON, FormatCSV:
return nil
default:
return fmt.Errorf(i18n.GetText("output_unsupported_format"), format)
}
}
// createOutputDir 创建输出目录
func createOutputDir(outputPath string) error {
dir := filepath.Dir(outputPath)
return os.MkdirAll(dir, DefaultDirPermissions)
}
// initializeWriter 初始化写入器
func (m *Manager) initializeWriter() error {
var writer OutputWriter
var err error
switch m.config.Format {
case FormatTXT:
writer, err = NewTXTWriter(m.config.OutputPath)
case FormatJSON:
writer, err = NewJSONWriter(m.config.OutputPath)
case FormatCSV:
writer, err = NewCSVWriter(m.config.OutputPath)
default:
return fmt.Errorf(i18n.GetText("output_unsupported_format"), m.config.Format)
}
if err != nil {
return fmt.Errorf(i18n.GetText("output_writer_init_failed"), err)
}
m.writer = writer
// 写入头部(如果需要)
return m.writer.WriteHeader()
}
// initializeReader 初始化读取器
func (m *Manager) initializeReader() {
// 目前只有CSV格式支持读取
if m.config.Format == FormatCSV {
m.reader = NewCSVReader(m.config.OutputPath)
}
}
// startAutoFlush 启动自动刷新
func (m *Manager) startAutoFlush() {
m.flushTicker = time.NewTicker(m.config.FlushInterval)
go func() {
for {
select {
case <-m.flushTicker.C:
m.flushBuffer()
case <-m.stopChan:
return
}
}
}()
}
// SaveResult 保存扫描结果
func (m *Manager) SaveResult(result *ScanResult) error {
m.mu.RLock()
defer m.mu.RUnlock()
if !m.initialized {
return fmt.Errorf(i18n.GetText("output_manager_not_init"))
}
if m.closed {
return fmt.Errorf(i18n.GetText("output_manager_closed"))
}
// 更新统计信息
m.statistics.AddResult(result.Type)
// 如果启用缓冲,先添加到缓冲区
if m.config.EnableBuffer {
return m.addToBuffer(result)
}
// 直接写入
return m.writer.Write(result)
}
// addToBuffer 添加结果到缓冲区
func (m *Manager) addToBuffer(result *ScanResult) error {
m.bufferMutex.Lock()
defer m.bufferMutex.Unlock()
m.buffer = append(m.buffer, result)
// 如果缓冲区已满,立即刷新
if len(m.buffer) >= m.config.BufferSize {
return m.flushBufferUnsafe()
}
return nil
}
// flushBuffer 刷新缓冲区(加锁版本)
func (m *Manager) flushBuffer() error {
m.bufferMutex.Lock()
defer m.bufferMutex.Unlock()
return m.flushBufferUnsafe()
}
// flushBufferUnsafe 刷新缓冲区(无锁版本,内部使用)
func (m *Manager) flushBufferUnsafe() error {
if len(m.buffer) == 0 {
return nil
}
// 批量写入
for _, result := range m.buffer {
if err := m.writer.Write(result); err != nil {
return fmt.Errorf(i18n.GetText("output_write_failed"), err)
}
}
// 刷新写入器
if err := m.writer.Flush(); err != nil {
return fmt.Errorf(i18n.GetText("output_flush_failed"), err)
}
// 清空缓冲区
m.buffer = m.buffer[:0]
return nil
}
// =============================================================================================
// 已删除的死代码(未使用):
// GetResults, GetResultsWithFilter, GetStatistics, Flush 方法
// =============================================================================================
// Close 关闭输出管理器
func (m *Manager) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.closed {
return nil
}
// 停止自动刷新
if m.flushTicker != nil {
m.flushTicker.Stop()
close(m.stopChan)
}
// 最后一次刷新缓冲区
if m.config.EnableBuffer {
m.flushBufferUnsafe()
}
// 关闭写入器
var err error
if m.writer != nil {
err = m.writer.Close()
}
// 关闭读取器
if m.reader != nil {
if closeErr := m.reader.Close(); closeErr != nil && err == nil {
err = closeErr
}
}
m.closed = true
return err
}
// =============================================================================================
// 已删除的死代码(未使用):
// IsInitialized, IsClosed, GetConfig, UpdateConfig 方法
// =============================================================================================
// =============================================================================================
// 已删除的死代码未使用globalManager 全局变量及 SetGlobalManager 函数
// =============================================================================================