mirror of
https://github.com/shadow1ng/fscan.git
synced 2026-02-09 02:09:17 +08:00
fix: 修复进度条在Windows终端满屏重复输出的问题
- 添加终端宽度检测,动态调整进度条长度 - 使用空格覆盖清除旧内容,避免残留 - 简化进度条格式,确保不超过终端宽度
This commit is contained in:
@@ -8,10 +8,16 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/term"
|
||||
|
||||
"github.com/shadow1ng/fscan/common/i18n"
|
||||
)
|
||||
|
||||
// 默认终端宽度
|
||||
const defaultTerminalWidth = 80
|
||||
|
||||
/*
|
||||
ProgressManager.go - 固定底部进度条管理器
|
||||
|
||||
@@ -198,47 +204,35 @@ func (pm *ProgressManager) renderProgress() {
|
||||
}
|
||||
|
||||
// generateProgressBar 生成进度条字符串
|
||||
// 根据终端宽度动态调整内容,确保不超过一行
|
||||
func (pm *ProgressManager) generateProgressBar() string {
|
||||
termWidth := getTerminalWidth()
|
||||
|
||||
// 获取发包统计
|
||||
packetInfo := pm.getPacketInfo()
|
||||
|
||||
if pm.total == 0 {
|
||||
spinner := pm.getActivityIndicator()
|
||||
memInfo := pm.getMemoryInfo()
|
||||
|
||||
// 获取TCP包统计(包含原HTTP请求)
|
||||
packetCount := GetGlobalState().GetPacketCount()
|
||||
tcpSuccess := GetGlobalState().GetTCPSuccessPacketCount()
|
||||
tcpFailed := GetGlobalState().GetTCPFailedPacketCount()
|
||||
udpCount := GetGlobalState().GetUDPPacketCount()
|
||||
|
||||
packetInfo := ""
|
||||
if packetCount > 0 {
|
||||
// 构建简化的包统计信息:只显示TCP和UDP
|
||||
details := make([]string, 0, 2)
|
||||
if tcpSuccess > 0 || tcpFailed > 0 {
|
||||
details = append(details, fmt.Sprintf("TCP:%d✓%d✗", tcpSuccess, tcpFailed))
|
||||
}
|
||||
if udpCount > 0 {
|
||||
details = append(details, fmt.Sprintf("UDP:%d", udpCount))
|
||||
}
|
||||
|
||||
if len(details) > 0 {
|
||||
packetInfo = fmt.Sprintf(" 发包:%d[%s]", packetCount, strings.Join(details, ","))
|
||||
} else {
|
||||
packetInfo = fmt.Sprintf(" 发包:%d", packetCount)
|
||||
}
|
||||
base := fmt.Sprintf("%s %s 等待中...", pm.description, spinner)
|
||||
if packetInfo != "" {
|
||||
return base + " " + packetInfo
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s 等待中...%s %s", pm.description, spinner, packetInfo, memInfo)
|
||||
return base
|
||||
}
|
||||
|
||||
percentage := float64(pm.current) / float64(pm.total) * 100
|
||||
elapsed := time.Since(pm.startTime)
|
||||
|
||||
// 获取并发状态
|
||||
concurrencyStatus := GetConcurrencyMonitor().GetConcurrencyStatus()
|
||||
// 计算速度
|
||||
speed := float64(pm.current) / elapsed.Seconds()
|
||||
speedStr := ""
|
||||
if speed > 0 {
|
||||
speedStr = fmt.Sprintf(" %.0f/s", speed)
|
||||
}
|
||||
|
||||
// 计算预估剩余时间
|
||||
var eta string
|
||||
if pm.current > 0 {
|
||||
if pm.current > 0 && pm.current < pm.total {
|
||||
totalTime := elapsed * time.Duration(pm.total) / time.Duration(pm.current)
|
||||
remaining := totalTime - elapsed
|
||||
if remaining > 0 {
|
||||
@@ -246,84 +240,63 @@ func (pm *ProgressManager) generateProgressBar() string {
|
||||
}
|
||||
}
|
||||
|
||||
// 计算速度
|
||||
speed := float64(pm.current) / elapsed.Seconds()
|
||||
speedStr := ""
|
||||
if speed > 0 {
|
||||
speedStr = fmt.Sprintf(" (%.1f/s)", speed)
|
||||
// 活跃指示器
|
||||
spinner := pm.getActivityIndicator()
|
||||
|
||||
// 计算固定部分的宽度
|
||||
fixedPart := fmt.Sprintf("%s %s %5.1f%% [] (%d/%d)%s%s %s",
|
||||
pm.description, spinner, percentage, pm.current, pm.total, speedStr, eta, packetInfo)
|
||||
fixedWidth := displayWidth(fixedPart)
|
||||
|
||||
// 计算进度条槽位可用宽度(预留2字符余量)
|
||||
barWidth := termWidth - fixedWidth - 2
|
||||
if barWidth < 10 {
|
||||
barWidth = 10 // 最小进度条宽度
|
||||
}
|
||||
if barWidth > 30 {
|
||||
barWidth = 30 // 最大进度条宽度
|
||||
}
|
||||
|
||||
// 生成进度条
|
||||
barWidth := 30
|
||||
filled := int(percentage * float64(barWidth) / 100)
|
||||
bar := ""
|
||||
|
||||
if GetFlagVars().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 += "|"
|
||||
if filled > barWidth {
|
||||
filled = barWidth
|
||||
}
|
||||
|
||||
// 生成活跃指示器
|
||||
spinner := pm.getActivityIndicator()
|
||||
bar := "[" + strings.Repeat("=", filled)
|
||||
if filled < barWidth {
|
||||
bar += ">"
|
||||
bar += strings.Repeat("-", barWidth-filled-1)
|
||||
}
|
||||
bar += "]"
|
||||
|
||||
// 获取TCP包统计(包含原HTTP请求)
|
||||
// 构建最终进度条
|
||||
result := fmt.Sprintf("%s %s %5.1f%% %s (%d/%d)%s%s",
|
||||
pm.description, spinner, percentage, bar, pm.current, pm.total, speedStr, eta)
|
||||
|
||||
if packetInfo != "" {
|
||||
result += " " + packetInfo
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// getPacketInfo 获取发包统计信息(简化版)
|
||||
func (pm *ProgressManager) getPacketInfo() string {
|
||||
packetCount := GetGlobalState().GetPacketCount()
|
||||
if packetCount == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
tcpSuccess := GetGlobalState().GetTCPSuccessPacketCount()
|
||||
tcpFailed := GetGlobalState().GetTCPFailedPacketCount()
|
||||
udpCount := GetGlobalState().GetUDPPacketCount()
|
||||
|
||||
packetInfo := ""
|
||||
if packetCount > 0 {
|
||||
// 构建简化的包统计信息:只显示TCP和UDP
|
||||
details := make([]string, 0, 2)
|
||||
if tcpSuccess > 0 || tcpFailed > 0 {
|
||||
details = append(details, fmt.Sprintf("TCP:%d✓%d✗", tcpSuccess, tcpFailed))
|
||||
}
|
||||
if udpCount > 0 {
|
||||
details = append(details, fmt.Sprintf("UDP:%d", udpCount))
|
||||
}
|
||||
|
||||
if len(details) > 0 {
|
||||
packetInfo = fmt.Sprintf(" 发包:%d[%s]", packetCount, strings.Join(details, ","))
|
||||
} else {
|
||||
packetInfo = fmt.Sprintf(" 发包:%d", packetCount)
|
||||
}
|
||||
// 简化格式:TCP:成功/失败
|
||||
if tcpSuccess > 0 || tcpFailed > 0 {
|
||||
return fmt.Sprintf("TCP:%d/%d", tcpSuccess, tcpFailed)
|
||||
}
|
||||
|
||||
// 构建基础进度条
|
||||
baseProgress := fmt.Sprintf("%s %s %6.1f%% %s (%d/%d)%s%s%s",
|
||||
pm.description, spinner, percentage, bar, pm.current, pm.total, speedStr, eta, packetInfo)
|
||||
|
||||
// 添加内存信息
|
||||
memInfo := pm.getMemoryInfo()
|
||||
|
||||
// 添加并发状态
|
||||
if concurrencyStatus != "" {
|
||||
return fmt.Sprintf("%s [%s] %s", baseProgress, concurrencyStatus, memInfo)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %s", baseProgress, memInfo)
|
||||
return fmt.Sprintf("Pkt:%d", packetCount)
|
||||
}
|
||||
|
||||
// showCompletionInfo 显示完成信息
|
||||
@@ -359,10 +332,91 @@ func (pm *ProgressManager) IsActive() bool {
|
||||
|
||||
// getTerminalHeight 获取终端高度
|
||||
func getTerminalHeight() int {
|
||||
// 对于固定底部进度条,我们暂时禁用终端高度检测
|
||||
// 因为在不同终端环境中可能会有问题
|
||||
// 改为使用相对定位方式
|
||||
return 0 // 返回0表示使用简化模式
|
||||
return 0
|
||||
}
|
||||
|
||||
// getTerminalWidth 获取终端宽度
|
||||
func getTerminalWidth() int {
|
||||
width, _, err := term.GetSize(int(os.Stdout.Fd()))
|
||||
if err != nil || width <= 0 {
|
||||
return defaultTerminalWidth
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
// displayWidth 计算字符串的显示宽度(中文字符占2列)
|
||||
func displayWidth(s string) int {
|
||||
width := 0
|
||||
for _, r := range s {
|
||||
if r >= 0x4E00 && r <= 0x9FFF || // CJK统一汉字
|
||||
r >= 0x3000 && r <= 0x303F || // CJK标点
|
||||
r >= 0xFF00 && r <= 0xFFEF { // 全角字符
|
||||
width += 2
|
||||
} else if r >= 0x2600 && r <= 0x27BF || // 杂项符号
|
||||
r >= 0x2700 && r <= 0x27BF { // 装饰符号
|
||||
width += 2
|
||||
} else {
|
||||
width += 1
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
|
||||
// truncateToWidth 截断字符串到指定显示宽度
|
||||
func truncateToWidth(s string, maxWidth int) string {
|
||||
if maxWidth <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
currentWidth := 0
|
||||
result := strings.Builder{}
|
||||
|
||||
for _, r := range s {
|
||||
var charWidth int
|
||||
if r >= 0x4E00 && r <= 0x9FFF ||
|
||||
r >= 0x3000 && r <= 0x303F ||
|
||||
r >= 0xFF00 && r <= 0xFFEF ||
|
||||
r >= 0x2600 && r <= 0x27BF ||
|
||||
r >= 0x2700 && r <= 0x27BF {
|
||||
charWidth = 2
|
||||
} else {
|
||||
charWidth = 1
|
||||
}
|
||||
|
||||
if currentWidth+charWidth > maxWidth {
|
||||
break
|
||||
}
|
||||
result.WriteRune(r)
|
||||
currentWidth += charWidth
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// stripAnsiCodes 移除 ANSI 转义码,用于计算实际显示宽度
|
||||
func stripAnsiCodes(s string) string {
|
||||
result := strings.Builder{}
|
||||
inEscape := false
|
||||
|
||||
for i := 0; i < len(s); {
|
||||
if s[i] == '\033' && i+1 < len(s) && s[i+1] == '[' {
|
||||
inEscape = true
|
||||
i += 2
|
||||
continue
|
||||
}
|
||||
if inEscape {
|
||||
if (s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z') {
|
||||
inEscape = false
|
||||
}
|
||||
i++
|
||||
continue
|
||||
}
|
||||
r, size := utf8.DecodeRuneInString(s[i:])
|
||||
result.WriteRune(r)
|
||||
i += size
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
||||
// formatDuration 格式化时间间隔
|
||||
@@ -460,11 +514,28 @@ func (pm *ProgressManager) renderProgressUnsafe() {
|
||||
}
|
||||
pm.lastRenderedPercent = currentPercent
|
||||
|
||||
// 获取终端宽度
|
||||
termWidth := getTerminalWidth()
|
||||
|
||||
// 生成进度条内容
|
||||
progressBar := pm.generateProgressBar()
|
||||
|
||||
// 移动到行首(Windows 已通过 progress_manager_win.go 启用 ANSI 支持)
|
||||
fmt.Print("\r")
|
||||
// 计算实际显示宽度(去除 ANSI 码后)
|
||||
plainBar := stripAnsiCodes(progressBar)
|
||||
actualWidth := displayWidth(plainBar)
|
||||
|
||||
// 如果超过终端宽度,截断内容
|
||||
// 预留 1 字符防止边界问题
|
||||
maxWidth := termWidth - 1
|
||||
if actualWidth > maxWidth {
|
||||
// 截断纯文本部分
|
||||
progressBar = truncateToWidth(plainBar, maxWidth)
|
||||
}
|
||||
|
||||
// 清除当前行并移动到行首
|
||||
// 使用空格覆盖旧内容,确保不留残留
|
||||
clearStr := "\r" + strings.Repeat(" ", termWidth-1) + "\r"
|
||||
fmt.Print(clearStr)
|
||||
|
||||
// 输出进度条(带颜色,如果启用)
|
||||
if GetFlagVars().NoColor {
|
||||
|
||||
5
go.mod
5
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/shadow1ng/fscan
|
||||
|
||||
go 1.20
|
||||
go 1.24.0
|
||||
|
||||
require (
|
||||
github.com/IBM/sarama v1.43.3
|
||||
@@ -28,7 +28,7 @@ require (
|
||||
go.mongodb.org/mongo-driver v1.17.4
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/net v0.32.0
|
||||
golang.org/x/sys v0.28.0
|
||||
golang.org/x/sys v0.40.0
|
||||
golang.org/x/text v0.21.0
|
||||
google.golang.org/genproto v0.0.0-20221027153422-115e99e71e1c
|
||||
google.golang.org/protobuf v1.28.1
|
||||
@@ -76,6 +76,7 @@ require (
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
)
|
||||
|
||||
|
||||
4
go.sum
4
go.sum
@@ -220,6 +220,8 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -230,6 +232,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
||||
Reference in New Issue
Block a user