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

主要改进: - 将output和parsers包中的硬编码错误消息迁移到i18n国际化系统 - 修复constants.go中未引用常量的使用问题 - 消除所有硬编码字符串、魔术数字和协议标识符 - 清理死代码常量MinIPv4OctetValue 具体变更: * output包: 删除19个硬编码错误消息,新增13个i18n消息键 * parsers包: 删除24个硬编码错误模板,新增22个i18n消息键 * 修复8个未引用的Default常量在对应默认选项函数中的使用 * 替换11处网络协议相关的硬编码值为常量引用 * 更新6个错误类型常量和验证逻辑的硬编码使用 * 修复2处IPv4八位组计数的硬编码为常量引用 提升效果: - 支持中英文错误消息国际化切换 - 统一常量管理,提高代码可维护性 - 消除代码重复,符合DRY原则 - 清理死代码,优化代码质量 涉及文件: 11个文件,新增210行,删除103行
293 lines
7.8 KiB
Go
293 lines
7.8 KiB
Go
package parsers
|
||
|
||
import (
|
||
"bufio"
|
||
"context"
|
||
"fmt"
|
||
"os"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
|
||
"github.com/shadow1ng/fscan/common/i18n"
|
||
)
|
||
|
||
// FileReader 高性能文件读取器
|
||
type FileReader struct {
|
||
mu sync.RWMutex
|
||
cache map[string]*FileResult // 文件缓存
|
||
maxCacheSize int // 最大缓存大小
|
||
enableCache bool // 是否启用缓存
|
||
maxFileSize int64 // 最大文件大小
|
||
timeout time.Duration // 读取超时
|
||
enableValidation bool // 是否启用内容验证
|
||
}
|
||
|
||
// FileResult 文件读取结果
|
||
type FileResult struct {
|
||
Lines []string `json:"lines"`
|
||
Source *FileSource `json:"source"`
|
||
ReadTime time.Duration `json:"read_time"`
|
||
ValidLines int `json:"valid_lines"`
|
||
Errors []error `json:"errors,omitempty"`
|
||
Cached bool `json:"cached"`
|
||
}
|
||
|
||
// NewFileReader 创建文件读取器
|
||
func NewFileReader(options *FileReaderOptions) *FileReader {
|
||
if options == nil {
|
||
options = DefaultFileReaderOptions()
|
||
}
|
||
|
||
return &FileReader{
|
||
cache: make(map[string]*FileResult),
|
||
maxCacheSize: options.MaxCacheSize,
|
||
enableCache: options.EnableCache,
|
||
maxFileSize: options.MaxFileSize,
|
||
timeout: options.Timeout,
|
||
enableValidation: options.EnableValidation,
|
||
}
|
||
}
|
||
|
||
// FileReaderOptions 文件读取器选项
|
||
type FileReaderOptions struct {
|
||
MaxCacheSize int // 最大缓存文件数
|
||
EnableCache bool // 启用文件缓存
|
||
MaxFileSize int64 // 最大文件大小(字节)
|
||
Timeout time.Duration // 读取超时
|
||
EnableValidation bool // 启用内容验证
|
||
TrimSpace bool // 自动清理空白字符
|
||
SkipEmpty bool // 跳过空行
|
||
SkipComments bool // 跳过注释行(#开头)
|
||
}
|
||
|
||
// DefaultFileReaderOptions 默认文件读取器选项
|
||
func DefaultFileReaderOptions() *FileReaderOptions {
|
||
return &FileReaderOptions{
|
||
MaxCacheSize: DefaultMaxCacheSize,
|
||
EnableCache: DefaultEnableCache,
|
||
MaxFileSize: DefaultFileReaderMaxFileSize,
|
||
Timeout: DefaultFileReaderTimeout,
|
||
EnableValidation: DefaultFileReaderEnableValidation,
|
||
TrimSpace: DefaultTrimSpace,
|
||
SkipEmpty: DefaultSkipEmpty,
|
||
SkipComments: DefaultSkipComments,
|
||
}
|
||
}
|
||
|
||
// ReadFile 读取文件内容
|
||
func (fr *FileReader) ReadFile(filename string, options ...*FileReaderOptions) (*FileResult, error) {
|
||
if filename == "" {
|
||
return nil, NewParseError("FILE_ERROR", "文件名为空", filename, 0, ErrEmptyInput)
|
||
}
|
||
|
||
// 检查缓存
|
||
if fr.enableCache {
|
||
if result := fr.getFromCache(filename); result != nil {
|
||
result.Cached = true
|
||
return result, nil
|
||
}
|
||
}
|
||
|
||
// 合并选项
|
||
opts := fr.mergeOptions(options...)
|
||
|
||
// 创建带超时的上下文
|
||
ctx, cancel := context.WithTimeout(context.Background(), fr.timeout)
|
||
defer cancel()
|
||
|
||
// 异步读取文件
|
||
resultChan := make(chan *FileResult, 1)
|
||
errorChan := make(chan error, 1)
|
||
|
||
go func() {
|
||
result, err := fr.readFileSync(filename, opts)
|
||
if err != nil {
|
||
errorChan <- err
|
||
} else {
|
||
resultChan <- result
|
||
}
|
||
}()
|
||
|
||
// 等待结果或超时
|
||
select {
|
||
case result := <-resultChan:
|
||
// 添加到缓存
|
||
if fr.enableCache {
|
||
fr.addToCache(filename, result)
|
||
}
|
||
return result, nil
|
||
case err := <-errorChan:
|
||
return nil, err
|
||
case <-ctx.Done():
|
||
return nil, NewParseError(ErrorTypeTimeout, "文件读取超时", filename, 0, ctx.Err())
|
||
}
|
||
}
|
||
|
||
// =============================================================================================
|
||
// 已删除的死代码(未使用):ReadFiles 并发读取多个文件的方法
|
||
// =============================================================================================
|
||
|
||
// readFileSync 同步读取文件
|
||
func (fr *FileReader) readFileSync(filename string, options *FileReaderOptions) (*FileResult, error) {
|
||
startTime := time.Now()
|
||
|
||
// 检查文件
|
||
fileInfo, err := os.Stat(filename)
|
||
if err != nil {
|
||
return nil, NewParseError("FILE_ERROR", "文件不存在或无法访问", filename, 0, err)
|
||
}
|
||
|
||
// 检查文件大小
|
||
if fileInfo.Size() > fr.maxFileSize {
|
||
return nil, NewParseError("FILE_ERROR",
|
||
fmt.Sprintf("文件过大: %d bytes, 最大限制: %d bytes", fileInfo.Size(), fr.maxFileSize),
|
||
filename, 0, nil)
|
||
}
|
||
|
||
// 打开文件
|
||
file, err := os.Open(filename)
|
||
if err != nil {
|
||
return nil, NewParseError("FILE_ERROR", "无法打开文件", filename, 0, err)
|
||
}
|
||
defer file.Close()
|
||
|
||
// 创建结果
|
||
result := &FileResult{
|
||
Lines: make([]string, 0),
|
||
Source: &FileSource{
|
||
Path: filename,
|
||
Size: fileInfo.Size(),
|
||
ModTime: fileInfo.ModTime(),
|
||
},
|
||
}
|
||
|
||
// 读取文件内容
|
||
scanner := bufio.NewScanner(file)
|
||
scanner.Split(bufio.ScanLines)
|
||
|
||
lineNum := 0
|
||
validLines := 0
|
||
|
||
for scanner.Scan() {
|
||
lineNum++
|
||
line := scanner.Text()
|
||
|
||
// 处理行内容
|
||
if processedLine, valid := fr.processLine(line, options); valid {
|
||
result.Lines = append(result.Lines, processedLine)
|
||
validLines++
|
||
}
|
||
}
|
||
|
||
// 检查扫描错误
|
||
if err := scanner.Err(); err != nil {
|
||
return nil, NewParseError(ErrorTypeReadError, i18n.GetText("parser_file_scan_failed"), filename, lineNum, err)
|
||
}
|
||
|
||
// 更新统计信息
|
||
result.Source.LineCount = lineNum
|
||
result.Source.ValidLines = validLines
|
||
result.ValidLines = validLines
|
||
result.ReadTime = time.Since(startTime)
|
||
|
||
return result, nil
|
||
}
|
||
|
||
// processLine 处理单行内容
|
||
func (fr *FileReader) processLine(line string, options *FileReaderOptions) (string, bool) {
|
||
// 清理空白字符
|
||
if options.TrimSpace {
|
||
line = strings.TrimSpace(line)
|
||
}
|
||
|
||
// 跳过空行
|
||
if options.SkipEmpty && line == "" {
|
||
return "", false
|
||
}
|
||
|
||
// 跳过注释行
|
||
if options.SkipComments && strings.HasPrefix(line, CommentPrefix) {
|
||
return "", false
|
||
}
|
||
|
||
// 内容验证
|
||
if options.EnableValidation && fr.enableValidation {
|
||
if !fr.validateLine(line) {
|
||
return "", false
|
||
}
|
||
}
|
||
|
||
return line, true
|
||
}
|
||
|
||
// validateLine 验证行内容
|
||
func (fr *FileReader) validateLine(line string) bool {
|
||
// 基本验证:检查是否包含特殊字符或过长
|
||
if len(line) > MaxLineLength { // 单行最大字符数
|
||
return false
|
||
}
|
||
|
||
// 检查是否包含控制字符
|
||
for _, r := range line {
|
||
if r < MaxValidRune && r != TabRune && r != NewlineRune && r != CarriageReturnRune { // 排除tab、换行、回车
|
||
return false
|
||
}
|
||
}
|
||
|
||
return true
|
||
}
|
||
|
||
// mergeOptions 合并选项
|
||
func (fr *FileReader) mergeOptions(options ...*FileReaderOptions) *FileReaderOptions {
|
||
opts := DefaultFileReaderOptions()
|
||
if len(options) > 0 && options[0] != nil {
|
||
opts = options[0]
|
||
}
|
||
return opts
|
||
}
|
||
|
||
// getFromCache 从缓存获取结果
|
||
func (fr *FileReader) getFromCache(filename string) *FileResult {
|
||
fr.mu.RLock()
|
||
defer fr.mu.RUnlock()
|
||
|
||
if result, exists := fr.cache[filename]; exists {
|
||
// 检查文件是否有更新
|
||
if fileInfo, err := os.Stat(filename); err == nil {
|
||
if fileInfo.ModTime().After(result.Source.ModTime) {
|
||
// 文件已更新,从缓存中移除
|
||
delete(fr.cache, filename)
|
||
return nil
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// addToCache 添加到缓存
|
||
func (fr *FileReader) addToCache(filename string, result *FileResult) {
|
||
fr.mu.Lock()
|
||
defer fr.mu.Unlock()
|
||
|
||
// 检查缓存大小
|
||
if len(fr.cache) >= fr.maxCacheSize {
|
||
// 移除最旧的条目(简单的LRU策略)
|
||
var oldestFile string
|
||
var oldestTime time.Time
|
||
for file, res := range fr.cache {
|
||
if oldestFile == "" || res.Source.ModTime.Before(oldestTime) {
|
||
oldestFile = file
|
||
oldestTime = res.Source.ModTime
|
||
}
|
||
}
|
||
delete(fr.cache, oldestFile)
|
||
}
|
||
|
||
fr.cache[filename] = result
|
||
}
|
||
|
||
// =============================================================================================
|
||
// 已删除的死代码(未使用):ClearCache 和 GetCacheStats 方法
|
||
// =============================================================================================
|