CF优选IP
最前面优选IP网站
https://ip.v2too.top/
https://ip.v2too.top/api/nodes
https://ipdb.030101.xyz/bestcfv4/
https://ip.164746.xyz/
https://stock.hostmonit.com/CloudFlareYes
https://www.cnae.top/
youxuan.cf.090227.xyz
cloudflare.182682.xyz
备注:这些优选的都是CF的边缘ip,其实还是CF的,不一定是优化的线路
一、WIN普通版
一键测试优选,并且把最快的IP解析到域名
1.下载CloudflareSpeedTest
https://github.com/XIU2/CloudflareSpeedTest
2.创建脚本
解压刚刚GitHub下载的文件,在解压的文件目录创建两个文件run_all.bat跟update_cf_dns.ps1

run_all.bat的代码如下
@echo off
:: 设置脚本输出的字符集为 UTF-8,以支持中文等字符
chcp 65001
:: 切换到当前批处理脚本所在的目录
cd /d "%~dp0"
:: 输出信息,提示正在删除 result.csv 文件
echo 正在删除 result.csv...
del /f /q "result.csv"
:: 启用延迟变量扩展(用于在同一行中使用变量)
setlocal enabledelayedexpansion
:: ================================
:: 获取当前小时数并判断是否为晚高峰时段
:: ================================
:: 1. 提取当前小时数(取第0到第2位)
set "current_hour=%time:~0,2%"
:: 2. 去除小时数前面的空格(防止 0-9 点时出现 " 8" 导致判断报错)
set "current_hour=%current_hour: =%"
:: 3. 设置晚高峰判断变量(默认设为 false)
set "IS_PEAK=false"
:: 4. 判断逻辑:如果是 20点 到 23点 (20, 21, 22, 23)
if %current_hour% geq 20 if %current_hour% lss 24 (
set "IS_PEAK=true"
)
:: ================================
:: 根据是否是晚高峰时段,启动不同的 cfst.exe 测速命令
:: ================================
if "%IS_PEAK%"=="true" (
:: 如果是晚高峰时段,执行更高频率、更高负载的测速任务
start "" /b "%~dp0cfst.exe" -n 600 -t 8 -dn 10 -dt 15 -tp 443 -url https://speed.cloudflare.com/__down?bytes=200000000 -tl 200 -tll 40 -tlr 1 -p 10 -sl 1 -o "%~dp0result.csv"
) else (
:: 如果不是晚高峰时段,执行较低频率的测速任务
start "" /b "%~dp0cfst.exe" -n 600 -t 4 -dn 10 -dt 15 -tp 443 -url https://speed.cloudflare.com/__down?bytes=200000000 -tl 120 -tll 30 -tlr 0.5 -p 10 -sl 2 -o "%~dp0result.csv"
)
:: ================================
:: 启动 PowerShell 脚本(用于更新 Cloudflare DNS)
:: ================================
start powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0update_cf_dns.ps1"
REM 主窗口自动关闭
update_cf_dns.ps1代码如下,只需要修改配置里面的三个项
# 循环监听 result.csv 文件
$csvFile = "result.csv"
Write-Host "正在监听 result.csv 文件..."
# 等待 cfst.exe 生成 result.csv 文件
while (-not (Test-Path $csvFile)) {
Write-Host "等待 result.csv 文件生成..."
Start-Sleep -Seconds 5
}
Write-Host "检测到 result.csv,等待 10 秒后开始进行 DNS 更新..."
# 等待 10 秒钟后再执行 DNS 更新
Start-Sleep -Seconds 10
Write-Host "开始进行 DNS 更新..."
# 在新的 CMD 窗口中启动 DNS 更新逻辑(例如 Cloudflare API 或任何 DNS 更新方法)
Start-Process cmd.exe -ArgumentList "/K echo 正在开始 DNS 更新... && REM 在这里添加你的 DNS 更新逻辑 && pause"
Write-Host "DNS 更新已在新的 CMD 窗口中启动。"
# ---------- 配置 ----------
$CF_API_TOKEN = "" # Cloudflare API Token,必须有 Zone.DNS:Edit 权限
$ZONE_ID = "" # Cloudflare Zone ID
$DNS_NAME = "" # 要更新的域名
$FILE_PATH = Join-Path $PSScriptRoot "result.csv"
# --------------------------
Write-Host "🚀 脚本开始执行..."
# 读取 CSV 第一行真实 IP 和 下载速度
$csvLine = (Get-Content $FILE_PATH -Encoding UTF8 | Select-Object -Skip 1 | Select-Object -First 1)
$fields = $csvLine.Split(",")
$IP = $fields[0].Trim()
$DownloadSpeed = [float]$fields[5].Trim()
# 检查下载速度是否为 0
if ($DownloadSpeed -eq 0) {
Write-Host "❌ 下载速度为 0,跳过 DNS 更新"
exit 0
}
Write-Host "📌 获取到 IP: $IP"
Write-Host "📌 下载速度: $DownloadSpeed MB/s"
# 设置请求头
$Headers = @{
Authorization = "Bearer $CF_API_TOKEN"
"Content-Type" = "application/json"
}
# 获取 DNS 记录 ID
$Resp = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=A&name=$DNS_NAME" -Method GET -Headers $Headers
if ($Resp.success -and $Resp.result.Count -gt 0) {
$DNS_RECORD_ID = $Resp.result[0].id
Write-Host "📌 DNS记录ID: $DNS_RECORD_ID"
} else {
Write-Host "❌ 获取 DNS 记录 ID 失败"
exit 1
}
# 构建 JSON Body(proxied = false,关闭小橙云)
$Body = @{
type = "A"
name = $DNS_NAME
content = $IP
ttl = 1
proxied = $false
} | ConvertTo-Json -Compress
# 更新 DNS
$UpdateResp = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$DNS_RECORD_ID" -Method PATCH -Headers $Headers -Body $Body
Write-Host "📌 Cloudflare 返回: $UpdateResp"
if ($UpdateResp.success) {
Write-Host "✅ DNS记录更新成功: $DNS_NAME -> $IP"
} else {
Write-Host "❌ DNS更新失败"
}二、WIN优化版
优化功能:电脑上测试优选IP,解析最快的IP到cf域名,tg通知结果,上传结果到网页
win的脚本代码
# ========================== 全局参数配置 ==========================
# 程序相关
$cfstPath = ".\cfst.exe" # cfst.exe 文件路径
$csvFile = ".\result.csv" # CSV 文件路径
# api相关
$uploadUrl = "https://xxx/api/upload" # 上传接口 URL
$authKey = "" # 授权密钥
# 域名更新相关
$zoneId = "" # Cloudflare Zone ID
$apiToken = "" # Cloudflare API Token
$recordName = "" # 要更新的 DNS 记录名称
# 测速相关
$url = "" # 测速用的文件地址
# ========================== 删除旧的 CSV 文件 ==========================
Write-Host "[INFO] 删除旧的 result.csv 文件..."
if (Test-Path $csvFile) {
Remove-Item $csvFile
}
# ========================== 开始测速 ==========================
Write-Host "[INFO] 开始测速..."
# 设置 cfst.exe 参数
$currentHour = (Get-Date).Hour
if ($currentHour -ge 19 -and $currentHour -lt 24) {
# 晚高峰时段的参数
$cfstArgs = "-n 800 -t 8 -dn 10 -dt 10 -tp 443 -tl 300 -tlr 0.5 -sl 0.01 -p 10 -url $url -o result.csv"
} else {
# 平时的参数
$cfstArgs = "-n 800 -t 4 -dn 10 -dt 10 -tp 443 -tl 300 -tlr 0.5 -sl 0.01 -p 10 -url $url -o result.csv"
}
# 在新的 PowerShell 窗口运行 cfst.exe
Start-Process powershell -ArgumentList "-NoExit", "-Command", "& '$cfstPath' $cfstArgs"
# 等待 result.csv 文件生成
$timeout = 900 # 设置最大等待时间 15 分钟
$startTime = Get-Date
while (-not (Test-Path $csvFile)) {
$elapsedTime = (Get-Date) - $startTime
if ($elapsedTime.TotalSeconds -gt $timeout) {
Write-Host "[ERROR] 等待时间超过 15 分钟,未生成 result.csv 文件"
exit
}
Write-Host "[INFO] 等待 result.csv 文件生成..."
Start-Sleep -Seconds 10
}
# ========================== 读取 CSV 文件 ==========================
Write-Host "[INFO] 读取 result.csv 文件..."
$csvContent = Import-Csv -Path $csvFile
# 选择最快的 IP
$bestIp = $csvContent | Sort-Object { [double]$_.'下载速度(MB/s)' } -Descending | Select-Object -First 1
if ($bestIp) {
Write-Host "[INFO] 选择的 IP: $($bestIp.'IP 地址') SPEED: $($bestIp.'下载速度(MB/s)')"
} else {
Write-Host "[ERROR] 找不到有效的 IP 地址"
exit
}
# ========================== 更新 DNS 记录 ==========================
Write-Host "[INFO] 准备更新 DNS,IP 地址: $($bestIp.'IP 地址')"
$recordUrl = "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records?name=$recordName"
$headers = @{
"Authorization" = "Bearer $apiToken"
"Content-Type" = "application/json"
}
$response = Invoke-RestMethod -Uri $recordUrl -Headers $headers -Method Get
if ($response.success -and $response.result.Count -gt 0) {
$recordId = $response.result[0].id
# 更新 DNS 记录
$updateUrl = "https://api.cloudflare.com/client/v4/zones/$zoneId/dns_records/$recordId"
$body = @{
"type" = "A"
"name" = $recordName
"content" = $bestIp.'IP 地址'
"ttl" = 60
"proxied" = $false
} | ConvertTo-Json
$updateResponse = Invoke-RestMethod -Uri $updateUrl -Headers $headers -Method Put -Body $body
if ($updateResponse.success) {
Write-Host "[INFO] DNS 更新成功"
} else {
Write-Host "[ERROR] 更新 DNS 失败: $($updateResponse.errors)"
}
} else {
Write-Host "[ERROR] 获取 DNS 记录失败"
exit
}
# ========================== 上传结果到 Worker ==========================
Write-Host "[INFO] 上传数据到 Worker..."
$jsonArray = @()
foreach ($row in $csvContent) {
$jsonArray += @{
"ip" = $row.'IP 地址'
"speed" = $row.'下载速度(MB/s)'
"latency" = $row.'平均延迟'
"packetLoss" = $row.'丢包率'
"region" = $row.'地区码'
"sent" = $row.'已发送'
"received" = $row.'已接收'
"time" = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
}
}
# 转换为 JSON 格式
$jsonBody = $jsonArray | ConvertTo-Json -Depth 4
try {
$uploadResponse = Invoke-RestMethod -Uri $uploadUrl -Method Post -Headers @{
"Authorization" = $authKey
"Content-Type" = "application/json"
} -Body $jsonBody
if ($uploadResponse.success) {
Write-Host "[INFO] 数据上传成功"
} else {
Write-Host "[ERROR] 数据上传失败: $($uploadResponse.errors)"
}
} catch {
Write-Host "[ERROR] 上传失败: $_"
}
# 防止脚本结束,保持当前窗口打开
Write-Host "[INFO] 脚本执行完成"
Read-Host "按任意键退出"
后端是部署在cloudfalre的worker,需要绑定一个kv,kv命名是IP_KV
tg通知的话,也还需要在变量里面添加变量名,TG_BOT_TOKEN tg机器人api,TG_CHAT_ID tg用户id
AUTH_KEY 可选,用户上传接口验证的密钥
CF的woker代码
// ================= 配置 =================
const CONFIG = {
// 节点地区映射
REGION_MAP: {
HKG: "香港", KHH: "高雄", NRT: "东京", LAX: "洛杉矶",
SEA: "西雅图", SJC: "圣何塞", FRA: "法兰克福",
MAD: "马德里", SIN: "新加坡", CAN: "广州", SHA: "上海"
},
MAX_NODES_DISPLAY: 10, // Telegram 最多显示节点数量
DATA_RETENTION_MS: 24 * 60 * 60 * 1000, // 节点数据保留 24 小时
BAR_MAX_LEN: 5, // Telegram 条形图长度
NODE_ACCESS_INTERVAL: 30 * 1000 // 节点接口访问最小间隔 30 秒
};
// ================= 获取北京时间 =================
const getBJTime = () => new Date(Date.now() + 8 * 3600 * 1000);
// ================= Telegram 消息生成函数(等宽条形图) =================
function generateTGMessageText(data) {
if (!data || data.length === 0)
return "❌ Cloudflare 优选 IP\n\n暂无有效数据";
// 1. 排序并取前 MAX_NODES_DISPLAY 条
const sortedData = [...data]
.map(i => ({ ip: String(i.ip), speed: parseFloat(i.speed) || 0 }))
.sort((a, b) => b.speed - a.speed)
.slice(0, CONFIG.MAX_NODES_DISPLAY);
const maxSpeed = sortedData[0].speed;
const barLen = CONFIG.BAR_MAX_LEN;
// 2. 构建条形图行
const nodeLines = sortedData.map(i => {
const ratio = maxSpeed > 0 ? i.speed / maxSpeed : 0;
let fill = Math.round(ratio * barLen);
if (fill < 1) fill = 1;
let block;
if (ratio > 0.7) block = "█"; // 高速
else if (ratio > 0.3) block = "▓"; // 中速
else block = "▒"; // 低速
const bar = block.repeat(fill).padEnd(barLen, " ");
const ip = i.ip.padEnd(16, " ");
const speed = i.speed.toFixed(2).padStart(6, " ");
return `${ip}${bar} ${speed} MB/s`;
}).join("\n");
const fastest = sortedData[0];
const slowest = sortedData[sortedData.length - 1];
const timeStr = getBJTime().toISOString().replace("T", " ").substring(0, 16);
// 3. 返回 Telegram 消息
return `✅ Cloudflare 优选 IP 更新通知
⚡️ 最快: ${fastest.ip} - ${fastest.speed.toFixed(2)} MB/s
☁️ 最慢: ${slowest.ip} - ${slowest.speed.toFixed(2)} MB/s
🕙 更新时间: ${timeStr}
<code>
${nodeLines}
</code>`;
}
// ================= HTML 页面渲染 =================
function renderHTML(list) {
const updateTime = list.length > 0 ? list[0].time : "-";
// 计算最大速度,用于条形图比例
const maxSpeed = list.length > 0 ? Math.max(...list.map(i => i.speed)) : 0;
// 生成每个节点卡片 HTML
const cards = list.sort((a, b) => b.speed - a.speed)
.map((i, idx) => {
const widthPct = maxSpeed > 0 ? Math.round((i.speed / maxSpeed) * 100) : 0;
return `
<div class="card" onclick="copyToClipboard('${i.ip}')">
<div class="card-header">
<span class="rank">#${idx + 1}</span>
<span class="region">${CONFIG.REGION_MAP[i.region] || i.region || "Any"}</span>
</div>
<div class="ip">${i.ip}</div>
<div class="speed">${i.speed} MB/s</div>
<div class="latency">${i.latency} ms</div>
<div class="card-footer">点击复制 IP</div>
</div>`;
}).join("");
return `<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Cloudflare 优选 IP</title>
<style>
:root {
--primary-color: #4caf50;
--secondary-color: #f3f6f9;
--card-bg: #ffffff;
--text-color: #333;
--muted-color: #888;
--highlight-color: #f3f8fb;
--shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
--hover-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
* { box-sizing: border-box; }
body {
margin: 0;
padding: 20px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(145deg, #e0f7fa, #ffffff);
color: var(--text-color);
}
.container { max-width: 1200px; margin: 0 auto; }
.header { text-align: center; margin-bottom: 40px; }
.header h1 { font-size: 2rem; color: var(--primary-color); }
.header p { font-size: 0.9rem; color: var(--muted-color); }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
/* 卡片样式 */
.card {
background-color: var(--card-bg);
border-radius: 12px;
padding: 20px;
box-shadow: var(--shadow);
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.card:hover { transform: translateY(-5px); box-shadow: var(--hover-shadow); }
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.rank {
font-size: 0.8rem;
font-weight: bold;
color: var(--primary-color);
background: var(--highlight-color);
padding: 6px 12px;
border-radius: 20px;
}
.region {
font-size: 0.8rem;
color: #1e40af;
background: #e0e7ff;
padding: 6px 12px;
border-radius: 12px;
}
.ip {
font-family: 'Courier New', Courier, monospace;
font-size: 1.2rem;
font-weight: 600;
text-align: center;
margin-bottom: 14px;
}
.speed {
font-size: 2rem;
font-weight: bold;
color: var(--primary-color);
text-align: center;
margin: 12px 0;
}
.latency {
font-size: 1.2rem;
font-weight: 600;
color: var(--muted-color);
text-align: center;
}
.card-footer {
text-align: center;
font-size: 0.75rem;
color: var(--muted-color);
margin-top: 14px;
}
#toast {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background: var(--primary-color);
color: white;
padding: 12px 20px;
border-radius: 20px;
font-size: 0.85rem;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s ease, transform 0.3s ease;
}
#toast.show {
opacity: 1;
transform: translateX(-50%) translateY(-6px);
}
</style>
<script>
// 复制 IP 并显示 Toast 提示
function copyToClipboard(text) {
navigator.clipboard.writeText(text).then(() => {
const toast = document.getElementById("toast");
toast.classList.add("show");
setTimeout(() => toast.classList.remove("show"), 1800);
});
}
</script>
</head>
<body>
<div class="container">
<div class="header">
<h1>Cloudflare 优选 IP</h1>
<p>最后更新 · ${updateTime}</p>
</div>
<div class="grid">
${cards || '<div style="grid-column: 1/-1; text-align:center; color:#9ca3af; padding:60px;">暂无数据</div>'}
</div>
</div>
<div id="toast">IP 已复制</div>
</body>
</html>`;
}
// ================= Worker 主逻辑 =================
export default {
async fetch(request, env) {
const { pathname } = new URL(request.url);
// ---------- 上传测速数据接口 ----------
if (pathname === "/api/upload" && request.method === "POST") {
try {
const auth = request.headers.get("Authorization");
if (env.AUTH_KEY && auth !== env.AUTH_KEY)
return new Response("Unauthorized", { status: 401 });
const rawData = await request.json();
if (!Array.isArray(rawData)) throw new Error("Invalid format");
const now = getBJTime().toISOString();
const cleanData = rawData.map(i => ({
ip: i.ip, speed: i.speed, latency: i.latency, region: i.region, time: now.replace("T", " ").substring(0, 16)
}));
// 每次上传的数据都完全覆盖之前的数据
await env.SPEED_TEST_KV.put("speed_test_data", JSON.stringify(cleanData));
// 发送 Telegram 消息
await Promise.allSettled([
fetch(`https://api.telegram.org/bot${env.TG_BOT_TOKEN}/sendMessage`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: env.TG_CHAT_ID,
text: generateTGMessageText(cleanData),
parse_mode: "HTML",
disable_notification: true
})
}),
]);
return new Response(JSON.stringify({ success: true }), {
headers: { "Content-Type": "application/json; charset=utf-8" }
});
} catch (e) {
return new Response(JSON.stringify({ error: e.message }), {
status: 400,
headers: { "Content-Type": "application/json; charset=utf-8" }
});
}
}
// ---------- 获取节点数据接口 ----------
if (pathname === "/api/nodes") {
const nowTs = Date.now();
const clientIP = request.headers.get("CF-Connecting-IP") || "unknown";
const rawAccess = await env.SPEED_TEST_KV.get("nodes_access_map");
let accessMap = rawAccess ? JSON.parse(rawAccess) : {};
// 删除过期访问记录
for (let ip in accessMap)
if (nowTs - accessMap[ip] > CONFIG.NODE_ACCESS_INTERVAL) delete accessMap[ip];
// 检查访问频率
if (accessMap[clientIP])
return new Response(JSON.stringify({ success: false, message: "请30秒后再请求" }), {
headers: { "Content-Type": "application/json; charset=utf-8" }
});
// 更新访问时间
accessMap[clientIP] = nowTs;
await env.SPEED_TEST_KV.put("nodes_access_map", JSON.stringify(accessMap));
// 获取 24 小时内有效节点数据
const raw = await env.SPEED_TEST_KV.get("speed_test_data");
let list = raw ? JSON.parse(raw) : [];
list = list.filter(i => nowTs - new Date(i.time).getTime() < CONFIG.DATA_RETENTION_MS);
return new Response(JSON.stringify({ success: true, data: list }), {
headers: { "Content-Type": "application/json; charset=utf-8" }
});
}
// ---------- 网页端展示 ----------
if (pathname === "/") {
const raw = await env.SPEED_TEST_KV.get("speed_test_data") || "[]";
return new Response(renderHTML(JSON.parse(raw)), {
headers: { "Content-Type": "text/html; charset=UTF-8" }
});
}
// ---------- 404 ----------
return new Response("Not Found", { status: 404 });
}
};
三、Linux版
1.官方安装说明
# 如果是第一次使用,则建议创建新文件夹(后续更新时,跳过该步骤)
mkdir cfst
# 进入文件夹(后续更新,只需要从这里重复下面的下载、解压命令即可)
cd cfst
# 下载 CFST 压缩包(自行根据需求替换 URL 中 [版本号] 和 [文件名])
wget -N https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_amd64.tar.gz
# 如果你是在国内网络环境中下载,那么请使用下面这几个镜像加速之一:
# wget -N https://ghfast.top/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# wget -N https://wget.la/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# wget -N https://ghproxy.net/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# wget -N https://gh-proxy.com/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# wget -N https://hk.gh-proxy.com/https://github.com/XIU2/CloudflareSpeedTest/releases/download/v2.3.4/cfst_linux_arm64.tar.gz
# 如果下载失败的话,尝试删除 -N 参数(如果是为了更新,则记得提前删除旧压缩包 rm cfst_linux_amd64.tar.gz )
# 解压(不需要删除旧文件,会直接覆盖,自行根据需求替换 文件名)
tar -zxf cfst_linux_amd64.tar.gz
# 赋予执行权限
chmod +x cfst
# 运行(不带参数)
./cfst
# 运行(带参数示例)
./cfst -tl 200 -dn 20在解压的文件里面,新建一个sh文件run_cf_ddns.sh
需要修改的内容都在配置信息里面
API_TOKEN # Cloudflare API Token,必须有 Zone.DNS:Edit 权限
ZONE_ID # Cloudflare Zone ID
RECORD_NAME # 要更新的域名记得给文件添加可执行权限,chmod 777 run_cf_ddns.sh,然后运行看看效果./run_cf_ddns.sh
#!/bin/bash
# ================= 配置信息 =================
# Cloudflare API Token,必须有 Zone.DNS:Edit 权限
API_TOKEN=""
# Cloudflare Zone ID
ZONE_ID=""
# 要更新的域名
RECORD_NAME=""
# 测速url
SPEED_URL=""
# ================= cfst 参数 =================
# 定义平时参数
CFST_PARAMS_DAY="-n 600 -t 4 -dn 10 -dt 15 -tp 443 -url $SPEED_URL -tl 120 -tll 40 -tlr 0.5 -p 10 -sl 1 -o result.csv"
# 定义晚高峰参数
CFST_PARAMS_PEAK="-n 600 -t 6 -dn 10 -dt 15 -tp 443 -url $SPEED_URL -tl 200 -tll 40 -tlr 1 -p 10 -sl 1 -o result.csv"
# 根据当前时间判断使用哪一套参数
current_hour=$(date +%H)
if [ "$current_hour" -ge 20 ] && [ "$current_hour" -lt 24 ]; then
CFST_PARAMS="$CFST_PARAMS_PEAK" # 晚高峰使用晚高峰参数
else
CFST_PARAMS="$CFST_PARAMS_DAY" # 非高峰期使用平时参数
fi
echo "使用 cfst 参数: $CFST_PARAMS"
# ============================================
# ================= 防止重复运行逻辑 =================
LOCK_FILE="/tmp/cfst_ddns_update.lock"
LOCK_TIMEOUT=900 # 15分钟超时
# 使用文件描述符 200 绑定锁文件
exec 200>"$LOCK_FILE"
# 尝试获取排他锁 (-n 表示非阻塞,如果失败立即返回)
if ! flock -n 200; then
echo "--- [$(date '+%Y-%m-%d %H:%M:%S')] 错误: 另一个脚本实例正在运行,本次任务跳过 ---"
exit 0
fi
# 设置超时控制:超过15分钟自动解除锁并停止脚本
{
sleep "$LOCK_TIMEOUT" && echo "--- [$(date '+%Y-%m-%d %H:%M:%S')] 错误: 超过 15 分钟未完成任务,自动解锁并退出 ---" && flock -u 200 && exit 1
} &
# 脚本的主要任务处理
echo "--- [$(date '+%Y-%m-%d %H:%M:%S')] 正在执行任务 ---"
# 任务代码...
# 脚本任务完成后解除锁
flock -u 200
echo "--- [$(date '+%Y-%m-%d %H:%M:%S')] 任务完成,已释放锁 ---"
# ===================================================
# 获取脚本所在绝对路径
BASE_DIR=$(cd "$(dirname "$0")"; pwd)
cd "$BASE_DIR" || exit 1
RESULT_FILE="$BASE_DIR/result.csv"
echo "--- 任务开始: $(date '+%Y-%m-%d %H:%M:%S') ---"
# 1. 环境清理
[ -f "$RESULT_FILE" ] && rm -f "$RESULT_FILE"
# 2. 执行测速
if [ -f "./cfst" ]; then
echo "[步骤1/4] 正在运行 cfst 测速..."
chmod +x ./cfst
./cfst $CFST_PARAMS
else
echo "错误: 目录下未找到 cfst 执行文件"
exit 1
fi
# 3. 结果校验与提取
if [ ! -s "$RESULT_FILE" ]; then
echo "错误: 测速失败,未生成有效的 result.csv"
exit 1
fi
READ_DATA=$(awk -F, 'NR==2 {print $1,$6}' "$RESULT_FILE")
read -r IP SPEED <<< "$READ_DATA"
CHECK_RESULT=$(echo "$SPEED" | awk '{if($1 <= 0.01) print "stop"; else print "go"}')
if [ "$CHECK_RESULT" == "stop" ] || [ -z "$IP" ]; then
echo "停止更新: 速度过低 ($SPEED MB/s) 或未获取到 IP。"
exit 0
fi
echo "[步骤2/4] 测速通过!最优 IP: $IP, 速度: $SPEED MB/s"
# 4. 更新 Cloudflare
echo "[步骤3/4] 正在获取 Record ID..."
RECORD_INFO=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$RECORD_NAME" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json")
RECORD_ID=$(echo "$RECORD_INFO" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p' | head -n 1)
if [ -z "$RECORD_ID" ] || [ ${#RECORD_ID} -lt 10 ]; then
echo "错误: 无法获取 Record ID。"
exit 1
fi
echo "[步骤4/4] 正在同步 DNS 记录..."
UPDATE_RES=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
-H "Authorization: Bearer $API_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$IP\",\"ttl\":60,\"proxied\":false}")
if [[ "$UPDATE_RES" == *"\"success\":true"* ]]; then
echo "=========================================="
echo " 更新成功! IP: $IP"
echo "=========================================="
else
echo "更新失败!"
echo "$UPDATE_RES"
exit 1
fi
echo "--- 任务结束: $(date '+%Y-%m-%d %H:%M:%S') ---"