mirror of
http://bgp.hk.skcks.cn:10088/github.com/oneclickvirt/ecs
synced 2026-04-21 05:10:32 +08:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c19502950 | ||
|
|
3c434781f5 | ||
|
|
f62636ca3e | ||
|
|
c4b11ae37d | ||
|
|
8f2fe236d5 | ||
|
|
347a0faa7a | ||
|
|
74640e3066 | ||
|
|
3b646eeeda | ||
|
|
eaad433395 | ||
|
|
03af7c423b | ||
|
|
0e96a6499b | ||
|
|
5b44f5f651 | ||
|
|
e4a759fceb | ||
|
|
e5c4b0ce8e | ||
|
|
188a1153e6 | ||
|
|
4c4887f487 | ||
|
|
feac73a427 | ||
|
|
f3048d074c | ||
|
|
58702b54e7 | ||
|
|
1e4d63ef57 | ||
|
|
9f3acacae0 | ||
|
|
e9755f0c20 | ||
|
|
d8b397b31b | ||
|
|
9e22d1bc23 |
@@ -21,112 +21,131 @@ def write_file(filepath, content):
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def modify_speed_go(filepath):
|
||||
def modify_go_mod(filepath):
|
||||
"""
|
||||
Modify internal/tests/speed.go to comment out privatespeedtest import and usage.
|
||||
This ensures the file remains syntactically correct.
|
||||
Modify go.mod to remove privatespeedtest (and optional security) dependencies.
|
||||
Automatically matches module names regardless of version or indirect comment.
|
||||
"""
|
||||
content = read_file(filepath)
|
||||
|
||||
# Modules to remove
|
||||
remove_modules = [
|
||||
r'github\.com/oneclickvirt/privatespeedtest',
|
||||
r'github\.com/oneclickvirt/security',
|
||||
]
|
||||
|
||||
for mod in remove_modules:
|
||||
# Remove full require line (with or without // indirect)
|
||||
content = re.sub(
|
||||
rf'^[ \t]*{mod}[ \t]+v[^\s]+(?:[ \t]+// indirect)?[ \t]*\n',
|
||||
'',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Removed privatespeedtest/security from {filepath}")
|
||||
|
||||
|
||||
def remove_code_block(lines, start_marker, end_condition='empty_line'):
|
||||
"""
|
||||
Remove code block from lines starting with start_marker.
|
||||
|
||||
# Comment out the privatespeedtest import
|
||||
content = re.sub(
|
||||
r'(\t)"github\.com/oneclickvirt/privatespeedtest/pst"',
|
||||
r'\1// "github.com/oneclickvirt/privatespeedtest/pst"',
|
||||
content
|
||||
)
|
||||
Args:
|
||||
lines: List of file lines
|
||||
start_marker: String or list of strings to identify block start
|
||||
end_condition: 'empty_line' (default) or 'closing_brace' or custom function
|
||||
|
||||
Returns:
|
||||
Modified lines with the block removed
|
||||
"""
|
||||
if isinstance(start_marker, str):
|
||||
start_marker = [start_marker]
|
||||
|
||||
result = []
|
||||
skip_mode = False
|
||||
brace_depth = 0
|
||||
|
||||
# Process line by line for precise control
|
||||
lines = content.split('\n')
|
||||
new_lines = []
|
||||
in_private_func = False
|
||||
in_private_call_block = False
|
||||
func_brace_count = 0
|
||||
i = 0
|
||||
|
||||
while i < len(lines):
|
||||
line = lines[i]
|
||||
|
||||
# Detect start of privateSpeedTest function
|
||||
if 'func privateSpeedTest(num int, operator string) error {' in line:
|
||||
in_private_func = True
|
||||
func_brace_count = 1
|
||||
new_lines.append('// ' + line)
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# If we're inside the privateSpeedTest function, comment out everything
|
||||
if in_private_func:
|
||||
# Count braces
|
||||
func_brace_count += line.count('{') - line.count('}')
|
||||
# Check if we should start skipping
|
||||
if not skip_mode:
|
||||
for marker in start_marker:
|
||||
if marker in line:
|
||||
skip_mode = True
|
||||
if end_condition == 'closing_brace':
|
||||
# Count opening braces on the function declaration line
|
||||
brace_depth = line.count('{') - line.count('}')
|
||||
break
|
||||
|
||||
# Comment out the line
|
||||
if line.strip():
|
||||
if not line.strip().startswith('//'):
|
||||
# Preserve indentation
|
||||
indent = len(line) - len(line.lstrip())
|
||||
new_lines.append(' ' * indent + '// ' + line.lstrip())
|
||||
else:
|
||||
new_lines.append(line)
|
||||
else:
|
||||
new_lines.append(line)
|
||||
|
||||
# Check if function ends
|
||||
if func_brace_count == 0:
|
||||
in_private_func = False
|
||||
i += 1
|
||||
continue
|
||||
if not skip_mode:
|
||||
result.append(line)
|
||||
else:
|
||||
# We're in skip mode
|
||||
if end_condition == 'empty_line':
|
||||
# Skip until we find an empty line
|
||||
if line.strip() == '':
|
||||
skip_mode = False
|
||||
# Don't add the empty line, continue to next
|
||||
elif end_condition == 'closing_brace':
|
||||
# Track brace depth
|
||||
brace_depth += line.count('{') - line.count('}')
|
||||
if brace_depth == 0 and '}' in line:
|
||||
# Function ended, skip until next empty line
|
||||
end_condition = 'empty_line'
|
||||
|
||||
# Detect the call to privateSpeedTest in CustomSP
|
||||
if '\terr := privateSpeedTest(num, opLower)' in line:
|
||||
in_private_call_block = True
|
||||
new_lines.append('\t\t// err := privateSpeedTest(num, opLower)')
|
||||
i += 1
|
||||
continue
|
||||
|
||||
# Handle the if-else block after the call
|
||||
if in_private_call_block:
|
||||
stripped = line.strip()
|
||||
|
||||
# Comment out these lines
|
||||
if stripped.startswith('if err != nil'):
|
||||
new_lines.append('\t\t// if err != nil {')
|
||||
i += 1
|
||||
continue
|
||||
elif 'fmt.Fprintf(os.Stderr, "[WARN] privatespeedtest failed' in line:
|
||||
new_lines.append('\t\t\t// fmt.Fprintf(os.Stderr, "[WARN] privatespeedtest failed\\n")')
|
||||
i += 1
|
||||
continue
|
||||
elif stripped == '// 继续使用原有的兜底方案':
|
||||
new_lines.append('\t\t\t// // 继续使用原有的兜底方案')
|
||||
i += 1
|
||||
continue
|
||||
elif stripped == '} else {':
|
||||
new_lines.append('\t\t// } else {')
|
||||
i += 1
|
||||
continue
|
||||
elif stripped == '// 测速成功,直接返回':
|
||||
new_lines.append('\t\t\t// // 测速成功,直接返回')
|
||||
i += 1
|
||||
continue
|
||||
elif stripped == 'return':
|
||||
new_lines.append('\t\t\t// return')
|
||||
i += 1
|
||||
# Next should be the closing brace
|
||||
if i < len(lines) and lines[i].strip() == '}':
|
||||
new_lines.append('\t\t// }')
|
||||
i += 1
|
||||
in_private_call_block = False
|
||||
continue
|
||||
|
||||
new_lines.append(line)
|
||||
i += 1
|
||||
|
||||
content = '\n'.join(new_lines)
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Modified {filepath}")
|
||||
return result
|
||||
|
||||
|
||||
def modify_speed_go(filepath):
|
||||
"""
|
||||
Remove privatespeedtest-related code from speed.go.
|
||||
Uses line-by-line processing for reliability.
|
||||
"""
|
||||
content = read_file(filepath)
|
||||
lines = content.split('\n')
|
||||
|
||||
# Remove specific code blocks by their comment markers
|
||||
blocks_to_remove = [
|
||||
'// formatString 格式化字符串到指定宽度',
|
||||
'// printTableRow 打印表格行',
|
||||
'// privateSpeedTest 使用 privatespeedtest 进行单个运营商测速',
|
||||
'// privateSpeedTestWithFallback 使用私有测速,如果失败则回退到 global 节点',
|
||||
'// 对于三网测速(cmcc、cu、ct)和 other,优先使用 privatespeedtest 进行私有测速',
|
||||
]
|
||||
|
||||
for block_marker in blocks_to_remove:
|
||||
lines = remove_code_block(lines, block_marker)
|
||||
|
||||
# Reconstruct content
|
||||
content = '\n'.join(lines)
|
||||
|
||||
# Remove privatespeedtest import
|
||||
content = re.sub(
|
||||
r'\n\s*"github\.com/oneclickvirt/privatespeedtest/pst"\s*\n',
|
||||
'\n',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
# Remove time import (only used by privatespeedtest)
|
||||
content = re.sub(
|
||||
r'\n\s*"time"\s*\n',
|
||||
'\n',
|
||||
content,
|
||||
flags=re.MULTILINE
|
||||
)
|
||||
|
||||
# Clean up multiple consecutive empty lines (optional)
|
||||
content = re.sub(r'\n{3,}', '\n\n', content)
|
||||
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Removed privatespeedtest from {filepath}")
|
||||
|
||||
def modify_utils_go(filepath):
|
||||
"""
|
||||
Modify utils/utils.go to:
|
||||
@@ -188,31 +207,6 @@ def modify_params_go(filepath):
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Modified {filepath}")
|
||||
|
||||
|
||||
def modify_go_mod(filepath):
|
||||
"""
|
||||
Modify go.mod to remove security and privatespeedtest dependencies.
|
||||
"""
|
||||
content = read_file(filepath)
|
||||
|
||||
# Remove security dependency from require section
|
||||
content = re.sub(
|
||||
r'\s+github\.com/oneclickvirt/security v[^\n]+\n',
|
||||
'',
|
||||
content
|
||||
)
|
||||
|
||||
# Remove privatespeedtest dependency from require section (including indirect)
|
||||
content = re.sub(
|
||||
r'\s+github\.com/oneclickvirt/privatespeedtest v[^\n]+\n',
|
||||
'',
|
||||
content
|
||||
)
|
||||
|
||||
write_file(filepath, content)
|
||||
print(f"✓ Modified {filepath}")
|
||||
|
||||
|
||||
def modify_readme(filepath, is_english=False):
|
||||
"""
|
||||
Modify README files to update Go version and security status.
|
||||
@@ -297,7 +291,7 @@ def main():
|
||||
print("Modifying go.mod...")
|
||||
modify_go_mod('go.mod')
|
||||
print()
|
||||
|
||||
|
||||
# Modify README files
|
||||
print("Modifying README files...")
|
||||
modify_readme('README.md', is_english=False)
|
||||
|
||||
29
.github/workflows/build_binary.yaml
vendored
29
.github/workflows/build_binary.yaml
vendored
@@ -19,11 +19,31 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: false
|
||||
docker-images: false
|
||||
swap-storage: false
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.25.3
|
||||
go-version: 1.25.4
|
||||
|
||||
# - name: Install UPX
|
||||
# run: |
|
||||
# apk add --no-cache upx
|
||||
|
||||
- name: Configure Git for Private Modules
|
||||
run: |
|
||||
git config --global url."https://${{ secrets.GHT }}@github.com/".insteadOf "https://github.com/"
|
||||
@@ -31,13 +51,18 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
|
||||
- name: Clean Go cache before build
|
||||
run: |
|
||||
go clean -cache -modcache -testcache
|
||||
df -h
|
||||
|
||||
- name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
distribution: goreleaser
|
||||
# version: latest
|
||||
version: '~> v2'
|
||||
args: release
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GHT }}
|
||||
GOPRIVATE: github.com/oneclickvirt/security,github.com/oneclickvirt/privatespeedtest
|
||||
|
||||
5
.github/workflows/build_public.yml
vendored
5
.github/workflows/build_public.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.25.3'
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: Update master branch README files
|
||||
run: |
|
||||
@@ -62,6 +62,9 @@ jobs:
|
||||
- name: Remove security package references
|
||||
run: |
|
||||
python3 .back/create_public_branch.py
|
||||
rm -f go.sum
|
||||
go clean -modcache
|
||||
go clean -cache -testcache -fuzzcache
|
||||
go mod tidy
|
||||
- name: Update Go version in README files
|
||||
run: |
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
before:
|
||||
hooks:
|
||||
- go mod tidy -v
|
||||
- go clean -cache
|
||||
|
||||
project_name: goecs
|
||||
|
||||
builds:
|
||||
- id: universal
|
||||
env:
|
||||
@@ -31,6 +35,7 @@ builds:
|
||||
goarch: arm
|
||||
main: ./
|
||||
binary: goecs
|
||||
|
||||
- id: darwin-amd64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
@@ -44,6 +49,7 @@ builds:
|
||||
- amd64
|
||||
main: ./
|
||||
binary: goecs
|
||||
|
||||
- id: darwin-arm64
|
||||
env:
|
||||
- CGO_ENABLED=1
|
||||
@@ -57,18 +63,23 @@ builds:
|
||||
- arm64
|
||||
main: ./
|
||||
binary: goecs
|
||||
|
||||
universal_binaries:
|
||||
- name_template: "goecs"
|
||||
replace: false
|
||||
|
||||
checksum:
|
||||
name_template: "checksums.txt"
|
||||
|
||||
snapshot:
|
||||
name_template: "goecs"
|
||||
|
||||
archives:
|
||||
- name_template: "goecs_{{ .Os }}_{{ .Arch }}"
|
||||
format: zip
|
||||
files:
|
||||
- none*
|
||||
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
@@ -79,4 +90,18 @@ changelog:
|
||||
- Merge pull request
|
||||
- Merge branch
|
||||
- go mod tidy
|
||||
- New translations
|
||||
- New translations
|
||||
|
||||
upx:
|
||||
- enabled: true
|
||||
brute: true
|
||||
goos:
|
||||
- linux
|
||||
- windows
|
||||
goarch:
|
||||
- amd64
|
||||
- 386
|
||||
- arm64
|
||||
- ppc64le
|
||||
- s390x
|
||||
- riscv64
|
||||
|
||||
@@ -60,7 +60,7 @@ Shell 版本:[https://github.com/spiritLHLS/ecs](https://github.com/spiritLHLS
|
||||
- 邮件端口测试:[portchecker](https://github.com/oneclickvirt/portchecker)
|
||||
- 上游及回程路由线路检测:借鉴 [zhanghanyun/backtrace](https://github.com/zhanghanyun/backtrace),二次开发至 [oneclickvirt/backtrace](https://github.com/oneclickvirt/backtrace)
|
||||
- 三网路由测试:基于 [NTrace-core](https://github.com/nxtrace/NTrace-core),二次开发至 [nt3](https://github.com/oneclickvirt/nt3)
|
||||
- 网速测试:基于 [speedtest.net](https://github.com/spiritLHLS/speedtest.net-CN-ID) 和 [speedtest.cn](https://github.com/spiritLHLS/speedtest.cn-CN-ID) 数据,开发至 [oneclickvirt/speedtest](https://github.com/oneclickvirt/speedtest),同时融合私有国内测速平台
|
||||
- 网速测试:基于 [speedtest.net](https://github.com/spiritLHLS/speedtest.net-CN-ID) 和 [speedtest.cn](https://github.com/spiritLHLS/speedtest.cn-CN-ID) 数据,开发 [oneclickvirt/speedtest](https://github.com/oneclickvirt/speedtest),同时融合私有国内测速节点
|
||||
- 三网 Ping 值测试:借鉴 [ecsspeed](https://github.com/spiritLHLS/ecsspeed),二次开发至 [pingtest](https://github.com/oneclickvirt/pingtest)
|
||||
- 支持root或admin环境下测试,支持非root或非admin环境下测试,支持离线环境下进行测试,**暂未**支持无DNS的在线环境下进行测试
|
||||
|
||||
@@ -333,7 +333,7 @@ cd ecs
|
||||
|
||||
2. 安装 Go 环境(如已安装可跳过)
|
||||
|
||||
选择 go 1.25.3 的版本进行安装
|
||||
选择 go 1.25.4 的版本进行安装
|
||||
|
||||
```bash
|
||||
curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/spiritLHLS/one-click-installation-script/main/install_scripts/go.sh -o go.sh && chmod +x go.sh && bash go.sh
|
||||
|
||||
@@ -330,7 +330,7 @@ cd ecs
|
||||
|
||||
2. Install Go environment (skip if already installed)
|
||||
|
||||
Select go 1.25.3 version to install
|
||||
Select go 1.25.4 version to install
|
||||
|
||||
```bash
|
||||
curl -L https://cdn.spiritlhl.net/https://raw.githubusercontent.com/spiritLHLS/one-click-installation-script/main/install_scripts/go.sh -o go.sh && chmod +x go.sh && bash go.sh
|
||||
|
||||
@@ -482,6 +482,8 @@ Abuser 或 Abuse 的滥用得分会直接影响机器的正常使用(中国境
|
||||
|
||||
先测的官方推荐的测速点,然后测有代表性的国际测速点,最后测国内三大运营商ping值最低的测速点。
|
||||
|
||||
由于 speedtest.net 和 speedtest.cn 平台公开的测速节点被刷BTPT的刷烂了(他们为了对等上传PCDN的流量狂刷下载),所以这块本人独家融合的境内私有测速节点不再公开,优先使用私有的境内运营商测速节点进行测速,且写死限制每个IP每日仅支持获取测速数据10次,超限自动降级为使用公共测速节点进行测速
|
||||
|
||||
境内使用为主就看境内测速即可,境外使用看境外测速,官方测速点可以代表受测的宿主机本地带宽基准。
|
||||
|
||||
一般来说中国境外的服务器的带宽100Mbps起步,中国境内的服务器1Mbps带宽起步,具体看线路优劣,带宽特别大有时候未必用得上,够用就行了。
|
||||
|
||||
2
goecs.go
2
goecs.go
@@ -27,7 +27,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
ecsVersion = "v0.1.108" // 融合怪版本号
|
||||
ecsVersion = "v0.1.112" // 融合怪版本号
|
||||
configs = params.NewConfig(ecsVersion) // 全局配置实例
|
||||
userSetFlags = make(map[string]bool) // 用于跟踪哪些参数是用户显式设置的
|
||||
)
|
||||
|
||||
6
goecs.sh
6
goecs.sh
@@ -152,7 +152,7 @@ goecs_check() {
|
||||
os=$(uname -s 2>/dev/null || echo "Unknown")
|
||||
arch=$(uname -m 2>/dev/null || echo "Unknown")
|
||||
check_china
|
||||
ECS_VERSION="0.1.107"
|
||||
ECS_VERSION="0.1.111"
|
||||
for api in \
|
||||
"https://api.github.com/repos/oneclickvirt/ecs/releases/latest" \
|
||||
"https://githubapi.spiritlhl.workers.dev/repos/oneclickvirt/ecs/releases/latest" \
|
||||
@@ -164,8 +164,8 @@ goecs_check() {
|
||||
sleep 1
|
||||
done
|
||||
if [ -z "$ECS_VERSION" ]; then
|
||||
_yellow "Unable to get version info, using default version 0.1.107"
|
||||
ECS_VERSION="0.1.107"
|
||||
_yellow "Unable to get version info, using default version 0.1.111"
|
||||
ECS_VERSION="0.1.111"
|
||||
fi
|
||||
version_output=""
|
||||
for cmd_path in "goecs" "./goecs" "/usr/bin/goecs" "/usr/local/bin/goecs"; do
|
||||
|
||||
@@ -330,6 +330,8 @@ func RunNetworkTests(config *params.Config, wg3 *sync.WaitGroup, ptInfo *string,
|
||||
fmt.Println(pt.WebsiteTest())
|
||||
}
|
||||
}
|
||||
// 等待第三方库的输出完全刷新到标准输出
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
@@ -348,10 +350,22 @@ func RunSpeedTests(config *params.Config, output, tempOutput string, outputMutex
|
||||
tests.CustomSP("net", "ct", config.SpNum, config.Language)
|
||||
tests.CustomSP("net", "cmcc", config.SpNum, config.Language)
|
||||
} else if config.Choice == "2" || config.Choice == "3" || config.Choice == "4" || config.Choice == "5" {
|
||||
tests.CustomSP("net", "global", 4, config.Language)
|
||||
// 中文模式:就近测速 + 三网各1个 + Other 1个(带回退)
|
||||
if config.Language == "zh" {
|
||||
tests.NearbySP()
|
||||
tests.CustomSP("net", "other", 1, config.Language)
|
||||
tests.CustomSP("net", "cu", 1, config.Language)
|
||||
tests.CustomSP("net", "ct", 1, config.Language)
|
||||
tests.CustomSP("net", "cmcc", 1, config.Language)
|
||||
} else {
|
||||
// 英文模式:保持原有逻辑,测4个global节点
|
||||
tests.CustomSP("net", "global", 4, config.Language)
|
||||
}
|
||||
} else if config.Choice == "6" {
|
||||
tests.CustomSP("net", "global", 11, config.Language)
|
||||
}
|
||||
// 等待第三方库的输出完全刷新到标准输出
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
@@ -370,6 +384,8 @@ func RunEnglishNetworkTests(config *params.Config, wg3 *sync.WaitGroup, ptInfo *
|
||||
fmt.Println(pt.WebsiteTest())
|
||||
}
|
||||
}
|
||||
// 等待第三方库的输出完全刷新到标准输出
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}, tempOutput, output)
|
||||
}
|
||||
|
||||
@@ -383,6 +399,8 @@ func RunEnglishSpeedTests(config *params.Config, output, tempOutput string, outp
|
||||
tests.ShowHead(config.Language)
|
||||
tests.NearbySP()
|
||||
tests.CustomSP("net", "global", -1, config.Language)
|
||||
// 等待第三方库的输出完全刷新到标准输出
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}, tempOutput, output)
|
||||
}
|
||||
@@ -442,7 +460,7 @@ func HandleSignalInterrupt(sig chan os.Signal, config *params.Config, startTime
|
||||
// 使用context来控制上传goroutine
|
||||
uploadCtx, uploadCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer uploadCancel()
|
||||
|
||||
|
||||
go func() {
|
||||
httpURL, httpsURL := utils.ProcessAndUpload(finalOutput, config.FilePath, config.EnableUpload)
|
||||
select {
|
||||
@@ -455,7 +473,7 @@ func HandleSignalInterrupt(sig chan os.Signal, config *params.Config, startTime
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
select {
|
||||
case result := <-resultChan:
|
||||
uploadCancel() // 成功完成,取消context
|
||||
|
||||
@@ -56,8 +56,8 @@ func printTableRow(result pst.SpeedTestResult) {
|
||||
}
|
||||
location = fmt.Sprintf("%s%s", carrier, result.City)
|
||||
}
|
||||
if len(location) > 14 {
|
||||
location = location[:14] + "..."
|
||||
if len(location) > 15 {
|
||||
location = location[:15]
|
||||
}
|
||||
upload := "N/A"
|
||||
if result.UploadMbps > 0 {
|
||||
@@ -69,7 +69,7 @@ func printTableRow(result pst.SpeedTestResult) {
|
||||
}
|
||||
latency := fmt.Sprintf("%.2f ms", result.PingLatency.Seconds()*1000)
|
||||
packetLoss := "N/A"
|
||||
fmt.Print(formatString(location, 16))
|
||||
fmt.Print(formatString(location, 15))
|
||||
fmt.Print(formatString(upload, 16))
|
||||
fmt.Print(formatString(download, 16))
|
||||
fmt.Print(formatString(latency, 16))
|
||||
@@ -78,25 +78,26 @@ func printTableRow(result pst.SpeedTestResult) {
|
||||
}
|
||||
|
||||
// privateSpeedTest 使用 privatespeedtest 进行单个运营商测速
|
||||
// operator 参数:只支持 "cmcc"、"cu"、"ct"
|
||||
func privateSpeedTest(num int, operator string) error {
|
||||
// operator 参数:只支持 "cmcc"、"cu"、"ct"、"other"
|
||||
// 返回值:实际测试的节点数量和错误信息
|
||||
func privateSpeedTest(num int, operator string) (int, error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] privateSpeedTest panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
*pst.NoProgress = true
|
||||
*pst.Quiet = true
|
||||
*pst.NoHeader = true
|
||||
*pst.NoProjectURL = true
|
||||
*pst.Quiet = true
|
||||
*pst.NoHeader = true
|
||||
*pst.NoProjectURL = true
|
||||
// 加载服务器列表
|
||||
serverList, err := pst.LoadServerList()
|
||||
if err != nil {
|
||||
return fmt.Errorf("加载自定义服务器列表失败")
|
||||
return 0, fmt.Errorf("加载自定义服务器列表失败")
|
||||
}
|
||||
// 使用三网测速模式(每个运营商选择指定数量的最低延迟节点)
|
||||
serversPerISP := num
|
||||
if serversPerISP <= 0 || serversPerISP > 5{
|
||||
if serversPerISP <= 0 || serversPerISP > 5 {
|
||||
serversPerISP = 2
|
||||
}
|
||||
// 单个运营商测速:先过滤服务器列表
|
||||
@@ -108,8 +109,10 @@ func privateSpeedTest(num int, operator string) error {
|
||||
carrierType = "Unicom"
|
||||
case "ct":
|
||||
carrierType = "Telecom"
|
||||
case "other":
|
||||
carrierType = "Other"
|
||||
default:
|
||||
return fmt.Errorf("不支持的运营商类型: %s", operator)
|
||||
return 0, fmt.Errorf("不支持的运营商类型: %s", operator)
|
||||
}
|
||||
// 过滤出指定运营商的服务器
|
||||
filteredServers := pst.FilterServersByISP(serverList.Servers, carrierType)
|
||||
@@ -121,17 +124,19 @@ func privateSpeedTest(num int, operator string) error {
|
||||
// 使用 FindBestServers 选择最佳服务器
|
||||
candidateServers, err := pst.FindBestServers(
|
||||
filteredServers,
|
||||
candidateCount, // 选择更多候选节点用于去重
|
||||
5*time.Second, // ping 超时
|
||||
true, // 显示进度条
|
||||
true, // 静默
|
||||
candidateCount, // 选择更多候选节点用于去重
|
||||
5*time.Second, // ping 超时
|
||||
true, // 显示进度条
|
||||
true, // 静默
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分组查找失败")
|
||||
return 0, fmt.Errorf("分组查找失败")
|
||||
}
|
||||
// 去重:确保同一运营商内城市不重复
|
||||
seenCities := make(map[string]bool)
|
||||
var bestServers []pst.ServerWithLatencyInfo
|
||||
// 保留更多备用节点,以应对测速失败的情况(保留 serversPerISP * 2 个备用节点)
|
||||
maxBackupServers := serversPerISP * 2
|
||||
for _, serverInfo := range candidateServers {
|
||||
city := serverInfo.Server.City
|
||||
if city == "" {
|
||||
@@ -140,35 +145,70 @@ func privateSpeedTest(num int, operator string) error {
|
||||
if !seenCities[city] {
|
||||
seenCities[city] = true
|
||||
bestServers = append(bestServers, serverInfo)
|
||||
// 去重后取前 serversPerISP 个
|
||||
if len(bestServers) >= serversPerISP {
|
||||
// 去重后保留足够的备用节点
|
||||
if len(bestServers) >= maxBackupServers {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(bestServers) == 0 {
|
||||
return fmt.Errorf("去重后没有可用的服务器")
|
||||
return 0, fmt.Errorf("去重后没有可用的服务器")
|
||||
}
|
||||
// 执行测速并逐个打印结果(不打印表头)
|
||||
// 统计成功输出的节点数
|
||||
successCount := 0
|
||||
for i, serverInfo := range bestServers {
|
||||
// 如果已经成功输出了足够的节点,则停止测试
|
||||
if successCount >= serversPerISP {
|
||||
break
|
||||
}
|
||||
result := pst.RunSpeedTest(
|
||||
serverInfo.Server,
|
||||
false, // 不禁用下载测试
|
||||
false, // 不禁用上传测试
|
||||
6, // 并发线程数
|
||||
12*time.Second, // 超时时间
|
||||
false, // 不禁用下载测试
|
||||
false, // 不禁用上传测试
|
||||
6, // 并发线程数
|
||||
12*time.Second, // 超时时间
|
||||
&serverInfo,
|
||||
false, // 不显示进度条
|
||||
false, // 不显示进度条
|
||||
)
|
||||
if result.Success {
|
||||
// 只要测试成功且有任意一个速度值有效,就输出结果(部分成功也显示)
|
||||
if result.Success && (result.UploadMbps > 0 || result.DownloadMbps > 0) {
|
||||
printTableRow(result)
|
||||
// 只有上传和下载都成功时才计入成功数
|
||||
if result.UploadMbps > 0 && result.DownloadMbps > 0 {
|
||||
successCount++
|
||||
}
|
||||
}
|
||||
// 在测试之间暂停(除了最后一个)
|
||||
if i < len(bestServers)-1 {
|
||||
// 在测试之间暂停(如果还需要继续测试的话)
|
||||
if successCount < serversPerISP && i < len(bestServers)-1 {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
// 返回实际成功输出的节点数量
|
||||
return successCount, nil
|
||||
}
|
||||
|
||||
// privateSpeedTestWithFallback 使用私有测速,如果失败则回退到 global 节点
|
||||
// 主要用于 Other 类型的测速
|
||||
func privateSpeedTestWithFallback(num int, operator, language string) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] privateSpeedTestWithFallback panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
// 先尝试私有节点测速
|
||||
testedCount, err := privateSpeedTest(num, operator)
|
||||
if err != nil || testedCount == 0 {
|
||||
// 私有节点失败,回退到 global 节点
|
||||
var url, parseType string
|
||||
url = model.NetGlobal
|
||||
parseType = "id"
|
||||
if runtime.GOOS == "windows" || sp.OfficialAvailableTest() != nil {
|
||||
sp.CustomSpeedTest(url, parseType, num, language)
|
||||
} else {
|
||||
sp.OfficialCustomSpeedTest(url, parseType, num, language)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CustomSP(platform, operator string, num int, language string) {
|
||||
@@ -177,18 +217,26 @@ func CustomSP(platform, operator string, num int, language string) {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] CustomSP panic: %v\n", r)
|
||||
}
|
||||
}()
|
||||
// 对于三网测速(cmcc、cu、ct),优先使用 privatespeedtest 进行私有测速
|
||||
// 对于三网测速(cmcc、cu、ct)和 other,优先使用 privatespeedtest 进行私有测速
|
||||
opLower := strings.ToLower(operator)
|
||||
if opLower == "cmcc" || opLower == "cu" || opLower == "ct" {
|
||||
err := privateSpeedTest(num, opLower)
|
||||
if opLower == "cmcc" || opLower == "cu" || opLower == "ct" || opLower == "other" {
|
||||
testedCount, err := privateSpeedTest(num, opLower)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[WARN] privatespeedtest failed\n")
|
||||
// 继续使用原有的兜底方案
|
||||
} else {
|
||||
// 测速成功,直接返回
|
||||
fmt.Fprintf(os.Stderr, "[WARN] privatespeedtest failed: %v\n", err)
|
||||
// 全部失败,继续使用原有的公共节点兜底方案
|
||||
} else if testedCount >= num {
|
||||
// 私有节点测速成功且数量达标,直接返回
|
||||
return
|
||||
} else if testedCount > 0 {
|
||||
// 部分私有节点测速成功,但数量不足,用公共节点补充
|
||||
fmt.Fprintf(os.Stderr, "[INFO] 私有节点仅测试了 %d 个,补充 %d 个公共节点\n", testedCount, num-testedCount)
|
||||
num = num - testedCount // 只测剩余数量的公共节点
|
||||
// 继续执行下面的公共节点测速逻辑
|
||||
} else {
|
||||
// testedCount == 0,继续使用公共节点
|
||||
}
|
||||
}
|
||||
|
||||
var url, parseType string
|
||||
if strings.ToLower(platform) == "cn" {
|
||||
if strings.ToLower(operator) == "cmcc" {
|
||||
@@ -222,7 +270,8 @@ func CustomSP(platform, operator string, num int, language string) {
|
||||
url = model.NetJP
|
||||
} else if strings.ToLower(operator) == "sg" {
|
||||
url = model.NetSG
|
||||
} else if strings.ToLower(operator) == "global" {
|
||||
} else if strings.ToLower(operator) == "global" || strings.ToLower(operator) == "other" {
|
||||
// other 类型回退到 global 节点
|
||||
url = model.NetGlobal
|
||||
}
|
||||
parseType = "id"
|
||||
|
||||
@@ -219,6 +219,11 @@ func CaptureOutput(f func()) string {
|
||||
}()
|
||||
// 执行函数
|
||||
f()
|
||||
// 确保所有输出都已经刷新
|
||||
os.Stdout.Sync()
|
||||
os.Stderr.Sync()
|
||||
// 等待一小段时间,确保后台goroutine的输出完成
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
// 关闭管道写入端,让管道读取端可以读取所有数据
|
||||
stdoutPipeW.Close()
|
||||
stderrPipeW.Close()
|
||||
|
||||
Reference in New Issue
Block a user