From 795fb207cb45a3c2ef94b9cad7f2b7eacc1aaae1 Mon Sep 17 00:00:00 2001 From: dsh-agent Date: Fri, 11 Sep 2026 10:30:54 +0800 Subject: [PATCH] =?UTF-8?q?refactor(stage9):=20=E5=90=8E=E7=AB=AF=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E5=9F=9F=E7=9B=AE=E5=BD=95=E5=BD=A2=E7=8A=B6=E7=BB=9F?= =?UTF-8?q?=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 每个域现在都是 src/server/routes// 目录: vehicles/routes.ts(原 routes/vehicles.ts)、vehicle-heatmap/{routes,model}.ts、 hydrogen-heatmap/{routes,model}.ts、ele/routes.ts、feedback/routes.ts。 - 命名约定:index.ts 是聚合器(挂子路由),routes.ts 是叶子路由。mileage/energy/ scheduling 保持 index.ts 聚合,其余统一为 routes.ts。 - 纯移动与改名,未改动任何 SQL、参数顺序、路由挂载前缀或处理器逻辑。 import 改写由一次性 codemod 完成;vehicles/repository.test.ts 读取的源码路径同步更新。 - docs/ARCHITECTURE.md 补充"后端业务域形状"表,并如实标注 ele / feedback 尚未拆出 repository(这两个域缺 SQL 指纹测试,建议先补测试再拆)。 lint / test(139) / build 全绿,可达性 0 未引用文件。 --- docs/ARCHITECTURE.md | 21 ++++++++++++++++--- src/server/app.ts | 10 ++++----- src/server/db/schema/feedback.ts | 2 +- src/server/routes/ele/{index.ts => routes.ts} | 0 .../routes/feedback/{index.ts => routes.ts} | 0 .../model.test.ts} | 4 ++-- .../model.ts} | 2 +- .../routes.ts} | 10 ++++----- src/server/routes/scheduling/suggestions.ts | 2 +- .../model.test.ts} | 4 ++-- .../model.ts} | 2 +- .../routes.ts} | 8 +++---- src/server/routes/vehicles/repository.test.ts | 2 +- .../{vehicles.ts => vehicles/routes.ts} | 14 ++++++------- 14 files changed, 48 insertions(+), 33 deletions(-) rename src/server/routes/ele/{index.ts => routes.ts} (100%) rename src/server/routes/feedback/{index.ts => routes.ts} (100%) rename src/server/routes/{hydrogen-heatmap-model.test.ts => hydrogen-heatmap/model.test.ts} (97%) rename src/server/routes/{hydrogen-heatmap-model.ts => hydrogen-heatmap/model.ts} (98%) rename src/server/routes/{hydrogen-heatmap.ts => hydrogen-heatmap/routes.ts} (97%) rename src/server/routes/{vehicle-heatmap-model.test.ts => vehicle-heatmap/model.test.ts} (98%) rename src/server/routes/{vehicle-heatmap-model.ts => vehicle-heatmap/model.ts} (98%) rename src/server/routes/{vehicle-heatmap.ts => vehicle-heatmap/routes.ts} (97%) rename src/server/routes/{vehicles.ts => vehicles/routes.ts} (99%) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index fc9cf02..9c8e675 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -82,9 +82,23 @@ server/ db/schema/ 运行时建表/改表的唯一位置(只读模式下整体跳过) middleware/ auth(JWT → 注入 user)、read-only auth/ 登录换票、固定密码、权限过滤与脱敏 - routes/ 按业务域的 HTTP 路由 + routes// 按业务域的 HTTP 路由 ``` +### 后端业务域的形状(`server/routes//`) + +命名约定:`index.ts` 是**聚合器**(把子路由挂到前缀上),`routes.ts` 是**叶子路由**。 + +| 域 | 文件 | 说明 | +| --- | --- | --- | +| `vehicles/` | `routes.ts` `repository.ts` `model.ts` `utils.ts` `types.ts` | 唯一已完整分层到 repository 的域,SQL 有指纹测试 | +| `mileage/` | `index.ts`(聚合)+ `monitoring.ts` `targets.ts` `trend.ts` `daily-report.ts` `vehicle-recent.ts` + `*-model.ts` + `cache.ts` `oneos-api.ts` `daily-report-{service,store,scheduler}.ts` | 路由 / 模型 / 服务已分开 | +| `energy/` | `index.ts`(聚合)+ `hydrogen-bi-v2.ts` `hydrogen-station-board.ts` `electric.ts` `etc.ts` + `query-model.ts` `cache.ts` `constants.ts` | 路由 / 模型已分开,SQL 仍在路由内 | +| `scheduling/` | `index.ts`(聚合)+ `suggestions.ts` `notify.ts` + `algorithm.ts` `notification-model.ts` | 同上 | +| `hydrogen-heatmap/`、`vehicle-heatmap/` | `routes.ts` + `model.ts` | 路由 / 模型已分开 | +| `ele/`、`feedback/` | `routes.ts`(+ `oss.ts`) | **尚未拆出 repository**,SQL 与处理器在同一文件 | + + ### 中间件顺序(在 `app.ts` 中显式体现) ``` @@ -118,8 +132,9 @@ cors → read-only → /api/auth(公开) → authMiddleware → 各业务域 诚实记录,避免后来者以为已经做完: -- **后端路由尚未全部拆成 routes / service / repository**。`routes/vehicles/` 已有 - `repository.ts` + `model.ts` 的雏形,其余域仍是单文件路由;拆分时不要改变 SQL 与参数顺序。 +- **后端仍有两个域没拆出 repository**:`ele/routes.ts` 与 `feedback/routes.ts` 仍把 SQL + 与处理器放在同一文件(形状见上表)。拆分时**不要改变 SQL 与参数顺序**——这两个域目前 + 没有 SQL 指纹测试,建议先补契约测试再动。 - **运行时建表已集中到 `server/db/schema/`**,并在 `DB_READ_ONLY=1` 时整体跳过(由架构测试守护, 已实测不触碰数据库)。它仍由业务接口在首次调用时触发,而不是只由 `bootstrap.ts` 调用—— 要彻底改成显式迁移,需要先建立数据库变更脚本流程,避免"代码里偷偷建表"。 diff --git a/src/server/app.ts b/src/server/app.ts index 84f447b..5e4641a 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -4,14 +4,14 @@ import { readOnlyMiddleware } from './middleware/read-only.js'; import { cors } from 'hono/cors'; import authRouter from './auth/login.js'; import { authMiddleware } from './middleware/auth.js'; -import eleRouter from './routes/ele/index.js'; +import eleRouter from './routes/ele/routes.js'; import energyRouter from './routes/energy/index.js'; -import feedbackRouter from './routes/feedback/index.js'; -import hydrogenHeatmapRouter from './routes/hydrogen-heatmap.js'; +import feedbackRouter from './routes/feedback/routes.js'; +import hydrogenHeatmapRouter from './routes/hydrogen-heatmap/routes.js'; import mileageRouter from './routes/mileage/index.js'; import schedulingRouter from './routes/scheduling/index.js'; -import vehicleHeatmapRouter from './routes/vehicle-heatmap.js'; -import vehiclesRouter from './routes/vehicles.js'; +import vehicleHeatmapRouter from './routes/vehicle-heatmap/routes.js'; +import vehiclesRouter from './routes/vehicles/routes.js'; /** * 创建 HTTP 应用,但不监听端口、不访问数据库,也不启动后台任务。 diff --git a/src/server/db/schema/feedback.ts b/src/server/db/schema/feedback.ts index 335428b..0cc2d8f 100644 --- a/src/server/db/schema/feedback.ts +++ b/src/server/db/schema/feedback.ts @@ -1,4 +1,4 @@ -import pool from '../../db/mysql.js'; +import pool from '../mysql.js'; import { ddlAllowed, logDdlSkipped } from './guard.js'; const CREATE_TABLE_SQL = ` diff --git a/src/server/routes/ele/index.ts b/src/server/routes/ele/routes.ts similarity index 100% rename from src/server/routes/ele/index.ts rename to src/server/routes/ele/routes.ts diff --git a/src/server/routes/feedback/index.ts b/src/server/routes/feedback/routes.ts similarity index 100% rename from src/server/routes/feedback/index.ts rename to src/server/routes/feedback/routes.ts diff --git a/src/server/routes/hydrogen-heatmap-model.test.ts b/src/server/routes/hydrogen-heatmap/model.test.ts similarity index 97% rename from src/server/routes/hydrogen-heatmap-model.test.ts rename to src/server/routes/hydrogen-heatmap/model.test.ts index 0fa5f67..409d95c 100644 --- a/src/server/routes/hydrogen-heatmap-model.test.ts +++ b/src/server/routes/hydrogen-heatmap/model.test.ts @@ -10,8 +10,8 @@ import { rankHydrogenStations, serializeHydrogenStation, type HydrogenStationRecord, -} from './hydrogen-heatmap-model.js'; -import { recentDayRange } from '../../shared/date-range.js'; +} from './model.js'; +import { recentDayRange } from '../../../shared/date-range.js'; // 默认区间是"近 30 天"滚动窗口,注入固定时钟让断言可复现。 const NOW = new Date('2026-07-13T12:00:00'); diff --git a/src/server/routes/hydrogen-heatmap-model.ts b/src/server/routes/hydrogen-heatmap/model.ts similarity index 98% rename from src/server/routes/hydrogen-heatmap-model.ts rename to src/server/routes/hydrogen-heatmap/model.ts index 8ead99b..7b7e788 100644 --- a/src/server/routes/hydrogen-heatmap-model.ts +++ b/src/server/routes/hydrogen-heatmap/model.ts @@ -1,4 +1,4 @@ -import { recentDayRange } from '../../shared/date-range.js'; +import { recentDayRange } from '../../../shared/date-range.js'; export type HydrogenHeatmapMetric = 'kg' | 'refuels' | 'vehicles'; export type HydrogenPayer = 'all' | 'lingniu' | 'customer'; diff --git a/src/server/routes/hydrogen-heatmap.ts b/src/server/routes/hydrogen-heatmap/routes.ts similarity index 97% rename from src/server/routes/hydrogen-heatmap.ts rename to src/server/routes/hydrogen-heatmap/routes.ts index 4b351fd..3d24528 100644 --- a/src/server/routes/hydrogen-heatmap.ts +++ b/src/server/routes/hydrogen-heatmap/routes.ts @@ -1,9 +1,9 @@ import { Hono } from 'hono'; import type { RowDataPacket } from 'mysql2'; -import pool from '../db/mysql.js'; -import type { AuthUser } from '../auth/types.js'; -import { canAccessEnergy } from '../auth/types.js'; -import { recentDayRange } from '../../shared/date-range.js'; +import pool from '../../db/mysql.js'; +import type { AuthUser } from '../../auth/types.js'; +import { canAccessEnergy } from '../../auth/types.js'; +import { recentDayRange } from '../../../shared/date-range.js'; import { buildHydrogenPoints, filterHydrogenStationsByRadius, @@ -14,7 +14,7 @@ import { serializeHydrogenStation, type HydrogenPayer, type HydrogenStationRecord, -} from './hydrogen-heatmap-model.js'; +} from './model.js'; type StationRow = RowDataPacket & HydrogenStationRecord; diff --git a/src/server/routes/scheduling/suggestions.ts b/src/server/routes/scheduling/suggestions.ts index 28c1d16..06afc14 100644 --- a/src/server/routes/scheduling/suggestions.ts +++ b/src/server/routes/scheduling/suggestions.ts @@ -2,7 +2,7 @@ import { Hono } from 'hono'; import pool from '../../db/mysql.js'; import { fetchVehicleInfoMap } from '../mileage/vehicle-info.js'; import { fetchOneOsMileageDates } from '../mileage/oneos-api.js'; -import { mapRegion } from '../vehicles.js'; +import { mapRegion } from '../vehicles/routes.js'; import { filterByPermission, maskCustomerNames } from '../../auth/permissions.js'; import { classifyVehicle, generateSuggestions } from './algorithm.js'; import { fetchActiveNotificationMap, fetchRecentInterventionCount } from './notify.js'; diff --git a/src/server/routes/vehicle-heatmap-model.test.ts b/src/server/routes/vehicle-heatmap/model.test.ts similarity index 98% rename from src/server/routes/vehicle-heatmap-model.test.ts rename to src/server/routes/vehicle-heatmap/model.test.ts index 76370cb..752d1f0 100644 --- a/src/server/routes/vehicle-heatmap-model.test.ts +++ b/src/server/routes/vehicle-heatmap/model.test.ts @@ -11,8 +11,8 @@ import { summarizeVehicleRecords, vehicleGridPrecision, type VehicleHeatmapRecord, -} from './vehicle-heatmap-model.js'; -import { recentDayRange } from '../../shared/date-range.js'; +} from './model.js'; +import { recentDayRange } from '../../../shared/date-range.js'; // 默认区间是"近 30 天"滚动窗口,注入固定时钟让断言可复现。 const NOW = new Date('2026-07-13T12:00:00'); diff --git a/src/server/routes/vehicle-heatmap-model.ts b/src/server/routes/vehicle-heatmap/model.ts similarity index 98% rename from src/server/routes/vehicle-heatmap-model.ts rename to src/server/routes/vehicle-heatmap/model.ts index 739ba1b..e7a5d21 100644 --- a/src/server/routes/vehicle-heatmap-model.ts +++ b/src/server/routes/vehicle-heatmap/model.ts @@ -1,4 +1,4 @@ -import { recentDayRange } from '../../shared/date-range.js'; +import { recentDayRange } from '../../../shared/date-range.js'; export type VehicleHeatmapMetric = 'locations' | 'vehicles'; export type VehicleHeatmapRecord = { diff --git a/src/server/routes/vehicle-heatmap.ts b/src/server/routes/vehicle-heatmap/routes.ts similarity index 97% rename from src/server/routes/vehicle-heatmap.ts rename to src/server/routes/vehicle-heatmap/routes.ts index e838ffc..959d02e 100644 --- a/src/server/routes/vehicle-heatmap.ts +++ b/src/server/routes/vehicle-heatmap/routes.ts @@ -1,7 +1,7 @@ import { Hono } from 'hono'; -import pool from '../db/mysql.js'; -import heatmapPool from '../db/heatmap.js'; -import { getCache } from './mileage/cache.js'; +import pool from '../../db/mysql.js'; +import heatmapPool from '../../db/heatmap.js'; +import { getCache } from '../mileage/cache.js'; import { buildVehicleGrid, buildVehicleRanking, @@ -11,7 +11,7 @@ import { summarizeVehicleRecords, vehicleGridPrecision, type VehicleHeatmapRecord, -} from './vehicle-heatmap-model.js'; +} from './model.js'; type MetaRow = { start_date: string; diff --git a/src/server/routes/vehicles/repository.test.ts b/src/server/routes/vehicles/repository.test.ts index 9778fdf..2754f79 100644 --- a/src/server/routes/vehicles/repository.test.ts +++ b/src/server/routes/vehicles/repository.test.ts @@ -26,7 +26,7 @@ function vehicle(id: string): Vehicle { } test('keeps all vehicle GET endpoints in their established order', () => { - const source = readFileSync(new URL('../vehicles.ts', import.meta.url), 'utf8'); + const source = readFileSync(new URL('./routes.ts', import.meta.url), 'utf8'); const paths = Array.from(source.matchAll(/app\.get\('([^']+)'/g), (match) => match[1]); assert.deepEqual(paths, [ diff --git a/src/server/routes/vehicles.ts b/src/server/routes/vehicles/routes.ts similarity index 99% rename from src/server/routes/vehicles.ts rename to src/server/routes/vehicles/routes.ts index b228751..60323da 100644 --- a/src/server/routes/vehicles.ts +++ b/src/server/routes/vehicles/routes.ts @@ -7,9 +7,9 @@ import type { BatchSummary, BatchGroup, InventoryTypeSummary, -} from '../types.js'; -import { filterByPermission, maskCustomerNames, maskCustomerName } from '../auth/permissions.js'; -import type { AuthUser } from '../auth/types.js'; +} from '../../types.js'; +import { filterByPermission, maskCustomerNames, maskCustomerName } from '../../auth/permissions.js'; +import type { AuthUser } from '../../auth/types.js'; import type { Context } from 'hono'; import { INVENTORY_REGIONS, @@ -21,7 +21,7 @@ import { mapInventoryRegion, mapMacroRegion, resolveCity, -} from './vehicles/model.js'; +} from './model.js'; import { compareDepartmentNames, countByType, @@ -29,10 +29,10 @@ import { getStats, listDateRange, normalizeDateRange, -} from './vehicles/utils.js'; -import { vehicleRepository, type FlowDetailRow, type WeeklyDetailType } from './vehicles/repository.js'; +} from './utils.js'; +import { vehicleRepository, type FlowDetailRow, type WeeklyDetailType } from './repository.js'; -export { mapRegion } from './vehicles/model.js'; +export { mapRegion } from './model.js'; const app = new Hono();