# 数据请求策略优化总结 ## 📋 问题发现 ### 原始问题 根据最初的规划,为了减少数据量: 1. **时间轴**: 只显示每天 **00:00:00** 的位置数据 2. **首页**: 只显示用户打开时**当前小时**的位置数据 但实际实现中存在以下问题: - ❌ 时间轴请求了**范围数据**(从 day_start 到 day_end),导致返回多个时间点 - ❌ 首页请求了**最近 24 小时**的所有数据,而不是单个时间点 - ❌ positions 表中存储了大量冗余数据(每天多个时间点) --- ## ✅ 解决方案 ### 核心策略 **所有请求都改为单个时间点查询**:`start_time = end_time` 这样 NASA Horizons API 只返回单个时间点的数据,而不是时间范围内的多个点。 --- ## 🔧 具体修改 ### 1. 前端修改 #### 1.1 首页数据请求 (`frontend/src/hooks/useSpaceData.ts`) **修改前**: ```tsx // 无参数请求,后端返回最近 24 小时的数据 const data = await fetchCelestialPositions(); ``` **修改后**: ```tsx // 请求当前小时的单个时间点 const now = new Date(); now.setMinutes(0, 0, 0); // 圆整到小时 const data = await fetchCelestialPositions( now.toISOString(), now.toISOString(), // start = end,单个时间点 '1h' ); ``` **API 请求示例**: ```http GET /api/celestial/positions?start_time=2025-11-29T12:00:00Z&end_time=2025-11-29T12:00:00Z&step=1h ``` **返回数据**: 每个天体 1 个位置点(当前小时) --- #### 1.2 时间轴数据请求 (`frontend/src/hooks/useHistoricalData.ts`) **修改前**: ```tsx const startDate = new Date(date); const endDate = new Date(date); endDate.setDate(endDate.getDate() + 1); // +1 天 const data = await fetchCelestialPositions( startDate.toISOString(), // 2025-01-15T00:00:00Z endDate.toISOString(), // 2025-01-16T00:00:00Z ❌ 多了1天 '1d' ); ``` **修改后**: ```tsx // 圆整到 UTC 午夜 const targetDate = new Date(date); targetDate.setUTCHours(0, 0, 0, 0); // start = end,只请求午夜这个时间点 const data = await fetchCelestialPositions( targetDate.toISOString(), // 2025-01-15T00:00:00Z targetDate.toISOString(), // 2025-01-15T00:00:00Z ✅ 单个时间点 '1d' ); ``` **API 请求示例**: ```http GET /api/celestial/positions?start_time=2025-01-15T00:00:00Z&end_time=2025-01-15T00:00:00Z&step=1d ``` **返回数据**: 每个天体 1 个位置点(2025-01-15 00:00:00) --- ### 2. 后端缓存预热修改 #### 2.1 当前位置预热 (`backend/app/services/cache_preheat.py`) **策略变更**: - **修改前**: 加载最近 24 小时的所有数据 - **修改后**: 只加载当前小时最接近的单个时间点 **实现逻辑**: ```python # 当前小时 now = datetime.utcnow() current_hour = now.replace(minute=0, second=0, microsecond=0) # 搜索窗口: 当前小时 ± 1 小时 start_window = current_hour - timedelta(hours=1) end_window = current_hour + timedelta(hours=1) # 找到最接近当前小时的位置 closest_pos = min( recent_positions, key=lambda p: abs((p.time - current_hour).total_seconds()) ) ``` **Redis Key**: ``` positions:2025-11-29T12:00:00+00:00:2025-11-29T12:00:00+00:00:1h ``` --- #### 2.2 历史位置预热 **策略变更**: - **修改前**: 每天加载所有时间点的数据 - **修改后**: 每天只加载 00:00:00 这个时间点 **实现逻辑**: ```python # 目标时间: 当天的午夜 (00:00:00) target_midnight = target_day.replace(hour=0, minute=0, second=0, microsecond=0) # 搜索窗口: 午夜 ± 30 分钟 search_start = target_midnight - timedelta(minutes=30) search_end = target_midnight + timedelta(minutes=30) # 找到最接近午夜的位置 closest_pos = min( positions, key=lambda p: abs((p.time - target_midnight).total_seconds()) ) ``` **Redis Key**: ``` positions:2025-11-26T00:00:00+00:00:2025-11-26T00:00:00+00:00:1d positions:2025-11-27T00:00:00+00:00:2025-11-27T00:00:00+00:00:1d positions:2025-11-28T00:00:00+00:00:2025-11-28T00:00:00+00:00:1d ``` --- ## 📊 数据量对比 ### 首页数据 | 指标 | 修改前 | 修改后 | 减少 | |------|--------|--------|------| | 时间范围 | 最近 24 小时 | 当前 1 小时 | - | | 每个天体位置点数 | 可能 1-24 个 | 1 个 | **96%** ⬇️ | | 总数据量(20 天体) | 20-480 个点 | 20 个点 | **96%** ⬇️ | ### 时间轴数据(3 天) | 指标 | 修改前 | 修改后 | 减少 | |------|--------|--------|------| | 每天每个天体位置点数 | 2 个(00:00 和 24:00) | 1 个(00:00) | **50%** ⬇️ | | 3 天总数据量(20 天体) | 120 个点 | 60 个点 | **50%** ⬇️ | ### positions 表数据量(假设每小时更新一次) | 场景 | 每个天体每天记录数 | 20 个天体每天总记录数 | 一年总记录数 | |------|-------------------|---------------------|-------------| | **首页**(每小时 1 条) | 24 | 480 | 175,200 | | **时间轴**(每天 1 条) | 1 | 20 | 7,300 | --- ## 🎯 建议的数据管理策略 ### 策略 1: 清空并重建 positions 表(推荐) **原因**: - 当前表中可能有大量冗余数据 - 重新开始可以确保数据质量 **步骤**: ```sql -- 1. 清空 positions 表 TRUNCATE TABLE positions; -- 2. 清空 nasa_cache 表(可选) TRUNCATE TABLE nasa_cache; ``` **重新获取数据**: ```bash # 1. 清空 Redis 缓存 curl -X POST "http://localhost:8000/api/celestial/cache/clear" # 2. 访问首页触发数据获取(当前小时) # 打开 http://localhost:5173 # 3. 访问时间轴触发数据获取(过去 3 天的午夜数据) # 点击"时间轴"按钮 ``` --- ### 策略 2: 定期更新数据(管理后台实现) #### 2.1 每小时更新当前位置 ```python @scheduler.scheduled_job('cron', minute=0) # 每小时整点 async def update_current_positions(): """每小时更新一次所有天体的位置""" now = datetime.utcnow() current_hour = now.replace(minute=0, second=0, microsecond=0) for body in all_bodies: # 查询 NASA API(单个时间点) positions = horizons_service.get_body_positions( body.id, current_hour, current_hour, # start = end "1h" ) # 保存到数据库 await position_service.save_positions( body.id, positions, "nasa_horizons", db ) # 预热缓存 await preheat_current_positions() ``` **数据量**: 20 天体 × 1 条/小时 × 24 小时 = **480 条/天** --- #### 2.2 每天凌晨更新历史数据(时间轴) ```python @scheduler.scheduled_job('cron', hour=0, minute=0) # 每天凌晨 async def update_midnight_positions(): """每天凌晨更新所有天体的午夜位置""" now = datetime.utcnow() midnight = now.replace(hour=0, minute=0, second=0, microsecond=0) for body in all_bodies: # 查询 NASA API(单个时间点) positions = horizons_service.get_body_positions( body.id, midnight, midnight, # start = end "1d" ) # 保存到数据库 await position_service.save_positions( body.id, positions, "nasa_horizons", db ) # 预热历史缓存(3天) await preheat_historical_positions(days=3) ``` **数据量**: 20 天体 × 1 条/天 = **20 条/天** --- ### 策略 3: 数据清理(可选) 定期清理旧数据以节省存储空间: ```python @scheduler.scheduled_job('cron', hour=3, minute=0) # 每天凌晨 3 点 async def cleanup_old_positions(): """清理 30 天前的位置数据""" cutoff_date = datetime.utcnow() - timedelta(days=30) # 删除旧数据 await db.execute( "DELETE FROM positions WHERE time < :cutoff", {"cutoff": cutoff_date} ) logger.info(f"Cleaned up positions older than {cutoff_date.date()}") ``` --- ## 📈 性能优化效果 ### 数据传输量 | 场景 | 修改前 | 修改后 | 优化 | |------|--------|--------|------| | 首页加载(20 天体) | ~5-50KB | ~2KB | **75-95%** ⬇️ | | 时间轴加载(3 天,20 天体) | ~10KB | ~6KB | **40%** ⬇️ | ### 数据库存储 | 周期 | 修改前 | 修改后 | 减少 | |------|--------|--------|------| | 每天新增记录 | 不确定(混乱) | 500 条(首页 480 + 时间轴 20) | - | | 每年总记录数 | 不确定 | ~18万 | - | --- ## ✅ 总结 ### 关键改进 1. ✅ **单点查询策略**: 所有请求都改为 `start_time = end_time` 2. ✅ **首页优化**: 只请求当前小时的单个时间点 3. ✅ **时间轴优化**: 只请求每天 00:00:00 的单个时间点 4. ✅ **缓存预热优化**: 预热逻辑匹配单点查询策略 5. ✅ **数据量减少**: 减少 50-96% 的数据传输和存储 ### 数据规范 | 场景 | 时间点 | 频率 | 用途 | |------|--------|------|------| | **首页** | 每小时整点(XX:00:00) | 每小时更新 1 次 | 显示当前位置 | | **时间轴** | 每天午夜(00:00:00) | 每天更新 1 次 | 显示历史轨迹 | ### 下一步操作 1. ⚠️ **重启前端和后端**,应用新的请求逻辑 2. ⚠️ **清空 positions 表**(可选但推荐),确保数据干净 3. ✅ **测试首页和时间轴**,验证数据正确性 4. ✅ **在管理后台实现定时任务**,每小时/每天更新数据 --- **文档版本**: v1.0 **最后更新**: 2025-11-29 **作者**: Cosmo Team