IP 地址查询是网络应用中常见的功能需求,无论是用于网络安全监控、用户行为分析,还是内容分发网络(CDN)优化,都需要准确、高效的 IP 归属地查询服务。本文将详细介绍如何在 Cordova 应用中集成高精度 IP 地址查询功能,支持 IPv4 和 IPv6 地址查询,并提供完整的实现方案和代码示例。

image-20251121213129872
https://v3.alapi.cn/api/ip)CordovaApp/
├── www/
│ ├── ip.html # IP查询页面
│ └── js/
│ └── ip.js # IP查询功能逻辑
├── harmonyos/
│ └── entry/src/main/resources/rawfile/www/
│ ├── ip.html # HarmonyOS版本IP查询页面
│ └── js/
│ └── ip.js # HarmonyOS版本IP查询逻辑
└── IP地址查询.md # API文档
创建 ip.html 文件,包含以下核心部分:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: https://ssl.gstatic.com https://v3.alapi.cn https://www.alapi.cn 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' https://v3.alapi.cn https://www.alapi.cn; frame-src https://www.alapi.cn;">
<meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover">
<title>IP地址查询</title>
<link rel="stylesheet" href="css/index.css">
</head>
<body>
<!-- 导航栏 -->
<div class="nav">
<a href="index.html">首页</a>
<a href="about.html">关于我们</a>
<a href="poem.html">藏头诗</a>
<a href="translate.html">翻译</a>
<a href="express.html">快递查询</a>
<a href="ip.html" class="active">IP查询</a>
</div>
<!-- 主容器 -->
<div class="ip-container">
<!-- 页面标题 -->
<div class="ip-header">
<h1>IP地址查询</h1>
<p>高精度IP归属地查询,支持IPv4和IPv6</p>
</div>
<!-- 表单区域 -->
<div class="form-container">
<form id="ip-form">
<div class="form-group">
<label for="token" class="label-with-help">
<span>API Token *</span>
<button type="button" class="help-btn" onclick="openTokenModal()">获取 Token</button>
</label>
<input type="text" id="token" name="token" placeholder="请输入API Token" required>
</div>
<div class="form-group">
<label for="ip">IP地址(可选)</label>
<input type="text" id="ip" name="ip"
placeholder="留空则查询当前IP地址"
pattern="^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$|^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$">
<div class="ip-hint">支持IPv4和IPv6格式,留空则自动查询当前IP地址</div>
</div>
<button type="submit" class="submit-btn" id="submit-btn">查询</button>
</form>
<!-- 加载状态 -->
<div class="loading" id="loading">正在查询</div>
<!-- 错误提示 -->
<div class="error-message" id="error-message"></div>
</div>
<!-- 结果展示区域 -->
<div class="result-container" id="result-container">
<div class="result-title">查询结果</div>
<div class="ip-info" id="ip-info"></div>
</div>
</div>
<!-- Token获取弹窗 -->
<div class="modal-overlay" id="token-modal" onclick="closeTokenModal(event)">
<div class="modal-content" onclick="event.stopPropagation()">
<div class="modal-header">
<h2>获取 API Token</h2>
<button class="modal-close" onclick="closeTokenModal()">×</button>
</div>
<div class="modal-body">
<p>请在下方页面注册并获取您的 API Token,然后将 Token 复制到输入框中。</p>
<iframe src="https://www.alapi.cn/aff/nutpi" id="token-iframe"></iframe>
</div>
</div>
</div>
<script src="cordova.js"></script>
<script src="js/ip.js"></script>
</body>
</html>
创建 ip.js 文件,实现 IP 查询的核心功能:
// 表单提交处理
document.getElementById('ip-form').addEventListener('submit', function(e) {
e.preventDefault();
queryIP();
});
function queryIP() {
const submitBtn = document.getElementById('submit-btn');
const loading = document.getElementById('loading');
const errorMessage = document.getElementById('error-message');
const resultContainer = document.getElementById('result-container');
// 获取表单数据
const formData = {
token: document.getElementById('token').value.trim(),
ip: document.getElementById('ip').value.trim()
};
// 验证token
if (!formData.token) {
showError('请输入API Token');
return;
}
// 验证IP格式(如果输入了IP)
if (formData.ip && !isValidIP(formData.ip)) {
showError('IP地址格式不正确,请输入有效的IPv4或IPv6地址');
return;
}
// 显示加载状态
submitBtn.disabled = true;
submitBtn.textContent = '查询中...';
loading.classList.add('show');
errorMessage.classList.remove('show');
resultContainer.classList.remove('show');
// 构建请求参数
const params = new URLSearchParams();
params.append('token', formData.token);
if (formData.ip) {
params.append('ip', formData.ip);
}
// 发送POST请求
fetch('https://v3.alapi.cn/api/ip', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString()
})
.then(response => {
if (!response.ok) {
thrownewError('网络请求失败: ' + response.status);
}
return response.json();
})
.then(data => {
console.log('API响应:', data);
if (data.success && data.code === 200) {
displayIPInfo(data.data);
} else {
showError(data.message || '查询失败,请检查参数');
}
})
.catch(error => {
console.error('请求错误:', error);
showError('请求失败: ' + error.message + '。请检查网络连接或API Token是否正确');
})
.finally(() => {
// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.textContent = '查询';
loading.classList.remove('show');
});
}
// 验证IP地址格式
function isValidIP(ip) {
// IPv4格式验证
const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
// IPv6格式验证(简化版)
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/;
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
}
// 显示IP信息
function displayIPInfo(data) {
const resultContainer = document.getElementById('result-container');
const ipInfo = document.getElementById('ip-info');
let html = '';
// IP基本信息
html += '<div class="info-section">';
html += '<div class="info-section-title">IP地址信息</div>';
html += '<div class="info-grid">';
html += `<div class="info-item"><div class="info-label">IP地址</div><div class="info-value">${data.ip || '未知'}</div></div>`;
html += `<div class="info-item"><div class="info-label">IP范围</div><div class="info-value">${data.beginip || '未知'} - ${data.endip || '未知'}</div></div>`;
html += `<div class="info-item"><div class="info-label">ISP运营商</div><div class="info-value">${data.isp || '未知'}</div></div>`;
html += `<div class="info-item"><div class="info-label">城市编码</div><div class="info-value">${data.adcode || '未知'}</div></div>`;
html += '</div>';
html += '</div>';
// 地理位置信息
html += '<div class="info-section">';
html += '<div class="info-section-title">地理位置</div>';
html += '<div class="info-grid">';
html += `<div class="info-item"><div class="info-label">大洲</div><div class="info-value">${data.continent || '未知'}</div></div>`;
html += `<div class="info-item"><div class="info-label">国家</div><div class="info-value">${data.country || '未知'} ${data.country_en ? '(' + data.country_en + ')' : ''}</div></div>`;
html += `<div class="info-item"><div class="info-label">国家代码</div><div class="info-value">${data.country_cc || '未知'}</div></div>`;
html += `<div class="info-item"><div class="info-label">省份</div><div class="info-value">${data.province || '未知'}</div></div>`;
html += `<div class="info-item"><div class="info-label">城市</div><div class="info-value">${data.city || '未知'}</div></div>`;
html += `<div class="info-item"><div class="info-label">区县</div><div class="info-value">${data.district || '未知'}</div></div>`;
html += `<div class="info-item"><div class="info-label">详细地址</div><div class="info-value">${data.pos || '未知'}</div></div>`;
html += '</div>';
html += '</div>';
// 坐标信息
if (data.lng && data.lat) {
html += '<div class="info-section">';
html += '<div class="info-section-title">坐标信息</div>';
html += '<div class="info-grid">';
html += `<div class="info-item"><div class="info-label">经度</div><div class="info-value">${data.lng}</div></div>`;
html += `<div class="info-item"><div class="info-label">纬度</div><div class="info-value">${data.lat}</div></div>`;
html += '</div>';
html += `<a href="https://www.openstreetmap.org/?mlat=${data.lat}&mlon=${data.lng}&zoom=15" target="_blank" class="map-link">在地图上查看</a>`;
html += '</div>';
}
ipInfo.innerHTML = html;
// 显示结果容器
resultContainer.classList.add('show');
// 滚动到结果区域
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function() {
// 尝试从本地存储加载token
const savedToken = localStorage.getItem('ip_api_token');
if (savedToken) {
document.getElementById('token').value = savedToken;
}
// 保存token到本地存储
document.getElementById('token').addEventListener('blur', function() {
const token = this.value.trim();
if (token) {
localStorage.setItem('ip_api_token', token);
}
});
console.log('IP查询页面加载完成');
});
.ip-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #fff;
min-height: calc(100vh - 100px);
}
@media (max-width:768px) {
.ip-headerh1 {
font-size: 2em;
}
.nava {
display: block;
margin: 5px0;
}
.info-grid {
grid-template-columns: 1fr;
}
}
.info-section {
margin-bottom: 25px;
padding-bottom: 25px;
border-bottom: 1px solid #eee;
}
.info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.info-item {
padding: 12px;
background-color: #f8f9fa;
border-radius: 6px;
}
.submit-btn {
width: 100%;
padding: 15px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 6px;
font-size: 18px;
font-weight: bold;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.submit-btn:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 04px12pxrgba(102, 126, 234, 0.4);
}
https://v3.alapi.cn/api/ipapplication/x-www-form-urlencoded参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
token | string | 是 | 接口调用 token |
ip | string | 否 | 要查询的 IP,默认获取请求客户端的 IP |
{
"request_id": "722950494561095681",
"success": true,
"message": "success",
"code": 200,
"data": {
"ip": "114.114.114.114",
"beginip": "114.114.114.114",
"endip": "114.114.114.114",
"continent": "亚洲",
"country": "中国",
"province": "江苏",
"city": "南京",
"district": "",
"isp": "114DNS",
"adcode": "320100",
"country_en": "China",
"country_cc": "CN",
"lng": "118.7674",
"lat": "32.0415",
"pos": "中国江苏南京"
},
"time": 1733324829,
"usage": 0
}
.catch(error => {
console.error('请求错误:', error);
showError('请求失败: ' + error.message + '。请检查网络连接或API Token是否正确');
})
const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
说明:
25[0-5]: 匹配 250-2552[0-4][0-9]: 匹配 200-249[01]?[0-9][0-9]?: 匹配 0-199(允许前导零)const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/;
说明:
([0-9a-fA-F]{1,4}:){7}: 匹配 7 组 4 位十六进制数加冒号[0-9a-fA-F]{1,4}: 匹配最后一组 4 位十六进制数^::1$: 匹配本地回环地址^::$: 匹配未指定地址通过弹窗方式引导用户获取 API Token,提升用户体验:
function openTokenModal() {
document.getElementById('token-modal').classList.add('show');
document.body.style.overflow = 'hidden';
}
function closeTokenModal(event) {
if (event && event.target !== event.currentTarget) {
return;
}
document.getElementById('token-modal').classList.remove('show');
document.body.style.overflow = '';
}
// 显示加载状态
submitBtn.disabled = true;
submitBtn.textContent = '查询中...';
loading.classList.add('show');
// 滚动到结果区域
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
当查询结果包含经纬度信息时,自动生成地图查看链接:
if (data.lng && data.lat) {
html += `<a href="https://www.openstreetmap.org/?mlat=${data.lat}&mlon=${data.lng}&zoom=15" target="_blank" class="map-link">在地图上查看</a>`;
}
症状: 输入有效的 IP 地址但提示格式不正确
原因: IPv6 格式验证过于严格,未考虑压缩格式
解决方案: 扩展 IPv6 验证规则,支持压缩格式
// 更完善的IPv6验证
function isValidIPv6(ip) {
// 支持压缩格式的IPv6验证
const ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
return ipv6Regex.test(ip);
}
症状: 查询失败,提示 token 无效
原因: API Token 可能过期或被撤销
解决方案: 添加 token 有效性检测和友好的错误提示
.then(data => {
if (data.success && data.code === 200) {
displayIPInfo(data.data);
} else if (data.code === 401 || data.message.includes('token')) {
showError('API Token无效或已过期,请重新获取Token');
// 清除本地存储的token
localStorage.removeItem('ip_api_token');
} else {
showError(data.message || '查询失败,请检查参数');
}
})
症状: 长时间无响应,最终超时
原因: 网络不稳定或 API 服务响应慢
解决方案: 添加请求超时处理
function queryIPWithTimeout(url, options, timeout = 10000) {
returnPromise.race([
fetch(url, options),
newPromise((_, reject) =>
setTimeout(() => reject(newError('请求超时')), timeout)
)
]);
}
// 使用超时请求
queryIPWithTimeout('https://v3.alapi.cn/api/ip', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString()
}, 10000)
症状: 浏览器控制台报 CORS 错误
原因: Content-Security-Policy 配置不当
解决方案: 正确配置 CSP,允许 API 域名
<meta http-equiv="Content-Security-Policy" content="
default-src 'self' data: https://ssl.gstatic.com https://v3.alapi.cn https://www.alapi.cn 'unsafe-eval';
style-src 'self' 'unsafe-inline';
media-src *;
img-src 'self' data: content:;
script-src 'self' 'unsafe-inline' 'unsafe-eval';
connect-src 'self' https://v3.alapi.cn https://www.alapi.cn;
frame-src https://www.alapi.cn;
">
避免用户快速点击查询按钮导致重复请求:
// 防抖函数
function debounce(func, delay) {
let timeoutId;
returnfunction(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// 应用防抖
const debouncedQueryIP = debounce(queryIP, 300);
document.getElementById('ip-form').addEventListener('submit', function(e) {
e.preventDefault();
debouncedQueryIP();
});
对于相同 IP 的查询结果进行缓存,减少 API 调用:
// IP查询缓存
const ipCache = newMap();
const CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存
function getCachedResult(ip) {
const cacheKey = ip || 'current';
const cached = ipCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
return cached.data;
}
returnnull;
}
function setCachedResult(ip, data) {
const cacheKey = ip || 'current';
ipCache.set(cacheKey, {
data: data,
timestamp: Date.now()
});
}
// 在queryIP函数中使用缓存
function queryIP() {
const ip = document.getElementById('ip').value.trim();
const cached = getCachedResult(ip);
if (cached) {
displayIPInfo(cached);
return;
}
// ... 继续API请求
}
地图链接仅在用户点击时加载,减少初始页面加载时间:
// 延迟加载地图iframe
function loadMap(lat, lng) {
const mapContainer = document.getElementById('map-container');
if (!mapContainer.querySelector('iframe')) {
const iframe = document.createElement('iframe');
iframe.src = `https://www.openstreetmap.org/?mlat=${lat}&mlon=${lng}&zoom=15`;
iframe.width = '100%';
iframe.height = '400px';
iframe.frameBorder = '0';
mapContainer.appendChild(iframe);
}
}
// Token加密存储(示例)
function encryptToken(token) {
// 简单的Base64编码(实际应用中应使用更安全的加密方法)
return btoa(token);
}
function decryptToken(encryptedToken) {
try {
return atob(encryptedToken);
} catch (e) {
return null;
}
}
对所有用户输入进行严格验证,防止 XSS 攻击:
// HTML转义函数
function escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
// 在显示数据时使用
html += `<div class="info-value">${escapeHtml(data.ip || '未知')}</div>`;
确保所有 API 请求都通过 HTTPS 进行:
// 检查协议
if (window.location.protocol !== 'https:' && window.location.hostname !== 'localhost') {
console.warn('建议使用HTTPS协议以确保安全');
}
确保 HarmonyOS 版本的文件与通用版本保持同步:
# 同步IP查询页面
cp www/ip.html harmonyos/entry/src/main/resources/rawfile/www/ip.html
cp www/js/ip.js harmonyos/entry/src/main/resources/rawfile/www/js/ip.js
在 module.json5 中确保网络权限已配置:
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET",
"reason": "$string:internet_permission",
"usedScene": {
"abilities": ["EntryAbility"],
"when": "inuse"
}
}
]
}
}
在 config.xml 中配置 Content-Security-Policy:
<preference name="Content-Security-Policy"
value="default-src 'self' data: https://ssl.gstatic.com https://v3.alapi.cn https://www.alapi.cn 'unsafe-eval';
style-src 'self' 'unsafe-inline';
media-src *;
img-src 'self' data: content:;
script-src 'self' 'unsafe-inline' 'unsafe-eval';
connect-src 'self' https://v3.alapi.cn https://www.alapi.cn;
frame-src https://www.alapi.cn;" />
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: https://ssl.gstatic.com https://v3.alapi.cn https://www.alapi.cn 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *; img-src 'self' data: content:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; connect-src 'self' https://v3.alapi.cn https://www.alapi.cn; frame-src https://www.alapi.cn;">
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="initial-scale=1, width=device-width, viewport-fit=cover">
<meta name="color-scheme" content="light dark">
<link rel="stylesheet" href="css/index.css">
<title>IP地址查询</title>
<!-- 样式代码见上文 -->
</head>
<body>
<!-- HTML结构代码见上文 -->
<script src="cordova.js"></script>
<script src="js/ip.js"></script>
</body>
</html>
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// 等待设备就绪
document.addEventListener('deviceready', function() {
console.log('设备就绪,IP查询功能可用');
}, false);
// IP查询缓存
const ipCache = newMap();
const CACHE_DURATION = 5 * 60 * 1000; // 5分钟缓存
// 表单提交处理
document.getElementById('ip-form').addEventListener('submit', function(e) {
e.preventDefault();
queryIP();
});
// 查询IP地址
function queryIP() {
const submitBtn = document.getElementById('submit-btn');
const loading = document.getElementById('loading');
const errorMessage = document.getElementById('error-message');
const resultContainer = document.getElementById('result-container');
// 获取表单数据
const formData = {
token: document.getElementById('token').value.trim(),
ip: document.getElementById('ip').value.trim()
};
// 验证token
if (!formData.token) {
showError('请输入API Token');
return;
}
// 检查缓存
const cacheKey = formData.ip || 'current';
const cached = ipCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < CACHE_DURATION && cached.token === formData.token) {
displayIPInfo(cached.data);
return;
}
// 验证IP格式(如果输入了IP)
if (formData.ip && !isValidIP(formData.ip)) {
showError('IP地址格式不正确,请输入有效的IPv4或IPv6地址');
return;
}
// 显示加载状态
submitBtn.disabled = true;
submitBtn.textContent = '查询中...';
loading.classList.add('show');
errorMessage.classList.remove('show');
resultContainer.classList.remove('show');
// 构建请求参数
const params = new URLSearchParams();
params.append('token', formData.token);
if (formData.ip) {
params.append('ip', formData.ip);
}
// 发送POST请求
fetch('https://v3.alapi.cn/api/ip', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: params.toString()
})
.then(response => {
if (!response.ok) {
thrownewError('网络请求失败: ' + response.status);
}
return response.json();
})
.then(data => {
console.log('API响应:', data);
if (data.success && data.code === 200) {
// 缓存结果
ipCache.set(cacheKey, {
data: data.data,
timestamp: Date.now(),
token: formData.token
});
displayIPInfo(data.data);
} elseif (data.code === 401 || data.message.includes('token')) {
showError('API Token无效或已过期,请重新获取Token');
localStorage.removeItem('ip_api_token');
} else {
showError(data.message || '查询失败,请检查参数');
}
})
.catch(error => {
console.error('请求错误:', error);
showError('请求失败: ' + error.message + '。请检查网络连接或API Token是否正确');
})
.finally(() => {
// 恢复按钮状态
submitBtn.disabled = false;
submitBtn.textContent = '查询';
loading.classList.remove('show');
});
}
// 验证IP地址格式
function isValidIP(ip) {
// IPv4格式验证
const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
// IPv6格式验证(简化版)
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^::1$|^::$/;
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
}
// HTML转义函数
function escapeHtml(text) {
if (!text) return'';
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
returnString(text).replace(/[&<>"']/g, m => map[m]);
}
// 显示错误信息
function showError(message) {
const errorMessage = document.getElementById('error-message');
errorMessage.textContent = message;
errorMessage.classList.add('show');
}
// 显示IP信息
function displayIPInfo(data) {
const resultContainer = document.getElementById('result-container');
const ipInfo = document.getElementById('ip-info');
let html = '';
// IP基本信息
html += '<div class="info-section">';
html += '<div class="info-section-title">IP地址信息</div>';
html += '<div class="info-grid">';
html += `<div class="info-item"><div class="info-label">IP地址</div><div class="info-value">${escapeHtml(data.ip || '未知')}</div></div>`;
html += `<div class="info-item"><div class="info-label">IP范围</div><div class="info-value">${escapeHtml(data.beginip || '未知')} - ${escapeHtml(data.endip || '未知')}</div></div>`;
html += `<div class="info-item"><div class="info-label">ISP运营商</div><div class="info-value">${escapeHtml(data.isp || '未知')}</div></div>`;
html += `<div class="info-item"><div class="info-label">城市编码</div><div class="info-value">${escapeHtml(data.adcode || '未知')}</div></div>`;
html += '</div>';
html += '</div>';
// 地理位置信息
html += '<div class="info-section">';
html += '<div class="info-section-title">地理位置</div>';
html += '<div class="info-grid">';
html += `<div class="info-item"><div class="info-label">大洲</div><div class="info-value">${escapeHtml(data.continent || '未知')}</div></div>`;
html += `<div class="info-item"><div class="info-label">国家</div><div class="info-value">${escapeHtml(data.country || '未知')} ${data.country_en ? '(' + escapeHtml(data.country_en) + ')' : ''}</div></div>`;
html += `<div class="info-item"><div class="info-label">国家代码</div><div class="info-value">${escapeHtml(data.country_cc || '未知')}</div></div>`;
html += `<div class="info-item"><div class="info-label">省份</div><div class="info-value">${escapeHtml(data.province || '未知')}</div></div>`;
html += `<div class="info-item"><div class="info-label">城市</div><div class="info-value">${escapeHtml(data.city || '未知')}</div></div>`;
html += `<div class="info-item"><div class="info-label">区县</div><div class="info-value">${escapeHtml(data.district || '未知')}</div></div>`;
html += `<div class="info-item"><div class="info-label">详细地址</div><div class="info-value">${escapeHtml(data.pos || '未知')}</div></div>`;
html += '</div>';
html += '</div>';
// 坐标信息
if (data.lng && data.lat) {
html += '<div class="info-section">';
html += '<div class="info-section-title">坐标信息</div>';
html += '<div class="info-grid">';
html += `<div class="info-item"><div class="info-label">经度</div><div class="info-value">${escapeHtml(data.lng)}</div></div>`;
html += `<div class="info-item"><div class="info-label">纬度</div><div class="info-value">${escapeHtml(data.lat)}</div></div>`;
html += '</div>';
html += `<a href="https://www.openstreetmap.org/?mlat=${data.lat}&mlon=${data.lng}&zoom=15" target="_blank" class="map-link">在地图上查看</a>`;
html += '</div>';
}
ipInfo.innerHTML = html;
// 显示结果容器
resultContainer.classList.add('show');
// 滚动到结果区域
resultContainer.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
// 页面加载完成后的初始化
document.addEventListener('DOMContentLoaded', function() {
// 尝试从本地存储加载token
const savedToken = localStorage.getItem('ip_api_token');
if (savedToken) {
document.getElementById('token').value = savedToken;
}
// 保存token到本地存储
document.getElementById('token').addEventListener('blur', function() {
const token = this.value.trim();
if (token) {
localStorage.setItem('ip_api_token', token);
}
});
console.log('IP查询页面加载完成');
});
本文详细介绍了如何在 Cordova 应用中实现 IP 地址查询功能,包括:
通过本文的指导,开发者可以快速集成 IP 地址查询功能到自己的 Cordova 应用中,并提供良好的用户体验和稳定的性能表现。
作者: 坚果派(NutPi)技术团队 版本: 1.0
参考资料
[1]
AlAPI IP查询接口文档: https://www.alapi.cn/
[2]
Apache Cordova官方文档: https://cordova.apache.org/docs/en/latest/
[3]
MDN Web API - Fetch: https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API
[4]
IPv4/IPv6地址格式规范: https://tools.ietf.org/html/rfc791
[5]
HarmonyOS应用开发指南: https://developer.harmonyos.com/