Files
ip-service/server.js
Coding Expert 8e25bf51b8 feat: 初始版本 - 跨平台 IP 地址查询服务
- 后端服务 (Express + ES5)
  - 支持获取真实客户端 IP
  - 支持代理服务器 (X-Forwarded-For)
  - IP 地理位置查询
  - 内存缓存优化 (10 分钟 TTL)
  - 健康检查接口

- 前端客户端 (ES5 兼容)
  - IPService 类库
  - 支持回调函数
  - 示例页面

- 跨平台部署
  - Windows 启动脚本 (start.bat)
  - Linux 启动脚本 (start.sh)
  - PM2 生产环境支持

- 文档
  - README.md 完整说明
  - .gitignore 配置
2026-03-23 09:39:32 +08:00

182 lines
4.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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);
});
});