- 后端服务 (Express + ES5) - 支持获取真实客户端 IP - 支持代理服务器 (X-Forwarded-For) - IP 地理位置查询 - 内存缓存优化 (10 分钟 TTL) - 健康检查接口 - 前端客户端 (ES5 兼容) - IPService 类库 - 支持回调函数 - 示例页面 - 跨平台部署 - Windows 启动脚本 (start.bat) - Linux 启动脚本 (start.sh) - PM2 生产环境支持 - 文档 - README.md 完整说明 - .gitignore 配置
182 lines
4.0 KiB
JavaScript
182 lines
4.0 KiB
JavaScript
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);
|
||
});
|
||
});
|