mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 05:56:46 +08:00

- 重命名 Common -> common,WebScan -> webscan,遵循 Go 包命名约定 - 修复模块路径大小写不匹配导致的编译错误 - 清理依赖项,优化 go.mod 文件 - 添加 Docker 测试环境配置文件 - 新增镜像拉取脚本以处理网络超时问题 - 成功编译生成 fscan v2.2.1 可执行文件 该修复解决了 Linux 系统下包名大小写敏感导致的模块解析失败问题。
257 lines
5.7 KiB
Go
257 lines
5.7 KiB
Go
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 函数
|
||
// =============================================================================================
|