// 浙江省油价历史: 走 Prisma 落库 + apizero price 接口写入 // 设计: // - 每天 04:00 cron 检查 schedule, 待定且 <= 昨天的调价日 -> 抓 price?province=浙江 存本表 // - unique (date, province, fuel) 防重复 // - 查询 API: 取某省某油品最近 N 天 (默认 180) import { prisma } from '../db.js' import * as client from './client.js' const PROVINCE = '浙江' // 油品 -> apizero prices 对象里的 key (price action 返回 "92号汽油" 这种带后缀的 key) const FUEL_KEYS = { '92': '92号汽油', '95': '95号汽油', '0': '0号柴油', } // 解析 apizero price action 返回的 prices (中文 key) -> 标准 fuel code function parsePrice(data) { if (!data || !data.prices) throw new Error('apizero 响应无 prices 字段') const out = {} for (const [fuel, label] of Object.entries(FUEL_KEYS)) { const raw = data.prices[label] if (typeof raw === 'string') { // "7.64 元/升" -> 7.64 const m = raw.match(/^([\d.]+)/) if (m) out[fuel] = parseFloat(m[1]) } else if (typeof raw === 'number') { out[fuel] = raw } } return out } // 抓一次浙江当前价并入库 (date = 今日) // 返回 { inserted, skipped } (按 unique 约束: 已存在则 skip) export async function fetchAndStore() { const data = await client.get('oil-price-forecast', { action: 'price', province: PROVINCE }) const prices = parsePrice(data) if (Object.keys(prices).length === 0) { throw new Error('apizero 返回价格为空') } const date = new Date() // 抹掉时分秒, 便于按天比较 date.setHours(0, 0, 0, 0) let inserted = 0 let skipped = 0 for (const [fuel, price] of Object.entries(prices)) { try { await prisma.oilPriceHistory.create({ data: { date, province: PROVINCE, fuel, price, source: 'apizero' }, }) inserted++ } catch (e) { // P2002: unique 冲突 -> 跳过 if (e?.code === 'P2002') skipped++ else throw e } } return { inserted, skipped, date: date.toISOString().slice(0, 10) } } // 查询某省某油品最近 N 天 // province 必填, 默认 浙江 // fuel 可选, 留空返回 4 个油品 // days 默认 180 export async function query({ province = PROVINCE, fuel, days = 180 }) { const since = new Date() since.setDate(since.getDate() - days) since.setHours(0, 0, 0, 0) const where = { province, date: { gte: since } } if (fuel) where.fuel = fuel const rows = await prisma.oilPriceHistory.findMany({ where, orderBy: [{ date: 'asc' }], }) // 整形: Decimal -> number return rows.map((r) => ({ date: r.date.toISOString().slice(0, 10), province: r.province, fuel: r.fuel, price: Number(r.price), })) }