From 6dbd36dcd37d6f90e1548ee3379cf14ec0bcc79a Mon Sep 17 00:00:00 2001 From: kkfluous Date: Thu, 2 Apr 2026 13:35:58 +0800 Subject: [PATCH] refactor: replace mileage monolith with modular route files Co-Authored-By: Claude Opus 4.6 (1M context) --- src/server/index.ts | 2 +- src/server/routes/mileage.ts | 569 ----------------------------- src/server/routes/mileage/index.ts | 18 + 3 files changed, 19 insertions(+), 570 deletions(-) delete mode 100644 src/server/routes/mileage.ts create mode 100644 src/server/routes/mileage/index.ts diff --git a/src/server/index.ts b/src/server/index.ts index 833adfd..d062f4b 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -4,7 +4,7 @@ import { Hono } from 'hono'; import { cors } from 'hono/cors'; import dotenv from 'dotenv'; import vehiclesRouter from './routes/vehicles.js'; -import mileageRouter from './routes/mileage.js'; +import mileageRouter from './routes/mileage/index.js'; dotenv.config(); diff --git a/src/server/routes/mileage.ts b/src/server/routes/mileage.ts deleted file mode 100644 index 741daba..0000000 --- a/src/server/routes/mileage.ts +++ /dev/null @@ -1,569 +0,0 @@ -import { Hono } from 'hono'; -import pool from '../db.js'; -import mileagePool from '../mileage-db.js'; - -const app = new Hono(); - -// 车辆关联信息 SQL(客户名、部门、经理) -const VEHICLE_INFO_SQL = `SELECT - truck.plate_number AS plate, - cus.customer_name AS customer, - dep.dep_name AS department, - u.user_name AS manager, - dic_status.dic_name AS rent_status, - org_truck.org_name AS entity, - c.project_name AS project -FROM tab_truck truck -LEFT JOIN tab_truck_status_info si ON si.truck_id = truck.id AND si.is_deleted = 0 -LEFT JOIN tab_contract c ON c.id = si.contract_id AND c.is_deleted = 0 -LEFT JOIN tab_customer cus ON cus.id = c.customer_id AND cus.is_deleted = 0 -LEFT JOIN tab_user u ON u.id = c.bd AND u.is_deleted = 0 -LEFT JOIN tab_department dep ON dep.id = u.dep_id AND dep.is_deleted = 0 -LEFT JOIN tab_dic dic_status ON dic_status.parent_code = 'dic_truck_rent_status' - AND dic_status.dic_code = truck.truck_rent_status AND dic_status.is_deleted = 0 -LEFT JOIN tab_org org_truck ON org_truck.id = truck.org_id AND org_truck.is_deleted = 0 -WHERE truck.is_deleted = 0 AND truck.is_operation = 1`; - -// ========== 实时监控缓存(每2分钟刷新) ========== -interface CachedVehicle { - plate: string; - vin: string; - dailyKm: number; - totalKm: number | null; - source: string; - isOnline: boolean; - isDataSynced: boolean; - customer: string | null; - department: string | null; - manager: string | null; - rentStatus: string | null; - entity: string | null; - project: string | null; - yesterdayKm: number; -} - -interface MonitoringCache { - vehicles: CachedVehicle[]; - stats: { totalToday: number; totalAll: number; vehicleCount: number }; - filters: { departments: string[]; customers: string[]; plates: string[]; projects: string[]; entities: string[]; rentStatuses: string[]; platePrefixes: { prefix: string; count: number }[]; targetNames: string[] }; - targetPlatesMap: Map>; - updatedAt: string; -} - -let monitoringCache: MonitoringCache | null = null; - -async function refreshMonitoringCache() { - try { - console.log('[mileage] refreshing monitoring cache...'); - const start = Date.now(); - - // 并行查询两个数据库 - const [mileageResult, yesterdayResult, infoRows, targetRows] = await Promise.all([ - (async () => { - const [dateRows] = await mileagePool.execute( - 'SELECT MAX(stat_date) as latest FROM v_vehicle_daily_stats' - ) as any; - const latestDate = dateRows[0]?.latest; - if (!latestDate) return []; - const [rows] = await mileagePool.execute( - `SELECT plate, vin, daily_km, total_km, source - FROM v_vehicle_daily_stats WHERE stat_date = ?`, - [latestDate] - ) as any; - return rows; - })(), - (async () => { - const [rows] = await mileagePool.execute( - `SELECT plate, daily_km FROM v_vehicle_daily_stats - WHERE stat_date = DATE_SUB((SELECT MAX(stat_date) FROM v_vehicle_daily_stats), INTERVAL 1 DAY)` - ) as any; - const map = new Map(); - for (const r of rows) { - const existing = map.get(r.plate) || 0; - const km = Number(r.daily_km) || 0; - if (km > existing) map.set(r.plate, km); - } - return map; - })(), - pool.execute(VEHICLE_INFO_SQL).then(([rows]) => rows as any[]), - pool.execute(`SELECT t.id, t.target_name, v.plate_number FROM tab_mileage_assessment_target t - JOIN tab_mileage_assessment_vehicle v ON v.target_id = t.id AND v.is_deleted = 0 - WHERE t.is_deleted = 0`).then(([rows]) => rows as any[]), - ]); - - // 车辆关联信息 map - const infoMap = new Map(); - for (const row of infoRows) { - infoMap.set(row.plate, row); - } - - // 型号批次→车牌映射 - const targetNameMap = new Map(); - const targetPlatesMap = new Map>(); // targetName -> plates - for (const r of targetRows) { - targetNameMap.set(r.id, r.target_name); - const set = targetPlatesMap.get(r.target_name) || new Set(); - set.add(r.plate_number); - targetPlatesMap.set(r.target_name, set); - } - const targetNames = Array.from(targetPlatesMap.keys()); - - // 去重:同一 plate 取 daily_km 最大的 - const mileageMap = new Map(); - for (const row of mileageResult) { - const existing = mileageMap.get(row.plate); - if (!existing || Number(row.daily_km) > Number(existing.daily_km)) { - mileageMap.set(row.plate, row); - } - } - - // 合并 - const vehicles: CachedVehicle[] = Array.from(mileageMap.values()).map((m: any) => { - const info = infoMap.get(m.plate); - const dailyKm = Number(m.daily_km) || 0; - const source = m.source || 'NONE'; - return { - plate: m.plate, - vin: m.vin, - dailyKm, - totalKm: m.total_km !== null ? Number(m.total_km) : null, - source, - isOnline: source !== 'NONE' && dailyKm > 0, - isDataSynced: source !== 'NONE', - customer: info?.customer || null, - department: info?.department || null, - manager: info?.manager || null, - rentStatus: info?.rent_status || null, - entity: info?.entity || null, - project: info?.project || null, - yesterdayKm: yesterdayResult.get(m.plate) || 0, - }; - }); - - // 预计算统计信息 - const totalToday = vehicles.reduce((sum, v) => sum + v.dailyKm, 0); - const totalAll = vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0); - - - // 预提取筛选选项 - const deptOrder = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']; - const departments = (Array.from(new Set(vehicles.map(v => v.department).filter(Boolean))) as string[]) - .sort((a, b) => { - const ai = deptOrder.findIndex(d => a.includes(d)); - const bi = deptOrder.findIndex(d => b.includes(d)); - return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi); - }); - const customers = Array.from(new Set(vehicles.map(v => v.customer).filter(Boolean))) as string[]; - const plates = vehicles.map(v => v.plate); - const projects = Array.from(new Set(vehicles.map(v => v.project).filter(Boolean))) as string[]; - const entities = Array.from(new Set(vehicles.map(v => v.entity).filter(Boolean))) as string[]; - const rentStatuses = Array.from(new Set(vehicles.map(v => v.rentStatus).filter(Boolean))) as string[]; - const prefixCount = new Map(); - for (const v of vehicles) { - const p = v.plate.charAt(0); - prefixCount.set(p, (prefixCount.get(p) || 0) + 1); - } - const platePrefixes = Array.from(prefixCount.entries()).map(([prefix, count]) => ({ prefix, count })).sort((a, b) => b.count - a.count); - - monitoringCache = { - vehicles, - stats: { totalToday, totalAll, vehicleCount: vehicles.length }, - filters: { departments, customers, plates, projects, entities, rentStatuses, platePrefixes, targetNames }, - targetPlatesMap, - updatedAt: new Date().toISOString(), - }; - - console.log(`[mileage] cache refreshed: ${vehicles.length} vehicles in ${Date.now() - start}ms`); - } catch (e) { - console.error('[mileage] cache refresh error:', e); - } -} - -// 启动时立即刷新,之后每2分钟刷新 -refreshMonitoringCache(); -setInterval(refreshMonitoringCache, 60 * 1000); - -// 查询指定日期的里程数据(非缓存) -async function queryDateMileage(dateStr: string): Promise<{ vehicles: CachedVehicle[] }> { - const [mileageRows, yesterdayRows, infoRows] = await Promise.all([ - mileagePool.execute(`SELECT plate, vin, daily_km, total_km, source FROM v_vehicle_daily_stats WHERE stat_date = ?`, [dateStr]).then(([r]) => r as any[]), - mileagePool.execute(`SELECT plate, daily_km FROM v_vehicle_daily_stats WHERE stat_date = DATE_SUB(?, INTERVAL 1 DAY)`, [dateStr]).then(([r]) => r as any[]), - pool.execute(VEHICLE_INFO_SQL).then(([r]) => r as any[]), - ]); - const infoMap = new Map(); - for (const row of infoRows) infoMap.set(row.plate, row); - const yesterdayMap = new Map(); - for (const r of yesterdayRows) { - const km = Number(r.daily_km) || 0; - const existing = yesterdayMap.get(r.plate) || 0; - if (km > existing) yesterdayMap.set(r.plate, km); - } - const mileageMap = new Map(); - for (const row of mileageRows) { - const existing = mileageMap.get(row.plate); - if (!existing || Number(row.daily_km) > Number(existing.daily_km)) mileageMap.set(row.plate, row); - } - const vehicles: CachedVehicle[] = Array.from(mileageMap.values()).map((m: any) => { - const info = infoMap.get(m.plate); - const dailyKm = Number(m.daily_km) || 0; - const source = m.source || 'NONE'; - return { - plate: m.plate, vin: m.vin, dailyKm, - totalKm: m.total_km !== null ? Number(m.total_km) : null, - source, isOnline: source !== 'NONE' && dailyKm > 0, isDataSynced: source !== 'NONE', - customer: info?.customer || null, department: info?.department || null, - manager: info?.manager || null, rentStatus: info?.rent_status || null, - entity: info?.entity || null, project: info?.project || null, - yesterdayKm: yesterdayMap.get(m.plate) || 0, - }; - }); - return { vehicles }; -} - -// GET /monitoring — 从缓存取数据(或指定日期实时查询),支持筛选/排序/分页 -app.get('/monitoring', async (c) => { - const emptyResponse = { vehicles: [], stats: { totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }, filters: { departments: [], customers: [], plates: [], projects: [], entities: [], rentStatuses: [], platePrefixes: [], targetNames: [] }, total: 0, page: 1, totalPages: 1, updatedAt: new Date().toISOString() }; - - const sortBy = c.req.query('sortBy') || 'today'; - const sortOrder = c.req.query('sortOrder') || 'desc'; - const limit = Number(c.req.query('limit')) || 50; - const page = Number(c.req.query('page')) || 1; - const search = c.req.query('search') || ''; - const dept = c.req.query('dept') || ''; - const customer = c.req.query('customer') || ''; - const project = c.req.query('project') || ''; - const entity = c.req.query('entity') || ''; - const mileageMin = c.req.query('mileageMin') || ''; - const mileageMax = c.req.query('mileageMax') || ''; - const platePrefix = c.req.query('platePrefix') || ''; - const targetName = c.req.query('targetName') || ''; - const plate = c.req.query('plate') || ''; - const rentStatus = c.req.query('rentStatus') || ''; - const date = c.req.query('date') || ''; - - let allVehicles: CachedVehicle[]; - let filters: MonitoringCache['filters']; - - if (date) { - // 指定日期:实时查询 - try { - const result = await queryDateMileage(date); - allVehicles = result.vehicles; - const deptOrder = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']; - filters = { - departments: (Array.from(new Set(allVehicles.map(v => v.department).filter(Boolean))) as string[]).sort((a, b) => { - const ai = deptOrder.findIndex(d => a.includes(d)); const bi = deptOrder.findIndex(d => b.includes(d)); - return (ai === -1 ? 99 : ai) - (bi === -1 ? 99 : bi); - }), - customers: Array.from(new Set(allVehicles.map(v => v.customer).filter(Boolean))) as string[], - plates: allVehicles.map(v => v.plate), - projects: Array.from(new Set(allVehicles.map(v => v.project).filter(Boolean))) as string[], - entities: Array.from(new Set(allVehicles.map(v => v.entity).filter(Boolean))) as string[], - rentStatuses: Array.from(new Set(allVehicles.map(v => v.rentStatus).filter(Boolean))) as string[], - platePrefixes: (() => { const m = new Map(); for (const v of allVehicles) { const p = v.plate.charAt(0); m.set(p, (m.get(p) || 0) + 1); } return Array.from(m.entries()).map(([prefix, count]) => ({ prefix, count })).sort((a, b) => b.count - a.count); })(), - targetNames: monitoringCache?.filters.targetNames || [], - }; - } catch (e) { - console.error('monitoring date query error:', e); - return c.json(emptyResponse, 500); - } - } else { - if (!monitoringCache) return c.json(emptyResponse); - allVehicles = monitoringCache.vehicles; - filters = monitoringCache.filters; - } - - let vehicles = allVehicles; - - // 筛选 - if (search) { - const q = search.toLowerCase(); - vehicles = vehicles.filter(v => - v.plate.toLowerCase().includes(q) || - (v.customer || '').toLowerCase().includes(q) || - (v.project || '').toLowerCase().includes(q) - ); - } - if (dept) vehicles = vehicles.filter(v => dept === '__EMPTY__' ? !v.department : v.department === dept); - if (customer) vehicles = vehicles.filter(v => customer === '__EMPTY__' ? !v.customer : v.customer === customer); - if (project) vehicles = vehicles.filter(v => v.project === project); - if (entity) vehicles = vehicles.filter(v => v.entity === entity); - if (rentStatus) vehicles = vehicles.filter(v => v.rentStatus === rentStatus); - if (plate) vehicles = vehicles.filter(v => v.plate === plate); - if (platePrefix) vehicles = vehicles.filter(v => v.plate.startsWith(platePrefix)); - if (targetName && monitoringCache?.targetPlatesMap) { - const tPlates = monitoringCache.targetPlatesMap.get(targetName); - if (tPlates) vehicles = vehicles.filter(v => tPlates.has(v.plate)); - else vehicles = []; - } - if (mileageMin) vehicles = vehicles.filter(v => v.dailyKm >= Number(mileageMin)); - if (mileageMax) vehicles = vehicles.filter(v => v.dailyKm <= Number(mileageMax)); - - const total = vehicles.length; - - // 基于筛选后的数据计算统计(yesterdayTotal 也基于筛选后的车辆) - const filteredStats = { - totalToday: vehicles.reduce((sum, v) => sum + v.dailyKm, 0), - totalAll: vehicles.reduce((sum, v) => sum + (v.totalKm || 0), 0), - vehicleCount: vehicles.length, - yesterdayTotal: vehicles.reduce((sum, v) => sum + v.yesterdayKm, 0), - }; - - // 排序 - vehicles = [...vehicles].sort((a, b) => { - const valA = sortBy === 'today' ? a.dailyKm : (a.totalKm || 0); - const valB = sortBy === 'today' ? b.dailyKm : (b.totalKm || 0); - return sortOrder === 'desc' ? valB - valA : valA - valB; - }); - - // 分页 - const offset = (page - 1) * limit; - const paged = vehicles.slice(offset, offset + limit); - - return c.json({ - vehicles: paged, - stats: filteredStats, - filters, - total, - page, - totalPages: Math.ceil(total / limit), - updatedAt: date || monitoringCache?.updatedAt || new Date().toISOString(), - }); -}); - -// GET /targets — 考核项目列表 + 汇总 -app.get('/targets', async (c) => { - try { - const [targets] = await pool.execute( - 'SELECT * FROM tab_mileage_assessment_target WHERE is_deleted = 0 ORDER BY id' - ) as any; - - const [vehicleStats] = await pool.execute(` - SELECT - target_id, - COUNT(*) as total, - SUM(today_mileage) as today_total, - SUM(current_mileage) as cumulative_total, - AVG(current_year_completion_rate) as avg_completion, - SUM(CASE WHEN is_qualified = 1 THEN 1 ELSE 0 END) as qualified_count, - SUM(CASE WHEN current_year_is_qualified = 1 THEN 1 ELSE 0 END) as year_qualified_count, - SUM(CASE WHEN current_year_completion_rate >= 0.5 THEN 1 ELSE 0 END) as half_qualified_count, - SUM(current_year_mileage_task) as current_year_target, - SUM(current_year_mileage) as current_year_completed, - MAX(current_year_assessment_end_date) as year_end_date - FROM tab_mileage_assessment_vehicle - WHERE is_deleted = 0 - GROUP BY target_id - `) as any; - - const statsMap = new Map(); - for (const s of vehicleStats) { - statsMap.set(s.target_id, s); - } - - // 查询每个 target 的不同考核区间 - const [periodRows] = await pool.execute(` - SELECT target_id, - DATE_FORMAT(assessment_start_date, '%Y-%m-%d') as start_date, - DATE_FORMAT(assessment_end_date, '%Y-%m-%d') as end_date, - COUNT(*) as cnt - FROM tab_mileage_assessment_vehicle - WHERE is_deleted = 0 - GROUP BY target_id, assessment_start_date, assessment_end_date - ORDER BY target_id, assessment_start_date - `) as any; - - const periodsMap = new Map(); - for (const p of periodRows) { - const list = periodsMap.get(p.target_id) || []; - list.push(`${p.start_date} ~ ${p.end_date} (${p.cnt}台)`); - periodsMap.set(p.target_id, list); - } - - // 使用监控缓存的里程数据(与里程看板一致) - const cacheVehicleMap = new Map(); - if (monitoringCache) { - for (const v of monitoringCache.vehicles) { - cacheVehicleMap.set(v.plate, Math.max(0, v.dailyKm || 0)); - } - } - const [targetVehicleRows] = await pool.execute( - `SELECT target_id, plate_number FROM tab_mileage_assessment_vehicle WHERE is_deleted = 0` - ) as any; - const targetIdPlatesMap = new Map(); - for (const r of targetVehicleRows) { - const list = targetIdPlatesMap.get(r.target_id) || []; - list.push(r.plate_number); - targetIdPlatesMap.set(r.target_id, list); - } - - const now = new Date(); - const result = targets.map((t: any) => { - const s = statsMap.get(t.id) || {}; - const currentYearTarget = Number(s.current_year_target) || 0; - const currentYearCompleted = Number(s.current_year_completed) || 0; - const remaining = Math.max(0, currentYearTarget - currentYearCompleted); - const yearEnd = s.year_end_date ? new Date(s.year_end_date) : now; - const daysLeft = Math.max(1, Math.ceil((yearEnd.getTime() - now.getTime()) / 86400000)); - const dailyTarget = remaining / daysLeft; - - const periods = periodsMap.get(t.id) || []; - if (periods.length === 0) { - const startDate = t.default_start_date - ? new Date(t.default_start_date).toISOString().split('T')[0] - : ''; - const endDate = t.default_end_date - ? new Date(t.default_end_date).toISOString().split('T')[0] - : ''; - if (startDate || endDate) periods.push(`${startDate} ~ ${endDate}`); - } - - return { - id: t.id, - targetName: t.target_name, - vehicleCount: Number(s.total) || t.vehicle_count, - totalMileagePerVehicle: Number(t.total_mileage_per_vehicle), - annualMileagePerVehicle: Number(t.annual_mileage_per_vehicle), - assessmentYears: t.assessment_years, - periods, - todayTotal: (targetIdPlatesMap.get(t.id) || []).reduce((sum: number, plate: string) => sum + (cacheVehicleMap.get(plate) || 0), 0), - cumulativeTotal: Number(s.cumulative_total) || 0, - avgCompletion: (Number(s.avg_completion) || 0) * 100, - qualifiedCount: Number(s.qualified_count) || 0, - yearQualifiedCount: Number(s.year_qualified_count) || 0, - halfQualifiedCount: Number(s.half_qualified_count) || 0, - currentYearTarget, - currentYearCompleted, - remaining, - daysLeft, - dailyTarget: Math.round(dailyTarget * 10) / 10, - }; - }); - - return c.json(result); - } catch (e) { - console.error('targets error:', e); - return c.json([], 500); - } -}); - -// GET /target/:id/vehicles — 某项目的车辆明细(支持 ?date= 查询指定日期里程) -app.get('/target/:id/vehicles', async (c) => { - const targetId = c.req.param('id'); - const date = c.req.query('date') || ''; - try { - // 获取考核车辆基本信息 - const [rows] = await pool.execute( - `SELECT plate_number, today_mileage, vehicle_total_mileage, - completion_rate, is_qualified, current_year_is_qualified, - daily_required_mileage - FROM tab_mileage_assessment_vehicle - WHERE target_id = ? AND is_deleted = 0 - ORDER BY today_mileage DESC`, - [targetId] - ) as any; - - // 获取车辆关联信息(租赁状态、部门、客户) - const plates = rows.map((r: any) => r.plate_number); - const infoMap = new Map(); - if (plates.length > 0) { - const [infoRows] = await pool.execute( - `${VEHICLE_INFO_SQL} AND truck.plate_number IN (${plates.map(() => '?').join(',')})`, - plates - ) as any; - for (const row of infoRows) { - infoMap.set(row.plate, row); - } - } - - // 如果指定了日期,从里程数据库查询该日期的里程 - const dateMileageMap = new Map(); - if (date && plates.length > 0) { - const [mileageRows] = await mileagePool.execute( - `SELECT plate, daily_km, total_km, source FROM v_vehicle_daily_stats - WHERE stat_date = ? AND plate IN (${plates.map(() => '?').join(',')})`, - [date, ...plates] - ) as any; - for (const m of mileageRows) { - const existing = dateMileageMap.get(m.plate); - const dailyKm = Number(m.daily_km) || 0; - if (!existing || dailyKm > existing.dailyKm) { - const source = m.source || 'NONE'; - dateMileageMap.set(m.plate, { - dailyKm, - totalKm: m.total_km !== null ? Number(m.total_km) : null, - isOnline: source !== 'NONE' && dailyKm > 0, - }); - } - } - } - - const result = rows.map((r: any) => { - const info = infoMap.get(r.plate_number); - const dateMileage = date ? dateMileageMap.get(r.plate_number) : null; - return { - plateNumber: r.plate_number, - todayMileage: dateMileage ? dateMileage.dailyKm : (Number(r.today_mileage) || 0), - totalMileage: dateMileage?.totalKm ?? (Number(r.vehicle_total_mileage) || 0), - completionRate: Number(r.completion_rate) || 0, - isQualified: r.is_qualified === 1, - currentYearIsQualified: r.current_year_is_qualified === 1, - dailyRequiredMileage: Number(r.daily_required_mileage) || 0, - rentStatus: info?.rent_status || null, - department: info?.department || null, - customer: info?.customer || null, - isOnline: dateMileage ? dateMileage.isOnline : true, - }; - }); - - return c.json(result); - } catch (e) { - console.error('target vehicles error:', e); - return c.json([], 500); - } -}); - -// GET /trend — 7天里程趋势 -app.get('/trend', async (c) => { - const targetId = c.req.query('targetId'); - const days = Number(c.req.query('days')) || 7; - try { - let plates: string[] = []; - if (targetId) { - const [vehicleRows] = await pool.execute( - 'SELECT plate_number FROM tab_mileage_assessment_vehicle WHERE target_id = ? AND is_deleted = 0', - [targetId] - ) as any; - plates = vehicleRows.map((r: any) => r.plate_number); - if (plates.length === 0) return c.json([]); - } - - let sql = ` - SELECT DATE_FORMAT(stat_date, '%m-%d') as date, SUM(daily_km) as mileage - FROM v_vehicle_daily_stats - WHERE stat_date >= DATE_SUB(CURDATE(), INTERVAL ? DAY) AND stat_date < CURDATE() - `; - const params: any[] = [days]; - - if (plates.length > 0) { - sql += ` AND plate IN (${plates.map(() => '?').join(',')})`; - params.push(...plates); - } - - sql += ' GROUP BY stat_date ORDER BY stat_date'; - - const [rows] = await mileagePool.execute(sql, params) as any; - - const result = rows.map((r: any) => ({ - date: r.date, - mileage: Math.round(Number(r.mileage) || 0), - })); - - return c.json(result); - } catch (e) { - console.error('trend error:', e); - return c.json([], 500); - } -}); - -export default app; diff --git a/src/server/routes/mileage/index.ts b/src/server/routes/mileage/index.ts new file mode 100644 index 0000000..0770a80 --- /dev/null +++ b/src/server/routes/mileage/index.ts @@ -0,0 +1,18 @@ +import { Hono } from 'hono'; +import { refreshMonitoringCache } from './cache.js'; +import monitoringRouter from './monitoring.js'; +import targetsRouter from './targets.js'; +import trendRouter from './trend.js'; + +const app = new Hono(); + +app.route('/monitoring', monitoringRouter); +app.route('/targets', targetsRouter); +app.route('/target', targetsRouter); +app.route('/trend', trendRouter); + +// 启动时立即刷新缓存,之后每分钟刷新 +refreshMonitoringCache(); +setInterval(refreshMonitoringCache, 60 * 1000); + +export default app;