docs: add amap implementation plan
This commit is contained in:
parent
5e249b2a59
commit
e24394d3f3
450
docs/superpowers/plans/2026-07-11-amap-single-provider.md
Normal file
450
docs/superpowers/plans/2026-07-11-amap-single-provider.md
Normal file
@ -0,0 +1,450 @@
|
|||||||
|
# 高德地图单供应商迁移 Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** 用高德 JS API 与高德 Web 服务替换 TREK 的全部运行时地图供应商,并移除旧供应商的代码和依赖。
|
||||||
|
|
||||||
|
**Architecture:** 数据与业务层保留 WGS-84;一个共享坐标模块在高德 JS、Web 服务和 URI 边界转换 GCJ-02。服务端以受鉴权的高德适配器提供通用搜索、POI、路线和公交契约;客户端只消费这些内部契约与单一高德地图组件。
|
||||||
|
|
||||||
|
**Tech Stack:** TypeScript、React、NestJS、Vitest、AMap JS API 2.0、AMap Web Service API。
|
||||||
|
|
||||||
|
## Global Constraints
|
||||||
|
|
||||||
|
- 服务端 Key 使用 `AMAP_WEB_SERVICE_KEY`;不得写入数据库、前端状态或日志。
|
||||||
|
- JS Key 使用 `VITE_AMAP_JS_API_KEY`;`AMAP_JS_SECURITY_CODE` 仅留在服务端并经 `serviceHost` 反代,禁止使用 `VITE_AMAP_JS_SECURITY_CODE`。
|
||||||
|
- 业务/数据库坐标始终是 WGS-84;高德入参、出参与地图叠加层是 GCJ-02;中国大陆外不转换。
|
||||||
|
- 旧数据字段只停止使用,不物理删除:`google_place_id`、`google_ftid`、OSM 标识、旧 URL 和既有照片缓存保留只读兼容。
|
||||||
|
- 不保留旧供应商回退,也不把直线显示为高德路线结果。
|
||||||
|
- 代码中不得遗留 Leaflet、Mapbox、MapLibre、Google Maps、Nominatim、Overpass、OSRM、Transitous/MOTIS、Naver 或旧瓦片供应商的运行时调用。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
| 文件/目录 | 责任 |
|
||||||
|
| --- | --- |
|
||||||
|
| `shared/src/maps/coordinates.ts` | WGS-84/GCJ-02 类型、转换和中国大陆边界判断 |
|
||||||
|
| `shared/src/maps/amap.schema.ts` | 客户端/服务端共享的高德中性地图与路线契约 |
|
||||||
|
| `server/src/services/amap/*` | 高德 HTTP 客户端、搜索/POI、路线、公交和 URI 适配器 |
|
||||||
|
| `server/src/nest/maps/*` | 对现有地图 API 的受鉴权 Nest 暴露层 |
|
||||||
|
| `server/src/nest/routes/*` | 新增道路路线 API,替代浏览器 OSRM |
|
||||||
|
| `client/src/components/Map/amap/*` | 加载器、单一地图组件、marker/route/poi/定位叠加层 |
|
||||||
|
| `client/src/api/client.ts` | 地图与路线 API 调用 |
|
||||||
|
| `client/src/components/Map/*` | 移除旧渲染器与 OSRM 计算器,接入高德组件/接口 |
|
||||||
|
| `client/src/pages/atlas/*` | 用高德能力重建 Atlas,或对不支持的世界级 GeoJSON 交互显示受控状态 |
|
||||||
|
| `client/src/sync/*`, `client/vite.config.js` | 移除未经授权的 XYZ 离线瓦片预取和缓存 |
|
||||||
|
|
||||||
|
## Implementation Order
|
||||||
|
|
||||||
|
### Task 1: 建立共享坐标边界与契约
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `shared/src/maps/coordinates.ts`
|
||||||
|
- Create: `shared/src/maps/coordinates.spec.ts`
|
||||||
|
- Create: `shared/src/maps/amap.schema.ts`
|
||||||
|
- Modify: `shared/src/maps/maps.schema.ts`
|
||||||
|
- Modify: `shared/src/index.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: `isInChinaMainland(lat, lng)`, `wgs84ToGcj02(point)`, `gcj02ToWgs84(point)`, `Wgs84Point`, `Gcj02Point`.
|
||||||
|
- Produces: shared internal route/POI response schemas; no AMap raw response crosses package boundaries.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing coordinate tests**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('converts Beijing WGS-84 into GCJ-02 and approximately reverses it', () => {
|
||||||
|
const gcj = wgs84ToGcj02({ lat: 39.908823, lng: 116.39747 })
|
||||||
|
expect(gcj.lng).not.toBeCloseTo(116.39747, 4)
|
||||||
|
expect(gcj02ToWgs84(gcj)).toMatchObject({ lat: expect.closeTo(39.908823, 5), lng: expect.closeTo(116.39747, 5) })
|
||||||
|
})
|
||||||
|
it('does not transform London', () => expect(wgs84ToGcj02({ lat: 51.5072, lng: -0.1276 })).toEqual({ lat: 51.5072, lng: -0.1276 }))
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the failing test**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=shared -- coordinates.spec.ts`
|
||||||
|
Expected: FAIL because the coordinate module does not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement branded coordinate types and conversions**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type Wgs84Point = Readonly<{ lat: number; lng: number }>
|
||||||
|
export type Gcj02Point = Readonly<{ lat: number; lng: number }>
|
||||||
|
export function wgs84ToGcj02(point: Wgs84Point): Gcj02Point { /* mainland guard then transform */ }
|
||||||
|
export function gcj02ToWgs84(point: Gcj02Point): Wgs84Point { /* iterative inverse */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Export the contracts and run shared tests**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=shared`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add shared/src/maps shared/src/index.ts
|
||||||
|
git commit -m "feat: add amap coordinate boundary"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 2: 实现受保护的服务端高德 HTTP 与地点适配器
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `server/src/services/amap/amap.client.ts`
|
||||||
|
- Create: `server/src/services/amap/amap.places.ts`
|
||||||
|
- Create: `server/tests/unit/services/amap/amap.client.test.ts`
|
||||||
|
- Create: `server/tests/unit/services/amap/amap.places.test.ts`
|
||||||
|
- Modify: `server/src/nest/maps/maps.service.ts`
|
||||||
|
- Modify: `server/src/nest/maps/maps.controller.ts`
|
||||||
|
- Modify: `server/src/middleware/globalMiddleware.ts`
|
||||||
|
- Delete: Google/Nominatim/Overpass branches from `server/src/services/mapsService.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Task 1 coordinate/response contracts and `AMAP_WEB_SERVICE_KEY`.
|
||||||
|
- Produces: `search`, `autocomplete`, `details`, `reverse`, `pois` backed only by AMap.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing adapter contract tests**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('returns a configuration error without AMAP_WEB_SERVICE_KEY', async () => {
|
||||||
|
await expect(client.get('/v3/place/text', {})).rejects.toMatchObject({ code: 'AMAP_NOT_CONFIGURED' })
|
||||||
|
})
|
||||||
|
it('converts AMap GCJ POI coordinates back to WGS-84', async () => {
|
||||||
|
fetchMock.mockResolvedValue(amapPoiResponse('116.404,39.915'))
|
||||||
|
await expect(searchPlaces('天安门')).resolves.toMatchObject({ places: [{ lat: expect.any(Number), lng: expect.any(Number) }] })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the failing server tests**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=server -- amap.client.test.ts amap.places.test.ts`
|
||||||
|
Expected: FAIL because the AMap adapter does not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement fixed-host client and mapping functions**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export class AmapClient {
|
||||||
|
async get<T>(path: string, params: URLSearchParams): Promise<T> {
|
||||||
|
// Append server-only key, enforce timeout, redact key from errors, map provider failure.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export async function autocomplete(input: string, bias?: LocationBias): Promise<MapsAutocompleteResult> { /* input tips → internal suggestions */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Replace `/api/maps` provider wiring and test timeout/rate-limit paths**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=server -- tests/unit/nest/maps.service.test.ts tests/unit/nest/maps.controller.test.ts tests/unit/services/amap`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add server/src/services/amap server/src/nest/maps server/src/middleware/globalMiddleware.ts server/tests
|
||||||
|
git commit -m "feat: proxy amap place services"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 3: 增加服务器路线与公交适配器
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `server/src/services/amap/amap.routes.ts`
|
||||||
|
- Create: `server/src/services/amap/amap.transit.ts`
|
||||||
|
- Create: `server/src/nest/routes/routes.module.ts`
|
||||||
|
- Create: `server/src/nest/routes/routes.controller.ts`
|
||||||
|
- Create: `server/src/nest/routes/routes.service.ts`
|
||||||
|
- Create: `server/tests/unit/services/amap/amap.routes.test.ts`
|
||||||
|
- Create: `server/tests/unit/services/amap/amap.transit.test.ts`
|
||||||
|
- Modify: `server/src/app.module.ts`
|
||||||
|
- Modify: `server/src/nest/transit/transit.controller.ts`
|
||||||
|
- Modify: `server/src/nest/transit/transit.module.ts`
|
||||||
|
- Delete: `server/src/services/transitService.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: WGS-84 waypoints from clients; transforms to AMap GCJ-02.
|
||||||
|
- Produces: `POST /api/routes/plan` for driving/walking/cycling and AMap-adapted transit responses.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing route chunking and transit mapping tests**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('splits a route with more than the AMap waypoint limit and joins segments without duplicate points', async () => {
|
||||||
|
const result = await planRoadRoute(twentyWgsWaypoints, 'driving')
|
||||||
|
expect(result.coordinates).toHaveLength(expect.any(Number))
|
||||||
|
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
it('maps an AMap transit segment sequence into internal legs', async () => {
|
||||||
|
await expect(planTransit(from, to)).resolves.toMatchObject({ itineraries: [{ legs: expect.arrayContaining([expect.objectContaining({ mode: 'walk' })]) }] })
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the failing tests**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=server -- amap.routes.test.ts amap.transit.test.ts`
|
||||||
|
Expected: FAIL because no route module exists.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement route chunking and explicit unsupported-area errors**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export async function planRoadRoute(points: Wgs84Point[], mode: RoadMode): Promise<RouteWithLegs> {
|
||||||
|
// validate, transform, split to provider limit, request sequential chunks, de-duplicate joins, inverse-transform geometry
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Point the transit endpoint at the AMap transit adapter**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=server -- tests/unit/services/amap tests/e2e/transit.e2e.test.ts`
|
||||||
|
Expected: PASS with AMap fixtures; no Transitous URL mock remains.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add server/src/services/amap server/src/nest/routes server/src/nest/transit server/src/app.module.ts server/tests
|
||||||
|
git commit -m "feat: add amap route and transit services"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 4: 切换客户端搜索、路线和高德外链
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `client/src/components/Planner/placeAmap.ts`
|
||||||
|
- Create: `client/src/components/Planner/placeAmap.test.ts`
|
||||||
|
- Modify: `client/src/api/client.ts`
|
||||||
|
- Modify: `client/src/hooks/useRouteCalculation.ts`
|
||||||
|
- Modify: `client/src/hooks/useTransportRoutes.ts`
|
||||||
|
- Modify: `client/src/components/Map/RouteCalculator.ts`
|
||||||
|
- Modify: `client/src/components/Map/transitGeometry.ts`
|
||||||
|
- Modify: `client/src/components/Planner/TransitSearchPanel.tsx`
|
||||||
|
- Modify: `client/src/components/Planner/PlaceInspector.tsx`
|
||||||
|
- Delete: `client/src/components/Planner/placeGoogleMaps.ts`
|
||||||
|
- Delete: `client/src/components/Map/RouteCalculator.test.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: Task 2 map endpoints and Task 3 route endpoints.
|
||||||
|
- Produces: no browser-to-OSRM request; all external map links are AMap URI URLs.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing client API and URI tests**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('creates an AMap URI with GCJ longitude before latitude', () => {
|
||||||
|
expect(amapPlaceUri({ name: '天安门', lat: 39.908823, lng: 116.39747 })).toContain('116.')
|
||||||
|
})
|
||||||
|
it('calls /api/routes/plan instead of an OSRM host', async () => {
|
||||||
|
await calculateRouteWithLegs(points)
|
||||||
|
expect(lastRequest.url).toContain('/api/routes/plan')
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run failing focused tests**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=client -- placeAmap.test.ts RouteCalculator.test.ts useTransportRoutes.test.ts`
|
||||||
|
Expected: FAIL before the client is migrated.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Replace direct provider calls and adapt transit geometry**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const routesApi = { plan: (body: RoutePlanRequest) => apiClient.post('/routes/plan', body).then(r => r.data) }
|
||||||
|
export function amapPlaceUri(place: Wgs84Point & { name: string }): string { /* WGS→GCJ then URLSearchParams */ }
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run planner and transport tests**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=client -- src/components/Planner/TransitSearchPanel.test.tsx src/components/Planner/PlaceInspector.test.tsx tests/integration/hooks/useTransportRoutes.test.ts`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add client/src/api/client.ts client/src/hooks client/src/components/Map client/src/components/Planner
|
||||||
|
git commit -m "feat: use amap for client routes and links"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 5: 用单一高德地图组件替换地图渲染器
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `client/src/components/Map/amap/loadAmap.ts`
|
||||||
|
- Create: `client/src/components/Map/amap/AmapMapView.tsx`
|
||||||
|
- Create: `client/src/components/Map/amap/AmapMapView.test.tsx`
|
||||||
|
- Create: `client/src/components/Map/amap/amapOverlays.ts`
|
||||||
|
- Modify: `client/src/components/Map/MapViewAuto.tsx`
|
||||||
|
- Modify: `client/src/components/Journey/JourneyMapAuto.tsx`
|
||||||
|
- Modify: `client/src/components/Journey/JourneyMap.tsx`
|
||||||
|
- Modify: `client/src/components/Collections/CollectionMap.tsx`
|
||||||
|
- Modify: `client/src/pages/SharedTripPage.tsx`
|
||||||
|
- Delete: `client/src/components/Map/MapView.tsx`
|
||||||
|
- Delete: `client/src/components/Map/MapViewGL.tsx`
|
||||||
|
- Delete: `client/src/components/Journey/JourneyMapGL.tsx`
|
||||||
|
- Delete: `client/src/components/Map/glProviders.ts`
|
||||||
|
- Delete: `client/src/components/Map/mapboxSetup.ts`
|
||||||
|
- Delete: `client/src/components/Map/locationMarkerMapbox.ts`
|
||||||
|
- Delete: `client/src/components/Map/reservationsMapbox.ts`
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Consumes: WGS-84 props from existing consumers.
|
||||||
|
- Produces: an AMap-only map with WGS-to-GCJ conversion entirely inside the map boundary.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing loader and rendering tests**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
it('shows the missing-key state without loading a map provider', async () => {
|
||||||
|
render(<AmapMapView places={[]} />)
|
||||||
|
expect(await screen.findByText(/高德地图未配置/i)).toBeVisible()
|
||||||
|
})
|
||||||
|
it('converts WGS place coordinates before creating an AMap marker', async () => {
|
||||||
|
render(<AmapMapView places={[beijingPlace]} />)
|
||||||
|
await waitFor(() => expect(AMap.Marker).toHaveBeenCalledWith(expect.objectContaining({ position: expect.anything() })))
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the failing component test**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=client -- AmapMapView.test.tsx`
|
||||||
|
Expected: FAIL because the loader and component do not exist.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Implement loader, security serviceHost configuration, and overlays**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
window._AMapSecurityConfig = { serviceHost: '/api/amap/security' }
|
||||||
|
await loadScript(`https://webapi.amap.com/maps?v=2.0&key=${import.meta.env.VITE_AMAP_JS_API_KEY}`)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Switch all map consumers and run focused UI tests**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=client -- src/components/Map src/components/Journey/JourneyMap.test.tsx src/pages/SharedTripPage.test.tsx`
|
||||||
|
Expected: PASS; no Leaflet/Mapbox/MapLibre module mock remains.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add client/src/components/Map client/src/components/Journey client/src/components/Collections client/src/pages/SharedTripPage.tsx
|
||||||
|
git commit -m "feat: replace map renderers with amap"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 6: 处理 Atlas 与离线地图下线
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `client/src/pages/atlas/useAtlas.ts`
|
||||||
|
- Modify: `client/src/pages/AtlasPage.test.tsx`
|
||||||
|
- Modify: `client/src/sync/tilePrefetcher.ts`
|
||||||
|
- Modify: `client/src/sync/tripSyncManager.ts`
|
||||||
|
- Modify: `client/src/components/Settings/OfflineTab.tsx`
|
||||||
|
- Modify: `client/vite.config.js`
|
||||||
|
- Delete: offline tile prefetch tests and rules that only cache old providers.
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: Atlas either uses supported AMap overlay primitives for supported views or presents an explicit unsupported-world-map state; no CARTO tile request is issued.
|
||||||
|
- Produces: no predownloaded third-party map tile cache.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add failing regression tests for provider removal**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('does not request a CARTO tile URL when Atlas mounts', async () => { render(<AtlasPage />); expect(fetchMock).not.toHaveBeenCalledWith(expect.stringMatching(/cartocdn/)) })
|
||||||
|
it('does not enqueue map tile downloads for offline sync', () => expect(buildTilePrefetchPlan(trip)).toEqual([]))
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run the failing tests**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=client -- AtlasPage.test.tsx`
|
||||||
|
Expected: FAIL while Leaflet/CARTO and tile prefetching remain.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Remove unsupported Leaflet/CARTO and offline XYZ behaviors**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Atlas must use the shared AMap boundary or render an explicit unsupported state.
|
||||||
|
// Offline sync must not create map-tile jobs.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Run Atlas, offline, and build checks**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=client -- AtlasPage.test.tsx && npm run build --workspace=client`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add client/src/pages/atlas client/src/sync client/src/components/Settings/OfflineTab.tsx client/vite.config.js
|
||||||
|
git commit -m "feat: remove legacy atlas tiles and offline maps"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 7: 删除旧设置、导入、MCP 和供应商依赖
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `client/src/types.ts`
|
||||||
|
- Modify: `client/src/store/settingsStore.ts`
|
||||||
|
- Modify: `client/src/components/Settings/MapSettingsTab.tsx`
|
||||||
|
- Modify: `client/src/components/Admin/DefaultUserSettingsTab.tsx`
|
||||||
|
- Modify: `server/src/services/settingsService.ts`
|
||||||
|
- Modify: `server/src/nest/places/places.controller.ts`
|
||||||
|
- Modify: `server/src/nest/places/places.service.ts`
|
||||||
|
- Modify: `server/src/mcp/tools/mapsWeather.ts`
|
||||||
|
- Modify: `server/src/mcp/tools/places.ts`
|
||||||
|
- Modify: `server/src/mcp/tools/days.ts`
|
||||||
|
- Modify: `server/src/db/migrations.ts`
|
||||||
|
- Modify: `client/package.json`
|
||||||
|
- Modify: all affected i18n/test/docs files from the provider scan.
|
||||||
|
- Delete: `client/src/components/Settings/MapboxPreview.tsx`, Google/Naver import UI and tests.
|
||||||
|
|
||||||
|
**Interfaces:**
|
||||||
|
- Produces: settings APIs accept only map-independent settings; historical old values are ignored safely.
|
||||||
|
- Produces: no Google/Naver list import or Google URL resolution endpoint.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Write failing settings and import-removal tests**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
it('does not expose a map provider or Mapbox token setting', () => expect(defaultSettings()).not.toHaveProperty('mapbox_access_token'))
|
||||||
|
it('rejects removed Google list import route with 404', async () => await request(app).post('/api/places/import/google-list').expect(404))
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run focused tests to establish failures**
|
||||||
|
|
||||||
|
Run: `npm run test --workspace=server -- tests/integration/settings.test.ts tests/integration/places.test.ts && npm run test --workspace=client -- MapSettingsTab.test.tsx`
|
||||||
|
Expected: FAIL until old settings/routes are removed.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Remove settings, imports, packages, and old runtime configuration**
|
||||||
|
|
||||||
|
```json
|
||||||
|
// Remove leaflet, react-leaflet, mapbox-gl and maplibre-gl from client/package.json.
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Regenerate lockfile and run targeted suite**
|
||||||
|
|
||||||
|
Run: `npm install --package-lock-only && npm run test --workspace=server -- tests/integration/settings.test.ts tests/integration/places.test.ts && npm run test --workspace=client -- MapSettingsTab.test.tsx`
|
||||||
|
Expected: PASS.
|
||||||
|
|
||||||
|
- [ ] **Step 5: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add client server shared package.json package-lock.json
|
||||||
|
git commit -m "refactor: remove legacy map providers"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task 8: 全仓验证、预发冒烟与文档
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `.env.example` or deployment documentation containing map configuration.
|
||||||
|
- Modify: `README.md` and affected feature docs.
|
||||||
|
- Modify: tests and MSW fixtures identified by the final provider scan.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add a provider-removal guard**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rg -n -i '(mapbox|maplibre|leaflet|nominatim|overpass|project-osrm|routing\.openstreetmap|transitous|motis|places\.googleapis|google maps|naver)' client/src server/src shared/src client/package.json server/package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: only explicitly documented historical-data compatibility comments; no active code, dependency, URL, setting, test fixture, or CSP rule.
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run complete verification**
|
||||||
|
|
||||||
|
Run: `npm run lint && npm run test && npm run build`
|
||||||
|
Expected: all workspaces pass.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run pre-production real-key smoke checklist**
|
||||||
|
|
||||||
|
1. Set the three AMap environment variables only in deployment secrets.
|
||||||
|
2. Verify Gitea-hosted staging app loads AMap JS map and no key reaches logs.
|
||||||
|
3. Search and save a China POI; verify marker and generated AMap URI location.
|
||||||
|
4. Verify driving, walking, cycling and transit paths.
|
||||||
|
5. Verify shared trip, collection, Journey and Atlas behavior.
|
||||||
|
6. Verify overseas and cross-border requests show the specified unsupported state.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit documentation and test guard**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add README.md docs .env.example client server shared
|
||||||
|
git commit -m "docs: document amap-only configuration"
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue
Block a user