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

- 新增constants.go文件统一管理各包常量定义 - 提取logging、output、parsers、proxy包中的硬编码值 - 将30+个魔法数字替换为语义化常量 - 统一错误代码和消息格式 - 清理死代码和未使用变量 - 优化代码可维护性和可读性 - 保持完全向后兼容性 涉及包: - common/logging: 日志级别和格式常量 - common/output: 输出配置和格式常量 - common/parsers: 解析器配置和验证常量 - common/proxy: 代理协议和错误常量
457 lines
8.5 KiB
Go
457 lines
8.5 KiB
Go
package output
|
||
|
||
import (
|
||
"encoding/csv"
|
||
"encoding/json"
|
||
"fmt"
|
||
"os"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// TXTWriter 文本格式写入器
|
||
type TXTWriter struct {
|
||
file *os.File
|
||
mu sync.Mutex
|
||
closed bool
|
||
}
|
||
|
||
// NewTXTWriter 创建文本写入器
|
||
func NewTXTWriter(filePath string) (*TXTWriter, error) {
|
||
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
|
||
if err != nil {
|
||
return nil, fmt.Errorf(ErrCreateFileFailed, "文本", err)
|
||
}
|
||
|
||
return &TXTWriter{
|
||
file: file,
|
||
}, nil
|
||
}
|
||
|
||
// WriteHeader 写入头部(文本格式无需头部)
|
||
func (w *TXTWriter) WriteHeader() error {
|
||
return nil
|
||
}
|
||
|
||
// Write 写入扫描结果
|
||
func (w *TXTWriter) Write(result *ScanResult) error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return fmt.Errorf(ErrWriterClosed)
|
||
}
|
||
|
||
// 格式化 Details 为键值对字符串
|
||
var details string
|
||
if len(result.Details) > 0 {
|
||
pairs := make([]string, 0, len(result.Details))
|
||
for k, v := range result.Details {
|
||
pairs = append(pairs, fmt.Sprintf(TxtKeyValueFormat, k, v))
|
||
}
|
||
details = strings.Join(pairs, TxtDetailsSeparator)
|
||
}
|
||
|
||
// 使用类似原有格式的文本输出
|
||
txt := fmt.Sprintf(TxtOutputTemplate,
|
||
result.Time.Format(TxtTimeFormat),
|
||
result.Type,
|
||
result.Target,
|
||
result.Status,
|
||
)
|
||
if details != "" {
|
||
txt += fmt.Sprintf(TxtDetailsFormat, details)
|
||
}
|
||
txt += TxtNewline
|
||
|
||
_, err := w.file.WriteString(txt)
|
||
return err
|
||
}
|
||
|
||
// Flush 刷新缓冲区
|
||
func (w *TXTWriter) Flush() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return nil
|
||
}
|
||
|
||
return w.file.Sync()
|
||
}
|
||
|
||
// Close 关闭写入器
|
||
func (w *TXTWriter) Close() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return nil
|
||
}
|
||
|
||
w.closed = true
|
||
return w.file.Close()
|
||
}
|
||
|
||
// GetFormat 获取格式类型
|
||
func (w *TXTWriter) GetFormat() OutputFormat {
|
||
return FormatTXT
|
||
}
|
||
|
||
// JSONWriter JSON格式写入器
|
||
type JSONWriter struct {
|
||
file *os.File
|
||
encoder *json.Encoder
|
||
mu sync.Mutex
|
||
closed bool
|
||
}
|
||
|
||
// NewJSONWriter 创建JSON写入器
|
||
func NewJSONWriter(filePath string) (*JSONWriter, error) {
|
||
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
|
||
if err != nil {
|
||
return nil, fmt.Errorf(ErrCreateFileFailed, "JSON", err)
|
||
}
|
||
|
||
encoder := json.NewEncoder(file)
|
||
encoder.SetIndent(JSONIndentPrefix, JSONIndentString)
|
||
|
||
return &JSONWriter{
|
||
file: file,
|
||
encoder: encoder,
|
||
}, nil
|
||
}
|
||
|
||
// WriteHeader 写入头部(JSON格式无需头部)
|
||
func (w *JSONWriter) WriteHeader() error {
|
||
return nil
|
||
}
|
||
|
||
// Write 写入扫描结果
|
||
func (w *JSONWriter) Write(result *ScanResult) error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return fmt.Errorf(ErrWriterClosed)
|
||
}
|
||
|
||
return w.encoder.Encode(result)
|
||
}
|
||
|
||
// Flush 刷新缓冲区
|
||
func (w *JSONWriter) Flush() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return nil
|
||
}
|
||
|
||
return w.file.Sync()
|
||
}
|
||
|
||
// Close 关闭写入器
|
||
func (w *JSONWriter) Close() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return nil
|
||
}
|
||
|
||
w.closed = true
|
||
return w.file.Close()
|
||
}
|
||
|
||
// GetFormat 获取格式类型
|
||
func (w *JSONWriter) GetFormat() OutputFormat {
|
||
return FormatJSON
|
||
}
|
||
|
||
// CSVWriter CSV格式写入器
|
||
type CSVWriter struct {
|
||
file *os.File
|
||
csvWriter *csv.Writer
|
||
mu sync.Mutex
|
||
closed bool
|
||
headerWritten bool
|
||
}
|
||
|
||
// NewCSVWriter 创建CSV写入器
|
||
func NewCSVWriter(filePath string) (*CSVWriter, error) {
|
||
file, err := os.OpenFile(filePath, DefaultFileFlags, DefaultFilePermissions)
|
||
if err != nil {
|
||
return nil, fmt.Errorf(ErrCreateFileFailed, "CSV", err)
|
||
}
|
||
|
||
csvWriter := csv.NewWriter(file)
|
||
|
||
return &CSVWriter{
|
||
file: file,
|
||
csvWriter: csvWriter,
|
||
}, nil
|
||
}
|
||
|
||
// WriteHeader 写入CSV头部
|
||
func (w *CSVWriter) WriteHeader() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.headerWritten {
|
||
return nil
|
||
}
|
||
|
||
headers := CSVHeaders
|
||
err := w.csvWriter.Write(headers)
|
||
if err != nil {
|
||
return fmt.Errorf(ErrWriteHeaderFailed, err)
|
||
}
|
||
|
||
w.csvWriter.Flush()
|
||
w.headerWritten = true
|
||
return w.csvWriter.Error()
|
||
}
|
||
|
||
// Write 写入扫描结果
|
||
func (w *CSVWriter) Write(result *ScanResult) error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return fmt.Errorf(ErrWriterClosed)
|
||
}
|
||
|
||
// 确保头部已写入
|
||
if !w.headerWritten {
|
||
if err := w.writeHeaderUnsafe(); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 序列化Details为JSON字符串
|
||
details, err := json.Marshal(result.Details)
|
||
if err != nil {
|
||
details = []byte(EmptyJSONObject)
|
||
}
|
||
|
||
record := []string{
|
||
result.Time.Format(DefaultTimeFormat),
|
||
string(result.Type),
|
||
result.Target,
|
||
result.Status,
|
||
string(details),
|
||
}
|
||
|
||
err = w.csvWriter.Write(record)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
w.csvWriter.Flush()
|
||
return w.csvWriter.Error()
|
||
}
|
||
|
||
// writeHeaderUnsafe 不安全的写入头部(内部使用,无锁)
|
||
func (w *CSVWriter) writeHeaderUnsafe() error {
|
||
if w.headerWritten {
|
||
return nil
|
||
}
|
||
|
||
headers := CSVHeaders
|
||
err := w.csvWriter.Write(headers)
|
||
if err != nil {
|
||
return fmt.Errorf(ErrWriteHeaderFailed, err)
|
||
}
|
||
|
||
w.csvWriter.Flush()
|
||
w.headerWritten = true
|
||
return w.csvWriter.Error()
|
||
}
|
||
|
||
// Flush 刷新缓冲区
|
||
func (w *CSVWriter) Flush() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return nil
|
||
}
|
||
|
||
w.csvWriter.Flush()
|
||
return w.csvWriter.Error()
|
||
}
|
||
|
||
// Close 关闭写入器
|
||
func (w *CSVWriter) Close() error {
|
||
w.mu.Lock()
|
||
defer w.mu.Unlock()
|
||
|
||
if w.closed {
|
||
return nil
|
||
}
|
||
|
||
w.csvWriter.Flush()
|
||
err := w.csvWriter.Error()
|
||
w.closed = true
|
||
|
||
if fileErr := w.file.Close(); fileErr != nil && err == nil {
|
||
err = fileErr
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
// GetFormat 获取格式类型
|
||
func (w *CSVWriter) GetFormat() OutputFormat {
|
||
return FormatCSV
|
||
}
|
||
|
||
// CSVReader CSV格式读取器
|
||
type CSVReader struct {
|
||
filePath string
|
||
mu sync.Mutex
|
||
}
|
||
|
||
// NewCSVReader 创建CSV读取器
|
||
func NewCSVReader(filePath string) *CSVReader {
|
||
return &CSVReader{
|
||
filePath: filePath,
|
||
}
|
||
}
|
||
|
||
// Read 读取所有结果
|
||
func (r *CSVReader) Read() ([]*ScanResult, error) {
|
||
return r.ReadWithFilter(nil)
|
||
}
|
||
|
||
// ReadWithFilter 带过滤条件读取结果
|
||
func (r *CSVReader) ReadWithFilter(filter *ResultFilter) ([]*ScanResult, error) {
|
||
r.mu.Lock()
|
||
defer r.mu.Unlock()
|
||
|
||
file, err := os.Open(r.filePath)
|
||
if err != nil {
|
||
return nil, fmt.Errorf(ErrOpenFileFailed, err)
|
||
}
|
||
defer file.Close()
|
||
|
||
reader := csv.NewReader(file)
|
||
records, err := reader.ReadAll()
|
||
if err != nil {
|
||
return nil, fmt.Errorf(ErrReadFileFailed, err)
|
||
}
|
||
|
||
var results []*ScanResult
|
||
for i, row := range records {
|
||
// 跳过CSV头部
|
||
if i == CSVHeaderRowIndex {
|
||
continue
|
||
}
|
||
if len(row) < CSVMinColumns {
|
||
continue // 数据不完整
|
||
}
|
||
|
||
result, err := r.parseCSVRow(row)
|
||
if err != nil {
|
||
continue // 跳过解析失败的行
|
||
}
|
||
|
||
// 应用过滤器
|
||
if filter != nil && !r.matchFilter(result, filter) {
|
||
continue
|
||
}
|
||
|
||
results = append(results, result)
|
||
|
||
// 应用限制
|
||
if filter != nil && filter.Limit > 0 && len(results) >= filter.Limit {
|
||
break
|
||
}
|
||
}
|
||
|
||
return results, nil
|
||
}
|
||
|
||
// parseCSVRow 解析CSV行
|
||
func (r *CSVReader) parseCSVRow(row []string) (*ScanResult, error) {
|
||
// 解析时间
|
||
t, err := parseTime(row[CSVTimeColumnIndex])
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 解析Details
|
||
var details map[string]interface{}
|
||
if err := json.Unmarshal([]byte(row[CSVDetailsColumnIndex]), &details); err != nil {
|
||
details = make(map[string]interface{})
|
||
}
|
||
|
||
return &ScanResult{
|
||
Time: t,
|
||
Type: ResultType(row[CSVTypeColumnIndex]),
|
||
Target: row[CSVTargetColumnIndex],
|
||
Status: row[CSVStatusColumnIndex],
|
||
Details: details,
|
||
}, nil
|
||
}
|
||
|
||
// matchFilter 检查结果是否匹配过滤器
|
||
func (r *CSVReader) matchFilter(result *ScanResult, filter *ResultFilter) bool {
|
||
// 检查类型过滤
|
||
if len(filter.Types) > 0 {
|
||
found := false
|
||
for _, t := range filter.Types {
|
||
if result.Type == t {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if !found {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 检查目标过滤
|
||
if len(filter.Targets) > 0 {
|
||
found := false
|
||
for _, target := range filter.Targets {
|
||
if strings.Contains(result.Target, target) {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if !found {
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 检查时间范围过滤
|
||
if filter.TimeRange != nil {
|
||
if result.Time.Before(filter.TimeRange.Start) || result.Time.After(filter.TimeRange.End) {
|
||
return false
|
||
}
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// Close 关闭读取器
|
||
func (r *CSVReader) Close() error {
|
||
return nil // CSV读取器无需特殊关闭操作
|
||
}
|
||
|
||
// parseTime 解析时间字符串
|
||
func parseTime(timeStr string) (time.Time, error) {
|
||
// 尝试多种时间格式
|
||
formats := GetSupportedTimeFormats()
|
||
|
||
for _, format := range formats {
|
||
if t, err := time.Parse(format, timeStr); err == nil {
|
||
return t, nil
|
||
}
|
||
}
|
||
|
||
return time.Time{}, fmt.Errorf(ErrParseTimeFailed, timeStr)
|
||
} |