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:
Coding Expert
2026-03-23 09:39:32 +08:00
commit 8e25bf51b8
8 changed files with 834 additions and 0 deletions

181
server.js Normal file
View 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);
});
});