feat(All): 第一版项目

This commit is contained in:
2025-12-19 09:33:04 +08:00
parent 154132f17e
commit 2f6831336e
35 changed files with 5120 additions and 0 deletions

172
src/server/index.ts Normal file
View File

@@ -0,0 +1,172 @@
import express, { Request, Response } from 'express'
import cors from 'cors'
import rateLimit from 'express-rate-limit'
import ipaddr from 'ipaddr.js'
const app = express()
const PORT = process.env.PORT || 3000
const GLOBALPING_API = 'https://api.globalping.io/v1'
app.use(express.json())
app.use(cors({ origin: 'http://localhost:5173' }))
app.use(rateLimit({
windowMs: 60 * 1000,
limit: 10,
message: { error: 'Rate limit exceeded' }
}))
interface GlobalPingLocation {
country: string
city?: string
}
const NODE_LOCATIONS: Record<string, GlobalPingLocation> = {
'us-west': { country: 'US', city: 'San Francisco' },
'us-east': { country: 'US', city: 'New York' },
'europe': { country: 'DE', city: 'Frankfurt' },
'asia': { country: 'JP', city: 'Tokyo' },
'south-america': { country: 'BR', city: 'Sao Paulo' },
'africa': { country: 'ZA', city: 'Cape Town' },
'oceania': { country: 'AU', city: 'Sydney' }
}
function extractClientIp(req: Request): string | null {
const forwarded = req.headers['x-forwarded-for']
const raw = typeof forwarded === 'string' ? forwarded.split(',')[0].trim() : req.socket.remoteAddress
if (!raw) return null
const normalized = raw.replace(/^::ffff:/, '')
if (ipaddr.isValid(normalized)) return normalized
return null
}
function isPublicIp(ip: string): boolean {
if (!ipaddr.isValid(ip)) return false
const parsed = ipaddr.parse(ip)
return parsed.range() === 'unicast'
}
interface MeasurementResponse {
id: string
probesCount: number
}
interface MeasurementResult {
id: string
type: string
status: 'in-progress' | 'finished'
results?: Array<{
probe: {
continent: string
country: string
city: string
asn: number
network: string
}
result: {
status: string
rawOutput: string
stats?: {
min: number
max: number
avg: number
total: number
loss: number
}
}
}>
}
async function createMeasurement(target: string, location: GlobalPingLocation): Promise<string> {
const res = await fetch(`${GLOBALPING_API}/measurements`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept-Encoding': 'gzip',
'User-Agent': 'LatencyTest/1.0.0'
},
body: JSON.stringify({
type: 'ping',
target,
locations: [location],
measurementOptions: {
packets: 3
}
})
})
if (!res.ok) {
const error = await res.json().catch(() => ({})) as { error?: { message?: string } }
throw new Error(error.error?.message || `GlobalPing API error: ${res.status}`)
}
const data = await res.json() as MeasurementResponse
return data.id
}
async function getMeasurementResult(id: string, timeout = 30000): Promise<{ latency: number | null; success: boolean }> {
const startTime = Date.now()
while (Date.now() - startTime < timeout) {
await new Promise(r => setTimeout(r, 500))
const res = await fetch(`${GLOBALPING_API}/measurements/${id}`, {
headers: {
'Accept-Encoding': 'gzip',
'User-Agent': 'LatencyTest/1.0.0'
}
})
if (!res.ok) {
throw new Error(`Failed to get measurement: ${res.status}`)
}
const data = await res.json() as MeasurementResult
if (data.status !== 'in-progress') {
const result = data.results?.[0]?.result
if (result?.status === 'finished' && result.stats?.avg != null) {
return { latency: Math.round(result.stats.avg), success: true }
}
return { latency: null, success: false }
}
}
return { latency: null, success: false }
}
app.get('/api/ip', (req: Request, res: Response) => {
const ip = extractClientIp(req)
if (!ip) {
return res.status(500).json({ error: 'Unable to determine IP' })
}
res.json({ ip })
})
app.post('/api/latency', async (req: Request, res: Response) => {
const { targetIp, nodeId } = req.body
if (!targetIp || typeof targetIp !== 'string') {
return res.status(400).json({ error: 'targetIp is required' })
}
if (!nodeId || !NODE_LOCATIONS[nodeId]) {
return res.status(400).json({ error: `Invalid nodeId. Available: ${Object.keys(NODE_LOCATIONS).join(', ')}` })
}
if (!isPublicIp(targetIp)) {
return res.status(400).json({ error: 'Invalid or private IP address' })
}
try {
const measurementId = await createMeasurement(targetIp, NODE_LOCATIONS[nodeId])
const { latency, success } = await getMeasurementResult(measurementId)
res.json({ nodeId, latency, success })
} catch (error) {
console.error('Latency test error:', error)
res.status(500).json({ nodeId, latency: null, success: false })
}
})
app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`)
})