基于 Cloudflare Workers 搭建多源 IP 查询工具
最近使用 AI 写了一个 IP 查询的工具,类似于 cip.cc,部署在 Cloudflare Workers 上,支持多数据源对比展示。这个工具不仅可以通过网页访问,还支持命令行通过 curl 方式查询当前 IP。现在的访问网址是 https://ip.liyp.cc。
下面介绍下项目完整的实现代码,方便自己部署到 Cloudflare 账号中。
功能特性
- 多数据源聚合查验:集成了
IP-API.com、Mir6.com和IP.SB多个接口,因为发现IP-API.com的结果不准确,所以加入了多个数据源进行对比。 - 全平台支持:
- 网页端浏览器访问:现代化的 UI 界面,卡片化展示各接口返回的地理坐标、ISP、组织等信息。
- 终端/CLI 访问:精准识别
curl或Wget请求,返回简洁的纯文本格式,还支持返回JSON格式。
- 国际化(i18n):根据请求的
Accept-Language自动切换中英双语展示。 - 零成本高可用:依托于 Cloudflare Workers 边缘计算网络,无需服务器,高并发且完全免费。

部署教程
- 登录 Cloudflare Dashboard,进入 Workers & Pages。
- 点击 Create application -> Create Worker,随便取一个名字并部署初始代码。
- 点击进入创建好的 Worker,选择 Edit code。
- 将下面的完整代码复制并替换掉原有代码,点击 Deploy 保存部署。
- (可选)在 Worker 的 Settings -> Triggers 中绑定自定义域名(如
ip.yourdomain.com)。
完整代码
这是一个单文件项目,包含所有逻辑、HTML/CSS UI渲染以及国际化字典:
/**
* Cloudflare Worker - IP 查询工具
* 多数据源对比展示
*/
const API_SOURCES = [
{ name: "IP-API.com", url: "http://ip-api.com/json/{ip}?fields=status,message,country,regionName,city,district,isp,org,lat,lon,timezone,as,mobile,proxy,hosting,query&lang=zh-CN" },
{ name: "Mir6.com", url: "https://api.mir6.com/api/ip_json?ip={ip}" },
{ name: "IP.SB", url: "https://api.ip.sb/geoip/{ip}" },
];
/**
* 国际化语言字典
*/
const i18nDict = {
en: {
title: "IP Inspector",
subtitle: "Multi-source IP geolocation intelligence routing",
placeholder: "Enter IP address (leave empty for yours)",
inspect: "Inspect",
queryResults: "Query Results",
apiUsage: "API & CLI Usage",
address: "Address",
isp: "ISP",
org: "Organization",
coord: "Coordinates",
active: "Active",
unknownLoc: "Unknown Location",
unknownIsp: "Unknown ISP",
unknownOrg: "Unknown Org",
errorMsg: "Query failed. The upstream APIs might be rate-limited or unreachable.",
terminal: "Terminal",
commentCurrent: "# Check your current IP directly",
commentSpecific: "# Query specific IP",
commentJson: "# Get JSON response",
commentAll: "# Multi-source full text response",
footerPowered: "Powered by Cloudflare Workers",
// CLI Texts
cliAllFailed: "All upstream query interfaces failed",
cliUnknown: "Unknown",
cliTimeout: "Query timeout or parse failed",
cliAddress: "Addr",
cliIsp: "ISP",
cliSource2: "Src 2",
cliSource3: "Src 3",
},
zh: {
title: "IP Inspector", // 保留英文感品牌名
subtitle: "多源 IP 归属地智能查询聚合",
placeholder: "输入 IP 地址(留空查询本机)",
inspect: "查询",
queryResults: "查询结果",
apiUsage: "API 与命令行调用",
address: "物理地址",
isp: "服务商",
org: "归属组织",
coord: "地理坐标",
active: "成功",
unknownLoc: "未知地址",
unknownIsp: "未知服务商",
unknownOrg: "未知组织",
errorMsg: "查询失败。接口超时或所有上游 API 均无法连通。",
terminal: "终端",
commentCurrent: "# 查询本机 IP",
commentSpecific: "# 查询指定 IP",
commentJson: "# 请求 JSON 格式",
commentAll: "# 多源完整文本展示",
footerPowered: "由 Cloudflare Workers 强力驱动",
// CLI Texts
cliAllFailed: "所有查询接口均失败",
cliUnknown: "未知",
cliTimeout: "查询超时或解析失败",
cliAddress: "地址",
cliIsp: "运营商",
cliSource2: "数据二",
cliSource3: "数据三",
}
};
/**
* 根据请求头解析语言
*/
function getLanguage(request) {
const acceptLang = request.headers.get("Accept-Language") || "";
return acceptLang.toLowerCase().includes("zh") ? "zh" : "en";
}
/**
* 带超时的 fetch
*/
async function fetchWithTimeout(url, timeoutMs = 8000) {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, { signal: controller.signal });
clearTimeout(timeoutId);
return response;
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
}
async function fetchSingleSource(ip, source) {
try {
const url = source.url.replace("{ip}", ip);
const response = await fetchWithTimeout(url, 8000);
if (!response.ok) return null;
const data = await response.json();
if (!data) return null;
return data;
} catch (error) { return null; }
}
function isValidResponse(data, source) {
if (!data) return false;
if (source.name === "IP-API.com") { return data.status !== "fail"; }
if (source.name === "Mir6.com") { return !(data.code && data.code === 0); }
if (source.name === "IP.SB") { return !!data.ip || !!data.country; }
return true;
}
function normalizeIPInfo(raw, source, targetIp) {
let result = {
ip: raw.ip || raw.query || raw.ip_address || raw["0"] || targetIp || "",
country: raw.country || raw.country_name || raw.countryCode || "",
region: raw.regionName || raw.region || raw.region_name || raw.province || raw.province || "",
city: raw.city || "",
isp: raw.isp || raw.carrier || raw.org || raw.organization || "",
org: raw.organization || raw.org || "",
lat: raw.lat || raw.latitude || null,
lon: raw.lon || raw.longitude || null,
timezone: raw.timezone || raw.timezone?.id || "",
source: source.name,
};
if (source.name === "Mir6.com") {
if (raw.data) {
const d = raw.data;
result.ip = d.ip || result.ip;
result.country = d.country || d.country_code || result.country;
result.region = d.province || d.region || result.region;
result.city = d.city || result.city;
result.isp = d.carrier || d.operator || d.isp || result.isp;
result.org = d.org || result.org;
result.lat = d.lat || result.lat;
result.lon = d.lon || result.lon;
}
result.ip = raw.ip || result.ip;
result.country = raw.country || result.country;
result.region = raw.province || result.region;
result.city = raw.city || result.city;
result.isp = raw.isp || raw.carrier || result.isp;
}
if (source.name === "IP.SB") {
result.country = raw.country || result.country;
result.region = raw.region || result.region;
result.city = raw.city || result.city;
result.isp = raw.isp || result.isp;
}
return result;
}
async function fetchAllSources(ip) {
const results = {};
const fetchPromises = API_SOURCES.map(async (source) => {
const rawData = await fetchSingleSource(ip, source);
if (rawData && isValidResponse(rawData, source)) {
const info = normalizeIPInfo(rawData, source, ip);
if (info && info.ip) return { name: source.name, data: info };
}
return null;
});
const responses = await Promise.all(fetchPromises);
responses.forEach(res => { if (res) results[res.name] = res.data; });
return results;
}
async function fetchFirstAvailable(ip) {
try {
const first = await Promise.any(API_SOURCES.map(async (source) => {
const rawData = await fetchSingleSource(ip, source);
if (rawData && isValidResponse(rawData, source)) {
const info = normalizeIPInfo(rawData, source, ip);
if (info && info.ip) return info;
}
throw new Error("Invalid");
}));
return first;
} catch {
return null;
}
}
function isValidIP(ip) {
if (!ip) return false;
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/;
if (ipv4Pattern.test(ip)) {
const parts = ip.split(".");
if (parts.every((part) => parseInt(part) <= 255)) return true;
}
if (ip.includes(":") && ip.split(":").length <= 8) return true;
return false;
}
function getClientIP(request) {
const url = new URL(request.url);
const queryIP = url.searchParams.get("ip");
if (queryIP && isValidIP(queryIP)) return queryIP;
const headerIP = request.headers.get("X-Forwarded-For");
if (headerIP && isValidIP(headerIP.split(",")[0].trim())) {
return headerIP.split(",")[0].trim();
}
return request.headers.get("CF-Connecting-IP") || "127.0.0.1";
}
function parsePathIP(request) {
const url = new URL(request.url);
const pathParts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
if (pathParts.length === 0) return null;
if (pathParts[0] === "json" && pathParts[1] && isValidIP(pathParts[1])) return pathParts[1];
if (isValidIP(pathParts[0])) return pathParts[0];
return null;
}
/**
* 命令行输出双语支持
*/
function formatCLIResponse(ip, allSources, baseUrl, lang) {
const t = i18nDict[lang];
const s1 = allSources["IP-API.com"];
const s2 = allSources["Mir6.com"];
const s3 = allSources["IP.SB"];
if (!s1 && !s2 && !s3) return `IP\t: ${ip}\n${t.cliAddress}\t: ${t.cliAllFailed}\n`;
let output = `IP\t: ${ip}\n`;
if (s1) {
let address1 = [s1.country, s1.region, s1.city].filter(Boolean).join(" ");
output += `${t.cliAddress}\t: ${address1} (${s1.source})\n`;
output += `${t.cliIsp}\t: ${s1.isp || t.cliUnknown}\n`;
} else {
output += `${t.cliAddress}\t: ${t.cliTimeout} (IP-API.com)\n`;
}
output += `\n`;
if (s2) {
let address2 = [s2.country, s2.region, s2.city].filter(Boolean).join(" | ");
output += `${t.cliSource2}\t: ${address2} | ${s2.isp || t.cliUnknown} (${s2.source})\n`;
} else {
output += `${t.cliSource2}\t: ${t.cliTimeout} (Mir6.com)\n`;
}
output += `\n`;
if (s3) {
let address3 = [s3.country, s3.region, s3.city].filter(Boolean).join(" ");
output += `${t.cliSource3}\t: ${address3} | ${s3.isp || t.cliUnknown} (${s3.source})\n`;
} else {
output += `${t.cliSource3}\t: ${t.cliTimeout} (IP.SB)\n`;
}
output += `\nURL\t: ${baseUrl}/${ip}\n`;
return output;
}
function formatCLIAllResponse(ip, allSources, lang) {
const t = i18nDict[lang];
const sources = Object.values(allSources);
if (sources.length === 0) return `IP: ${ip}\n${t.cliAllFailed}\n`;
let output = `IP: ${ip}\n\n`;
sources.forEach((s) => {
output += `[${s.source}]\n`;
output += ` IP: ${s.ip}\n`;
output += ` ${t.cliAddress}: ${[s.country, s.region, s.city].filter(Boolean).join(" ")}\n`;
output += ` ${t.cliIsp}: ${s.isp}\n`;
output += ` ${t.coord}: ${s.lat ? `${s.lat}, ${s.lon}` : t.cliUnknown}\n\n`;
});
return output;
}
/**
* 格式化 HTML 页面 (中英双语自适应)
*/
function formatHTMLResponse(ip, allSources, hostname, lang) {
const t = i18nDict[lang];
const sources = Object.values(allSources);
let content = "";
if (sources.length === 0) {
content = `
<div class="alert alert-error">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>
<span>${t.errorMsg}</span>
</div>`;
} else {
content = `<div class="sources-grid">`;
sources.forEach((s) => {
const address = [s.country, s.region, s.city].filter(Boolean).join(", ") || t.unknownLoc;
const coordinates = s.lat ? `${s.lat}, ${s.lon}` : "N/A";
const isp = s.isp || t.unknownIsp;
const org = s.org || t.unknownOrg;
content += `
<div class="source-card">
<div class="card-header">
<div class="source-name">
<span class="dot"></span>
${s.source}
</div>
<div class="source-badge">${t.active}</div>
</div>
<div class="source-body">
<div class="info-row">
<span class="info-label">${t.address}</span>
<span class="info-value">${address}</span>
</div>
<div class="info-row">
<span class="info-label">${t.isp}</span>
<span class="info-value">${isp}</span>
</div>
<div class="info-row">
<span class="info-label">${t.org}</span>
<span class="info-value">${org}</span>
</div>
<div class="info-row">
<span class="info-label">${t.coord}</span>
<span class="info-value mono">${coordinates}</span>
</div>
</div>
</div>
`;
});
content += `</div>`;
}
return `<!DOCTYPE html>
<html lang="${lang}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${t.title} - ${ip}</title>
<style>
:root {
--bg: #fafafa;
--surface: #ffffff;
--border: #eaeaea;
--border-hover: #d3d3d3;
--text-main: #111111;
--text-muted: #666666;
--text-light: #999999;
--black: #000000;
--radius-md: 8px;
--radius-lg: 12px;
/* 优化中文字体支持 PingFang SC 让 Safari/iOS 下显示更具高级纤细感 */
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--font-mono: "JetBrains Mono", "Menlo", "Monaco", "Courier New", monospace;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: var(--font-sans);
background-color: var(--bg);
color: var(--text-main);
line-height: 1.5;
-webkit-font-smoothing: antialiased;
background-image: radial-gradient(#e5e5e5 1px, transparent 0);
background-size: 20px 20px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
.layout {
max-width: 800px;
margin: 0 auto;
padding: 60px 20px;
width: 100%;
}
header {
margin-bottom: 40px;
}
h1 {
font-size: 24px;
font-weight: 600;
letter-spacing: -0.02em;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 10px;
}
.target-ip {
font-family: var(--font-mono);
font-weight: 400;
color: var(--text-muted);
font-size: 20px;
margin-top: 2px;
}
.subtitle {
color: var(--text-muted);
font-size: 14px;
}
/* Search Form */
.search-box {
display: flex;
gap: 12px;
margin-bottom: 40px;
}
input {
flex: 1;
padding: 12px 16px;
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-size: 14px;
font-family: var(--font-mono);
outline: none;
transition: all 0.2s ease;
box-shadow: 0 1px 2px rgba(0,0,0,0.02);
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(4px);
}
input:focus {
border-color: var(--text-muted);
box-shadow: 0 2px 8px rgba(0,0,0,0.04);
background: #fff;
}
input::placeholder {
color: var(--text-light);
font-family: var(--font-sans);
}
button {
background-color: var(--black);
color: white;
border: none;
padding: 0 24px;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
white-space: nowrap;
}
button:hover {
background-color: #333;
}
/* Data Cards */
.section-title {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-light);
font-weight: 600;
margin-bottom: 16px;
}
.sources-grid {
display: grid;
gap: 20px;
margin-bottom: 40px;
}
@media (min-width: 640px) {
.sources-grid {
grid-template-columns: repeat(2, 1fr);
}
.sources-grid .source-card:first-child {
grid-column: 1 / -1; /* First result spans full width */
}
}
.source-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 24px;
box-shadow: 0 1px 3px rgba(0,0,0,0.02);
transition: border-color 0.2s;
}
.source-card:hover {
border-color: var(--border-hover);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border);
}
.source-name {
font-weight: 600;
font-size: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.dot {
width: 8px;
height: 8px;
background-color: #000;
border-radius: 50%;
}
.source-badge {
font-size: 11px;
padding: 2px 8px;
background: #f1f1f1;
color: var(--text-muted);
border-radius: 10px;
font-family: var(--font-mono);
}
.info-row {
display: flex;
flex-direction: column;
margin-bottom: 12px;
}
.info-row:last-child {
margin-bottom: 0;
}
.info-label {
font-size: 12px;
color: var(--text-light);
margin-bottom: 2px;
}
.info-value {
font-size: 14px;
color: var(--text-main);
}
.mono {
font-family: var(--font-mono);
font-size: 13px;
}
/* CLI usage */
.cli-section {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
}
.cli-header {
padding: 12px 20px;
border-bottom: 1px solid var(--border);
font-size: 13px;
font-weight: 500;
color: var(--text-muted);
display: flex;
gap: 6px;
align-items: center;
background: #fafafa;
}
.window-dots {
display: flex;
gap: 6px;
margin-right: 10px;
}
.window-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--border); }
.cli-body {
padding: 20px;
background: #000;
color: #f1f1f1;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.6;
overflow-x: auto;
}
.cli-comment { color: #888; }
.cli-command { color: #fff; margin-bottom: 12px; display: block; }
.cli-command::before { content: "$ "; color: #666; }
.alert-error {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #fff0f0;
border: 1px solid #ffd6d6;
color: #d10000;
border-radius: var(--radius-md);
font-size: 14px;
}
footer {
margin-top: auto;
text-align: center;
padding: 40px 0;
font-size: 12px;
color: var(--text-light);
font-family: var(--font-mono);
}
</style>
</head>
<body>
<div class="layout">
<header>
<h1>${t.title} <span class="target-ip">${ip}</span></h1>
<p class="subtitle">${t.subtitle}</p>
</header>
<form class="search-box" method="GET">
<input type="text" name="ip" placeholder="${t.placeholder}" value="">
<button type="submit">${t.inspect}</button>
</form>
<div class="section-title">${t.queryResults}</div>
${content}
<div class="section-title">${t.apiUsage}</div>
<div class="cli-section">
<div class="cli-header">
<div class="window-dots">
<div class="window-dot"></div><div class="window-dot"></div><div class="window-dot"></div>
</div>
${t.terminal}
</div>
<div class="cli-body">
<span class="cli-comment">${t.commentCurrent}</span>
<span class="cli-command">curl ${hostname}</span>
<span class="cli-comment">${t.commentSpecific}</span>
<span class="cli-command">curl ${hostname}/${ip}</span>
<span class="cli-comment">${t.commentJson}</span>
<span class="cli-command">curl ${hostname}/json/${ip}</span>
<span class="cli-comment">${t.commentAll}</span>
<span class="cli-command">curl ${hostname}/all</span>
</div>
</div>
</div>
<footer>
© ${new Date().getFullYear()} Toolkit · ${t.footerPowered}
</footer>
</body>
</html>`;
}
function formatJSONResponse(ip, info) {
const response = {
ip: ip,
timestamp: new Date().toISOString(),
source: info?.source || "unknown",
};
if (info) {
response.location = {
country: info.country,
region: info.region,
city: info.city,
coordinates: info.lat ? { lat: parseFloat(info.lat), lon: parseFloat(info.lon) } : null,
};
response.network = {
isp: info.isp,
org: info.org,
};
}
return JSON.stringify(response, null, 2);
}
async function handleRequest(request) {
const url = new URL(request.url);
const pathParts = url.pathname.replace(/^\/+|\/+$/g, "").split("/");
const firstPath = pathParts[0] || "";
// 判断是否在终端请求
const isCLI = (request.headers.get("User-Agent") || "").includes("curl") ||
(request.headers.get("User-Agent") || "").includes("Wget");
const hostname = url.hostname;
const lang = getLanguage(request); // 获取语言
let ip = parsePathIP(request);
if (!ip) ip = getClientIP(request);
if (firstPath === "health") {
return new Response("OK", { status: 200, headers: { "Content-Type": "text/plain" } });
}
if (firstPath === "json") {
const info = await fetchFirstAvailable(ip);
return new Response(formatJSONResponse(ip, info), {
headers: { "Content-Type": "application/json; charset=utf-8", "Access-Control-Allow-Origin": "*" },
});
}
if (firstPath === "all") {
const allSources = await fetchAllSources(ip);
if (isCLI) {
return new Response(formatCLIAllResponse(ip, allSources, lang), { headers: { "Content-Type": "text/plain; charset=utf-8" } });
}
return new Response(formatHTMLResponse(ip, allSources, hostname, lang), { headers: { "Content-Type": "text/html; charset=utf-8" } });
}
const allSources = await fetchAllSources(ip);
if (isCLI) {
return new Response(formatCLIResponse(ip, allSources, hostname, lang), { headers: { "Content-Type": "text/plain; charset=utf-8" } });
}
return new Response(formatHTMLResponse(ip, allSources, hostname, lang), { headers: { "Content-Type": "text/html; charset=utf-8" } });
}
addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
});使用方法
部署完成后,你可以通过不同的方式来调用这个服务:
1. 浏览器访问: 直接在浏览器中打开你的域名,在搜索框输入任意 IP 即可查询,不输入则默认查询本机 IP。
2. 终端命令请求:
工具会自动识别 User-Agent,如果在终端使用 curl 请求,会输出格式友好的纯文本内容:
可能遇到的问题:
因为大部分使用Cloudflare的可能打开了SSL的严格模式,会导致curl请求自动301重定向,这时候需要关闭SSL的严格模式,或者使用-k参数来忽略SSL证书验证,为了维持其他网站正常SSL,需要使用规则让其他网站自动跳转HTTPS网站,下面写一下具体流程:
1. 关闭全局的“始终使用HTTPS”:
进入 Cloudflare 控制台:
SSL/TLS → 边缘证书 (Edge Certificates) → 关闭 “始终使用 HTTPS (Always Use HTTPS)”
2. 为其他子域名重新启用 HTTPS 跳转:
因为关掉全局开关后,你的其他子域名也会失去 HTTPS 强制跳转。你需要用 Redirect Rules(重定向规则) 来给除 ip.liyp.cc 之外的域名重新加上 HTTPS 跳转:
进入:规则 (Rules) → 创建规则 → 重定向规则 (Redirect Rules) → 创建规则
选择 自定义筛选表达式 (Custom filter expression)
点切换到 表达式编辑器 (Expression Editor),粘贴:
(not ssl and http.host ne "ip.liyp.cc")下方"Then"部分配置:
| 字段 | 值 |
|---|---|
| 类型 | 动态 (Dynamic) |
| 目标 URL | concat(“https://”, http.host, http.request.uri.path) |
| 状态码 | 301 |
| 保留查询字符串 | ✅ 开启 |
这样的效果:
✅ curl ip.liyp.cc → HTTP 请求直达 Worker,正常返回
✅ curl https://ip.liyp.cc → HTTPS 也正常
✅ http://liyp.cc → 301 跳转到 https://liyp.cc
✅ 其他所有子域名的 HTTP 请求仍会强制跳转 HTTPS
如下图:

# 查询本机 IP
$ curl ip.liyp.cc
IP : 160.248.69.188
地址 : 日本 神奈川縣 横滨市 (IP-API.com)
运营商 : NTT PC Communications, Inc.
数据二 : 日本 | Unknown (Mir6.com)
数据三 : Japan Kanagawa Yokohama | NTT PC Communications (IP.SB)
URL : ip.liyp.cc/160.248.69.188
# 查询指定 IP
$ curl ip.liyp.cc/8.8.8.8
IP : 8.8.8.8
地址 : 美国 弗吉尼亚州 Ashburn (IP-API.com)
运营商 : Google LLC
数据二 : 美国 堪萨斯州 里诺县 | 谷歌云 (Mir6.com)
数据三 : United States | Google (IP.SB)
URL : ip.liyp.cc/8.8.8.8
# 请求多源完整文本展示
$ curl ip.liyp.cc/all
IP: 160.248.69.188
[IP-API.com]
IP: 160.248.69.188
物理地址: 日本 神奈川縣 横滨市
服务商: NTT PC Communications, Inc.
地理坐标: 35.4338, 139.6496
[Mir6.com]
IP: 160.248.69.188
物理地址: 日本
服务商:
地理坐标: 未知
[IP.SB]
IP: 160.248.69.188
物理地址: Japan Kanagawa Yokohama
服务商: NTT PC Communications
地理坐标: 35.4338, 139.6496使用 Cloudflare Workers 编写这样的小工具不仅省去了服务器维护成本,响应速度也极快。配合 AI 辅助编程,我们可以快速把日常浮现的小需求落地为实用工具。