commit 8e25bf51b8244a1622177cf8faee50085774c5fd Author: Coding Expert Date: Mon Mar 23 09:39:32 2026 +0800 feat: 初始版本 - 跨平台 IP 地址查询服务 - 后端服务 (Express + ES5) - 支持获取真实客户端 IP - 支持代理服务器 (X-Forwarded-For) - IP 地理位置查询 - 内存缓存优化 (10 分钟 TTL) - 健康检查接口 - 前端客户端 (ES5 兼容) - IPService 类库 - 支持回调函数 - 示例页面 - 跨平台部署 - Windows 启动脚本 (start.bat) - Linux 启动脚本 (start.sh) - PM2 生产环境支持 - 文档 - README.md 完整说明 - .gitignore 配置 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ae4241 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..f49c3c4 --- /dev/null +++ b/README.md @@ -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 + + +``` + +### 自定义 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 兼容 diff --git a/index.html b/index.html new file mode 100644 index 0000000..2e01140 --- /dev/null +++ b/index.html @@ -0,0 +1,153 @@ + + + + + + IP 地址查询 + + + +

🌐 IP 地址查询

+ +
+ + +
+

点击按钮查询 IP 地址和地理位置信息

+
+
+ + + + + diff --git a/ip-service.js b/ip-service.js new file mode 100644 index 0000000..e85809f --- /dev/null +++ b/ip-service.js @@ -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; +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5a07faf --- /dev/null +++ b/package.json @@ -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": "" + } +} diff --git a/server.js b/server.js new file mode 100644 index 0000000..7560df0 --- /dev/null +++ b/server.js @@ -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); + }); +}); diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..287ac7c --- /dev/null +++ b/start.bat @@ -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 diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..ba7ef50 --- /dev/null +++ b/start.sh @@ -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