油价集成
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user