1)feature: IP测速功能,针对请求的端口进行测速,不再固定 `443` 端口进行测速;

2)bugfix: IP测速功能,域名长时间未访问时,再访问后不会重启自动测速功能的问题修复;
3)optimize: 优化DNS获取的IP再经过IpTester校验的逻辑。
develop
王良 2025-03-14 17:12:47 +08:00
parent ab74a44e83
commit ab76d03f0b
14 changed files with 352 additions and 130 deletions

View File

@ -455,9 +455,9 @@ export default {
</a>
<a-tag
v-for="(element, index) of item.backupList" :key="index" style="margin:2px;"
:title="element.dns" :color="element.time ? (element.time > config.server.setting.lowSpeedDelay ? 'orange' : 'green') : 'red'"
:title="element.title || `测速中:${element.host}`" :color="element.time ? (element.time > config.server.setting.lowSpeedDelay ? 'orange' : 'green') : (element.title ? 'red' : '')"
>
{{ element.host }} {{ element.time }}{{ element.time ? 'ms' : '' }} {{ element.dns }}
{{ element.host }} {{ element.time ? `${element.time}ms` : (element.title ? '' : '测速中') }} {{ element.dns }}
</a-tag>
</a-card>
</a-col>

View File

@ -152,6 +152,12 @@ $dark-input: #777; //输入框:背景色
border-color: #5a5750;
color: #cfa572;
}
/* 标签:未知 */
.ant-tag:not(.ant-tag-red, .ant-tag-green, .ant-tag-orange) {
background-color: #5a5a5a;
border-color: #5a5a5a;
color: #ccc;
}
/* 按钮 */
.ant-btn:not(.ant-btn-danger, .ant-btn-primary) {

View File

@ -52,13 +52,18 @@ module.exports = class BaseDNS {
}
}
async lookup (hostname) {
async lookup (hostname, ipChecker) {
try {
let ipCache = this.cache.get(hostname)
if (ipCache) {
if (ipCache.value != null) {
ipCache.doCount(ipCache.value, false)
return ipCache.value
const ip = ipCache.value
if (ip != null) {
if (ipChecker && ipChecker(ip)) {
ipCache.doCount(ip, false)
return ip
} else {
return hostname
}
}
} else {
ipCache = new IpCache(hostname)
@ -66,7 +71,7 @@ module.exports = class BaseDNS {
}
const t = new Date()
let ipList = await this._lookupInternal(hostname)
let ipList = await this._lookupWithPreSetIpList(hostname)
if (ipList == null) {
// 没有获取到ipv4地址
ipList = []
@ -74,28 +79,42 @@ module.exports = class BaseDNS {
ipList.push(hostname) // 把原域名加入到统计里去
ipCache.setBackupList(ipList)
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname}${ipCache.value} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache))
return ipCache.value
const ip = ipCache.value
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] ${hostname}${ip} (${new Date() - t} ms), ipList: ${JSON.stringify(ipList)}, ipCache:`, JSON.stringify(ipCache))
if (ipChecker) {
if (ip != null && ip !== hostname && ipChecker(ip)) {
return ip
}
for (const ip of ipList) {
if (ip !== hostname && ipChecker(ip)) {
return ip
}
}
}
return ip != null ? ip : hostname
} catch (error) {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] cannot resolve hostname ${hostname}, error:`, error)
return hostname
}
}
async _lookupInternal (hostname) {
async _lookupWithPreSetIpList (hostname) {
// 获取当前域名的预设IP列表
let hostnamePreSetIpList = matchUtil.matchHostname(this.preSetIpList, hostname, `matched preSetIpList(${this.dnsName})`)
if (hostnamePreSetIpList && (hostnamePreSetIpList.length > 0 || hostnamePreSetIpList.length === undefined)) {
if (hostnamePreSetIpList.length > 0) {
hostnamePreSetIpList = hostnamePreSetIpList.slice()
hostnamePreSetIpList = hostnamePreSetIpList.slice() // 复制一份列表数据,避免配置数据被覆盖
} else {
hostnamePreSetIpList = mapToList(hostnamePreSetIpList)
}
if (hostnamePreSetIpList.length > 0) {
hostnamePreSetIpList.isPreSet = true
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的预设IP列表 ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
log.info(`[DNS-over-PreSet '${this.dnsName}'] 获取到该域名的预设IP列表 ${hostname} - ${JSON.stringify(hostnamePreSetIpList)}`)
return hostnamePreSetIpList
}
}
@ -106,23 +125,59 @@ module.exports = class BaseDNS {
async _lookup (hostname) {
const start = Date.now()
try {
// 执行DNS查询
log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query start: ${hostname}`)
const response = await this._doDnsQuery(hostname)
const cost = Date.now() - start
log.debug(`[DNS-over-${this.dnsType} '${this.dnsName}'] query end: ${hostname}, cost: ${cost} ms, response:`, response)
if (response == null || response.answers == null || response.answers.length == null || response.answers.length === 0) {
// 说明没有获取到ip
log.warn(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms, response:`, response)
return []
}
const ret = response.answers.filter(item => item.type === 'A').map(item => item.data)
if (ret.length === 0) {
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IPv4地址: ${hostname}, cost: ${cost} ms`)
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 没有该域名的IP地址: ${hostname}, cost: ${cost} ms`)
} else {
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IPv4地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`)
log.info(`[DNS-over-${this.dnsType} '${this.dnsName}'] 获取到该域名的IP地址: ${hostname} - ${JSON.stringify(ret)}, cost: ${cost} ms`)
}
return ret
} catch (e) {
log.error(`[DNS-over-${this.dnsType} '${this.dnsName}'] DNS query error, hostname: ${hostname}${this.dnsServer ? `, dnsServer: ${this.dnsServer}` : ''}, cost: ${Date.now() - start} ms, error:`, e)
return []
}
}
_doDnsQuery (hostname, type) {
return new Promise((resolve, reject) => {
// 设置超时任务
let isOver = false
const timeout = 6000
const timeoutId = setTimeout(() => {
if (!isOver) {
reject(new Error('DNS查询超时'))
}
}, timeout)
try {
this._dnsQueryPromise(hostname, type)
.then((response) => {
isOver = true
clearTimeout(timeoutId)
resolve(response)
})
.catch((e) => {
isOver = true
clearTimeout(timeoutId)
reject(e)
})
} catch (e) {
isOver = true
clearTimeout(timeoutId)
reject(e)
}
})
}
}

View File

@ -10,7 +10,7 @@ module.exports = class DNSOverHTTPS extends BaseDNS {
this.dnsServer = dnsServer
}
async _doDnsQuery (hostname) {
return await dohQueryAsync({ url: this.dnsServer }, [{ type: 'A', name: hostname }])
_dnsQueryPromise (hostname, type = 'A') {
return dohQueryAsync({ url: this.dnsServer }, [{ type, name: hostname }])
}
}

View File

@ -13,7 +13,7 @@ module.exports = class DNSOverTCP extends BaseDNS {
this.dnsServerPort = Number.parseInt(dnsServerPort) || defaultPort
}
_doDnsQuery (hostname) {
_dnsQueryPromise (hostname, type = 'A') {
return new Promise((resolve, reject) => {
// 构造 DNS 查询报文
const packet = dnsPacket.encode({
@ -21,7 +21,7 @@ module.exports = class DNSOverTCP extends BaseDNS {
type: 'query',
id: randi(0x0, 0xFFFF),
questions: [{
type: 'A',
type,
name: hostname,
}],
})

View File

@ -11,7 +11,7 @@ module.exports = class DNSOverTLS extends BaseDNS {
this.dnsServerName = dnsServerName
}
async _doDnsQuery (hostname) {
_dnsQueryPromise (hostname, type = 'A') {
const options = {
host: this.dnsServer,
port: this.dnsServerPort,
@ -19,9 +19,9 @@ module.exports = class DNSOverTLS extends BaseDNS {
name: hostname,
klass: 'IN',
type: 'A',
type,
}
return await dnstls.query(options)
return dnstls.query(options)
}
}

View File

@ -15,21 +15,28 @@ module.exports = class DNSOverUDP extends BaseDNS {
this.socketType = this.isIPv6 ? 'udp6' : 'udp4'
}
_doDnsQuery (hostname) {
_dnsQueryPromise (hostname, type = 'A') {
return new Promise((resolve, reject) => {
let isOver = false
const timeout = 5000
let timeoutId = null
// 构造 DNS 查询报文
const packet = dnsPacket.encode({
flags: dnsPacket.RECURSION_DESIRED,
type: 'query',
id: randi(0x0, 0xFFFF),
questions: [{
type: 'A',
type,
name: hostname,
}],
})
// 创建客户端
const udpClient = dgram.createSocket(this.socketType, (msg, _rinfo) => {
isOver = true
clearTimeout(timeoutId)
const response = dnsPacket.decode(msg)
resolve(response)
udpClient.close()
@ -38,10 +45,20 @@ module.exports = class DNSOverUDP extends BaseDNS {
// 发送 UDP 查询
udpClient.send(packet, 0, packet.length, this.dnsServerPort, this.dnsServer, (err, _bytes) => {
if (err) {
isOver = true
clearTimeout(timeoutId)
reject(err)
udpClient.close()
}
})
// 设置超时任务
timeoutId = setTimeout(() => {
if (!isOver) {
reject(new Error('查询超时'))
udpClient.close()
}
}, timeout)
})
}
}

View File

@ -114,7 +114,7 @@ function connect (req, cltSocket, head, hostname, port, dnsConfig = null, isDire
if (dnsConfig && dnsConfig.dnsMap) {
const dns = DnsUtil.hasDnsLookup(dnsConfig, hostname)
if (dns) {
options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, isDnsIntercept)
options.lookup = dnsLookup.createLookupFunc(null, dns, 'connect', hostport, port, isDnsIntercept)
}
}
// 代理连接事件监听

View File

@ -124,7 +124,7 @@ module.exports = function createRequestHandler (createIntercepts, middlewares, e
}
}
if (dns) {
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, isDnsIntercept)
rOptions.lookup = dnsLookup.createLookupFunc(res, dns, 'request url', url, rOptions.port, isDnsIntercept)
log.debug(`域名 ${rOptions.hostname} DNS: ${dns.dnsName}`)
res.setHeader('DS-DNS', dns.dnsName)
} else {

View File

@ -2,12 +2,35 @@ const defaultDns = require('node:dns')
const log = require('../../../utils/util.log.server')
const speedTest = require('../../speed')
function createIpChecker (tester) {
if (!tester || tester.backupList == null || tester.backupList.length === 0) {
return null
}
return (ip) => {
for (let i = 0; i < tester.backupList.length; i++) {
const item = tester.backupList[i]
if (item.host === ip) {
if (item.time > 0) {
return true // IP测速成功
}
if (item.status === 'failed') {
return false // IP测速失败
}
break
}
}
return true // IP测速未知
}
}
module.exports = {
createLookupFunc (res, dns, action, target, isDnsIntercept) {
createLookupFunc (res, dns, action, target, port, isDnsIntercept) {
target = target ? (`, target: ${target}`) : ''
return (hostname, options, callback) => {
const tester = speedTest.getSpeedTester(hostname)
const tester = speedTest.getSpeedTester(hostname, port)
if (tester) {
const aliveIpObj = tester.pickFastAliveIpObj()
if (aliveIpObj) {
@ -21,7 +44,10 @@ module.exports = {
log.info(`----- ${action}: ${hostname}, no alive ip${target}, tester: { "ready": ${tester.ready}, "backupList": ${JSON.stringify(tester.backupList)} }`)
}
}
dns.lookup(hostname).then((ip) => {
const ipChecker = createIpChecker(tester)
dns.lookup(hostname, ipChecker).then((ip) => {
if (isDnsIntercept) {
isDnsIntercept.dns = dns
isDnsIntercept.hostname = hostname
@ -29,35 +55,16 @@ module.exports = {
}
if (ip !== hostname) {
// 判断是否为测速失败的IP如果是则不使用当前IP
let isTestFailedIp = false
if (tester && tester.ready && tester.backupList && tester.backupList.length > 0) {
for (let i = 0; i < tester.backupList.length; i++) {
const item = tester.backupList[i]
if (item.host === ip) {
if (item.time == null) {
isTestFailedIp = true
}
break
}
}
}
if (isTestFailedIp === false) {
log.info(`----- ${action}: ${hostname}, use ip from dns '${dns.dnsName}': ${ip}${target} -----`)
if (res) {
res.setHeader('DS-DNS-Lookup', `DNS: ${ip} ${dns.dnsName === '预设IP' ? 'PreSet' : dns.dnsName}`)
}
callback(null, ip, 4)
return
} else {
// 使用默认dns
log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}, skip test failed ip from dns '${dns.dnsName}: ${ip}'${target}, options:`, options)
}
} else {
// 使用默认dns
log.info(`----- ${action}: ${hostname}, use hostname by default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
}
log.info(`----- ${action}: ${hostname}, use default DNS: ${hostname}${target}, options:`, options, ', dns:', dns)
defaultDns.lookup(hostname, options, callback)
}
})
}
},

View File

@ -1,31 +1,44 @@
// 1个小时不访问取消获取
// const { exec } = require('node:child_process')
const net = require('node:net')
const _ = require('lodash')
const log = require('../../utils/util.log.server')
const config = require('./config.js')
// const isWindows = process.platform === 'win32'
const DISABLE_TIMEOUT = 60 * 60 * 1000
class SpeedTester {
constructor ({ hostname }) {
constructor ({ hostname, port }) {
this.dnsMap = config.getConfig().dnsMap
this.hostname = hostname
this.lastReadTime = Date.now()
this.port = port || 443
this.ready = false
this.alive = []
this.backupList = []
this.keepCheckId = false
this.loadingIps = false
this.loadingTest = false
this.testCount = 0
this.test()
this.lastReadTime = Date.now()
this.keepCheckIntervalId = false
this.tryTestCount = 0
this.test() // 异步:初始化完成后先测速一次
}
pickFastAliveIpObj () {
this.touch()
if (this.alive.length === 0) {
if (this.backupList.length > 0 && this.tryTestCount % 10 > 0) {
this.testBackups() // 异步
} else if (this.tryTestCount % 10 === 0) {
this.test() // 异步
}
this.tryTestCount++
return null
}
return this.alive[0]
@ -33,26 +46,27 @@ class SpeedTester {
touch () {
this.lastReadTime = Date.now()
if (!this.keepCheckId) {
if (!this.keepCheckIntervalId) {
this.startChecker()
}
}
startChecker () {
if (this.keepCheckId) {
clearInterval(this.keepCheckId)
if (this.keepCheckIntervalId) {
clearInterval(this.keepCheckIntervalId)
}
this.keepCheckId = setInterval(() => {
this.keepCheckIntervalId = setInterval(() => {
if (Date.now() - DISABLE_TIMEOUT > this.lastReadTime) {
// 超过很长时间没有访问,取消测试
clearInterval(this.keepCheckId)
clearInterval(this.keepCheckIntervalId)
this.keepCheckIntervalId = false
return
}
if (this.alive.length > 0) {
this.testBackups()
return
this.testBackups() // 异步
} else {
this.test() // 异步
}
this.test()
}, config.getConfig().interval)
}
@ -71,44 +85,56 @@ class SpeedTester {
promiseList.push(one)
}
await Promise.all(promiseList)
const items = []
for (const ip in ips) {
items.push({ host: ip, port: 443, dns: ips[ip].dns })
items.push({ host: ip, dns: ips[ip].dns })
}
return items
}
async getFromOneDns (dns) {
return await dns._lookupInternal(this.hostname)
return await dns._lookupWithPreSetIpList(this.hostname)
}
async test () {
this.testCount++
log.debug(`[speed] test start: ${this.hostname}, testCount: ${this.testCount}`)
try {
const newList = await this.getIpListFromDns(this.dnsMap)
const newBackupList = [...newList, ...this.backupList]
this.backupList = _.unionBy(newBackupList, 'host')
this.testCount++
log.info('[speed]', this.hostname, '➜ ip-list:', this.backupList)
await this.testBackups()
log.info(`[speed] test end: ${this.hostname} ➜ ip-list:`, this.backupList, `, testCount: ${this.testCount}`)
if (config.notify) {
config.notify({ key: 'test' })
}
} catch (e) {
log.error(`[speed] test failed: ${this.hostname}, testCount: ${this.testCount}, error:`, e)
}
}
async testBackups () {
const testAll = []
if (this.backupList.length > 0) {
const aliveList = []
const testAll = []
for (const item of this.backupList) {
testAll.push(this.doTest(item, aliveList))
}
await Promise.all(testAll)
this.alive = aliveList
}
this.ready = true
}
async doTest (item, aliveList) {
try {
const ret = await this.testOne(item)
item.title = `${ret.by}测速成功:${item.host}`
log.info(`[speed] test success: ${this.hostname}${item.host}:${this.port} from DNS '${item.dns}'`)
_.merge(item, ret)
aliveList.push({ ...ret, ...item })
aliveList.sort((a, b) => a.time - b.time)
@ -125,48 +151,131 @@ class SpeedTester {
return a.time - b.time
})
} catch (e) {
if (e.message !== 'timeout') {
log.warn('[speed] test error: ', this.hostname, `${item.host}:${item.port} from DNS '${item.dns}'`, ', errorMsg:', e.message)
if (item.time == null) {
item.title = e.message
item.status = 'failed'
}
if (!e.message.includes('timeout')) {
log.warn(`[speed] test error: ${this.hostname}${item.host}:${this.port} from DNS '${item.dns}', errorMsg: ${e.message}`)
}
}
}
testOne (item) {
const timeout = 5000
const { host, port, dns } = item
const startTime = Date.now()
let isOver = false
testByTCP (item) {
return new Promise((resolve, reject) => {
const { host, dns } = item
const startTime = Date.now()
let isOver = false
const timeout = 5000
let timeoutId = null
const client = net.createConnection({ host, port }, () => {
// 'connect' 监听器
const connectionTime = Date.now()
const client = net.createConnection({ host, port: this.port }, () => {
isOver = true
clearTimeout(timeoutId)
resolve({ status: 'success', time: connectionTime - startTime })
const connectionTime = Date.now()
resolve({ status: 'success', by: 'TCP', time: connectionTime - startTime })
client.end()
})
client.on('end', () => {
})
client.on('error', (e) => {
if (e.message !== 'timeout') {
log.warn('[speed] test error: ', this.hostname, `${host}:${port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message)
}
isOver = true
clearTimeout(timeoutId)
log.warn('[speed] test by TCP error: ', this.hostname, `${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms, errorMsg:`, e.message)
reject(e)
client.end()
})
timeoutId = setTimeout(() => {
if (isOver) {
return
}
log.warn('[speed] test timeout:', this.hostname, `${host}:${port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
log.warn('[speed] test by TCP timeout:', this.hostname, `${host}:${this.port} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
reject(new Error('timeout'))
client.end()
}, timeout)
})
}
// 暂不使用
// testByPing (item) {
// return new Promise((resolve, reject) => {
// const { host, dns } = item
// const startTime = Date.now()
//
// // 设置超时程序
// let isOver = false
// const timeout = 5000
// const timeoutId = setTimeout(() => {
// if (!isOver) {
// log.warn('[speed] test by PING timeout:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms`)
// reject(new Error('timeout'))
// }
// }, timeout)
//
// // 协议选择如强制ping6
// const usePing6 = !isWindows && host.includes(':') // Windows无ping6命令
// const cmd = usePing6
// ? `ping6 -c 2 ${host}`
// : isWindows
// ? `ping -n 2 ${host}`
// : `ping -c 2 ${host}`
//
// log.debug('[speed] test by PING start:', this.hostname, `➜ ${host} from DNS '${dns}'`)
// exec(cmd, (error, stdout, _stderr) => {
// isOver = true
// clearTimeout(timeoutId)
//
// if (error) {
// log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 目标不可达或超时`)
// reject(new Error('目标不可达或超时'))
// return
// }
//
// // 提取延迟数据(正则匹配)
// const regex = /[=<](\d+(?:\.\d*)?)ms/gi // 适配Linux/Windows
// const times = []
// let match
// // eslint-disable-next-line no-cond-assign
// while ((match = regex.exec(stdout)) !== null) {
// times.push(Number.parseFloat(match[1]))
// }
//
// if (times.length === 0) {
// log.warn('[speed] test by PING error:', this.hostname, `➜ ${host} from DNS '${dns}', cost: ${Date.now() - startTime} ms, error: 无法解析延迟`)
// reject(new Error('无法解析延迟'))
// } else {
// // 计算平均延迟
// const avg = times.reduce((a, b) => a + b, 0) / times.length
// resolve({ status: 'success', by: 'PING', time: Math.round(avg) })
// }
// })
// })
// }
testOne (item) {
return new Promise((resolve, reject) => {
const thenFun = (ret) => {
resolve(ret)
}
// 先用TCP测速
this.testByTCP(item)
.then(thenFun)
.catch((e) => {
// // TCP测速失败再用 PING 测速
// this.testByPing(item)
// .then(thenFun)
// .catch((e2) => {
// reject(new Error(`TCP测速失败${e.message}PING测速失败${e2.message}`))
// })
reject(new Error(`TCP测速失败${e.message}`))
})
})
}
}
module.exports = SpeedTester

