fscan/Common/output/Writers.go
ZacharyZcR 84b0bb1e28 refactor: 完成common包常量提取和代码重构优化
- 新增constants.go文件统一管理各包常量定义
- 提取logging、output、parsers、proxy包中的硬编码值
- 将30+个魔法数字替换为语义化常量
- 统一错误代码和消息格式
- 清理死代码和未使用变量
- 优化代码可维护性和可读性
- 保持完全向后兼容性

涉及包:
- common/logging: 日志级别和格式常量
- common/output: 输出配置和格式常量
- common/parsers: 解析器配置和验证常量
- common/proxy: 代理协议和错误常量
2025-08-06 21:29:30 +08:00

457 lines
8.5 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 (
"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)
}