feat: 初始版本 - 跨平台 IP 地址查询服务
- 后端服务 (Express + ES5) - 支持获取真实客户端 IP - 支持代理服务器 (X-Forwarded-For) - IP 地理位置查询 - 内存缓存优化 (10 分钟 TTL) - 健康检查接口 - 前端客户端 (ES5 兼容) - IPService 类库 - 支持回调函数 - 示例页面 - 跨平台部署 - Windows 启动脚本 (start.bat) - Linux 启动脚本 (start.sh) - PM2 生产环境支持 - 文档 - README.md 完整说明 - .gitignore 配置
This commit is contained in:
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 依赖
|
||||||
|
node_modules/
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# 日志
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# 系统文件
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
desktop.ini
|
||||||
|
|
||||||
|
# 编辑器
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# 环境变量
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# 临时文件
|
||||||
|
tmp/
|
||||||
|
temp/
|
||||||
|
*.tmp
|
||||||
269
README.md
Normal file
269
README.md
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
# IP 地址查询服务
|
||||||
|
|
||||||
|
跨平台 IP 地址查询服务,支持 Windows/Linux 部署,前端采用 ES5 标准 JavaScript。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- ✅ 获取访问者真实 IP 地址
|
||||||
|
- ✅ 支持代理服务器(X-Forwarded-For)
|
||||||
|
- ✅ IP 地理位置查询(国家/省份/城市/运营商/时区)
|
||||||
|
- ✅ 内存缓存优化(10 分钟 TTL)
|
||||||
|
- ✅ 跨平台兼容(Windows/Linux)
|
||||||
|
- ✅ 前端 ES5 兼容(支持 IE9+)
|
||||||
|
- ✅ CORS 跨域支持
|
||||||
|
|
||||||
|
## 快速开始
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 开发环境
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# 生产环境
|
||||||
|
npm start
|
||||||
|
|
||||||
|
# 或直接运行
|
||||||
|
node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
服务默认运行在 `http://localhost:3000`
|
||||||
|
|
||||||
|
## API 接口
|
||||||
|
|
||||||
|
### 1. 获取 IP 地址
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/get-ip
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ip": "123.45.67.89",
|
||||||
|
"timestamp": 1711162800000,
|
||||||
|
"server": "linux"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 获取 IP + 地理位置
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/get-ip-info
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ip": "123.45.67.89",
|
||||||
|
"country": "中国",
|
||||||
|
"region": "北京市",
|
||||||
|
"city": "北京市",
|
||||||
|
"isp": "中国电信",
|
||||||
|
"timezone": "Asia/Shanghai",
|
||||||
|
"query_time": 1711162800000,
|
||||||
|
"fromCache": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 健康检查
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /health
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"uptime": 1234.56,
|
||||||
|
"platform": "linux",
|
||||||
|
"memory": {
|
||||||
|
"rss": 52428800,
|
||||||
|
"heapTotal": 8388608,
|
||||||
|
"heapUsed": 6291456,
|
||||||
|
"external": 1024000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 前端使用
|
||||||
|
|
||||||
|
### 浏览器中使用
|
||||||
|
|
||||||
|
```html
|
||||||
|
<script src="ip-service.js"></script>
|
||||||
|
<script>
|
||||||
|
var ipService = new IPService();
|
||||||
|
|
||||||
|
// 获取 IP
|
||||||
|
ipService.getIP(function(error, data) {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('IP:', data.ip);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取完整信息
|
||||||
|
ipService.getIPInfo(function(error, data) {
|
||||||
|
if (error) {
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log('位置:', data.city, data.region, data.country);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义 API 地址
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 如果后端部署在不同域名
|
||||||
|
var ipService = new IPService('https://api.example.com');
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
|
||||||
|
```batch
|
||||||
|
# 双击运行
|
||||||
|
start.bat
|
||||||
|
|
||||||
|
# 或命令行
|
||||||
|
npm install
|
||||||
|
node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 赋予执行权限
|
||||||
|
chmod +x start.sh
|
||||||
|
|
||||||
|
# 运行
|
||||||
|
./start.sh
|
||||||
|
|
||||||
|
# 或
|
||||||
|
npm install
|
||||||
|
node server.js
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生产环境(PM2)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 安装 PM2
|
||||||
|
npm install -g pm2
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
pm2 start server.js --name ip-service
|
||||||
|
|
||||||
|
# 开机自启
|
||||||
|
pm2 startup
|
||||||
|
pm2 save
|
||||||
|
|
||||||
|
# 查看状态
|
||||||
|
pm2 status
|
||||||
|
|
||||||
|
# 查看日志
|
||||||
|
pm2 logs ip-service
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nginx 反向代理
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
root /var/www/ip-service;
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 配置
|
||||||
|
|
||||||
|
### 环境变量
|
||||||
|
|
||||||
|
| 变量名 | 说明 | 默认值 |
|
||||||
|
|--------|------|--------|
|
||||||
|
| PORT | 服务端口 | 3000 |
|
||||||
|
|
||||||
|
### 缓存配置
|
||||||
|
|
||||||
|
在 `server.js` 中修改:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var CACHE_TTL = 10 * 60 * 1000; // 缓存时间(毫秒),默认 10 分钟
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
ip-service/
|
||||||
|
├── server.js # 后端服务
|
||||||
|
├── ip-service.js # 前端客户端库
|
||||||
|
├── index.html # 示例页面
|
||||||
|
├── package.json # 项目配置
|
||||||
|
├── .gitignore # Git 忽略配置
|
||||||
|
├── start.bat # Windows 启动脚本
|
||||||
|
├── start.sh # Linux 启动脚本
|
||||||
|
└── README.md # 说明文档
|
||||||
|
```
|
||||||
|
|
||||||
|
## 兼容性
|
||||||
|
|
||||||
|
| 组件 | 要求 |
|
||||||
|
|------|------|
|
||||||
|
| Node.js | v14.0.0+ |
|
||||||
|
| 操作系统 | Windows 7+, Linux (所有主流发行版) |
|
||||||
|
| 浏览器 | IE9+, Chrome, Firefox, Safari, Edge |
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
|
||||||
|
- **后端**: Node.js + Express
|
||||||
|
- **前端**: 原生 JavaScript (ES5)
|
||||||
|
- **IP 数据**: ip-api.com (免费,无需 API Key)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **ip-api.com 限制**: 免费版限速 45 次/分钟,商用需购买授权
|
||||||
|
2. **HTTPS**: 生产环境建议启用 HTTPS
|
||||||
|
3. **隐私合规**: 收集 IP 地址可能涉及隐私法规(如 GDPR),请确保合规
|
||||||
|
|
||||||
|
## 替代 IP 数据源
|
||||||
|
|
||||||
|
如需更高精度或商用,可替换 `server.js` 中的查询接口:
|
||||||
|
|
||||||
|
- [ipapi.com](https://ipapi.com/)
|
||||||
|
- [IPinfo](https://ipinfo.io/)
|
||||||
|
- [MaxMind GeoIP2](https://www.maxmind.com/)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|
||||||
|
## 更新日志
|
||||||
|
|
||||||
|
### v1.0.0 (2026-03-23)
|
||||||
|
- 初始版本发布
|
||||||
|
- 支持 IP 地址查询
|
||||||
|
- 支持地理位置查询
|
||||||
|
- 跨平台部署支持
|
||||||
|
- 前端 ES5 兼容
|
||||||
153
index.html
Normal file
153
index.html
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>IP 地址查询</title>
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial, "Microsoft YaHei", sans-serif;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
color: #333;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
background: white;
|
||||||
|
padding: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.ip-info {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.ip-info p {
|
||||||
|
margin: 10px 0;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.ip-info strong {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
background: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #0056b3;
|
||||||
|
}
|
||||||
|
button:active {
|
||||||
|
background: #004085;
|
||||||
|
}
|
||||||
|
.loading {
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.error {
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
.success {
|
||||||
|
color: #28a745;
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
.badge-cache {
|
||||||
|
background: #ffc107;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
.badge-live {
|
||||||
|
background: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🌐 IP 地址查询</h1>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<button id="query-btn">查询我的 IP</button>
|
||||||
|
|
||||||
|
<div id="ip-info-container">
|
||||||
|
<p class="loading">点击按钮查询 IP 地址和地理位置信息</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="ip-service.js"></script>
|
||||||
|
<script>
|
||||||
|
// 初始化服务
|
||||||
|
var ipService = new IPService();
|
||||||
|
|
||||||
|
// 显示 IP 信息
|
||||||
|
function displayIPInfo() {
|
||||||
|
var container = document.getElementById('ip-info-container');
|
||||||
|
var btn = document.getElementById('query-btn');
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
console.error('未找到容器元素 #ip-info-container');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = '<p class="loading">加载中...</p>';
|
||||||
|
btn.disabled = true;
|
||||||
|
|
||||||
|
ipService.getIPInfo(function(error, data) {
|
||||||
|
btn.disabled = false;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
container.innerHTML = '<p class="error">加载失败:' + error.message + '</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cacheBadge = data.fromCache
|
||||||
|
? '<span class="badge badge-cache">缓存</span>'
|
||||||
|
: '<span class="badge badge-live">实时</span>';
|
||||||
|
|
||||||
|
var html = [
|
||||||
|
'<div class="ip-info">',
|
||||||
|
'<p><strong>IP 地址:</strong> ' + data.ip + ' ' + cacheBadge + '</p>',
|
||||||
|
'<p><strong>国家:</strong> ' + (data.country || '未知') + '</p>',
|
||||||
|
'<p><strong>省份:</strong> ' + (data.region || '未知') + '</p>',
|
||||||
|
'<p><strong>城市:</strong> ' + (data.city || '未知') + '</p>',
|
||||||
|
'<p><strong>运营商:</strong> ' + (data.isp || '未知') + '</p>',
|
||||||
|
'<p><strong>时区:</strong> ' + (data.timezone || '未知') + '</p>',
|
||||||
|
'<p style="margin-top:15px;font-size:12px;color:#999;">查询时间:' + new Date(data.query_time).toLocaleString('zh-CN') + '</p>',
|
||||||
|
'</div>'
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定按钮事件
|
||||||
|
document.getElementById('query-btn').addEventListener('click', displayIPInfo);
|
||||||
|
|
||||||
|
// 页面加载完成后自动查询
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', displayIPInfo);
|
||||||
|
} else {
|
||||||
|
displayIPInfo();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
86
ip-service.js
Normal file
86
ip-service.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* IP 服务客户端 (ES5 兼容)
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
var IPService = (function() {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
function IPService(baseURL) {
|
||||||
|
this.baseURL = baseURL || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 IP 地址
|
||||||
|
* @param {Function} callback - 回调函数 callback(error, data)
|
||||||
|
*/
|
||||||
|
IPService.prototype.getIP = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._request(this.baseURL + '/api/get-ip', function(err, data) {
|
||||||
|
if (callback) {
|
||||||
|
callback(err, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 IP + 地理位置信息
|
||||||
|
* @param {Function} callback - 回调函数 callback(error, data)
|
||||||
|
*/
|
||||||
|
IPService.prototype.getIPInfo = function(callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this._request(this.baseURL + '/api/get-ip-info', function(err, data) {
|
||||||
|
if (callback) {
|
||||||
|
callback(err, data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内部请求方法
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
IPService.prototype._request = function(url, callback) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.open('GET', url, true);
|
||||||
|
xhr.setRequestHeader('Accept', 'application/json');
|
||||||
|
|
||||||
|
xhr.onload = function() {
|
||||||
|
if (xhr.status >= 200 && xhr.status < 300) {
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(xhr.responseText);
|
||||||
|
callback(null, data);
|
||||||
|
} catch (e) {
|
||||||
|
callback(new Error('JSON parse failed'), null);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback(new Error('HTTP ' + xhr.status), null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.onerror = function() {
|
||||||
|
callback(new Error('Network error'), null);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.ontimeout = function() {
|
||||||
|
callback(new Error('Request timeout'), null);
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.timeout = 10000; // 10 秒超时
|
||||||
|
xhr.send();
|
||||||
|
};
|
||||||
|
|
||||||
|
return IPService;
|
||||||
|
})();
|
||||||
|
|
||||||
|
// 导出到全局作用域
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.IPService = IPService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出为 CommonJS 模块
|
||||||
|
if (typeof module !== 'undefined' && module.exports) {
|
||||||
|
module.exports = IPService;
|
||||||
|
}
|
||||||
31
package.json
Normal file
31
package.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "ip-service",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "跨平台 IP 地址查询服务 - Cross-platform IP Address Lookup Service",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js",
|
||||||
|
"dev": "node server.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ip",
|
||||||
|
"ip-address",
|
||||||
|
"geo-location",
|
||||||
|
"express",
|
||||||
|
"cross-platform"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"cors": "^2.8.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
181
server.js
Normal file
181
server.js
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
var express = require('express');
|
||||||
|
var cors = require('cors');
|
||||||
|
var http = require('http');
|
||||||
|
var os = require('os');
|
||||||
|
|
||||||
|
var app = express();
|
||||||
|
var PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
// 启用 CORS
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取真实客户端 IP(跨平台兼容)
|
||||||
|
*/
|
||||||
|
function getClientIP(req) {
|
||||||
|
var headers = req.headers;
|
||||||
|
|
||||||
|
// 优先级:X-Forwarded-For > X-Real-IP > 直连 IP
|
||||||
|
var forwarded = headers['x-forwarded-for'];
|
||||||
|
if (forwarded) {
|
||||||
|
var ips = forwarded.split(',');
|
||||||
|
return ips[0].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var realIP = headers['x-real-ip'];
|
||||||
|
if (realIP) {
|
||||||
|
return realIP.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
var remoteAddr = req.socket.remoteAddress ||
|
||||||
|
req.connection.remoteAddress ||
|
||||||
|
req.ip;
|
||||||
|
|
||||||
|
// IPv6 转 IPv4(兼容 Windows/Linux)
|
||||||
|
if (remoteAddr && remoteAddr.indexOf('::ffff:') === 0) {
|
||||||
|
return remoteAddr.substring(7);
|
||||||
|
}
|
||||||
|
|
||||||
|
return remoteAddr || 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 只返回 IP(最高效)
|
||||||
|
*/
|
||||||
|
app.get('/api/get-ip', function(req, res) {
|
||||||
|
var ip = getClientIP(req);
|
||||||
|
res.json({
|
||||||
|
ip: ip,
|
||||||
|
timestamp: new Date().getTime(),
|
||||||
|
server: os.platform()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回 IP + 地理位置(带缓存)
|
||||||
|
*/
|
||||||
|
var ipCache = {};
|
||||||
|
var CACHE_TTL = 10 * 60 * 1000; // 10 分钟
|
||||||
|
|
||||||
|
app.get('/api/get-ip-info', function(req, res) {
|
||||||
|
var ip = getClientIP(req);
|
||||||
|
var now = new Date().getTime();
|
||||||
|
|
||||||
|
// 检查缓存
|
||||||
|
var cached = ipCache[ip];
|
||||||
|
if (cached && (now - cached.timestamp) < CACHE_TTL) {
|
||||||
|
var result = JSON.parse(JSON.stringify(cached.data)); // 深拷贝
|
||||||
|
result.fromCache = true;
|
||||||
|
return res.json(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用原生 http 模块请求(无需额外依赖)
|
||||||
|
var options = {
|
||||||
|
hostname: 'ip-api.com',
|
||||||
|
port: 80,
|
||||||
|
path: '/json/' + encodeURIComponent(ip) + '?lang=zh-CN',
|
||||||
|
method: 'GET',
|
||||||
|
timeout: 5000
|
||||||
|
};
|
||||||
|
|
||||||
|
var request = http.request(options, function(response) {
|
||||||
|
var chunks = [];
|
||||||
|
|
||||||
|
response.on('data', function(chunk) {
|
||||||
|
chunks.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
response.on('end', function() {
|
||||||
|
var body = Buffer.concat(chunks).toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
var data = JSON.parse(body);
|
||||||
|
|
||||||
|
var result = {
|
||||||
|
ip: ip,
|
||||||
|
country: data.country || '',
|
||||||
|
region: data.regionName || '',
|
||||||
|
city: data.city || '',
|
||||||
|
isp: data.isp || '',
|
||||||
|
timezone: data.timezone || '',
|
||||||
|
query_time: now,
|
||||||
|
fromCache: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// 写入缓存
|
||||||
|
ipCache[ip] = {
|
||||||
|
data: result,
|
||||||
|
timestamp: now
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清理过期缓存(防止内存泄漏)
|
||||||
|
cleanupCache();
|
||||||
|
|
||||||
|
res.json(result);
|
||||||
|
} catch (e) {
|
||||||
|
res.json({ ip: ip, error: 'Parse failed', fromCache: false });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('error', function(e) {
|
||||||
|
res.json({ ip: ip, error: e.message, fromCache: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on('timeout', function() {
|
||||||
|
request.destroy();
|
||||||
|
res.json({ ip: ip, error: 'Request timeout', fromCache: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清理过期缓存
|
||||||
|
*/
|
||||||
|
function cleanupCache() {
|
||||||
|
var now = new Date().getTime();
|
||||||
|
var keys = Object.keys(ipCache);
|
||||||
|
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
var key = keys[i];
|
||||||
|
if ((now - ipCache[key].timestamp) > CACHE_TTL * 2) {
|
||||||
|
delete ipCache[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 健康检查
|
||||||
|
*/
|
||||||
|
app.get('/health', function(req, res) {
|
||||||
|
res.json({
|
||||||
|
status: 'ok',
|
||||||
|
uptime: process.uptime(),
|
||||||
|
platform: os.platform(),
|
||||||
|
memory: process.memoryUsage()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
var server = app.listen(PORT, function() {
|
||||||
|
console.log('🚀 Server running on port ' + PORT);
|
||||||
|
console.log(' Platform: ' + os.platform());
|
||||||
|
console.log(' Test: http://localhost:' + PORT + '/api/get-ip');
|
||||||
|
});
|
||||||
|
|
||||||
|
// 优雅关闭(兼容 Windows/Linux)
|
||||||
|
process.on('SIGTERM', function() {
|
||||||
|
console.log('SIGTERM received, shutting down...');
|
||||||
|
server.close(function() {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('SIGINT', function() {
|
||||||
|
console.log('SIGINT received, shutting down...');
|
||||||
|
server.close(function() {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
44
start.bat
Normal file
44
start.bat
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
@echo off
|
||||||
|
chcp 65001 >nul
|
||||||
|
echo ========================================
|
||||||
|
echo IP 地址查询服务 - 启动脚本
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: 检查 Node.js
|
||||||
|
where node >nul 2>nul
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [错误] 未检测到 Node.js
|
||||||
|
echo.
|
||||||
|
echo 请先安装 Node.js: https://nodejs.org/
|
||||||
|
echo.
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo [✓] Node.js 版本:
|
||||||
|
node --version
|
||||||
|
echo.
|
||||||
|
|
||||||
|
:: 检查依赖
|
||||||
|
if not exist "node_modules" (
|
||||||
|
echo [提示] 首次运行,正在安装依赖...
|
||||||
|
call npm install
|
||||||
|
if %errorlevel% neq 0 (
|
||||||
|
echo [错误] 依赖安装失败
|
||||||
|
pause
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
echo.
|
||||||
|
)
|
||||||
|
|
||||||
|
:: 启动服务
|
||||||
|
echo [✓] 启动服务...
|
||||||
|
echo [提示] 按 Ctrl+C 停止服务
|
||||||
|
echo.
|
||||||
|
echo ========================================
|
||||||
|
echo.
|
||||||
|
|
||||||
|
node server.js
|
||||||
|
|
||||||
|
pause
|
||||||
39
start.sh
Normal file
39
start.sh
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo " IP 地址查询服务 - 启动脚本"
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 检查 Node.js
|
||||||
|
if ! command -v node &> /dev/null; then
|
||||||
|
echo "[错误] 未检测到 Node.js"
|
||||||
|
echo ""
|
||||||
|
echo "请先安装 Node.js: https://nodejs.org/"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "[✓] Node.js 版本:"
|
||||||
|
node --version
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 检查依赖
|
||||||
|
if [ ! -d "node_modules" ]; then
|
||||||
|
echo "[提示] 首次运行,正在安装依赖..."
|
||||||
|
npm install
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "[错误] 依赖安装失败"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
echo "[✓] 启动服务..."
|
||||||
|
echo "[提示] 按 Ctrl+C 停止服务"
|
||||||
|
echo ""
|
||||||
|
echo "========================================"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
node server.js
|
||||||
Reference in New Issue
Block a user