fscan/common/ProgressManager.go
ZacharyZcR c2b63a57e2 refactor: 修正包命名规范并修复编译问题
- 重命名 Common -> common,WebScan -> webscan,遵循 Go 包命名约定
- 修复模块路径大小写不匹配导致的编译错误
- 清理依赖项,优化 go.mod 文件
- 添加 Docker 测试环境配置文件
- 新增镜像拉取脚本以处理网络超时问题
- 成功编译生成 fscan v2.2.1 可执行文件

该修复解决了 Linux 系统下包名大小写敏感导致的模块解析失败问题。
2025-09-01 22:41:54 +00:00

457 lines
11 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 common
import (
"fmt"
"os"
"runtime"
"sync"
"time"
"github.com/shadow1ng/fscan/common/i18n"
)
/*
ProgressManager.go - 固定底部进度条管理器
提供固定在终端底部的进度条显示,与正常输出内容分离。
使用终端控制码实现位置固定和内容保护。
*/
// ProgressManager 进度条管理器
type ProgressManager struct {
mu sync.RWMutex
enabled bool
total int64
current int64
description string
startTime time.Time
isActive bool
terminalHeight int
reservedLines int // 为进度条保留的行数
lastContentLine int // 最后一行内容的位置
// 输出缓冲相关
outputMutex sync.Mutex
// 活跃指示器相关
spinnerIndex int
lastActivity time.Time
activityTicker *time.Ticker
stopActivityChan chan struct{}
// 内存监控相关
lastMemUpdate time.Time
memStats runtime.MemStats
}
var (
globalProgressManager *ProgressManager
progressMutex sync.Mutex
// 活跃指示器字符序列(旋转动画)
spinnerChars = []string{"|", "/", "-", "\\"}
// 活跃指示器更新间隔
activityUpdateInterval = 500 * time.Millisecond
)
// GetProgressManager 获取全局进度条管理器
func GetProgressManager() *ProgressManager {
progressMutex.Lock()
defer progressMutex.Unlock()
if globalProgressManager == nil {
globalProgressManager = &ProgressManager{
enabled: true,
reservedLines: 2, // 保留2行进度条 + 空行
terminalHeight: getTerminalHeight(),
}
}
return globalProgressManager
}
// InitProgress 初始化进度条
func (pm *ProgressManager) InitProgress(total int64, description string) {
if !ShowProgress || Silent {
pm.enabled = false
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.total = total
pm.current = 0
pm.description = description
pm.startTime = time.Now()
pm.isActive = true
pm.enabled = true
pm.lastActivity = time.Now()
pm.spinnerIndex = 0
pm.lastMemUpdate = time.Now().Add(-2 * time.Second) // 强制首次更新内存
// 为进度条保留空间
pm.setupProgressSpace()
// 启动活跃指示器
pm.startActivityIndicator()
// 初始显示进度条
pm.renderProgress()
}
// UpdateProgress 更新进度
func (pm *ProgressManager) UpdateProgress(increment int64) {
if !pm.enabled || !pm.isActive {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.current += increment
if pm.current > pm.total {
pm.current = pm.total
}
// 更新活跃时间
pm.lastActivity = time.Now()
pm.renderProgress()
}
// =============================================================================================
// 已删除的死代码未使用SetProgress 设置当前进度
// =============================================================================================
// FinishProgress 完成进度条
func (pm *ProgressManager) FinishProgress() {
if !pm.enabled || !pm.isActive {
return
}
pm.mu.Lock()
defer pm.mu.Unlock()
pm.current = pm.total
pm.renderProgress()
// 停止活跃指示器
pm.stopActivityIndicator()
// 显示完成信息
pm.showCompletionInfo()
// 清理进度条区域,恢复正常输出
pm.clearProgressArea()
pm.isActive = false
}
// setupProgressSpace 设置进度条空间
func (pm *ProgressManager) setupProgressSpace() {
// 简化设计:进度条在原地更新,不需要预留额外空间
// 只是标记进度条开始的位置
pm.lastContentLine = 0
}
// =============================================================================================
// 已删除的死代码未使用moveToContentArea 和 moveToProgressLine 方法
// =============================================================================================
// renderProgress 渲染进度条(使用锁避免输出冲突)
func (pm *ProgressManager) renderProgress() {
pm.outputMutex.Lock()
defer pm.outputMutex.Unlock()
pm.renderProgressUnsafe()
}
// generateProgressBar 生成进度条字符串
func (pm *ProgressManager) generateProgressBar() string {
if pm.total == 0 {
spinner := pm.getActivityIndicator()
memInfo := pm.getMemoryInfo()
return fmt.Sprintf("%s %s 等待中... %s", pm.description, spinner, memInfo)
}
percentage := float64(pm.current) / float64(pm.total) * 100
elapsed := time.Since(pm.startTime)
// 获取并发状态
concurrencyStatus := GetConcurrencyMonitor().GetConcurrencyStatus()
// 计算预估剩余时间
var eta string
if pm.current > 0 {
totalTime := elapsed * time.Duration(pm.total) / time.Duration(pm.current)
remaining := totalTime - elapsed
if remaining > 0 {
eta = fmt.Sprintf(" ETA:%s", formatDuration(remaining))
}
}
// 计算速度
speed := float64(pm.current) / elapsed.Seconds()
speedStr := ""
if speed > 0 {
speedStr = fmt.Sprintf(" (%.1f/s)", speed)
}
// 生成进度条
barWidth := 30
filled := int(percentage * float64(barWidth) / 100)
bar := ""
if NoColor {
// 无颜色版本
bar = "[" +
fmt.Sprintf("%s%s",
string(make([]rune, filled)),
string(make([]rune, barWidth-filled))) +
"]"
for i := 0; i < filled; i++ {
bar = bar[:i+1] + "=" + bar[i+2:]
}
for i := filled; i < barWidth; i++ {
bar = bar[:i+1] + "-" + bar[i+2:]
}
} else {
// 彩色版本
bar = "|"
for i := 0; i < barWidth; i++ {
if i < filled {
bar += "#"
} else {
bar += "."
}
}
bar += "|"
}
// 生成活跃指示器
spinner := pm.getActivityIndicator()
// 构建基础进度条
baseProgress := fmt.Sprintf("%s %s %6.1f%% %s (%d/%d)%s%s",
pm.description, spinner, percentage, bar, pm.current, pm.total, speedStr, eta)
// 添加内存信息
memInfo := pm.getMemoryInfo()
// 添加并发状态
if concurrencyStatus != "" {
return fmt.Sprintf("%s [%s] %s", baseProgress, concurrencyStatus, memInfo)
}
return fmt.Sprintf("%s %s", baseProgress, memInfo)
}
// showCompletionInfo 显示完成信息
func (pm *ProgressManager) showCompletionInfo() {
elapsed := time.Since(pm.startTime)
// 换行并显示完成信息
fmt.Print("\n")
completionMsg := i18n.GetText("progress_scan_completed")
if NoColor {
fmt.Printf("[完成] %s %d/%d (耗时: %s)\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
} else {
fmt.Printf("\033[32m[完成] %s %d/%d\033[0m \033[90m(耗时: %s)\033[0m\n",
completionMsg, pm.total, pm.total, formatDuration(elapsed))
}
}
// clearProgressArea 清理进度条区域
func (pm *ProgressManager) clearProgressArea() {
// 简单清除当前行
fmt.Print("\033[2K\r")
}
// IsActive 检查进度条是否活跃
func (pm *ProgressManager) IsActive() bool {
pm.mu.RLock()
defer pm.mu.RUnlock()
return pm.isActive && pm.enabled
}
// getTerminalHeight 获取终端高度
func getTerminalHeight() int {
// 对于固定底部进度条,我们暂时禁用终端高度检测
// 因为在不同终端环境中可能会有问题
// 改为使用相对定位方式
return 0 // 返回0表示使用简化模式
}
// formatDuration 格式化时间间隔
func formatDuration(d time.Duration) string {
if d < time.Minute {
return fmt.Sprintf("%.1fs", d.Seconds())
} else if d < time.Hour {
return fmt.Sprintf("%.1fm", d.Minutes())
} else {
return fmt.Sprintf("%.1fh", d.Hours())
}
}
// 全局函数,方便其他模块调用
func InitProgressBar(total int64, description string) {
GetProgressManager().InitProgress(total, description)
}
func UpdateProgressBar(increment int64) {
GetProgressManager().UpdateProgress(increment)
}
// =============================================================================================
// 已删除的死代码未使用SetProgressBar 全局函数
// =============================================================================================
func FinishProgressBar() {
GetProgressManager().FinishProgress()
}
func IsProgressActive() bool {
return GetProgressManager().IsActive()
}
// =============================================================================
// 日志输出协调功能
// =============================================================================
// LogWithProgress 在进度条活跃时协调日志输出
func LogWithProgress(message string) {
pm := GetProgressManager()
if !pm.IsActive() {
// 如果进度条不活跃,直接输出
fmt.Println(message)
return
}
pm.outputMutex.Lock()
defer pm.outputMutex.Unlock()
// 清除当前行(清除进度条)
fmt.Print("\033[2K\r")
// 输出日志消息
fmt.Println(message)
// 重绘进度条
pm.renderProgressUnsafe()
}
// renderProgressUnsafe 不加锁的进度条渲染(内部使用)
func (pm *ProgressManager) renderProgressUnsafe() {
if !pm.enabled || !pm.isActive {
return
}
// 移动到行首并清除当前行
fmt.Print("\033[2K\r")
// 生成进度条内容
progressBar := pm.generateProgressBar()
// 输出进度条(带颜色,如果启用)
if NoColor {
fmt.Print(progressBar)
} else {
fmt.Printf("\033[36m%s\033[0m", progressBar) // 青色
}
// 刷新输出
os.Stdout.Sync()
}
// =============================================================================
// 活跃指示器相关方法
// =============================================================================
// startActivityIndicator 启动活跃指示器
func (pm *ProgressManager) startActivityIndicator() {
// 防止重复启动
if pm.activityTicker != nil {
return
}
pm.activityTicker = time.NewTicker(activityUpdateInterval)
pm.stopActivityChan = make(chan struct{})
go func() {
for {
select {
case <-pm.activityTicker.C:
// 只有在活跃状态下才更新指示器
if pm.isActive && pm.enabled {
pm.mu.Lock()
pm.spinnerIndex = (pm.spinnerIndex + 1) % len(spinnerChars)
pm.mu.Unlock()
// 只有在长时间没有进度更新时才重新渲染
// 这样可以避免频繁更新时的性能问题
if time.Since(pm.lastActivity) > 2*time.Second {
pm.renderProgress()
}
}
case <-pm.stopActivityChan:
return
}
}
}()
}
// stopActivityIndicator 停止活跃指示器
func (pm *ProgressManager) stopActivityIndicator() {
if pm.activityTicker != nil {
pm.activityTicker.Stop()
pm.activityTicker = nil
}
if pm.stopActivityChan != nil {
close(pm.stopActivityChan)
pm.stopActivityChan = nil
}
}
// getActivityIndicator 获取当前活跃指示器字符
func (pm *ProgressManager) getActivityIndicator() string {
// 如果最近有活动2秒内显示静态指示器
if time.Since(pm.lastActivity) <= 2*time.Second {
return "●" // 实心圆表示活跃
}
// 如果长时间没有活动,显示旋转指示器表明程序仍在运行
return spinnerChars[pm.spinnerIndex]
}
// getMemoryInfo 获取内存使用信息
func (pm *ProgressManager) getMemoryInfo() string {
// 限制内存统计更新频率以提高性能(每秒最多一次)
now := time.Now()
if now.Sub(pm.lastMemUpdate) >= time.Second {
runtime.ReadMemStats(&pm.memStats)
pm.lastMemUpdate = now
}
// 获取当前使用的内存以MB为单位
memUsedMB := float64(pm.memStats.Alloc) / 1024 / 1024
// 根据内存使用量选择颜色
var colorCode string
if NoColor {
return fmt.Sprintf("内存:%.1fMB", memUsedMB)
}
// 根据内存使用量设置颜色
if memUsedMB < 50 {
colorCode = "\033[32m" // 绿色 - 内存使用较低
} else if memUsedMB < 100 {
colorCode = "\033[33m" // 黄色 - 内存使用中等
} else {
colorCode = "\033[31m" // 红色 - 内存使用较高
}
return fmt.Sprintf("%s内存:%.1fMB\033[0m", colorCode, memUsedMB)
}