78 lines
3.3 KiB
JavaScript
78 lines
3.3 KiB
JavaScript
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
const baseUrl = process.env.GB32960_BASE_URL || "http://115.29.187.205:20200";
|
|
const vin = process.env.GB32960_VIN || "LB9A32A24R0LS1720";
|
|
const plate = process.env.GB32960_PLATE || "粤AGP4377";
|
|
const exportDate = process.env.GB32960_DATE || "2026-08-17";
|
|
const dateFrom = `${exportDate} 00:00:00`;
|
|
const dateTo = `${exportDate} 23:59:59`;
|
|
const limit = 500;
|
|
const outputPath = process.env.GB32960_DATA_PATH || new URL("./raw-frames.json", import.meta.url).pathname;
|
|
|
|
async function fetchPage(offset, includeTotal = false) {
|
|
const params = new URLSearchParams({
|
|
protocol: "GB32960",
|
|
vin,
|
|
dateFrom,
|
|
dateTo,
|
|
orderBy: "eventTime",
|
|
limit: String(limit),
|
|
offset: String(offset),
|
|
includeFields: "true",
|
|
includePayload: "true",
|
|
includeTotal: includeTotal ? "true" : "false",
|
|
});
|
|
const response = await fetch(`${baseUrl}/api/history/raw-frames?${params}`, {
|
|
headers: { "Accept-Encoding": "gzip" },
|
|
signal: AbortSignal.timeout(60000),
|
|
});
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}: ${await response.text()}`);
|
|
return response.json();
|
|
}
|
|
|
|
const first = await fetchPage(0, true);
|
|
const total = Number(first.total ?? 0);
|
|
const offsets = [];
|
|
for (let offset = limit; offset < total; offset += limit) offsets.push(offset);
|
|
const remaining = await Promise.all(offsets.map((offset) => fetchPage(offset)));
|
|
const items = [...(first.items ?? []), ...remaining.flatMap((page) => page.items ?? [])];
|
|
|
|
const frameIds = new Set(items.map((item) => item.frame_id));
|
|
const eventIds = new Set(items.map((item) => item.event_id));
|
|
const fieldKeys = [...new Set(items.flatMap((item) => Object.keys(item.parsed_fields ?? {})))].sort();
|
|
const messageCounts = Object.fromEntries(
|
|
[...items.reduce((map, item) => map.set(item.message_id_hex, (map.get(item.message_id_hex) ?? 0) + 1), new Map())]
|
|
.sort(([a], [b]) => a.localeCompare(b)),
|
|
);
|
|
|
|
if (items.length !== total) throw new Error(`row count mismatch: fetched=${items.length} total=${total}`);
|
|
if (frameIds.size !== items.length) throw new Error(`duplicate frame_id: unique=${frameIds.size} rows=${items.length}`);
|
|
if (eventIds.size !== items.length) throw new Error(`duplicate event_id: unique=${eventIds.size} rows=${items.length}`);
|
|
for (const item of items) {
|
|
if (item.protocol !== "GB32960" || item.vin !== vin) throw new Error(`scope mismatch at ${item.frame_id}`);
|
|
if (item.ts < dateFrom || item.ts > dateTo) throw new Error(`time out of range at ${item.frame_id}: ${item.ts}`);
|
|
}
|
|
|
|
const output = {
|
|
exportedAt: new Date().toISOString(),
|
|
source: `${baseUrl}/api/history/raw-frames`,
|
|
query: { plate, vin, protocol: "GB32960", dateFrom, dateTo, orderBy: "eventTime" },
|
|
verification: {
|
|
apiTotal: total,
|
|
fetchedRows: items.length,
|
|
uniqueFrameIds: frameIds.size,
|
|
uniqueEventIds: eventIds.size,
|
|
earliestTs: items.reduce((min, item) => !min || item.ts < min ? item.ts : min, ""),
|
|
latestTs: items.reduce((max, item) => !max || item.ts > max ? item.ts : max, ""),
|
|
parsedFieldCount: fieldKeys.length,
|
|
messageCounts,
|
|
},
|
|
fieldKeys,
|
|
items,
|
|
};
|
|
|
|
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
await fs.writeFile(outputPath, JSON.stringify(output), "utf8");
|
|
console.log(JSON.stringify({ outputPath, ...output.verification }, null, 2));
|