mirror of
https://github.com/shadow1ng/fscan.git
synced 2025-09-14 14:06:44 +08:00

主要更改: - 重构Log.go和Output.go为模块化架构 - 创建独立的logging和output模块 - 新增LevelBaseInfoSuccess默认日志级别(显示BASE、INFO、SUCCESS) - 添加运行时间显示到每条日志前面 - 保持完全向后兼容的API接口 - 支持多种输出格式(TXT、JSON、CSV) - 优化日志格式化和颜色显示 技术改进: - 模块化设计便于扩展和维护 - 智能时间格式化(毫秒→秒→分钟→小时) - 支持缓冲和批量输出 - 线程安全的并发处理
461 lines
8.6 KiB
Go
461 lines
8.6 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, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建文本文件失败: %v", 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("写入器已关闭")
|
||
}
|
||
|
||
// 格式化 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("%s=%v", k, v))
|
||
}
|
||
details = strings.Join(pairs, ", ")
|
||
}
|
||
|
||
// 使用类似原有格式的文本输出
|
||
txt := fmt.Sprintf("[%s] [%s] %s - %s",
|
||
result.Time.Format("2006-01-02 15:04:05"),
|
||
result.Type,
|
||
result.Target,
|
||
result.Status,
|
||
)
|
||
if details != "" {
|
||
txt += fmt.Sprintf(" (%s)", details)
|
||
}
|
||
txt += "\n"
|
||
|
||
_, 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, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建JSON文件失败: %v", err)
|
||
}
|
||
|
||
encoder := json.NewEncoder(file)
|
||
encoder.SetIndent("", " ")
|
||
|
||
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("写入器已关闭")
|
||
}
|
||
|
||
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, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("创建CSV文件失败: %v", 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 := []string{"Time", "Type", "Target", "Status", "Details"}
|
||
err := w.csvWriter.Write(headers)
|
||
if err != nil {
|
||
return fmt.Errorf("写入CSV头部失败: %v", 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("写入器已关闭")
|
||
}
|
||
|
||
// 确保头部已写入
|
||
if !w.headerWritten {
|
||
if err := w.writeHeaderUnsafe(); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
// 序列化Details为JSON字符串
|
||
details, err := json.Marshal(result.Details)
|
||
if err != nil {
|
||
details = []byte("{}")
|
||
}
|
||
|
||
record := []string{
|
||
result.Time.Format("2006-01-02 15:04:05"),
|
||
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 := []string{"Time", "Type", "Target", "Status", "Details"}
|
||
err := w.csvWriter.Write(headers)
|
||
if err != nil {
|
||
return fmt.Errorf("写入CSV头部失败: %v", 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("打开CSV文件失败: %v", err)
|
||
}
|
||
defer file.Close()
|
||
|
||
reader := csv.NewReader(file)
|
||
records, err := reader.ReadAll()
|
||
if err != nil {
|
||
return nil, fmt.Errorf("读取CSV文件失败: %v", err)
|
||
}
|
||
|
||
var results []*ScanResult
|
||
for i, row := range records {
|
||
// 跳过CSV头部
|
||
if i == 0 {
|
||
continue
|
||
}
|
||
if len(row) < 5 {
|
||
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[0])
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
// 解析Details
|
||
var details map[string]interface{}
|
||
if err := json.Unmarshal([]byte(row[4]), &details); err != nil {
|
||
details = make(map[string]interface{})
|
||
}
|
||
|
||
return &ScanResult{
|
||
Time: t,
|
||
Type: ResultType(row[1]),
|
||
Target: row[2],
|
||
Status: row[3],
|
||
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 := []string{
|
||
"2006-01-02 15:04:05",
|
||
"2006-01-02T15:04:05Z07:00",
|
||
"2006-01-02T15:04:05.000Z07:00",
|
||
}
|
||
|
||
for _, format := range formats {
|
||
if t, err := time.Parse(format, timeStr); err == nil {
|
||
return t, nil
|
||
}
|
||
}
|
||
|
||
return time.Time{}, fmt.Errorf("无法解析时间: %s", timeStr)
|
||
} |