78 lines
2.4 KiB
JavaScript
78 lines
2.4 KiB
JavaScript
// 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
|
|
}
|