// apizero (https://apizero.cn) 通用 HTTP 客户端 // - 统一从 env APIZERO_KEY 读取 API key (test key 也能用) // - 统一 QPS 节流 (apizero 限制 3 req/s) // - 统一错误格式: 抛出 ApizeroError(code, message, httpStatus) // - 所有 apizero 接口都进这一层, 后续新增模块复用 // // 依赖: 原生 fetch (Node 18+) // 简易令牌桶, 限速 3 req/s let lastCallAt = 0 const MIN_INTERVAL_MS = 1000 / 3 // ~333ms async function throttle() { const now = Date.now() const wait = lastCallAt + MIN_INTERVAL_MS - now if (wait > 0) await new Promise((r) => setTimeout(r, wait)) lastCallAt = Date.now() } // apizero 标准错误 export class ApizeroError extends Error { constructor(code, message, httpStatus) { super(message || `apizero error code=${code}`) this.name = 'ApizeroError' this.code = code this.httpStatus = httpStatus } } const BASE_URL = 'https://v1.apizero.cn/api' // 读 key: 优先 env APIZERO_KEY, 否则用空 (匿名调用, 配额更小) function getKey() { return process.env.APIZERO_KEY || '' } // 通用 GET 入口 // - endpoint: 'oil-price-forecast' (不带 /api 前缀) // - params: 普通对象 // 返回: apizero 的 data 字段 (已解一层) export async function get(endpoint, params = {}) { await throttle() const qs = new URLSearchParams() for (const [k, v] of Object.entries(params)) { if (v !== undefined && v !== null && v !== '') qs.set(k, String(v)) } const key = getKey() if (key) qs.set('key', key) const url = `${BASE_URL}/${endpoint}?${qs.toString()}` let res try { res = await fetch(url, { method: 'GET', headers: { 'Accept': 'application/json' } }) } catch (e) { throw new ApizeroError('NETWORK', `apizero 网络错误: ${e.message}`, 0) } // HTTP 层错误 if (!res.ok) { let body = '' try { body = await res.text() } catch { /* ignore */ } throw new ApizeroError('HTTP', `apizero HTTP ${res.status}: ${body.substring(0, 200)}`, res.status) } // 解析 body let json try { json = await res.json() } catch (e) { throw new ApizeroError('PARSE', 'apizero 响应非 JSON: ' + e.message, res.status) } // apizero 业务层错误: code !== 0 if (json && typeof json.code === 'number' && json.code !== 0) { throw new ApizeroError(json.code, json.msg || 'apizero 业务错误', res.status) } return json && json.data !== undefined ? json.data : json }