(base: string)
| 284 | const cached = this.fiatRatesCache[key]; |
| 285 | if (cached && now - cached.ts < 5 * 60 * 1000) return cached.rates; |
| 286 | const endpoints = [ |
| 287 | `https://api.exchangerate.host/latest?base=${encodeURIComponent(key)}`, |
| 288 | `https://open.er-api.com/v6/latest/${encodeURIComponent(key)}`, |
| 289 | `https://api.frankfurter.app/latest?from=${encodeURIComponent(key)}`, |
| 290 | // Coinbase 公共汇率(含法币与加密货币) |
| 291 | `https://api.coinbase.com/v2/exchange-rates?currency=${encodeURIComponent(key.toUpperCase())}`, |
| 292 | // jsDelivr 镜像的每日更新静态汇率(无钥,稳定) |
| 293 | `https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api@1/latest/currencies/${encodeURIComponent(key.toLowerCase())}.json` |
| 294 | ]; |
| 295 | for (const url of endpoints) { |
| 296 | try { |
| 297 | const { data } = await axios.get(url, { timeout: 8000 }); |
| 298 | let rates: Record<string, number> | null = null; |
| 299 | // 标准结构与 open.er-api、frankfurter |
| 300 | if (data?.rates) rates = data.rates; |
| 301 | if (data?.result === 'success' && data?.rates) rates = data.rates; |
| 302 | // Coinbase 结构: { data: { rates: { USD: "1", ... } } } |
| 303 | if (!rates && data?.data?.rates) rates = data.data.rates; |
| 304 | // Fawaz Ahmed currency API: { date: '...', usd: { eur: 0.93, ... } } |
| 305 | if (!rates && typeof data === 'object' && data && data[key]) rates = data[key]; |
| 306 | if (rates) { |
| 307 | const normalized = Object.fromEntries( |
| 308 | Object.entries(rates).map(([k, v]) => [k.toLowerCase(), Number(v)]) |
| 309 | ); |
| 310 | this.fiatRatesCache[key] = { rates: normalized, ts: now }; |
| 311 | return normalized; |
| 312 | } |
| 313 | } catch {} |
| 314 | } |
| 315 | throw new Error('法币汇率服务不可用'); |
| 316 | } |
| 317 | |
| 318 | // 智能解析参数:抓取两种货币与数量(数量可在任意位置) |
| 319 | private parseArgs(args: string[]): { base: string, quote: string, amount: number } { |
| 320 | const tokens = (args || []).map(a => this.normalizeCode(a)); |
| 321 | let amount = 1; |
| 322 | const curr: string[] = []; |
| 323 | for (const t of tokens) { |
| 324 | const n = parseFloat(t); |
no test coverage detected