View File

@ -1,9 +1,9 @@
const config = {
notify () {},
dnsMap: {},
}
module.exports = {
getConfig () {
return config
},
notify: null,
}

View File

@ -6,6 +6,28 @@ const SpeedTester = require('./SpeedTester.js')
const SpeedTestPool = {
}
function addSpeedTest (hostname, port) {
if (!port) {
const idx = hostname.indexOf(':')
if (idx > 0 && idx === hostname.lastIndexOf(':')) {
const arr = hostname.split(':')
hostname = arr[0]
port = Number.parseInt(arr[1]) || 443
} else {
port = 443
}
}
// 443端口不拼接在key上
const key = port === 443 ? hostname : `${hostname}:${port}`
if (SpeedTestPool[key] == null) {
return SpeedTestPool[key] = new SpeedTester({ hostname, port })
}
return SpeedTestPool[key]
}
function initSpeedTest (runtimeConfig) {
const { enabled, hostnameList } = runtimeConfig
const conf = config.getConfig()
@ -14,45 +36,42 @@ function initSpeedTest (runtimeConfig) {
return
}
_.forEach(hostnameList, (hostname) => {
SpeedTestPool[hostname] = new SpeedTester({ hostname })
addSpeedTest(hostname)
})
log.info('[speed] enabled')
log.info('[speed] enabledSpeedTestPool:', SpeedTestPool)
}
function getAllSpeedTester () {
const allSpeed = {}
if (!config.getConfig().enabled) {
return allSpeed
}
if (config.getConfig().enabled) {
_.forEach(SpeedTestPool, (item, key) => {
allSpeed[key] = {
hostname: key,
hostname: item.hostname,
port: item.port,
alive: item.alive,
backupList: item.backupList,
}
})
}
return allSpeed
}
function getSpeedTester (hostname) {
function getSpeedTester (hostname, port) {
if (!config.getConfig().enabled) {
return
return null
}
let instance = SpeedTestPool[hostname]
if (instance == null) {
instance = new SpeedTester({ hostname })
SpeedTestPool[hostname] = instance
}
return instance
return addSpeedTest(hostname, port)
}
function registerNotify (notify) {
config.notify = notify
}
// function registerNotify (notify) {
// config.notify = notify
// }
function reSpeedTest () {
_.forEach(SpeedTestPool, (item, key) => {
item.test()
_.forEach(SpeedTestPool, (item, _key) => {
item.test() // 异步
})
}
@ -68,8 +87,8 @@ module.exports = {
SpeedTester,
initSpeedTest,
getSpeedTester,
getAllSpeedTester,
registerNotify,
// getAllSpeedTester,
// registerNotify,
reSpeedTest,
action,
}

View File

@ -26,3 +26,12 @@ assert.strictEqual(isEmpty(0), false)
assert.strictEqual(isEmpty(-1), false)
assert.strictEqual(isEmpty(''), false)
assert.strictEqual(isEmpty('1'), false)
// test lodash.unionBy
const list = [
{ host: 1, port: 1, dns: 2 },
{ host: 1, port: 1, dns: 3 },
{ host: 1, port: 2, dns: 3 },
{ host: 1, port: 2, dns: 3 },
]
console.info(lodash.unionBy(list, 'host', 'port'))