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:
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user