package output import ( "encoding/csv" "encoding/json" "fmt" "os" "strings" "sync" "time" "github.com/shadow1ng/fscan/common/i18n" ) // 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(i18n.GetText("output_create_file_failed"), "文本", 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(i18n.GetText("output_writer_closed")) } // 格式化 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(i18n.GetText("output_create_file_failed"), "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(i18n.GetText("output_writer_closed")) } 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(i18n.GetText("output_create_file_failed"), "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(i18n.GetText("output_write_header_failed"), 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(i18n.GetText("output_writer_closed")) } // 确保头部已写入 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(i18n.GetText("output_write_header_failed"), 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(i18n.GetText("output_open_file_failed"), err) } defer file.Close() reader := csv.NewReader(file) records, err := reader.ReadAll() if err != nil { return nil, fmt.Errorf(i18n.GetText("output_read_file_failed"), 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(i18n.GetText("output_parse_time_failed"), timeStr) }