2024-11-09 23:14:51 +08:00

327 lines
11 KiB
JavaScript
Raw 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.

const { exec } = require('child_process')
const os = require('os')
const net = require('net')
const iconv = require('iconv-lite')
const axios = require('axios')
const request = axios.create({ timeout: 3000 })
// 为空时请求本地IP
const getNetIPInfo = async (searchIp = '') => {
console.log('searchIp:', searchIp)
if (isLocalIP(searchIp)) {
return {
ip: searchIp,
country: '本地',
city: '局域网',
error: null
}
}
try {
let date = Date.now()
let ipUrls = [
// 45次/分钟&支持中文(无限制)
`http://ip-api.com/json/${ searchIp }?lang=zh-CN`,
// 10000次/月&支持中文(依赖IP计算调用次数)
`http://ipwho.is/${ searchIp }?lang=zh-CN`,
// 1500次/天(依赖密钥, 超出自行注册)
`https://api.ipdata.co/${ searchIp }?api-key=c6d4d04d5f11f2cd0839ee03c47c58621d74e361c945b5c1b4f668f3`,
// 50000/月(依赖密钥, 超出自行注册)
`https://ipinfo.io/${ searchIp }/json?token=41c48b54f6d78f`,
// 1000次/天(依赖密钥, 超出自行注册)
`https://api.ipgeolocation.io/ipgeo?apiKey=105fc2c7e8864ec08b98e1ad4e8cbc6d&ip=${ searchIp }`,
// 1000次/天(依赖IP计算调用次数)
`https://ipapi.co${ searchIp ? `/${ searchIp }` : '' }/json`,
// 国内IP138提供(无限制)
`https://sp1.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?query=${ searchIp }&resource_id=5809`
]
let result = await Promise.allSettled(ipUrls.map(url => request.get(url)))
let [ipApi, ipwho, ipdata, ipinfo, ipgeolocation, ipApi01, ip138] = result
let searchResult = []
if (ipApi.status === 'fulfilled') {
let { query: ip, country, regionName, city } = ipApi.value?.data || {}
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
}
if (ipwho.status === 'fulfilled') {
let { ip, country, region: regionName, city } = ipwho.value?.data || {}
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
}
if (ipdata.status === 'fulfilled') {
let { ip, country_name: country, region: regionName, city } = ipdata.value?.data || {}
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
}
if (ipinfo.status === 'fulfilled') {
let { ip, country, region: regionName, city } = ipinfo.value?.data || {}
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
}
if (ipgeolocation.status === 'fulfilled') {
let { ip, country_name: country, state_prov: regionName, city } = ipgeolocation.value?.data || {}
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
}
if (ipApi01.status === 'fulfilled') {
let { ip, country_name: country, region: regionName, city } = ipApi01.value?.data || {}
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
}
if (ip138.status === 'fulfilled') {
let [res] = ip138.value?.data?.data || []
let { origip: ip, location: country, city = '', regionName = '' } = res || {}
searchResult.push({ ip, country, city: `${ regionName } ${ city }`, date })
}
console.log(searchResult)
let validInfo = searchResult.find(item => Boolean(item.country))
consola.info('查询IP信息', validInfo)
return validInfo || { ip: '获取IP信息API出错,请排查或更新API', country: '未知', city: '未知', date }
} catch (error) {
// consola.error('getIpInfo Error: ', error)
return {
ip: '未知',
country: '未知',
city: '未知',
error
}
}
}
const getLocalNetIP = async () => {
try {
let ipUrls = [
'http://whois.pconline.com.cn/ipJson.jsp?json=true',
'https://www.ip.cn/api/index?ip=&type=0',
'https://freeipapi.com/api/json'
]
let result = await Promise.allSettled(ipUrls.map(url => axios.get(url)))
let [pconline, ipCN, freeipapi] = result
if (pconline.status === 'fulfilled') {
let ip = pconline.value?.data?.ip
if (ip) return ip
}
if (ipCN.status === 'fulfilled') {
let ip = ipCN.value?.data?.ip
consola.log('ipCN:', ip)
if (ip) return ip
}
if (freeipapi.status === 'fulfilled') {
let ip = pconline.value?.data?.ipAddress
if (ip) return ip
}
return null
} catch (error) {
console.error('getIpInfo Error: ', error?.message || error)
return null
}
}
function isLocalIP(ip) {
// Check if IPv4 or IPv6 address
const isIPv4 = net.isIPv4(ip)
const isIPv6 = net.isIPv6(ip)
// Local IPv4 ranges
const localIPv4Ranges = [
{ start: '10.0.0.0', end: '10.255.255.255' },
{ start: '172.16.0.0', end: '172.31.255.255' },
{ start: '192.168.0.0', end: '192.168.255.255' },
{ start: '127.0.0.0', end: '127.255.255.255' } // Loopback
]
// Local IPv6 ranges
const localIPv6Ranges = [
'::1', // Loopback
'fc00::', // Unique local address
'fd00::' // Unique local address
]
function isInRange(ip, start, end) {
const ipNum = ipToNumber(ip)
return ipNum >= ipToNumber(start) && ipNum <= ipToNumber(end)
}
function ipToNumber(ip) {
return ip.split('.').reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0)
}
if (isIPv4) {
for (const range of localIPv4Ranges) {
if (isInRange(ip, range.start, range.end)) {
return true
}
}
}
if (isIPv6) {
if (localIPv6Ranges.includes(ip)) {
return true
}
// Handle IPv4-mapped IPv6 addresses (e.g., ::ffff:192.168.1.1)
if (ip.startsWith('::ffff:')) {
const ipv4Part = ip.split('::ffff:')[1]
if (ipv4Part && net.isIPv4(ipv4Part)) {
for (const range of localIPv4Ranges) {
if (isInRange(ipv4Part, range.start, range.end)) {
return true
}
}
}
}
}
return false
}
const throwError = ({ status = 500, msg = 'defalut error' } = {}) => {
const err = new Error(msg)
err.status = status // 主动抛错
throw err
}
const isIP = (ip = '') => {
const isIPv4 = /^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$/
const isIPv6 = /^([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|:((:[\dafAF]1,4)1,6|:)|:((:[\dafAF]1,4)1,6|:)|^[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,5}|:)|([\dafAF]1,4:)2((:[\dafAF]1,4)1,4|:)|([\dafAF]1,4:)2((:[\dafAF]1,4)1,4|:)|^([\da-fA-F]{1,4}:){3}((:[\da-fA-F]{1,4}){1,3}|:)|([\dafAF]1,4:)4((:[\dafAF]1,4)1,2|:)|([\dafAF]1,4:)4((:[\dafAF]1,4)1,2|:)|^([\da-fA-F]{1,4}:){5}:([\da-fA-F]{1,4})?|([\dafAF]1,4:)6:|([\dafAF]1,4:)6:/
return isIPv4.test(ip) || isIPv6.test(ip)
}
const randomStr = (len) => {
len = len || 16
let str = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678',
a = str.length,
res = ''
for (let i = 0; i < len; i++) res += str.charAt(Math.floor(Math.random() * a))
return res
}
// 获取UTC-x时间
const getUTCDate = (num = 8) => {
let date = new Date()
let now_utc = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(),
date.getUTCDate(), date.getUTCHours() + num,
date.getUTCMinutes(), date.getUTCSeconds())
return new Date(now_utc)
}
const formatTimestamp = (timestamp = Date.now(), format = 'time') => {
if (typeof (timestamp) !== 'number') return '--'
let date = new Date(timestamp)
let padZero = (num) => String(num).padStart(2, '0')
let year = date.getFullYear()
let mounth = padZero(date.getMonth() + 1)
let day = padZero(date.getDate())
let hours = padZero(date.getHours())
let minute = padZero(date.getMinutes())
let second = padZero(date.getSeconds())
let weekday = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
let week = weekday[date.getDay()]
switch (format) {
case 'date':
return `${ year }-${ mounth }-${ day }`
case 'week':
return `${ year }-${ mounth }-${ day } ${ week }`
case 'hour':
return `${ year }-${ mounth }-${ day } ${ hours }`
case 'time':
return `${ year }-${ mounth }-${ day } ${ hours }:${ minute }:${ second }`
default:
return `${ year }-${ mounth }-${ day } ${ hours }:${ minute }:${ second }`
}
}
function resolvePath(dir, path) {
return path.resolve(dir, path)
}
let shellThrottle = (fn, delay = 1000) => {
let timer = null
let args = null
function throttled() {
args = arguments
if (!timer) {
timer = setTimeout(() => {
fn(...args)
timer = null
}, delay)
}
}
function delayMs() {
return new Promise(resolve => setTimeout(resolve, delay))
}
throttled.last = async () => {
await delayMs()
fn(...args)
}
return throttled
}
const isProd = () => {
const EXEC_ENV = process.env.EXEC_ENV || 'production'
return EXEC_ENV === 'production'
}
let allowedIPs = process.env.ALLOWED_IPS ? process.env.ALLOWED_IPS.split(',') : ''
if (allowedIPs) consola.warn('allowedIPs:', allowedIPs)
const isAllowedIp = (requestIP) => {
if (allowedIPs.length === 0) return true
let flag = allowedIPs.some(item => requestIP.includes(item))
if (!flag) consola.warn('requestIP:', requestIP, '不在允许的IP列表中')
return flag
}
const ping = (ip, timeout = 5000) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: false, msg: 'ping timeout!' })
}, timeout)
let isWin = os.platform() === 'win32'
const command = isWin ? `ping -n 1 ${ ip }` : `ping -c 1 ${ ip }`
const options = isWin ? { encoding: 'buffer' } : {}
exec(command, options, (error, stdout) => {
if (error) {
resolve({ success: false, msg: 'ping error!' })
return
}
let output
if (isWin) {
output = iconv.decode(stdout, 'cp936')
} else {
output = stdout.toString()
}
// console.log('output:', output)
let match
if (isWin) {
match = output.match(/平均 = (\d+)ms/)
if (!match) {
match = output.match(/Average = (\d+)ms/)
}
} else {
match = output.match(/rtt min\/avg\/max\/mdev = [\d.]+\/([\d.]+)\/[\d.]+\/[\d.]+/)
}
if (match) {
resolve({ success: true, time: parseFloat(match[1]) })
} else {
resolve({ success: false, msg: 'Could not find time in ping output!' })
}
})
})
}
module.exports = {
getNetIPInfo,
getLocalNetIP,
throwError,
isIP,
randomStr,
getUTCDate,
formatTimestamp,
resolvePath,
shellThrottle,
isProd,
isAllowedIp,
ping
}