106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import { Hono } from 'hono';
|
||
import { fetchOneOsMileageDates } from './oneos-api.js';
|
||
import {
|
||
parseMileageSourcePriority,
|
||
protocolsForSourcePriority,
|
||
sourceCategoryFromProtocol,
|
||
} from './source-policy.js';
|
||
|
||
const app = new Hono();
|
||
|
||
function fmt(d: Date): string {
|
||
const y = d.getFullYear();
|
||
const m = String(d.getMonth() + 1).padStart(2, '0');
|
||
const dd = String(d.getDate()).padStart(2, '0');
|
||
return `${y}-${m}-${dd}`;
|
||
}
|
||
|
||
function parseYmd(s: string): Date | null {
|
||
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
||
if (!m) return null;
|
||
const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
|
||
d.setHours(0, 0, 0, 0);
|
||
return Number.isFinite(d.getTime()) ? d : null;
|
||
}
|
||
|
||
const MAX_DAYS = 366;
|
||
|
||
app.get('/:plate/recent', async (c) => {
|
||
const plate = c.req.param('plate');
|
||
if (!plate) return c.json({ plate: '', days: [] }, 400);
|
||
const sourcePriority = parseMileageSourcePriority(c.req.query('sourcePriority'));
|
||
const protocolPriority = protocolsForSourcePriority(sourcePriority);
|
||
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0);
|
||
|
||
// 区间参数:优先 start/end;否则回退 days(兼容旧调用)
|
||
const startQ = c.req.query('start');
|
||
const endQ = c.req.query('end');
|
||
let start: Date;
|
||
let end: Date;
|
||
if (startQ) {
|
||
const ps = parseYmd(startQ);
|
||
if (!ps) return c.json({ plate, days: [] }, 400);
|
||
start = ps;
|
||
end = endQ ? (parseYmd(endQ) ?? today) : today;
|
||
} else {
|
||
const days = Math.min(Math.max(Number(c.req.query('days')) || 15, 1), MAX_DAYS);
|
||
end = today;
|
||
start = new Date(today);
|
||
start.setDate(today.getDate() - (days - 1));
|
||
}
|
||
if (start > end) [start, end] = [end, start];
|
||
// 限制区间长度
|
||
const span = Math.round((end.getTime() - start.getTime()) / 86400000) + 1;
|
||
if (span > MAX_DAYS) {
|
||
start = new Date(end);
|
||
start.setDate(end.getDate() - (MAX_DAYS - 1));
|
||
}
|
||
|
||
try {
|
||
const dates: string[] = [];
|
||
const dateCursor = new Date(start);
|
||
while (dateCursor <= end) {
|
||
dates.push(fmt(dateCursor));
|
||
dateCursor.setDate(dateCursor.getDate() + 1);
|
||
}
|
||
const rowsByDate = await fetchOneOsMileageDates(dates, [plate], protocolPriority);
|
||
|
||
// 补全:从 start 到 end 每天一条
|
||
const result: {
|
||
date: string;
|
||
dailyKm: number;
|
||
isDataSynced: boolean;
|
||
sourceProtocol: 'GB32960' | 'MQTT' | 'JT808' | null;
|
||
sourceCategory: 'INSTRUMENT' | 'GPS' | null;
|
||
}[] = [];
|
||
const cursor = new Date(start);
|
||
while (cursor <= end) {
|
||
const key = fmt(cursor);
|
||
const hit = (rowsByDate.get(key) || []).find(row => row.plateNumber === plate);
|
||
result.push({
|
||
date: key,
|
||
dailyKm: hit?.status === 'NORMAL' ? (hit.dailyMileageKm || 0) : 0,
|
||
isDataSynced: hit?.status === 'NORMAL',
|
||
sourceProtocol: hit?.sourceProtocol || null,
|
||
sourceCategory: sourceCategoryFromProtocol(hit?.sourceProtocol || null),
|
||
});
|
||
cursor.setDate(cursor.getDate() + 1);
|
||
}
|
||
|
||
return c.json({
|
||
plate,
|
||
start: fmt(start),
|
||
end: fmt(end),
|
||
sourcePriority,
|
||
days: result,
|
||
});
|
||
} catch (e: unknown) {
|
||
console.error('vehicle recent error:', e);
|
||
return c.json({ plate, days: [] }, 500);
|
||
}
|
||
});
|
||
|
||
export default app;
|