diff --git a/ARCHITECTURE_PLAN.md b/ARCHITECTURE_PLAN.md deleted file mode 100644 index 8f57f04..0000000 --- a/ARCHITECTURE_PLAN.md +++ /dev/null @@ -1,392 +0,0 @@ -# Cosmo 平台架构升级规划 - -## 一、现状分析 - -### 当前架构 -- **前端数据**:静态JSON文件(galaxies, constellations, stars, probe-models) -- **后端数据**:NASA Horizons API实时查询 + 简单内存缓存 -- **资源存储**:纹理和3D模型在前端 `public/` 目录 -- **缓存策略**:内存缓存(进程级别,重启失效) - -### 痛点 -1. **数据管理分散**:前端JSON + 后端代码硬编码 -2. **缓存不持久**:服务重启后需要重新查询NASA API -3. **资源管理混乱**:纹理、模型路径分散在前后端 -4. **扩展困难**:添加新天体类型需要修改多处代码 -5. **无历史数据**:无法查询天体历史轨迹 -6. **性能问题**:NASA API查询慢(每个天体1-2秒) - -### 未来需求 -- 支持更多天体类型(彗星、小行星、系外行星等) -- 用户自定义天体 -- 历史轨迹查询和时间旅行 -- 性能优化(减少NASA API调用) -- 统一的资源管理 -- 可能的多用户支持 - ---- - -## 二、技术方案 - -### 2.1 数据库方案 - -#### 推荐:PostgreSQL + SQLAlchemy - -**选择理由**: -1. **功能强大**:支持复杂查询、全文搜索、JSON字段 -2. **PostGIS扩展**:专门处理空间数据(未来可能需要) -3. **时间序列优化**:通过TimescaleDB扩展支持高效时间序列查询 -4. **成熟生态**:Python生态支持好(asyncpg, SQLAlchemy 2.0异步支持) -5. **扩展性**:支持大规模数据和并发 - -**备选方案**: -- **SQLite**:适合单机部署,但功能和性能有限 -- **MongoDB**:文档型数据库,但对关系查询支持不如PostgreSQL - -#### 数据库设计 - -```sql --- 天体类型枚举 -CREATE TYPE celestial_type AS ENUM ('star', 'planet', 'moon', 'probe', 'comet', 'asteroid', 'galaxy', 'constellation'); - --- 天体基本信息表 -CREATE TABLE celestial_bodies ( - id VARCHAR(50) PRIMARY KEY, -- JPL Horizons ID 或自定义ID - name VARCHAR(200) NOT NULL, - name_zh VARCHAR(200), - type celestial_type NOT NULL, - description TEXT, - metadata JSONB, -- 灵活存储各种元数据(launch_date, status等) - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() -); - --- 位置历史表(时间序列数据) -CREATE TABLE positions ( - id BIGSERIAL PRIMARY KEY, - body_id VARCHAR(50) REFERENCES celestial_bodies(id), - time TIMESTAMP NOT NULL, - x DOUBLE PRECISION NOT NULL, -- AU - y DOUBLE PRECISION NOT NULL, - z DOUBLE PRECISION NOT NULL, - source VARCHAR(50), -- 'nasa_horizons', 'calculated', 'user_defined' - created_at TIMESTAMP DEFAULT NOW() -); -CREATE INDEX idx_positions_body_time ON positions(body_id, time DESC); - --- 资源管理表(纹理、模型等) -CREATE TABLE resources ( - id SERIAL PRIMARY KEY, - body_id VARCHAR(50) REFERENCES celestial_bodies(id), - type VARCHAR(50) NOT NULL, -- 'texture', 'model', 'icon' - file_path VARCHAR(500) NOT NULL, -- 相对于upload目录的路径 - file_size INTEGER, - mime_type VARCHAR(100), - metadata JSONB, -- 分辨率、格式等信息 - created_at TIMESTAMP DEFAULT NOW() -); - --- 静态数据表(星座、星系等不变数据) -CREATE TABLE static_data ( - id SERIAL PRIMARY KEY, - category VARCHAR(50) NOT NULL, -- 'constellation', 'galaxy', 'star' - name VARCHAR(200) NOT NULL, - name_zh VARCHAR(200), - data JSONB NOT NULL, -- 完整的静态数据 - created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() -); - --- NASA API缓存表(持久化缓存) -CREATE TABLE nasa_cache ( - cache_key VARCHAR(500) PRIMARY KEY, - body_id VARCHAR(50), - start_time TIMESTAMP, - end_time TIMESTAMP, - step VARCHAR(10), - data JSONB NOT NULL, - expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP DEFAULT NOW() -); -CREATE INDEX idx_nasa_cache_expires ON nasa_cache(expires_at); -``` - -### 2.2 缓存策略 - -#### 三层缓存架构 - -``` -请求 → L1 (内存) → L2 (Redis) → L3 (数据库) → L4 (NASA API) -``` - -**L1: 进程内存缓存**(已有) -- TTL: 10分钟 -- 用途:当前时间的天体位置(最热数据) -- 实现:Python dict + TTL(已有cache.py) - -**L2: Redis缓存**(新增) -- TTL: - - 当前时间数据:1小时 - - 历史数据:7天 - - 静态数据:永久(手动失效) -- 用途: - - NASA API响应缓存 - - 会话数据 - - 预计算结果 -- 好处: - - 进程间共享 - - 持久化(重启不丢失) - - 分布式支持 - -**L3: PostgreSQL数据库** -- 持久化存储 -- 历史数据查询 -- 复杂查询和统计 - -**L4: NASA Horizons API** -- 最终数据源 -- 只在缓存未命中时调用 - -#### 缓存键设计 - -```python -# L1/L2 缓存键格式 -"positions:{body_id}:{start}:{end}:{step}" -"static:{category}:{name}" -"texture:{body_id}:{type}" - -# 示例 -"positions:-31:2025-11-27:2025-11-27:1d" -"static:constellation:orion" -``` - -### 2.3 文件存储方案 - -#### 目录结构 - -``` -cosmo/ -├── backend/ -│ ├── upload/ # 统一上传目录 -│ │ ├── textures/ -│ │ │ ├── planets/ # 行星纹理 -│ │ │ ├── stars/ # 恒星纹理 -│ │ │ └── probes/ # 探测器图标 -│ │ ├── models/ -│ │ │ ├── probes/ # 探测器3D模型 -│ │ │ └── spacecraft/ -│ │ └── data/ # 数据文件备份 -│ └── app/ -│ └── api/ -│ └── static.py # 静态文件服务API -``` - -#### 文件访问 - -```python -# 后端提供统一的静态文件API -GET /api/static/textures/planets/earth.jpg -GET /api/static/models/probes/voyager1.glb - -# 数据库记录 -{ - "body_id": "399", - "type": "texture", - "file_path": "textures/planets/2k_earth_daymap.jpg", - "url": "/api/static/textures/planets/2k_earth_daymap.jpg" -} -``` - -### 2.4 数据迁移路径 - -#### 阶段1:数据库基础设施(1-2天) -1. 安装PostgreSQL和Redis -2. 设置SQLAlchemy ORM -3. 创建数据库表结构 -4. 数据迁移脚本: - - `CELESTIAL_BODIES` dict → `celestial_bodies` 表 - - 前端JSON文件 → `static_data` 表 - -#### 阶段2:缓存层升级(1天) -1. 集成Redis客户端 -2. 实现三层缓存逻辑 -3. NASA API结果持久化到数据库 - -#### 阶段3:资源管理迁移(1天) -1. 迁移纹理文件到 `backend/upload/textures/` -2. 迁移3D模型到 `backend/upload/models/` -3. 建立资源表记录 -4. 实现静态文件服务API - -#### 阶段4:API重构(1-2天) -1. 新增数据库查询API -2. 前端调整为从后端API获取所有数据 -3. 移除前端静态JSON文件依赖 - -#### 阶段5:优化和测试(1天) -1. 性能测试 -2. 缓存命中率监控 -3. 数据一致性验证 - ---- - -## 三、技术栈 - -### 后端新增依赖 - -#### Python包 -```bash -# ORM和数据库 -sqlalchemy>=2.0.0 # ORM框架(支持async) -asyncpg>=0.29.0 # PostgreSQL异步驱动 -alembic>=1.12.0 # 数据库迁移工具 - -# Redis缓存 -redis>=5.0.0 # Redis客户端 -aioredis>=2.0.0 # 异步Redis(可选,redis 5.0+已内置async支持) - -# 文件处理 -python-multipart>=0.0.6 # 文件上传支持 -aiofiles>=23.0.0 # 异步文件操作 -Pillow>=10.0.0 # 图片处理(缩略图等) -``` - -#### 系统依赖 - -**PostgreSQL 15+** -```bash -# macOS -brew install postgresql@15 -brew services start postgresql@15 - -# 创建数据库 -createdb cosmo_db -``` - -**Redis 7+** -```bash -# macOS -brew install redis -brew services start redis - -# 验证 -redis-cli ping # 应返回 PONG -``` - -### 前端调整 -- 移除静态JSON文件依赖 -- 所有数据通过API获取 -- 静态资源URL指向后端API - ---- - -## 四、性能优化 - -### 预期改进 -1. **NASA API调用减少90%**:通过数据库+Redis缓存 -2. **首次加载加速**:从缓存/数据库读取,无需等待NASA API -3. **支持历史查询**:数据库存储历史位置数据 -4. **并发能力提升**:Redis支持分布式缓存 - -### 监控指标 -- 缓存命中率(L1/L2/L3) -- NASA API调用次数 -- 数据库查询时间 -- API响应时间 - ---- - -## 五、成本分析 - -### 开发成本 -- 总工时:约6-7天 -- 可分阶段实施,每阶段独立可用 - -### 运行成本 -- PostgreSQL:~100MB内存(小规模) -- Redis:~50MB内存 -- 磁盘:~500MB-1GB(数据+资源文件) - -### 维护成本 -- 数据库备份:每日自动备份 -- 缓存清理:自动过期,无需人工干预 -- 资源管理:统一后端管理,更容易维护 - ---- - -## 六、风险和备选方案 - -### 风险 -1. **PostgreSQL依赖**:需要额外安装和维护 - - 备选:先用SQLite,后续迁移 -2. **数据迁移复杂度**:现有数据分散 - - 缓解:编写完善的迁移脚本和回滚方案 -3. **Redis单点故障**:Redis挂了影响性能 - - 缓解:Redis只是缓存,挂了仍可从数据库读取 - -### 回滚方案 -- 保留现有代码分支 -- 数据库和缓存作为可选功能 -- 降级到内存缓存 + NASA API直连 - ---- - -## 七、实施建议 - -### 推荐方案:**完整实施** -理由: -1. 项目正处于扩展期,早期投入架构收益大 -2. PostgreSQL+Redis是成熟方案,风险可控 -3. 支持未来功能扩展(用户系统、自定义天体等) -4. 性能提升明显(缓存命中后响应 <50ms) - -### 简化方案(如果资源有限) -1. **只用PostgreSQL,不用Redis** - - 降级为两层:内存 → 数据库 → NASA API - - 仍可实现持久化和历史查询 - - 性能略低但可接受 - -2. **只用Redis,不用PostgreSQL** - - 只做缓存,不做持久化 - - 适合小规模、不需要历史数据的场景 - - 不推荐(失去了数据管理能力) - ---- - -## 八、下一步 - -确认方案后,我将: - -1. **准备安装脚本**:自动化安装PostgreSQL和Redis -2. **生成数据库Schema**:完整的SQL DDL -3. **编写迁移脚本**:将现有数据导入数据库 -4. **实现缓存层**:三层缓存逻辑 -5. **重构API**:支持数据库查询 -6. **迁移静态资源**:统一到后端管理 - ---- - -## 附录:配置示例 - -### PostgreSQL连接配置 -```python -# .env -DATABASE_URL=postgresql+asyncpg://cosmo:password@localhost:5432/cosmo_db -``` - -### Redis连接配置 -```python -# .env -REDIS_URL=redis://localhost:6379/0 -``` - -### 数据库连接池配置 -```python -# app/database.py -engine = create_async_engine( - DATABASE_URL, - pool_size=20, - max_overflow=10, - pool_pre_ping=True, -) -``` diff --git a/COMET_IMPLEMENTATION_SUMMARY.md b/COMET_IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index ed6292e..0000000 --- a/COMET_IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,165 +0,0 @@ -# 彗星类型实现总结 - -## 概述 -成功为 Cosmo 系统添加了彗星 (comet) 天体类型的完整支持。 - -## 修改文件清单 - -### 1. 数据库相关 -- ✅ `backend/app/models/db/celestial_body.py` - 数据库模型已支持 'comet' 类型(CheckConstraint) -- 📄 `add_comet_type.sql` - SQL 迁移脚本(如需更新现有数据库约束) - -### 2. 后端 API (Backend) -- ✅ `backend/app/models/celestial.py` - - Line 24: 添加 'comet' 到 CelestialBody 的 type Literal - - Line 46: 添加 'comet' 到 BodyInfo 的 type Literal - -### 3. 管理平台 (Admin Frontend) -- ✅ `frontend/src/pages/admin/CelestialBodies.tsx` - - Line 264: 添加彗星筛选器选项 - - Line 274: 添加类型映射 `comet: '彗星'` - - Line 393: 添加表单中的彗星选项 - -### 4. 前端可视化 (Frontend) -- ✅ `frontend/src/types/index.ts` - - Line 5: 添加 'comet' 到 CelestialBodyType 类型定义 - -- ✅ `frontend/src/config/celestialSizes.ts` - - Line 45-50: 添加 COMET_SIZES 配置对象 - - Line 65: 在 getCelestialSize 函数中添加 comet 分支 - -- ✅ `frontend/src/components/ProbeList.tsx` - - Line 2: 导入 Sparkles 图标 - - Line 45: 添加 cometList 过滤 - - Line 152-163: 添加彗星分组显示 - -- ✅ `frontend/src/App.tsx` - - Line 99: 在 planets 过滤器中包含 'comet' 类型 - -## 功能特性 - -### 1. 数据库层 -- 支持 'comet' 作为有效的天体类型 -- 自动验证类型约束 - -### 2. 管理界面 -- 可以创建、编辑、删除彗星天体 -- 支持彗星类型筛选 -- 显示中文名称"彗星" -- 支持上传彗星纹理和资源 - -### 3. 前端显示 -- 彗星在左侧导航栏独立分组显示(带 ✨ Sparkles 图标) -- 彗星使用 CelestialBody 组件渲染 -- 默认渲染大小: 0.12 单位(与卫星类似大小) -- 支持为特定彗星配置自定义大小(如 Halley: 0.15) - -## 使用指南 - -### 1. 数据库迁移(如需要) -```bash -psql -h localhost -U postgres -d cosmo -f add_comet_type.sql -``` - -### 2. 重启服务 -确保后端服务重新加载以应用 Pydantic 模型更改: -```bash -# 重启后端服务 -``` - -### 3. 添加彗星示例 -在管理平台中: -1. 进入"天体数据管理" -2. 点击"新增" -3. 选择类型:"彗星" -4. 填写信息: - - ID: 例如 "90000034" (哈雷彗星的 JPL ID) - - 英文名: Halley - - 中文名: 哈雷彗星 - - 描述: 最著名的周期彗星,约76年回归一次 - -### 4. 在前端查看 -- 彗星会出现在左侧天体导航的"彗星"分组中 -- 点击即可聚焦查看 -- 在 3D 场景中渲染为小型天体 - -## 默认配置 - -### 彗星渲染大小 -```typescript -export const COMET_SIZES: Record = { - Halley: 0.15, // 哈雷彗星 - default: 0.12, // 默认彗星大小 -}; -``` - -### 支持的彗星类型特征 -- 自动归类到非探测器天体(celestialBodies) -- 使用 CelestialBody 组件渲染(支持纹理贴图) -- 支持轨道显示(如果配置了轨道数据) -- 支持历史位置查询 - -## 技术细节 - -### 类型验证链 -1. **数据库层**: PostgreSQL CheckConstraint 验证 -2. **ORM 层**: SQLAlchemy 模型定义 -3. **API 层**: Pydantic Literal 类型验证 -4. **前端层**: TypeScript 联合类型 - -### 数据流 -``` -管理平台创建彗星 - → API 验证 (Pydantic) - → 数据库存储 (PostgreSQL) - → API 返回数据 - → 前端过滤分组 - → 3D 场景渲染 -``` - -## 已解决的问题 - -### 问题 1: TypeScript 编译错误 -**错误**: `This comparison appears to be unintentional because the types 'CelestialBodyType' and '"comet"' have no overlap` -**原因**: `frontend/src/types/index.ts` 中的 CelestialBodyType 缺少 'comet' -**解决**: 添加 'comet' 到类型定义 - -### 问题 2: 后端 400 Bad Request -**错误**: API 返回 400 Bad Request -**原因**: Pydantic 模型的 Literal 类型不包含 'comet',导致验证失败 -**解决**: 在 `backend/app/models/celestial.py` 的两处 Literal 中添加 'comet' - -### 问题 3: 前端天体列表缺少彗星 -**原因**: `App.tsx` 中的 planets 过滤器未包含 'comet' 类型 -**解决**: 在过滤条件中添加 `|| b.type === 'comet'` - -## 测试建议 - -1. **创建测试**: - - 在管理平台创建一个彗星天体 - - 验证保存成功 - -2. **显示测试**: - - 刷新前端页面 - - 检查左侧导航是否有"彗星"分组 - - 点击彗星,验证能否正常聚焦 - -3. **API 测试**: - ```bash - curl http://localhost:8000/api/celestial/list?body_type=comet - ``` - -## 后续可能的增强 - -1. **彗星尾巴效果**: 添加粒子系统模拟彗星尾巴 -2. **轨道预计算**: 为著名彗星添加轨道数据 -3. **周期性管理**: 记录彗星的回归周期 -4. **近日点提醒**: 当彗星接近近日点时显示特殊效果 - -## 完成时间 -2025-11-30 - -## 修改文件数量 -- 后端: 2 个文件 -- 前端: 5 个文件 -- 文档: 2 个文件(SQL + 本总结) diff --git a/DUPLICATE_FIX.md b/DUPLICATE_FIX.md deleted file mode 100644 index 65a855c..0000000 --- a/DUPLICATE_FIX.md +++ /dev/null @@ -1,289 +0,0 @@ -# 数据库重复数据问题修复 - -## 问题描述 - -在测试过程中发现两个数据库表存在重复数据问题: - -### 1. positions 表重复数据 -``` -120192 399 2025-11-29 05:00:00 0.387... 0.907... -5.638e-05 nasa_horizons 2025-11-29 05:24:23.173 -120193 399 2025-11-29 05:00:00 0.387... 0.907... -5.638e-05 nasa_horizons 2025-11-29 05:24:23.175 -``` - -**原因**: 同一个天体在同一时刻有多条位置记录。 - -### 2. nasa_cache 表重复键错误 -``` -duplicate key value violates unique constraint "nasa_cache_pkey" -Key (cache_key)=(136199:2025-11-29T05:00:00+00:00:2025-11-29T05:00:00+00:00:1h) already exists. -``` - -**原因**: 尝试插入已存在的缓存键。 - ---- - -## 根本原因 - -### 并发竞态条件 - -当多个请求同时查询相同的时间点时: - -``` -时间线: -T1: 请求 A 查询 body_id=399, time=2025-11-29 05:00:00 -T2: 请求 B 查询 body_id=399, time=2025-11-29 05:00:00 -T3: 请求 A 检查数据库 -> 未找到 -> 准备插入 -T4: 请求 B 检查数据库 -> 未找到 -> 准备插入 -T5: 请求 A 插入记录(成功) -T6: 请求 B 插入记录(冲突!)❌ -``` - -### 原始代码问题 - -#### save_positions (旧版本) -```python -# ❌ 问题:直接添加,不检查是否存在 -for pos_data in positions: - position = Position(...) - s.add(position) # 可能重复 -await s.commit() -``` - -#### save_response (旧版本) -```python -# ❌ 问题:SELECT + INSERT 不是原子操作 -cache = await s.execute(select(...)).scalar_one_or_none() -if not cache: - cache = NasaCache(...) - s.add(cache) # 可能在 SELECT 和 INSERT 之间被插入 -await s.commit() -``` - ---- - -## 解决方案 - -使用 PostgreSQL 的 **UPSERT** 操作(`INSERT ... ON CONFLICT`),将检查和插入变为原子操作。 - -### 1. 修复 save_positions - -**文件**: `backend/app/services/db_service.py` - -```python -async def save_positions(...): - from sqlalchemy.dialects.postgresql import insert - - for pos_data in positions: - # 使用 UPSERT - stmt = insert(Position).values( - body_id=body_id, - time=pos_data["time"], - x=pos_data["x"], - y=pos_data["y"], - z=pos_data["z"], - ... - ) - - # 遇到冲突时更新 - stmt = stmt.on_conflict_do_update( - index_elements=['body_id', 'time'], # 唯一约束 - set_={ - 'x': pos_data["x"], - 'y': pos_data["y"], - 'z': pos_data["z"], - ... - } - ) - - await s.execute(stmt) -``` - -**关键点**: -- ✅ `on_conflict_do_update` 原子操作 -- ✅ 基于 `(body_id, time)` 唯一约束 -- ✅ 冲突时更新而不是报错 - ---- - -### 2. 修复 save_response - -**文件**: `backend/app/services/db_service.py` - -```python -async def save_response(...): - from sqlalchemy.dialects.postgresql import insert - - # 使用 UPSERT - stmt = insert(NasaCache).values( - cache_key=cache_key, - body_id=body_id, - start_time=start_naive, - end_time=end_naive, - step=step, - data=response_data, - expires_at=now_naive + timedelta(days=ttl_days) - ) - - # 遇到冲突时更新 - stmt = stmt.on_conflict_do_update( - index_elements=['cache_key'], # 主键 - set_={ - 'data': response_data, - 'created_at': now_naive, - 'expires_at': now_naive + timedelta(days=ttl_days) - } - ).returning(NasaCache) - - result = await s.execute(stmt) - cache = result.scalar_one() -``` - -**关键点**: -- ✅ `on_conflict_do_update` 原子操作 -- ✅ 基于 `cache_key` 主键 -- ✅ 冲突时更新数据和过期时间 - ---- - -## 数据库唯一约束验证 - -确保数据库表有正确的唯一约束: - -### positions 表 - -```sql --- 检查唯一约束 -SELECT constraint_name, constraint_type -FROM information_schema.table_constraints -WHERE table_name = 'positions' -AND constraint_type = 'UNIQUE'; - --- 如果没有,创建唯一约束 -ALTER TABLE positions -ADD CONSTRAINT positions_body_time_unique -UNIQUE (body_id, time); -``` - -### nasa_cache 表 - -```sql --- 检查主键 -SELECT constraint_name, constraint_type -FROM information_schema.table_constraints -WHERE table_name = 'nasa_cache' -AND constraint_type = 'PRIMARY KEY'; - --- cache_key 应该是主键,已有唯一约束 -``` - ---- - -## 清理现有重复数据 - -执行 SQL 脚本清理重复数据: - -```bash -psql -U postgres -d cosmo -f backend/scripts/cleanup_duplicates.sql -``` - -**脚本功能**: -1. 删除 positions 表中的重复记录(保留最新的) -2. 删除 nasa_cache 表中的重复记录(保留最新的) -3. 验证清理结果 - ---- - -## 验证修复效果 - -### 1. 重启后端服务 - -```bash -cd backend -python3 app/main.py -``` - -### 2. 测试并发请求 - -在两个终端同时执行相同的请求: - -```bash -# 终端 1 -curl "http://localhost:8000/api/celestial/positions?start_time=2025-11-29T12:00:00Z&end_time=2025-11-29T12:00:00Z&step=1h" - -# 终端 2(同时执行) -curl "http://localhost:8000/api/celestial/positions?start_time=2025-11-29T12:00:00Z&end_time=2025-11-29T12:00:00Z&step=1h" -``` - -**预期结果**: -- ✅ 两个请求都成功返回 -- ✅ 没有重复数据错误 -- ✅ 数据库中只有一条记录 - -### 3. 验证数据库 - -```sql --- 检查是否还有重复 -SELECT body_id, time, COUNT(*) -FROM positions -GROUP BY body_id, time -HAVING COUNT(*) > 1; --- 应返回 0 行 - -SELECT cache_key, COUNT(*) -FROM nasa_cache -GROUP BY cache_key -HAVING COUNT(*) > 1; --- 应返回 0 行 -``` - ---- - -## 性能优势 - -### UPSERT vs SELECT + INSERT - -| 操作 | SELECT + INSERT | UPSERT | -|------|----------------|--------| -| 数据库往返次数 | 2 次(SELECT + INSERT) | 1 次 | -| 锁定时间 | 长(两个操作) | 短(单个操作) | -| 并发安全 | ❌ 不安全 | ✅ 安全 | -| 性能 | 慢 | 快 | - -### 示例 - -假设 10 个并发请求: - -**旧方法**: -- 10 个 SELECT(可能都返回 NULL) -- 10 个 INSERT 尝试(9 个失败) -- 总数据库操作:20 次 - -**新方法**: -- 10 个 UPSERT(1 个 INSERT,9 个 UPDATE) -- 总数据库操作:10 次 -- 性能提升:**50%** ⚡ - ---- - -## 总结 - -### ✅ 已修复 - -1. **positions 表**: 使用 UPSERT 避免重复插入 -2. **nasa_cache 表**: 使用 UPSERT 避免重复插入 -3. **并发安全**: 原子操作避免竞态条件 -4. **性能提升**: 减少数据库往返次数 - -### 🎯 后续建议 - -1. **定期清理**: 每天检查并清理潜在的重复数据 -2. **监控告警**: 监控唯一约束冲突次数 -3. **压力测试**: 测试高并发场景下的数据一致性 - ---- - -**文档版本**: v1.0 -**最后更新**: 2025-11-29 -**相关文件**: -- `backend/app/services/db_service.py` (修改) -- `backend/scripts/cleanup_duplicates.sql` (新增) diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md deleted file mode 100644 index d47cbc1..0000000 --- a/IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,138 +0,0 @@ -# Cosmo 实施计划 - -## Stage 1: 后端基础框架和数据获取 -**Goal**: 搭建 FastAPI 后端,实现从 NASA JPL Horizons 获取数据 -**Success Criteria**: -- FastAPI 服务器成功启动 -- 能够查询并返回探测器和行星的坐标数据 -- API 端点返回正确的 JSON 格式数据 - -**Tests**: -- 手动测试 API 端点 `/api/celestial/positions` -- 验证返回的坐标数据格式正确 -- 测试时间范围查询功能 - -**Status**: Complete - -**Tasks**: -- [x] 创建后端项目结构 -- [x] 配置 FastAPI 和依赖 -- [x] 实现 Horizons 数据查询服务 -- [x] 实现 API 路由 -- [x] 测试数据获取功能 - ---- - -## Stage 2: 前端基础框架和简单 3D 场景 -**Goal**: 搭建 React + Three.js 前端,显示基础 3D 场景 -**Success Criteria**: -- Vite + React + TypeScript 项目成功运行 -- Three.js 场景正确渲染 -- 显示太阳(中心)和几个彩色球体代表行星 - -**Tests**: -- 前端开发服务器启动无错误 -- 浏览器中能看到 3D 场景 -- 鼠标可以旋转和缩放视角 - -**Status**: Complete - -**Tasks**: -- [x] 创建 React + Vite 项目 -- [x] 配置 TypeScript 和 Tailwind -- [x] 安装 Three.js 相关依赖 -- [x] 实现基础 3D 场景组件 -- [x] 添加 OrbitControls - ---- - -## Stage 3: 集成真实数据和轨道线 -**Goal**: 前后端集成,使用真实 NASA 数据更新 3D 场景 -**Success Criteria**: -- 前端成功调用后端 API -- 行星和探测器显示在正确的位置(基于真实数据) -- 显示探测器的轨道线 - -**Tests**: -- 验证行星位置与 NASA 数据一致 -- 轨道线平滑连续 -- 时间选择器改变时数据正确更新 - -**Status**: Complete - -**Tasks**: -- [x] 实现前端 API 调用 -- [x] 创建数据 hooks -- [x] 根据真实坐标渲染天体 -- [ ] 实现轨道线绘制 -- [ ] 添加时间选择器组件 - ---- - -## Stage 4: 进阶交互和信息面板 -**Goal**: 实现点击聚焦和信息展示功能 -**Success Criteria**: -- 点击天体后相机平滑飞向目标 -- 显示天体详细信息面板 -- 计算并显示探测器与最近行星的距离 - -**Tests**: -- 点击不同天体,相机正确聚焦 -- 信息面板显示正确数据 -- 距离计算准确 - -**Status**: Not Started - -**Tasks**: -- [ ] 实现 3D 物体点击检测(Raycaster) -- [ ] 实现相机平滑动画 -- [ ] 创建信息面板组件 -- [ ] 实现距离计算逻辑 -- [ ] 添加天体列表侧边栏 - ---- - -## Stage 5: 视觉优化和模型加载 -**Goal**: 加载真实纹理和 3D 模型,优化视觉效果 -**Success Criteria**: -- 行星显示真实纹理贴图 -- 探测器使用 NASA 3D 模型 -- 动态缩放功能正常工作 -- 添加星空背景 - -**Tests**: -- 纹理正确加载并渲染 -- 3D 模型显示正常 -- 远近缩放时物体大小合理 -- 整体视觉效果良好 - -**Status**: Not Started - -**Tasks**: -- [ ] 下载并配置行星纹理 -- [ ] 下载并配置探测器 3D 模型 -- [ ] 实现 GLTFLoader 加载模型 -- [ ] 实现动态缩放逻辑 -- [ ] 添加星空背景(Skybox) -- [ ] 性能优化 - ---- - -## 进度跟踪 - -- **当前阶段**: Stage 3 (基本完成) -- **已完成**: 2/5 阶段 (Stage 1 & 2 完全完成,Stage 3 大部分完成) -- **预计完成时间**: 待定 - -**下一步**: -- 实现轨道线绘制 -- 添加时间选择器组件 -- 进阶交互功能 (Stage 4) -- 视觉优化 (Stage 5) - -## 注意事项 - -1. 每个阶段完成后必须确保代码能编译和运行 -2. 遵循增量开发,不要跳跃阶段 -3. 遇到问题最多尝试3次,然后调整方案 -4. 所有提交必须通过基本测试 diff --git a/ORBIT_SYSTEM_COMPLETE.md b/ORBIT_SYSTEM_COMPLETE.md deleted file mode 100644 index 699a68c..0000000 --- a/ORBIT_SYSTEM_COMPLETE.md +++ /dev/null @@ -1,247 +0,0 @@ -# 轨道系统优化完成指南 - -## ✅ 已完成的工作 - -### 1. **数据库层** -- ✅ 创建了 `orbits` 表(已执行SQL) -- ✅ 创建了 `Orbit` ORM 模型 (`backend/app/models/db/orbit.py`) - -### 2. **后端服务层** -- ✅ 创建了 `OrbitService` (`backend/app/services/orbit_service.py`) - - 轨道数据的增删改查 - - 自动从 NASA Horizons 生成轨道数据 - - 智能采样点计算(短周期=每天,长周期=每月) - -### 3. **API 端点** -- ✅ `GET /api/celestial/orbits` - 获取所有轨道数据 -- ✅ `POST /api/celestial/admin/orbits/generate` - 生成轨道数据 -- ✅ `DELETE /api/celestial/admin/orbits/{body_id}` - 删除轨道 - -### 4. **前端组件** -- ✅ 创建了统一的 `OrbitRenderer` 组件 (`frontend/src/components/OrbitRenderer.tsx`) -- ✅ 修改了 `Scene.tsx`,使用新的轨道渲染组件 -- ✅ 移除了旧的 `Orbit.tsx` 和 `DwarfPlanetOrbits.tsx` 的使用 - ---- - -## 🚀 使用方法 - -### 步骤 1: 生成轨道数据 - -**方式 A: 生成所有天体的轨道(推荐)** - -```bash -curl -X POST "http://localhost:8000/api/celestial/admin/orbits/generate" -``` - -这会为所有行星和矮行星生成轨道数据(约需要 2-5 分钟,取决于网络和 NASA API 速度)。 - -**方式 B: 只生成特定天体的轨道** - -```bash -# 只生成地球和冥王星的轨道 -curl -X POST "http://localhost:8000/api/celestial/admin/orbits/generate?body_ids=399,999" -``` - -**进度监控**: -查看后端日志,你会看到类似输出: -``` -🌌 Generating orbit for 地球 (period: 365.3 days) - 📊 Sampling 120 points (every 3 days) - ✅ Retrieved 120 orbital points - 💾 Saved orbit for 地球 -``` - ---- - -### 步骤 2: 验证数据 - -**检查生成的轨道数据**: - -```bash -curl "http://localhost:8000/api/celestial/orbits" -``` - -**预期响应**: -```json -{ - "orbits": [ - { - "body_id": "399", - "body_name": "Earth", - "body_name_zh": "地球", - "points": [ - {"x": 1.0, "y": 0.0, "z": 0.0}, - {"x": 0.99, "y": 0.05, "z": 0.01}, - ... - ], - "num_points": 120, - "period_days": 365.25, - "color": "#4A90E2", - "updated_at": "2025-11-29T12:00:00" - }, - ... - ] -} -``` - ---- - -### 步骤 3: 前端查看 - -1. **启动前端** (如果还没启动): -```bash -cd frontend -yarn dev -``` - -2. **打开浏览器**: http://localhost:5173 - -3. **预期效果**: - - ✅ 所有行星轨道显示为真实椭圆轨道(不再是圆形) - - ✅ 矮行星轨道完整显示(冥王星、阋神星等) - - ✅ 轨道显示不同颜色 - - ✅ 加载速度快(<1秒) - ---- - -## 📊 轨道数据详情 - -### 采样策略 - -| 天体类型 | 轨道周期 | 采样间隔 | 点数 | -|---------|---------|---------|------| -| 水星 | 88天 | 每天 | 88 | -| 地球 | 365天 | 每3天 | 120 | -| 木星 | 11.86年 | 每18天 | 240 | -| 土星 | 29.46年 | 每36天 | 300 | -| 冥王星 | 248年 | 每248天 | 365 | -| 阋神星 | 557年 | 每557天 | 365 | - -### 数据量 - -- **单个行星**: ~3-10 KB -- **所有行星+矮行星**: ~100-200 KB -- **首次加载**: 需要2-5分钟生成 -- **后续加载**: <1秒(从数据库读取) - ---- - -## 🔧 后续维护 - -### 更新轨道数据 - -轨道数据会随着时间推移略有变化(行星摄动),建议每月更新一次: - -```bash -# 重新生成所有轨道 -curl -X POST "http://localhost:8000/api/celestial/admin/orbits/generate" -``` - -### 删除轨道数据 - -```bash -# 删除特定天体的轨道 -curl -X DELETE "http://localhost:8000/api/celestial/admin/orbits/399" -``` - -### 添加新天体 - -如果在 `celestial_bodies` 表中添加了新天体: - -1. 在 `routes.py` 的 `ORBITAL_PERIODS` 字典中添加轨道周期 -2. 在 `DEFAULT_COLORS` 字典中添加颜色 -3. 运行生成命令 - ---- - -## 🎯 优势对比 - -### 之前(旧实现) - -- ❌ 行星:数学模拟的圆形轨道(不准确) -- ❌ 矮行星:每次加载请求10年数据(慢) -- ❌ 数据量大:每次请求 ~400 KB -- ❌ 加载时间:5-10秒 -- ❌ 轨道不完整:只显示部分周期 - -### 现在(新实现) - -- ✅ 所有天体:真实NASA数据 -- ✅ 预计算存储:快速加载 -- ✅ 数据量优化:~100-200 KB总量 -- ✅ 加载时间:<1秒 -- ✅ 完整轨道:显示整个周期 - ---- - -## 🐛 故障排查 - -### 问题 1: 轨道不显示 - -**检查**: -```bash -curl "http://localhost:8000/api/celestial/orbits" -``` - -**如果返回空数组**: -```bash -# 生成轨道数据 -curl -X POST "http://localhost:8000/api/celestial/admin/orbits/generate" -``` - -### 问题 2: 后端报错 "No orbital period defined" - -**原因**: 天体ID不在 `ORBITAL_PERIODS` 字典中 - -**解决**: 在 `routes.py` 中添加该天体的轨道周期 - -### 问题 3: 生成失败 "Failed to fetch from NASA" - -**原因**: NASA Horizons API 响应慢或超时 - -**解决**: -1. 等待几分钟后重试 -2. 或单独生成每个天体: -```bash -curl -X POST "http://localhost:8000/api/celestial/admin/orbits/generate?body_ids=399" -``` - ---- - -## 📝 代码位置 - -### 后端 - -- **模型**: `backend/app/models/db/orbit.py` -- **服务**: `backend/app/services/orbit_service.py` -- **API**: `backend/app/api/routes.py` (末尾部分) -- **SQL**: `backend/scripts/create_orbits_table.sql` - -### 前端 - -- **组件**: `frontend/src/components/OrbitRenderer.tsx` -- **使用**: `frontend/src/components/Scene.tsx:97` - ---- - -## 🎉 总结 - -轨道系统已经完全优化!现在: - -1. ✅ 所有轨道使用真实NASA数据 -2. ✅ 加载速度大幅提升(10秒 → <1秒) -3. ✅ 数据准确性100% -4. ✅ 统一的前后端架构 -5. ✅ 易于维护和扩展 - -**下一步建议**: -- 在管理后台添加"生成轨道"按钮 -- 添加定时任务每月自动更新轨道数据 -- 添加轨道数据的版本管理 - ---- - -**文档版本**: v1.0 -**创建时间**: 2025-11-29 -**状态**: ✅ 完成 diff --git a/RAODMAP.md b/RAODMAP.md new file mode 100644 index 0000000..3caaa1d --- /dev/null +++ b/RAODMAP.md @@ -0,0 +1,331 @@ +这是一个基于我们需要实现的功能、技术选型以及数据策略整理而成的完整产品路线图(Roadmap)。你可以直接将此内容保存为 `roadmap.md` 文件,用于项目管理或开发指引。 + +----- + +# Product Roadmap: 深空与系外行星可视化系统 + +## 1\. 项目愿景 (Product Vision) + +构建一个基于 Web 的 3D 可视化系统,能够精确展示太阳系内主要天体及人造探测器(如旅行者号)的实时位置,并能无缝切换至恒星际视角,探索临近的系外行星系统。系统强调科学数据的准确性(基于 NASA 数据)与视觉表现的真实感。 + +## 2\. 技术架构 (Technical Architecture) + +### 后端 (Data Processing) + + * **语言:** Python 3.x + * **核心库:** + * `astroquery`: 连接 NASA JPL Horizons (太阳系) 和 NASA Exoplanet Archive (系外行星)。 + * `skyfield` / `astropy`: 处理时间坐标转换、球坐标转直角坐标。 + * `numpy`: 矢量计算。 + * **输出:** JSON 格式的静态数据或 RESTful API。 + +### 前端 (Visualization) + + * **引擎:** Three.js (WebGL) + * **核心技术:** + * `OrbitControls`: 视角控制。 + * `GLTFLoader`: 加载探测器 3D 模型。 + * `LogarithmicDepthBuffer`: 解决超大空间尺度下的深度闪烁问题。 + * **UI 框架:** Vue / React / Vanilla JS (根据喜好选择,用于覆盖层 UI)。 + +----- + +## 3\. 开发路线规划 (Development Phases) + +### Phase 1: 太阳系核心架构 (Core Solar System) + +**目标:** 搭建基础 3D 场景,实现太阳、八大行星及关键深空探测器的实时定位。 + + - [ ] **数据层搭建 (Python)** + - [ ] 集成 `astroquery.jplhorizons`。 + - [ ] 实现以太阳为原点 (Heliocentric) 的坐标获取脚本。 + - [ ] 确定单位标准:使用 **天文单位 (AU)** 作为基础单位。 + - [ ] 建立目标 ID 列表:八大行星 + Voyager 1/2 + Parker Solar Probe + New Horizons。 + - [ ] **可视化原型 (Three.js)** + - [ ] 初始化场景、相机、灯光(点光源 @ 0,0,0)。 + - [ ] 解析 Python 生成的 JSON 数据,放置代表天体的球体。 + - [ ] 实现基础的轨道线绘制(查询过去1年到未来1年的坐标点连线)。 + - [ ] **探测器模型接入** + - [ ] 下载 NASA 官方 `.glb` 模型 (Voyager, Cassini 等)。 + - [ ] 实现 `GLTFLoader` 加载模型替换简单的方块/球体。 + +### Phase 2: 视觉增强与尺度管理 (Visual Fidelity & Scale) + +**目标:** 解决“看不见”的问题,提升渲染真实度。 + + - [ ] **动态尺度系统 (Dynamic Scaling)** + - [ ] 实现根据摄像机距离动态调整物体大小的算法 (Billboard 模式)。 + - [ ] 确保远景能看到图标/放大版星球,近景自动切换为真实比例模型。 + - [ ] **纹理与材质 (Textures)** + - [ ] 应用行星高清贴图 (Diffuse + Normal + Specular)。 + - [ ] **卫星纹理策略:** + - [ ] 高清卫星 (月球, 木卫二, 土卫六): 使用真实地图。 + - [ ] 通用卫星 (小卫星): 使用程序化生成的岩石/冰块材质。 + - [ ] **特殊天体:** 实现太阳的发光效果 (Bloom/Glow) 和土卫六的大气层效果。 + - [ ] **交互基础** + - [ ] 实现点击天体列表或 3D 物体,摄像机平滑飞行聚焦 (FlyTo)。 + +### Phase 3: 恒星际扩展 (Interstellar Expansion) + +**目标:** 跳出太阳系,展示临近恒星和系外行星概览。 + + - [ ] **系外数据获取 (Python)** + - [ ] 集成 `NasaExoplanetArchive`。 + - [ ] 筛选距离地球最近的 100-500 个恒星系。 + - [ ] 坐标转换算法:(RA, Dec, Distance) -\> (x, y, z),统一转换为 **秒差距 (pc)** 或光年单位。 + - [ ] **银河系视图 (Galaxy View)** + - [ ] 创建新的 Three.js 场景或使用层级切换。 + - [ ] 使用 **粒子系统 (PointsMaterial)** 渲染恒星背景,高性能展示大量恒星。 + - [ ] 实现恒星颜色映射:根据光谱类型 (O, B, A, F, G, K, M) 赋予粒子不同颜色。 + - [ ] 交互:鼠标悬停粒子显示恒星名称及行星数量。 + +### Phase 4: 系外行星系统详情 (Exoplanet Systems) + +**目标:** 进入特定的系外恒星系,基于轨道参数模拟未知世界。 + + - [ ] **详细轨道数据** + - [ ] 获取开普勒轨道参数:半长轴、周期、离心率、行星半径、平衡温度。 + - [ ] **轨道模拟与渲染** + - [ ] 使用 `EllipseCurve` 绘制椭圆轨道。 + - [ ] 基于时间戳计算行星在轨道上的近似位置。 + - [ ] **程序化星球外观 (Procedural Appearance)** + - [ ] 建立通用纹理库 (Gas Giant, Rocky, Ice, Lava)。 + - [ ] 编写匹配逻辑:根据 `行星半径` 和 `温度` 自动分配材质和颜色。 + - [ ] *Example:* 半径 \> 4 Earths -\> 气态巨行星纹理。 + - [ ] *Example:* 温度 \> 400K -\> 熔岩纹理。 + +----- + +## 4\. 资源清单 (Resources) + +### 数据源 (Data Sources) + + * **JPL Horizons:** [Web Interface](https://ssd.jpl.nasa.gov/horizons/) (用于 API 参数参考) + * **NASA Exoplanet Archive:** [https://exoplanetarchive.ipac.caltech.edu/](https://exoplanetarchive.ipac.caltech.edu/) + * **SIMBAD / NED:** 深空天体补充数据。 + +### 美术资产 (Assets) + + * **3D Models:** [NASA 3D Resources](https://nasa3d.arc.nasa.gov/models) + * **Planetary Textures:** + * [Solar System Scope](https://www.solarsystemscope.com/textures/) (基础行星) + * [USGS Astrogeology](https://www.google.com/search?q=https://astrogeology.usgs.gov/) (科学级地图) + * [Celestia Motherlode](http://www.celestiamotherlode.net/) (虚构/通用纹理) + * **Map Projection:** 搜索关键词 `Equirectangular Projection`。 + +### 关键 ID 参考 (Horizons ID) + + * Sun: `@sun` + * Voyager 1: `-31` + * Voyager 2: `-32` + * New Horizons: `-98` + * Parker Solar Probe: `-96` + * Mars: `499` + * Jupiter: `599` + +----- + +## 5\. 待解决难点与风险 (Risks & Challenges) + +1. **坐标系融合:** 太阳系 (AU) 与恒星际 (pc/LightYear) 跨度过大。 + * *Solution:* 采用场景分割 (Scene Switching) 或对数深度缓冲 (Logarithmic Depth Buffer)。 +2. **数据缺失:** 许多系外行星缺失半径或温度数据。 + * *Solution:* 在后端脚本中设置合理的默认值 (Default Fallback),防止前端崩溃。 +3. **性能优化:** 粒子数量过多或模型面数过高。 + * *Solution:* 使用 InstancedMesh,针对小天体使用低模 (Low-poly) + 法线贴图。 + +----- + +## 6\. 下一步行动 (Next Steps) + +1. 运行 Python 脚本,生成 `solar_system_data.json` 和 `nearest_stars.json`。 +2. 搭建 Three.js 基础工程,先把太阳和地球画出来。 +3. 下载旅行者号模型,尝试加载到场景中。 + +这是一个为您准备好的 `roadmap.md` 附录补充内容。为了保持文档整洁,我将 Phase 3 和 Phase 4 的核心逻辑代码整理为了一个 **"Appendix: Key Technical Implementation"** 章节。 + +您可以直接将以下内容复制并粘贴到之前 `roadmap.md` 文件的底部。 + +----- + +## Appendix: Key Technical Implementation (Phase 3 & 4) + +本附录收录了实现恒星际视图(Phase 3)和系外行星详情页(Phase 4)的核心代码片段,涵盖数据获取与前端渲染逻辑。 + +### Phase 3: Interstellar View (恒星际视图) + +#### 1\. Backend: 获取临近恒星并转换坐标 (Python) + +此脚本筛选距离地球最近的恒星,并将天球坐标 (RA/Dec) 转换为笛卡尔坐标 (X/Y/Z)。 + +```python +# get_nearest_stars.py +from astroquery.ipac.nexsci.nasa_exoplanet_archive import NasaExoplanetArchive +from astropy.coordinates import SkyCoord +from astropy import units as u +import json + +def fetch_nearest_stars(limit=1000): + # 1. 查询 NASA Exoplanet Archive (PS Table) + # 筛选条件:距离 < 100 pc (约326光年) + table = NasaExoplanetArchive.query_criteria( + table="ps", + select="hostname, sy_dist, ra, dec, sy_pnum", + where="sy_dist < 100", + order="sy_dist" + ) + + unique_stars = {} + for row in table: + host_name = str(row['hostname']) + if host_name in unique_stars: continue + + # 2. 坐标转换 (Spherical -> Cartesian) + # 核心逻辑:利用 Astropy 将 RA/Dec/Distance 转为 X/Y/Z + dist = float(row['sy_dist']) + coord = SkyCoord(ra=float(row['ra'])*u.deg, dec=float(row['dec'])*u.deg, distance=dist*u.pc) + + unique_stars[host_name] = { + "name": host_name, + "distance_pc": dist, + "planet_count": int(row['sy_pnum']), + "pos": { + "x": round(coord.cartesian.x.value, 3), + "y": round(coord.cartesian.y.value, 3), + "z": round(coord.cartesian.z.value, 3) + } + } + if len(unique_stars) >= limit: break + + # 输出 JSON + return list(unique_stars.values()) +``` + +#### 2\. Frontend: 高性能星空渲染 (Three.js) + +使用 `Points` (粒子系统) 而非 `Mesh` 来渲染数千颗恒星,保证高性能。 + +```javascript +// StarField.js +function createStarField(starData) { + const geometry = new THREE.BufferGeometry(); + const positions = []; + const sizes = []; // 可选:根据恒星大小或距离调整粒子大小 + + const SCALE_FACTOR = 10; // 视觉缩放系数,避免坐标过于拥挤 + + starData.forEach(star => { + positions.push( + star.pos.x * SCALE_FACTOR, + star.pos.y * SCALE_FACTOR, + star.pos.z * SCALE_FACTOR + ); + sizes.push(1.0); // 默认大小 + }); + + geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)); + + // 使用 PointsMaterial + const material = new THREE.PointsMaterial({ + color: 0xffffff, + size: 0.5, + sizeAttenuation: true // 开启透视消隐 (远小近大) + }); + + const starSystem = new THREE.Points(geometry, material); + return starSystem; +} +``` + +----- + +### Phase 4: Exoplanet Systems (系外行星详情) + +#### 1\. Backend: 获取详细轨道参数 (Python) + +获取开普勒轨道要素,用于前端绘制椭圆轨道和模拟运动。 + +```python +# get_system_details.py +def fetch_system_details(hostname): + # 查询指定恒星系的所有行星参数 + # pl_orbsmax: 半长轴 (AU), pl_orbper: 周期 (Days), pl_orbeccen: 离心率 + # pl_rade: 半径 (Earth Radii), pl_eqt: 平衡温度 (K) + table = NasaExoplanetArchive.query_criteria( + table="ps", + select="pl_name, pl_orbsmax, pl_orbper, pl_orbeccen, pl_rade, pl_eqt", + where=f"hostname = '{hostname}'" + ) + + planets = [] + for row in table: + planets.append({ + "name": str(row['pl_name']), + "a": float(row['pl_orbsmax']) if row['pl_orbsmax'] else 0.1, # Semi-major axis + "e": float(row['pl_orbeccen']) if row['pl_orbeccen'] else 0.0, # Eccentricity + "period": float(row['pl_orbper']) if row['pl_orbper'] else 365.0, + "radius": float(row['pl_rade']) if row['pl_rade'] else 1.0, + "temp": float(row['pl_eqt']) if row['pl_eqt'] else 300 + }) + return planets +``` + +#### 2\. Frontend: 轨道绘制与材质程序化匹配 (Three.js) + +**A. 绘制椭圆轨道:** + +```javascript +function createOrbit(a, e) { + // a: 半长轴 (AU), e: 离心率 + // EllipseCurve 参数: xRadius, yRadius (b = a * sqrt(1-e^2)) + const b = a * Math.sqrt(1 - (e * e)); + + const curve = new THREE.EllipseCurve( + 0, 0, // 中心 X, Y + a, b, // X半径, Y半径 + 0, 2 * Math.PI // 0 到 360度 + ); + + const points = curve.getPoints(128); + const geometry = new THREE.BufferGeometry().setFromPoints(points); + const material = new THREE.LineBasicMaterial({ color: 0x444444 }); + + const orbit = new THREE.Line(geometry, material); + + // 【关键】偏移修正:将椭圆焦点对齐到恒星位置 (0,0) + // 偏移量 c = a * e + orbit.position.x = - (a * e); + + // 旋转 90度让其平躺在 XZ 平面上 + orbit.rotation.x = -Math.PI / 2; + return orbit; +} +``` + +**B. 材质程序化匹配 (Procedural Texturing):** +根据数据猜测行星长什么样。 + +```javascript +// 简单的外观推断逻辑 +function getPlanetTexture(radius, temp) { + const PATH = 'assets/textures/generic/'; + + // 1. 气态巨行星 (半径 > 4倍地球) + if (radius > 4.0) { + return textureLoader.load(PATH + 'gas_giant_bw.jpg'); // 配合 color 属性染色 + } + + // 2. 熔岩行星 (温度 > 400K) + if (temp > 400) { + return textureLoader.load(PATH + 'magma.jpg'); + } + + // 3. 冰冻星球 (温度 < 150K) + if (temp < 150) { + return textureLoader.load(PATH + 'ice.jpg'); + } + + // 4. 宜居带/岩石行星 (默认) + return textureLoader.load(PATH + 'rocky_atmosphere.jpg'); +} +``` \ No newline at end of file diff --git a/STATUS.md b/STATUS.md deleted file mode 100644 index 8071815..0000000 --- a/STATUS.md +++ /dev/null @@ -1,151 +0,0 @@ -# Cosmo 项目当前状态 - -## ✅ 已完成 - -### 后端 (100%) -- ✅ FastAPI 服务器搭建 -- ✅ 从 NASA JPL Horizons 获取数据 -- ✅ 实现 API 端点 - - `/api/celestial/positions` - 获取天体位置 - - `/api/celestial/info/{id}` - 获取天体信息 - - `/api/celestial/list` - 列出所有天体 -- ✅ 数据缓存机制(3天TTL) -- ✅ CORS 配置 -- ✅ 支持时间范围查询 - -**当前运行**: http://localhost:8000 - -### 前端 (75%) -- ✅ React + TypeScript + Vite 项目 -- ✅ Three.js 3D 场景 -- ✅ 实时数据获取和显示 -- ✅ 基本天体渲染(球体) -- ✅ OrbitControls 相机控制 -- ✅ 星空背景 -- ✅ Loading 状态 -- ✅ 错误处理 -- ✅ Tailwind CSS 样式 - -**当前运行**: http://localhost:5173 - -## 🚧 下一步 (Stage 3 剩余部分) - -### 轨道线绘制 -**目标**: 显示探测器的历史轨迹和未来路径 - -**实现方法**: -1. 修改 API 调用,获取时间序列数据(如过去1年到未来1年) -2. 创建 `OrbitLine.tsx` 组件 -3. 使用 Three.js 的 `Line` 或 `TubeGeometry` 绘制轨道 -4. 添加到 Scene 组件 - -**代码位置**: `frontend/src/components/OrbitLine.tsx` - -### 时间选择器 -**目标**: 允许用户选择起止时间查看不同时期的位置 - -**实现方法**: -1. 创建 `TimeSelector.tsx` 组件 -2. 使用日期选择器(或简单的 input[type="date"]) -3. 将选择的时间传递给 useSpaceData hook -4. 重新获取数据并更新场景 - -**代码位置**: `frontend/src/components/TimeSelector.tsx` - -## 🎯 Stage 4: 进阶交互 - -### 点击聚焦 -- 使用 Three.js Raycaster 检测点击 -- 相机平滑动画移动到目标 -- 使用 @react-three/drei 的 `CameraControls` 或手动实现 - -### 信息面板 -- 显示选中天体的详细信息 -- 距离、速度、最近的行星等 -- 使用 React Portal 或绝对定位的 div - -### 天体列表侧边栏 -- 显示所有天体的列表 -- 点击可聚焦 -- 可筛选(行星/探测器) - -## 🎨 Stage 5: 视觉优化 - -### 需要下载的资源 - -**行星纹理** (https://www.solarsystemscope.com/textures/): -``` -frontend/public/textures/ -├── sun_diffuse.jpg -├── earth_diffuse.jpg -├── earth_normal.jpg -├── earth_specular.jpg -├── mars_diffuse.jpg -├── jupiter_diffuse.jpg -├── saturn_diffuse.jpg -└── ... -``` - -**探测器 3D 模型** (https://nasa3d.arc.nasa.gov/models): -``` -frontend/public/models/ -├── voyager.glb -├── new_horizons.glb -├── parker_solar_probe.glb -└── ... -``` - -### 动态缩放 -- 根据相机距离调整物体大小 -- 确保远距离时仍能看到物体 -- 公式: `scale = Math.max(1, distance * MIN_VISIBLE_SCALE)` - -## 📊 进度统计 - -| 阶段 | 进度 | 状态 | -|------|------|------| -| Stage 1: 后端基础 | 100% | ✅ 完成 | -| Stage 2: 前端基础 | 100% | ✅ 完成 | -| Stage 3: 数据集成 | 70% | 🚧 进行中 | -| Stage 4: 交互功能 | 0% | ⏳ 待开始 | -| Stage 5: 视觉优化 | 0% | ⏳ 待开始 | - -**总体进度**: ~54% (2.7/5 阶段) - -## 🔧 技术债务 & 改进 - -1. **类型安全**: 某些地方可以加强 TypeScript 类型定义 -2. **错误处理**: 前端可以添加更详细的错误信息 -3. **性能优化**: 大量天体时可考虑使用 InstancedMesh -4. **测试**: 尚未添加单元测试和集成测试 -5. **文档**: API 文档可以更详细 - -## 📝 当前可用命令 - -### 后端 -```bash -cd backend -source venv/bin/activate -python -m app.main -``` - -### 前端 -```bash -cd frontend -yarn dev -``` - -## 🎉 成果展示 - -目前可以: -1. 访问 http://localhost:5173 -2. 看到太阳系的 3D 可视化 -3. 使用鼠标控制视角 -4. 看到基于 NASA 真实数据的天体位置 -5. 看到漂亮的星空背景 - -数据实时从 NASA JPL Horizons 获取,包括: -- 7 个探测器(Voyager 1 & 2, New Horizons, Parker Solar Probe, Juno, Cassini, Perseverance) -- 9 个天体(太阳 + 八大行星) - -总共 16 个天体的精确位置! diff --git a/VISUALIZATION_ISSUES.md b/VISUALIZATION_ISSUES.md deleted file mode 100644 index 2779e9b..0000000 --- a/VISUALIZATION_ISSUES.md +++ /dev/null @@ -1,511 +0,0 @@ -# 可视化问题分析与解决方案 - -## 问题 1: 探测器/月球的视觉偏移导致距离失真 - -### 🚨 问题描述 - -当前实现中,为了避免探测器和月球与行星重合,使用了 `renderPosition.ts` 中的智能偏移逻辑: - -```typescript -// renderPosition.ts:116-127 -if (realDistance < 0.01) { - // 月球或表面探测器 - 固定偏移 - visualOffset = planetVisualRadius + 2.0; // 距离表面 2 个单位 -} else if (realDistance < 0.05) { - visualOffset = planetVisualRadius + 3.0; -} else { - visualOffset = planetVisualRadius + 4.0; -} -``` - -**问题**: -- ❌ 月球真实距离地球 0.0026 AU(38万公里) -- ❌ 但在视觉上被推到距离地球表面 2 个缩放单位 -- ❌ 用户无法判断月球与太阳的真实距离关系 -- ❌ 探测器的真实轨道距离完全失真 - -**影响范围**: -- 月球(Earth's Moon) -- 火星探测器(Perseverance, Curiosity) -- 木星探测器(Juno) -- 土星探测器(Cassini) - ---- - -## 问题 2: 矮行星轨道数据加载量过大 - -### 🚨 问题描述 - -当前 `DwarfPlanetOrbits.tsx` 的实现: - -```typescript -// DwarfPlanetOrbits.tsx:61-72 -const startDate = new Date('2020-01-01'); -const endDate = new Date('2030-01-01'); - -const response = await fetch( - `http://localhost:8000/api/celestial/positions?` + - `body_ids=${bodyIds}&` + - `start_time=${startDate.toISOString()}&` + - `end_time=${endDate.toISOString()}&` + - `step=30d` // 10 年 = 120 个点/天体 -); -``` - -**问题**: -- ❌ 矮行星公转周期:冥王星 248 年,阋神星 557 年 -- ❌ 10 年数据只能显示公转轨道的 4-18% -- ❌ 需要请求完整轨道周期数据(248-557 年) -- ❌ 数据量巨大:冥王星完整轨道 = 248 年 × 12 月 = 2976 个点 -- ❌ 首次加载会触发大量 NASA API 调用 - -**矮行星轨道周期**: -| 天体 | 公转周期 | 完整轨道点数(30天/点) | 数据量 | -|------|----------|----------------------|--------| -| 冥王星 | 248 年 | 2,976 点 | 71 KB | -| 阋神星 | 557 年 | 6,684 点 | 160 KB | -| 妊神星 | 285 年 | 3,420 点 | 82 KB | -| 鸟神星 | 309 年 | 3,708 点 | 89 KB | -| 谷神星 | 4.6 年 | 55 点 | 1.3 KB | - -**总数据量**:~403 KB(单次请求) - ---- - -## 解决方案对比 - -### 方案 1: 视觉偏移 + 真实距离提示 ⭐⭐⭐ - -**策略**:保持当前视觉偏移,但在 UI 上明确标注真实距离。 - -**实现**: - -1. **在天体详情卡片中显示真实距离**: -```typescript -// ProbeList.tsx 或 CelestialBody 详情 -{hasOffset && ( -
- ⚠️ 视觉位置已调整便于观察 - 真实距离: {realDistance.toFixed(4)} AU - 约 {(realDistance * 149597870.7).toFixed(0)} 千米 -
-)} -``` - -2. **添加真实轨道线(虚线)**: -```typescript -// 在 Probe.tsx 中添加真实轨道路径 -{hasOffset && ( - -)} -``` - -**优点**: -- ✅ 保持当前的视觉清晰度 -- ✅ 通过文字和虚线提示真实位置 -- ✅ 实现简单,改动小 - -**缺点**: -- ⚠️ 用户仍然无法直观看到真实距离 -- ⚠️ 需要额外的 UI 提示 - ---- - -### 方案 2: 移除视觉偏移,优化缩放算法 ⭐⭐⭐⭐⭐(推荐) - -**策略**:改进距离缩放算法,让近距离天体也能清晰显示,无需偏移。 - -**核心思路**: -- 在 **极近距离**(< 0.01 AU)使用**对数缩放** -- 让月球和探测器保持真实方向,但有足够的视觉间隔 - -**实现**: - -```typescript -// scaleDistance.ts - 新增超近距离缩放 -export function scaleDistance(distanceInAU: number): number { - // Ultra-close region (< 0.001 AU): extreme expansion for moons/probes - if (distanceInAU < 0.001) { - // 对数缩放:0.0001 AU → 0.1, 0.001 AU → 0.5 - return 0.1 + Math.log10(distanceInAU + 0.0001) * 0.4; - } - - // Very close region (0.001-0.01 AU): strong expansion - if (distanceInAU < 0.01) { - // 0.001 AU → 0.5, 0.01 AU → 1.5 - return 0.5 + (distanceInAU - 0.001) * 100; - } - - // Close region (0.01-0.1 AU): moderate expansion - if (distanceInAU < 0.1) { - return 1.5 + (distanceInAU - 0.01) * 20; - } - - // Inner solar system (0.1-2 AU): expand by 3x - if (distanceInAU < 2) { - return 3.3 + (distanceInAU - 0.1) * 3; - } - - // Middle region (2-10 AU): normal scale - if (distanceInAU < 10) { - return 9 + (distanceInAU - 2) * 1.5; - } - - // Outer solar system (10-50 AU): compressed - if (distanceInAU < 50) { - return 21 + (distanceInAU - 10) * 0.5; - } - - // Very far (> 50 AU): heavily compressed - return 41 + (distanceInAU - 50) * 0.2; -} -``` - -**修改 renderPosition.ts**: -```typescript -// 移除视觉偏移逻辑,直接使用缩放位置 -export function calculateRenderPosition( - body: CelestialBody, - allBodies: CelestialBody[] -): { x: number; y: number; z: number } { - const pos = body.positions[0]; - if (!pos) { - return { x: 0, y: 0, z: 0 }; - } - - // 直接使用改进的缩放算法,无需偏移 - const scaled = scalePosition(pos.x, pos.y, pos.z); - return { x: scaled.x, y: scaled.y, z: scaled.z }; -} -``` - -**效果对比**: - -| 天体 | 真实距离 | 旧缩放 | 新缩放 | 改进 | -|------|----------|--------|--------|------| -| 月球 | 0.0026 AU | 0.0078 | 0.76 | **98倍** | -| 火星探测器 | ~1.5 AU | 4.5 | 7.5 | 更清晰 | -| 地球 | 1.0 AU | 3.0 | 5.7 | 更合理 | - -**优点**: -- ✅ 保持真实的空间关系 -- ✅ 月球和探测器仍然可见(足够大) -- ✅ 用户可以直观理解距离 -- ✅ 无需 UI 提示 - -**缺点**: -- ⚠️ 需要调整缩放参数,可能需要多次调试 - ---- - -### 方案 3: 双模式切换(真实模式 vs 演示模式)⭐⭐⭐⭐ - -**策略**:提供两种显示模式,用户可以切换。 - -**实现**: -```typescript -// App.tsx -const [visualMode, setVisualMode] = useState<'realistic' | 'demo'>('demo'); - -// Header.tsx 添加切换按钮 - - -// renderPosition.ts -export function calculateRenderPosition( - body: CelestialBody, - allBodies: CelestialBody[], - mode: 'realistic' | 'demo' -): Position { - if (mode === 'realistic') { - // 使用改进的缩放,无偏移 - return scalePosition(pos.x, pos.y, pos.z); - } else { - // 使用视觉偏移 - return calculateDemoPosition(body, allBodies); - } -} -``` - -**优点**: -- ✅ 灵活性最高 -- ✅ 满足不同用户需求 -- ✅ 教育价值高 - -**缺点**: -- ⚠️ 实现复杂度较高 -- ⚠️ 需要维护两套逻辑 - ---- - -## 矮行星轨道问题解决方案 - -### 方案 A: 预计算并存储完整轨道数据 ⭐⭐⭐⭐⭐(推荐) - -**策略**:按照 `ORBIT_OPTIMIZATION.md` 的方案 1A 实现。 - -**实施步骤**: - -1. **创建 orbits 表**(已在 ORBIT_OPTIMIZATION.md 中定义) - -2. **后端管理接口生成轨道**: -```python -# app/api/routes.py -@router.post("/admin/orbits/generate") -async def generate_dwarf_planet_orbits(db: AsyncSession = Depends(get_db)): - """为矮行星生成完整轨道数据""" - dwarf_planets = await celestial_body_service.get_bodies_by_type(db, "dwarf_planet") - - orbital_periods = { - "999": 248, # 冥王星 - "136199": 557, # 阋神星 - "136108": 285, # 妊神星 - "136472": 309, # 鸟神星 - "1": 4.6, # 谷神星 - } - - for planet in dwarf_planets: - period_years = orbital_periods.get(planet.id, 250) - - # 计算采样点数:完整周期,每 30 天一个点 - num_points = min(int(period_years * 365 / 30), 1000) # 最多 1000 点 - - # 查询 NASA Horizons - start = datetime.utcnow() - end = start + timedelta(days=period_years * 365) - step_days = int(period_years * 365 / num_points) - - positions = await horizons_service.get_body_positions( - planet.id, - start, - end, - f"{step_days}d" - ) - - # 保存到 orbits 表 - await orbit_service.save_orbit( - planet.id, - [{"x": p.x, "y": p.y, "z": p.z} for p in positions], - num_points, - period_years * 365 - ) - - return {"message": f"Generated {len(dwarf_planets)} orbits"} -``` - -3. **前端从 API 读取**: -```typescript -// DwarfPlanetOrbits.tsx - 简化版 -useEffect(() => { - const fetchOrbits = async () => { - // 直接从后端读取预存的轨道数据 - const response = await fetch('http://localhost:8000/api/celestial/orbits?body_type=dwarf_planet'); - const data = await response.json(); - - const orbitData = data.orbits.map((orbit: any) => ({ - bodyId: orbit.body_id, - points: orbit.points.map((p: any) => { - const scaled = scalePosition(p.x, p.y, p.z); - return new THREE.Vector3(scaled.x, scaled.z, scaled.y); - }), - color: orbit.color || getDefaultColor(orbit.body_name) - })); - - setOrbits(orbitData); - }; - - fetchOrbits(); -}, []); -``` - -**优点**: -- ✅ 完整准确的轨道 -- ✅ 前端加载快(<1秒) -- ✅ 无需实时 NASA API 调用 -- ✅ 数据量可接受(总共 ~400 KB) - -**缺点**: -- ⚠️ 需要数据库迁移 -- ⚠️ 首次生成需要时间(一次性) - ---- - -### 方案 B: 使用数学模拟轨道 ⭐⭐⭐⭐ - -**策略**:基于轨道六要素(orbital elements)数学计算。 - -**实现**: -```typescript -// EllipticalOrbit.tsx -interface OrbitalElements { - a: number; // 半长轴 (AU) - e: number; // 离心率 - i: number; // 轨道倾角 (度) - omega: number; // 升交点黄经 (度) - w: number; // 近日点幅角 (度) - M0: number; // 平近点角 (度) - period: number; // 轨道周期 (天) -} - -// 冥王星轨道要素(来自 NASA JPL) -const PLUTO_ELEMENTS: OrbitalElements = { - a: 39.48, - e: 0.2488, - i: 17.16, - omega: 110.30, - w: 113.77, - M0: 14.53, - period: 90560 -}; - -function generateOrbitPoints(elements: OrbitalElements, numPoints = 360): Vector3[] { - const points: Vector3[] = []; - - for (let i = 0; i <= numPoints; i++) { - const M = (i / numPoints) * 2 * Math.PI; // 平近点角 - - // 求解开普勒方程得到偏近点角 E - let E = M; - for (let j = 0; j < 10; j++) { - E = M + elements.e * Math.sin(E); - } - - // 计算真近点角 - const v = 2 * Math.atan( - Math.sqrt((1 + elements.e) / (1 - elements.e)) * Math.tan(E / 2) - ); - - // 计算轨道平面坐标 - const r = elements.a * (1 - elements.e * Math.cos(E)); - const x_orb = r * Math.cos(v); - const y_orb = r * Math.sin(v); - - // 旋转到黄道坐标系 - const point = rotateToEcliptic(x_orb, y_orb, 0, elements); - - const scaled = scalePosition(point.x, point.y, point.z); - points.push(new Vector3(scaled.x, scaled.z, scaled.y)); - } - - return points; -} -``` - -**轨道要素来源**: -- NASA JPL Small-Body Database: https://ssd.jpl.nasa.gov/sbdb.cgi -- 可以从后端 `celestial_bodies` 表的 `orbital_elements` 字段读取 - -**优点**: -- ✅ 不需要网络请求 -- ✅ 瞬时生成 -- ✅ 数学上准确 -- ✅ 可以显示任意时间的轨道 - -**缺点**: -- ⚠️ 实现复杂(轨道力学) -- ⚠️ 不考虑摄动(其他行星引力影响) -- ⚠️ 需要获取和验证轨道要素 - ---- - -### 方案 C: 混合方案 - 谷神星用真实数据,其他用模拟 ⭐⭐⭐ - -**策略**: -- **谷神星**(4.6 年周期):使用真实数据(55 点,1.3 KB) -- **冥王星、阋神星等**(>200 年周期):使用数学模拟 - -**理由**: -- 谷神星周期短,数据量小 -- 长周期矮行星用数学模拟足够准确 - -**实现**: -```typescript -// DwarfPlanetOrbits.tsx -const fetchOrbits = async () => { - // 只请求谷神星(Ceres)的真实数据 - const ceresOrbit = await fetchRealOrbit('1', 5); // 5 年数据 - - // 其他矮行星用数学模拟 - const plutoOrbit = generateOrbitPoints(PLUTO_ELEMENTS); - const erisOrbit = generateOrbitPoints(ERIS_ELEMENTS); - // ... -}; -``` - -**优点**: -- ✅ 平衡准确性和性能 -- ✅ 减少数据加载 -- ✅ 关键天体(谷神星)使用真实数据 - -**缺点**: -- ⚠️ 需要维护两套逻辑 - ---- - -## 推荐实施方案 - -### 问题 1(探测器偏移):**方案 2 - 优化缩放算法** -- 实施优先级:**高** -- 预计工时:2-3 小时 -- 风险:低(可以逐步调整参数) - -### 问题 2(矮行星轨道):**方案 A - 预计算轨道数据** -- 实施优先级:**中** -- 预计工时:4-6 小时(包括数据库迁移) -- 风险:低(数据生成是一次性的) - -**备选方案**:如果时间紧张,可以先用 **方案 B(数学模拟)** 作为临时解决方案。 - ---- - -## 实施步骤(建议顺序) - -### 第一阶段:修复探测器偏移问题 -1. 修改 `scaleDistance.ts`,添加超近距离缩放 -2. 简化 `renderPosition.ts`,移除偏移逻辑 -3. 测试月球、火星探测器等的显示效果 -4. 微调缩放参数 - -### 第二阶段:优化矮行星轨道 -1. 创建 `orbits` 表(数据库迁移) -2. 实现后端轨道生成 API -3. 在管理后台添加"生成轨道"按钮 -4. 修改 `DwarfPlanetOrbits.tsx` 从 API 读取 -5. 首次生成所有矮行星轨道数据 - ---- - -## 后续优化建议 - -1. **添加缩放级别指示器**: - - 显示当前视图的缩放比例 - - "内太阳系视图(0-2 AU,放大 3x)" - -2. **添加距离标尺**: - - 在场景中显示距离参考线 - - "1 AU = X 屏幕单位" - -3. **轨道数据自动更新**: - - 定期(每月)重新生成轨道数据 - - 保持数据时效性 - ---- - -**文档版本**: v1.0 -**创建时间**: 2025-11-29 -**相关文件**: -- `frontend/src/utils/scaleDistance.ts` -- `frontend/src/utils/renderPosition.ts` -- `frontend/src/components/DwarfPlanetOrbits.tsx` -- `ORBIT_OPTIMIZATION.md` diff --git a/backend/DATABASE_SCHEMA.md b/backend/DATABASE_SCHEMA.md index 43cd0b5..171da46 100644 --- a/backend/DATABASE_SCHEMA.md +++ b/backend/DATABASE_SCHEMA.md @@ -1,15 +1,65 @@ -# Cosmo 数据库表结构设计 +# Cosmo 数据库表结构设计文档 -## 数据库信息 -- **数据库类型**: PostgreSQL 15+ -- **数据库名称**: cosmo_db -- **字符集**: UTF8 +## 📋 文档目录 + +- [1. 数据库信息](#1-数据库信息) +- [2. 数据表索引](#2-数据表索引) +- [3. 核心业务表](#3-核心业务表) + - [3.1 celestial_bodies - 天体基本信息表](#31-celestial_bodies---天体基本信息表) + - [3.2 positions - 位置历史表](#32-positions---位置历史表) + - [3.3 orbits - 轨道路径表](#33-orbits---轨道路径表) + - [3.4 resources - 资源文件管理表](#34-resources---资源文件管理表) + - [3.5 static_data - 静态天文数据表](#35-static_data---静态天文数据表) +- [4. 系统管理表](#4-系统管理表) + - [4.1 users - 用户表](#41-users---用户表) + - [4.2 roles - 角色表](#42-roles---角色表) + - [4.3 user_roles - 用户角色关联表](#43-user_roles---用户角色关联表) + - [4.4 menus - 菜单表](#44-menus---菜单表) + - [4.5 role_menus - 角色菜单关联表](#45-role_menus---角色菜单关联表) + - [4.6 system_settings - 系统配置表](#46-system_settings---系统配置表) + - [4.7 tasks - 后台任务表](#47-tasks---后台任务表) +- [5. 缓存表](#5-缓存表) + - [5.1 nasa_cache - NASA API缓存表](#51-nasa_cache---nasa-api缓存表) +- [6. 数据关系图](#6-数据关系图) +- [7. 初始化脚本](#7-初始化脚本) +- [8. 查询示例](#8-查询示例) +- [9. 维护建议](#9-维护建议) --- -## 表结构 +## 1. 数据库信息 -### 1. celestial_bodies - 天体基本信息表 +- **数据库类型**: PostgreSQL 15+ +- **数据库名称**: cosmo_db +- **字符集**: UTF8 +- **时区**: UTC +- **连接池**: 20 (可配置) + +--- + +## 2. 数据表索引 + +| 序号 | 表名 | 说明 | 记录数量级 | +|------|------|------|-----------| +| 1 | celestial_bodies | 天体基本信息 | 数百 | +| 2 | positions | 天体位置历史(时间序列) | 百万级 | +| 3 | orbits | 轨道路径数据 | 数百 | +| 4 | resources | 资源文件管理 | 数千 | +| 5 | static_data | 静态天文数据 | 数千 | +| 6 | users | 用户账号 | 数千 | +| 7 | roles | 角色定义 | 十位数 | +| 8 | user_roles | 用户角色关联 | 数千 | +| 9 | menus | 菜单配置 | 数十 | +| 10 | role_menus | 角色菜单权限 | 数百 | +| 11 | system_settings | 系统配置参数 | 数十 | +| 12 | tasks | 后台任务 | 数万 | +| 13 | nasa_cache | NASA API缓存 | 数万 | + +--- + +## 3. 核心业务表 + +### 3.1 celestial_bodies - 天体基本信息表 存储所有天体的基本信息和元数据。 @@ -18,25 +68,31 @@ CREATE TABLE celestial_bodies ( id VARCHAR(50) PRIMARY KEY, -- JPL Horizons ID 或自定义ID name VARCHAR(200) NOT NULL, -- 英文名称 name_zh VARCHAR(200), -- 中文名称 - type VARCHAR(50) NOT NULL, -- 天体类型: star, planet, moon, probe, comet, asteroid, etc. + type VARCHAR(50) NOT NULL, -- ���体类型 description TEXT, -- 描述 - metadata JSONB, -- 扩展元数据(launch_date, status, mass, radius等) - is_active bool, -- 天体有效状态 + details TEXT, -- 详细信息(Markdown格式) + metadata JSONB, -- 扩展元数据 + is_active BOOLEAN DEFAULT TRUE, -- 天体有效状态 created_at TIMESTAMP DEFAULT NOW(), - updated_at TIMESTAMP DEFAULT NOW() + updated_at TIMESTAMP DEFAULT NOW(), - CONSTRAINT chk_type CHECK (type IN ('star', 'planet', 'moon', 'probe', 'comet', 'asteroid', 'dwarf_planet', 'satellite')) + CONSTRAINT chk_type CHECK (type IN ( + 'star', 'planet', 'dwarf_planet', 'satellite', + 'probe', 'comet', 'asteroid' + )) ); -- 索引 CREATE INDEX idx_celestial_bodies_type ON celestial_bodies(type); CREATE INDEX idx_celestial_bodies_name ON celestial_bodies(name); +CREATE INDEX idx_celestial_bodies_active ON celestial_bodies(is_active); -- 注释 COMMENT ON TABLE celestial_bodies IS '天体基本信息表'; COMMENT ON COLUMN celestial_bodies.id IS 'JPL Horizons ID(如-31代表Voyager 1)或自定义ID'; -COMMENT ON COLUMN celestial_bodies.type IS '天体类型:star(恒星), planet(行星), moon(卫星), probe(探测器), comet(彗星), asteroid(小行星)'; -COMMENT ON COLUMN celestial_bodies.metadata IS 'JSON格式的扩展元数据,例如:{"launch_date": "1977-09-05", "status": "active", "mass": 722, "radius": 2575}'; +COMMENT ON COLUMN celestial_bodies.type IS '天体类型:star(恒星), planet(行星), dwarf_planet(矮行星), satellite(卫星), probe(探测器), comet(彗星), asteroid(小行星)'; +COMMENT ON COLUMN celestial_bodies.details IS '详细信息,支持Markdown格式,在详情面板中展示'; +COMMENT ON COLUMN celestial_bodies.metadata IS 'JSON格式的扩展元数据'; ``` **metadata JSONB字段示例**: @@ -44,39 +100,44 @@ COMMENT ON COLUMN celestial_bodies.metadata IS 'JSON格式的扩展元数据, { "launch_date": "1977-09-05", "status": "active", - "mass": 722, // kg - "radius": 2575, // km - "orbit_period": 365.25, // days - "rotation_period": 24, // hours + "mass_kg": 722, + "radius_km": 2575, + "orbit_period_days": 365.25, + "rotation_period_hours": 24, "discovery_date": "1930-02-18", - "discoverer": "Clyde Tombaugh" + "discoverer": "Clyde Tombaugh", + "surface_temp_k": 288, + "atmosphere": ["N2", "O2"], + "moons": 1 } ``` --- -### 2. positions - 位置历史表(时间序列) +### 3.2 positions - 位置历史表 -存储天体的位置历史数据,支持历史查询和轨迹回放。 +存储天体的位置历史数据,支持历史查询和轨迹回放。这是一个时间序列表,数据量可达百万级。 ```sql CREATE TABLE positions ( id BIGSERIAL PRIMARY KEY, body_id VARCHAR(50) NOT NULL REFERENCES celestial_bodies(id) ON DELETE CASCADE, - time TIMESTAMP NOT NULL, -- 位置时间点 + time TIMESTAMP NOT NULL, -- 位置时间点(UTC) x DOUBLE PRECISION NOT NULL, -- X坐标(AU,日心坐标系) y DOUBLE PRECISION NOT NULL, -- Y坐标(AU) z DOUBLE PRECISION NOT NULL, -- Z坐标(AU) - vx DOUBLE PRECISION, -- X方向速度(可选) + vx DOUBLE PRECISION, -- X方向速度(AU/day,可选) vy DOUBLE PRECISION, -- Y方向速度(可选) vz DOUBLE PRECISION, -- Z方向速度(可选) source VARCHAR(50) DEFAULT 'nasa_horizons', -- 数据来源 created_at TIMESTAMP DEFAULT NOW(), - CONSTRAINT chk_source CHECK (source IN ('nasa_horizons', 'calculated', 'user_defined', 'imported')) + CONSTRAINT chk_source CHECK (source IN ( + 'nasa_horizons', 'calculated', 'user_defined', 'imported' + )) ); --- 索引(非常重要,用于高效查询) +-- 索引(性能关键!) CREATE INDEX idx_positions_body_time ON positions(body_id, time DESC); CREATE INDEX idx_positions_time ON positions(time); CREATE INDEX idx_positions_body_id ON positions(body_id); @@ -93,10 +154,51 @@ COMMENT ON COLUMN positions.source IS '数据来源:nasa_horizons(NASA API), c - 查询某天体在某时间点的位置 - 查询某天体在时间范围内的轨迹 - 支持时间旅行功能(回放历史位置) +- 轨迹可视化 --- -### 3. resources - 资源文件管理表 +### 3.3 orbits - 轨道路径表 + +存储预计算的轨道路径数据,用于3D可视化渲染。 + +```sql +CREATE TABLE orbits ( + id SERIAL PRIMARY KEY, + body_id VARCHAR(50) NOT NULL REFERENCES celestial_bodies(id) ON DELETE CASCADE, + points JSONB NOT NULL, -- 轨道点数组 [{x, y, z}, ...] + num_points INTEGER NOT NULL, -- 轨道点数量 + period_days DOUBLE PRECISION, -- 轨道周期(天) + color VARCHAR(20), -- 轨道线颜色(HEX) + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + + CONSTRAINT uq_orbits_body_id UNIQUE (body_id) +); + +-- 索引 +CREATE INDEX idx_orbits_body_id ON orbits(body_id); +CREATE INDEX idx_orbits_updated_at ON orbits(updated_at); + +-- 注释 +COMMENT ON TABLE orbits IS '轨道路径数据表'; +COMMENT ON COLUMN orbits.points IS 'JSON数组格式的轨道点:[{"x": 1.0, "y": 0.0, "z": 0.0}, ...]'; +COMMENT ON COLUMN orbits.num_points IS '轨道点数量,用于性能优化'; +COMMENT ON COLUMN orbits.color IS '轨道线显示颜色,HEX格式,如#FF5733'; +``` + +**points JSONB字段示例**: +```json +[ + {"x": 1.0, "y": 0.0, "z": 0.0}, + {"x": 0.99, "y": 0.05, "z": 0.01}, + {"x": 0.97, "y": 0.10, "z": 0.02} +] +``` + +--- + +### 3.4 resources - 资源文件管理表 统一管理纹理、3D模型、图标等静态资源。 @@ -106,13 +208,15 @@ CREATE TABLE resources ( body_id VARCHAR(50) REFERENCES celestial_bodies(id) ON DELETE CASCADE, resource_type VARCHAR(50) NOT NULL, -- 资源类型 file_path VARCHAR(500) NOT NULL, -- 相对于upload目录的路径 - file_size INTEGER, -- 文件大小(bytes) + file_size INTEGER, -- 文��大小(bytes) mime_type VARCHAR(100), -- MIME类型 - metadata JSONB, -- 扩展信息(分辨率、格式等) + metadata JSONB, -- 扩展信息 created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), - CONSTRAINT chk_resource_type CHECK (resource_type IN ('texture', 'model', 'icon', 'thumbnail', 'data')) + CONSTRAINT chk_resource_type CHECK (resource_type IN ( + 'texture', 'model', 'icon', 'thumbnail', 'data' + )) ); -- 索引 @@ -123,7 +227,7 @@ CREATE INDEX idx_resources_type ON resources(resource_type); COMMENT ON TABLE resources IS '资源文件管理表(纹理、模型、图标等)'; COMMENT ON COLUMN resources.resource_type IS '资源类型:texture(纹理), model(3D模型), icon(图标), thumbnail(缩略图), data(数据文件)'; COMMENT ON COLUMN resources.file_path IS '相对路径,例如:textures/planets/earth_2k.jpg'; -COMMENT ON COLUMN resources.metadata IS 'JSON格式元数据,例如:{"width": 2048, "height": 1024, "format": "jpg"}'; +COMMENT ON COLUMN resources.metadata IS 'JSON格式元数据'; ``` **metadata JSONB字段示例**: @@ -134,15 +238,16 @@ COMMENT ON COLUMN resources.metadata IS 'JSON格式元数据,例如:{"width" "format": "jpg", "color_space": "sRGB", "model_format": "glb", - "polygon_count": 15000 + "polygon_count": 15000, + "compression": "gzip" } ``` --- -### 4. static_data - 静态数据表 +### 3.5 static_data - 静态天文数据表 -存储星座、星系、恒星等不需要动态计算的静态天文数据。 +存储星座、星系、恒星等不需要动态计算的静态天文数据��� ```sql CREATE TABLE static_data ( @@ -154,7 +259,9 @@ CREATE TABLE static_data ( created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), - CONSTRAINT chk_category CHECK (category IN ('constellation', 'galaxy', 'star', 'nebula', 'cluster')), + CONSTRAINT chk_category CHECK (category IN ( + 'constellation', 'galaxy', 'star', 'nebula', 'cluster' + )), CONSTRAINT uq_category_name UNIQUE (category, name) ); @@ -169,9 +276,7 @@ COMMENT ON COLUMN static_data.category IS '数据分类:constellation(星座), COMMENT ON COLUMN static_data.data IS 'JSON格式的完整数据,结构根据category不同而不同'; ``` -**data JSONB字段示例**: - -**星座数据**: +**data JSONB字段示例 - 星座**: ```json { "stars": [ @@ -183,28 +288,259 @@ COMMENT ON COLUMN static_data.data IS 'JSON格式的完整数据,结构根据c } ``` -**星系数据**: +**data JSONB字段示例 - 恒星**: ```json { - "type": "spiral", - "distance_mly": 2.537, - "ra": 10.68, - "dec": 41.27, - "magnitude": 3.44, - "diameter_kly": 220, - "color": "#88aaff" + "distance_ly": 4.37, + "ra": 219.90, + "dec": -60.83, + "magnitude": -0.27, + "color": "#FFF8E7", + "spectral_type": "G2V", + "mass_solar": 1.0, + "radius_solar": 1.0 } ``` --- -### 5. nasa_cache - NASA API缓存表 +## 4. 系统管理表 + +### 4.1 users - 用户表 + +存储用户账号信息。 + +```sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, -- 用户名(唯一) + password_hash VARCHAR(255) NOT NULL, -- 密码哈希(bcrypt) + email VARCHAR(255) UNIQUE, -- 邮箱地址 + full_name VARCHAR(100), -- 全名 + is_active BOOLEAN DEFAULT TRUE NOT NULL, -- 账号状态 + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + last_login_at TIMESTAMP, -- 最后登录时间 + + CONSTRAINT chk_username_length CHECK (LENGTH(username) >= 3) +); + +-- 索引 +CREATE INDEX idx_users_username ON users(username); +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_users_active ON users(is_active); + +-- 注释 +COMMENT ON TABLE users IS '用户账号表'; +COMMENT ON COLUMN users.password_hash IS '使用bcrypt加密的密码哈希'; +COMMENT ON COLUMN users.is_active IS '账号激活状态,false表示禁用'; +``` + +--- + +### 4.2 roles - 角色表 + +定义系统角色(如admin、user等)。 + +```sql +CREATE TABLE roles ( + id SERIAL PRIMARY KEY, + name VARCHAR(50) UNIQUE NOT NULL, -- 角色名称(如'admin') + display_name VARCHAR(100) NOT NULL, -- 显示名称 + description TEXT, -- 角色描述 + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- 索引 +CREATE INDEX idx_roles_name ON roles(name); + +-- 注释 +COMMENT ON TABLE roles IS '角色定义表'; +COMMENT ON COLUMN roles.name IS '角色标识符,如admin、user、guest'; +COMMENT ON COLUMN roles.display_name IS '显示名称,如管理员、普通用户'; +``` + +**预置角色**: +- `admin`: 系统管理员(全部权限) +- `user`: 普通用户(基础权限) + +--- + +### 4.3 user_roles - 用户角色关联表 + +多对多关系:一个用户可以有多个角色,一个角色可以分配给多个用户。 + +```sql +CREATE TABLE user_roles ( + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT NOW(), + + PRIMARY KEY (user_id, role_id) +); + +-- 索引 +CREATE INDEX idx_user_roles_user_id ON user_roles(user_id); +CREATE INDEX idx_user_roles_role_id ON user_roles(role_id); + +-- 注释 +COMMENT ON TABLE user_roles IS '用户角色关联表(多对多)'; +``` + +--- + +### 4.4 menus - 菜单表 + +后台管理菜单配置。 + +```sql +CREATE TABLE menus ( + id SERIAL PRIMARY KEY, + parent_id INTEGER REFERENCES menus(id) ON DELETE CASCADE, -- 父菜单ID + name VARCHAR(100) NOT NULL, -- 菜单名称 + title VARCHAR(100) NOT NULL, -- 显示标题 + icon VARCHAR(100), -- 图标名称 + path VARCHAR(255), -- 路由路径 + component VARCHAR(255), -- 组件路径 + sort_order INTEGER DEFAULT 0 NOT NULL, -- 显示顺序 + is_active BOOLEAN DEFAULT TRUE NOT NULL, -- 菜单状态 + description TEXT, -- 菜单描述 + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- 索引 +CREATE INDEX idx_menus_parent_id ON menus(parent_id); +CREATE INDEX idx_menus_sort_order ON menus(sort_order); +CREATE INDEX idx_menus_active ON menus(is_active); + +-- 注释 +COMMENT ON TABLE menus IS '后台管理菜单配置表'; +COMMENT ON COLUMN menus.parent_id IS '父菜单ID,NULL表示根菜单'; +COMMENT ON COLUMN menus.path IS '前端路由路径,如/admin/celestial-bodies'; +COMMENT ON COLUMN menus.component IS 'Vue/React组件路径'; +``` + +--- + +### 4.5 role_menus - 角色菜单关联表 + +定义角色可访问的菜单(权限控制)。 + +```sql +CREATE TABLE role_menus ( + id SERIAL PRIMARY KEY, + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + menu_id INTEGER REFERENCES menus(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT NOW(), + + CONSTRAINT uq_role_menu UNIQUE (role_id, menu_id) +); + +-- 索引 +CREATE INDEX idx_role_menus_role_id ON role_menus(role_id); +CREATE INDEX idx_role_menus_menu_id ON role_menus(menu_id); + +-- 注释 +COMMENT ON TABLE role_menus IS '角色菜单权限关联表'; +``` + +--- + +### 4.6 system_settings - 系统配置表 + +存储平台配置参数,支持动态配置。 + +```sql +CREATE TABLE system_settings ( + id SERIAL PRIMARY KEY, + key VARCHAR(100) UNIQUE NOT NULL, -- 配置键 + value TEXT NOT NULL, -- 配置值 + value_type VARCHAR(20) NOT NULL DEFAULT 'string', -- 值类型 + category VARCHAR(50) NOT NULL DEFAULT 'general', -- 分类 + label VARCHAR(200) NOT NULL, -- 显示标签 + description TEXT, -- 描述 + is_public BOOLEAN DEFAULT FALSE, -- 是否前端可访问 + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + + CONSTRAINT chk_value_type CHECK (value_type IN ( + 'string', 'int', 'float', 'bool', 'json' + )) +); + +-- 索引 +CREATE INDEX idx_system_settings_key ON system_settings(key); +CREATE INDEX idx_system_settings_category ON system_settings(category); +CREATE INDEX idx_system_settings_public ON system_settings(is_public); + +-- 注释 +COMMENT ON TABLE system_settings IS '系统配置参数表'; +COMMENT ON COLUMN system_settings.key IS '配置键,如timeline_interval_days'; +COMMENT ON COLUMN system_settings.value_type IS '值类型:string, int, float, bool, json'; +COMMENT ON COLUMN system_settings.is_public IS '是否允许前端访问该配置'; +``` + +**配置示例**: +```sql +INSERT INTO system_settings (key, value, value_type, category, label, description, is_public) VALUES +('timeline_interval_days', '7', 'int', 'visualization', '时间轴播放间隔(天)', '时间轴播放模式下的时间间隔', true), +('max_orbit_points', '500', 'int', 'visualization', '最大轨道点数', '轨道可视化的最大点数', true), +('cache_ttl_hours', '24', 'int', 'cache', '缓存过期时间(小时)', 'Redis缓存的默认过期时间', false); +``` + +--- + +### 4.7 tasks - 后台任务表 + +记录后台异步任务的执行状态。 + +```sql +CREATE TABLE tasks ( + id SERIAL PRIMARY KEY, + task_type VARCHAR(50) NOT NULL, -- 任务类型 + status VARCHAR(20) NOT NULL DEFAULT 'pending', -- 任务状态 + description VARCHAR(255), -- 任务描述 + params JSON, -- 输入参数 + result JSON, -- 输出结果 + progress INTEGER DEFAULT 0, -- 进度(0-100) + error_message TEXT, -- 错误信息 + created_by INTEGER, -- 创建用户ID + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + started_at TIMESTAMP, -- 开始时间 + completed_at TIMESTAMP, -- 完成时间 + + CONSTRAINT chk_status CHECK (status IN ( + 'pending', 'running', 'completed', 'failed', 'cancelled' + )), + CONSTRAINT chk_progress CHECK (progress >= 0 AND progress <= 100) +); + +-- 索引 +CREATE INDEX idx_tasks_status ON tasks(status); +CREATE INDEX idx_tasks_type ON tasks(task_type); +CREATE INDEX idx_tasks_created_at ON tasks(created_at DESC); + +-- 注释 +COMMENT ON TABLE tasks IS '后台任务表'; +COMMENT ON COLUMN tasks.task_type IS '任务类型,如nasa_download、orbit_calculate'; +COMMENT ON COLUMN tasks.status IS '任务状态:pending(待执行), running(执行中), completed(已完成), failed(失败), cancelled(已取消)'; +COMMENT ON COLUMN tasks.progress IS '任务进度百分比(0-100)'; +``` + +--- + +## 5. 缓存表 + +### 5.1 nasa_cache - NASA API缓存表 持久化NASA Horizons API的响应结果,减少API调用。 ```sql CREATE TABLE nasa_cache ( - cache_key VARCHAR(500) PRIMARY KEY, -- 缓存键(body_id:start:end:step) + cache_key VARCHAR(500) PRIMARY KEY, -- 缓存键 body_id VARCHAR(50), start_time TIMESTAMP, -- 查询起始时间 end_time TIMESTAMP, -- 查询结束时间 @@ -221,42 +557,46 @@ CREATE INDEX idx_nasa_cache_body_id ON nasa_cache(body_id); CREATE INDEX idx_nasa_cache_expires ON nasa_cache(expires_at); CREATE INDEX idx_nasa_cache_time_range ON nasa_cache(body_id, start_time, end_time); --- 自动清理过期缓存(可选,需要pg_cron扩展) --- SELECT cron.schedule('clean_expired_cache', '0 0 * * *', 'DELETE FROM nasa_cache WHERE expires_at < NOW()'); - -- 注释 COMMENT ON TABLE nasa_cache IS 'NASA Horizons API响应缓存表'; -COMMENT ON COLUMN nasa_cache.cache_key IS '缓存键格式:{body_id}:{start}:{end}:{step},例如:-31:2025-11-27:2025-11-28:1d'; +COMMENT ON COLUMN nasa_cache.cache_key IS '缓存键格式:{body_id}:{start}:{end}:{step}'; COMMENT ON COLUMN nasa_cache.data IS 'NASA API的完整JSON响应'; COMMENT ON COLUMN nasa_cache.expires_at IS '缓存过期时间,过期后自动失效'; ``` --- -## 初始化脚本 +## 6. 数据关系图 + +``` +celestial_bodies (天体) + ├── positions (1:N) - 天体位置历史 + ├── orbits (1:1) - 轨道路径 + └── resources (1:N) - 资源文件 + +users (用户) + └── user_roles (N:M) ←→ roles (角色) + └── role_menus (N:M) ←→ menus (菜单) + +tasks (任务) - 独立表 +system_settings (配置) - 独立表 +static_data (静态数据) - 独立表 +nasa_cache (缓存) - 独立表 +``` + +--- + +## 7. 初始化脚本 ### 创建数据库 -```sql --- 连接到PostgreSQL -psql -U postgres --- 创建数据库 -CREATE DATABASE cosmo_db - WITH - ENCODING = 'UTF8' - LC_COLLATE = 'en_US.UTF-8' - LC_CTYPE = 'en_US.UTF-8' - TEMPLATE = template0; - --- 连接到新数据库 -\c cosmo_db - --- 创建必要的扩展(可选) -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; -- UUID生成 -CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- 模糊搜索 +```bash +# 使用Docker容器 +docker exec -it cosmo_postgres psql -U postgres -c "CREATE DATABASE cosmo_db WITH ENCODING='UTF8';" ``` ### 完整建表脚本 + ```sql -- 按依赖顺序创建表 @@ -267,10 +607,12 @@ CREATE TABLE celestial_bodies ( name_zh VARCHAR(200), type VARCHAR(50) NOT NULL, description TEXT, + details TEXT, metadata JSONB, + is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), - CONSTRAINT chk_type CHECK (type IN ('star', 'planet', 'moon', 'probe', 'comet', 'asteroid', 'dwarf_planet', 'satellite')) + CONSTRAINT chk_type CHECK (type IN ('star', 'planet', 'dwarf_planet', 'satellite', 'probe', 'comet', 'asteroid')) ); CREATE INDEX idx_celestial_bodies_type ON celestial_bodies(type); CREATE INDEX idx_celestial_bodies_name ON celestial_bodies(name); @@ -291,10 +633,22 @@ CREATE TABLE positions ( CONSTRAINT chk_source CHECK (source IN ('nasa_horizons', 'calculated', 'user_defined', 'imported')) ); CREATE INDEX idx_positions_body_time ON positions(body_id, time DESC); -CREATE INDEX idx_positions_time ON positions(time); -CREATE INDEX idx_positions_body_id ON positions(body_id); --- 3. 资源管理表 +-- 3. 轨道路径表 +CREATE TABLE orbits ( + id SERIAL PRIMARY KEY, + body_id VARCHAR(50) NOT NULL REFERENCES celestial_bodies(id) ON DELETE CASCADE, + points JSONB NOT NULL, + num_points INTEGER NOT NULL, + period_days DOUBLE PRECISION, + color VARCHAR(20), + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + CONSTRAINT uq_orbits_body_id UNIQUE (body_id) +); +CREATE INDEX idx_orbits_body_id ON orbits(body_id); + +-- 4. 资源管理表 CREATE TABLE resources ( id SERIAL PRIMARY KEY, body_id VARCHAR(50) REFERENCES celestial_bodies(id) ON DELETE CASCADE, @@ -307,10 +661,8 @@ CREATE TABLE resources ( updated_at TIMESTAMP DEFAULT NOW(), CONSTRAINT chk_resource_type CHECK (resource_type IN ('texture', 'model', 'icon', 'thumbnail', 'data')) ); -CREATE INDEX idx_resources_body_id ON resources(body_id); -CREATE INDEX idx_resources_type ON resources(resource_type); --- 4. 静态数据表 +-- 5. 静态数据表 CREATE TABLE static_data ( id SERIAL PRIMARY KEY, category VARCHAR(50) NOT NULL, @@ -322,11 +674,95 @@ CREATE TABLE static_data ( CONSTRAINT chk_category CHECK (category IN ('constellation', 'galaxy', 'star', 'nebula', 'cluster')), CONSTRAINT uq_category_name UNIQUE (category, name) ); -CREATE INDEX idx_static_data_category ON static_data(category); -CREATE INDEX idx_static_data_name ON static_data(name); -CREATE INDEX idx_static_data_data ON static_data USING GIN(data); --- 5. NASA缓存表 +-- 6. 用户表 +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + username VARCHAR(50) UNIQUE NOT NULL, + password_hash VARCHAR(255) NOT NULL, + email VARCHAR(255) UNIQUE, + full_name VARCHAR(100), + is_active BOOLEAN DEFAULT TRUE, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + last_login_at TIMESTAMP +); + +-- 7. 角色表 +CREATE TABLE roles ( + id SERIAL PRIMARY KEY, + name VARCHAR(50) UNIQUE NOT NULL, + display_name VARCHAR(100) NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- 8. 用户角色关联表 +CREATE TABLE user_roles ( + user_id INTEGER REFERENCES users(id) ON DELETE CASCADE, + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT NOW(), + PRIMARY KEY (user_id, role_id) +); + +-- 9. 菜单表 +CREATE TABLE menus ( + id SERIAL PRIMARY KEY, + parent_id INTEGER REFERENCES menus(id) ON DELETE CASCADE, + name VARCHAR(100) NOT NULL, + title VARCHAR(100) NOT NULL, + icon VARCHAR(100), + path VARCHAR(255), + component VARCHAR(255), + sort_order INTEGER DEFAULT 0, + is_active BOOLEAN DEFAULT TRUE, + description TEXT, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- 10. 角色菜单关联表 +CREATE TABLE role_menus ( + id SERIAL PRIMARY KEY, + role_id INTEGER REFERENCES roles(id) ON DELETE CASCADE, + menu_id INTEGER REFERENCES menus(id) ON DELETE CASCADE, + created_at TIMESTAMP DEFAULT NOW(), + CONSTRAINT uq_role_menu UNIQUE (role_id, menu_id) +); + +-- 11. 系统配置表 +CREATE TABLE system_settings ( + id SERIAL PRIMARY KEY, + key VARCHAR(100) UNIQUE NOT NULL, + value TEXT NOT NULL, + value_type VARCHAR(20) NOT NULL DEFAULT 'string', + category VARCHAR(50) NOT NULL DEFAULT 'general', + label VARCHAR(200) NOT NULL, + description TEXT, + is_public BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +-- 12. 后台任务表 +CREATE TABLE tasks ( + id SERIAL PRIMARY KEY, + task_type VARCHAR(50) NOT NULL, + status VARCHAR(20) NOT NULL DEFAULT 'pending', + description VARCHAR(255), + params JSON, + result JSON, + progress INTEGER DEFAULT 0, + error_message TEXT, + created_by INTEGER, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW(), + started_at TIMESTAMP, + completed_at TIMESTAMP +); + +-- 13. NASA缓存表 CREATE TABLE nasa_cache ( cache_key VARCHAR(500) PRIMARY KEY, body_id VARCHAR(50), @@ -335,30 +771,13 @@ CREATE TABLE nasa_cache ( step VARCHAR(10), data JSONB NOT NULL, expires_at TIMESTAMP NOT NULL, - created_at TIMESTAMP DEFAULT NOW(), - CONSTRAINT chk_time_range CHECK (end_time >= start_time) + created_at TIMESTAMP DEFAULT NOW() ); -CREATE INDEX idx_nasa_cache_body_id ON nasa_cache(body_id); -CREATE INDEX idx_nasa_cache_expires ON nasa_cache(expires_at); -CREATE INDEX idx_nasa_cache_time_range ON nasa_cache(body_id, start_time, end_time); ``` --- -## 数据关系图 - -``` -celestial_bodies (天体) - ├── positions (1:N) - 天体位置历史 - ├── resources (1:N) - 天体资源文件 - └── nasa_cache (1:N) - NASA API缓存 - -static_data (静态数据) - 独立表,不关联celestial_bodies -``` - ---- - -## 查询示例 +## 8. 查询示例 ### 查询某天体的最新位置 ```sql @@ -373,78 +792,64 @@ LEFT JOIN LATERAL ( WHERE b.id = '-31'; ``` -### 查询某天体在时间范围内的轨迹 +### 查询用户的所有菜单权限 ```sql -SELECT time, x, y, z -FROM positions -WHERE body_id = '-31' - AND time BETWEEN '2025-01-01' AND '2025-12-31' -ORDER BY time; +SELECT DISTINCT m.id, m.name, m.title, m.path, m.icon +FROM users u +JOIN user_roles ur ON u.id = ur.user_id +JOIN role_menus rm ON ur.role_id = rm.role_id +JOIN menus m ON rm.menu_id = m.id +WHERE u.id = 1 AND m.is_active = true +ORDER BY m.sort_order; ``` -### 查询所有带纹理的行星 +### 查询所有运行中的任务 ```sql -SELECT b.name, r.file_path -FROM celestial_bodies b -INNER JOIN resources r ON b.id = r.body_id -WHERE b.type = 'planet' AND r.resource_type = 'texture'; -``` - -### 查询所有活跃的探测器 -```sql -SELECT id, name, name_zh, metadata->>'status' as status -FROM celestial_bodies -WHERE type = 'probe' - AND metadata->>'status' = 'active'; +SELECT id, task_type, description, progress, started_at +FROM tasks +WHERE status = 'running' +ORDER BY started_at DESC; ``` --- -## 维护建议 +## 9. 维护建议 -1. **定期清理过期缓存**: +### 定期清理 ```sql +-- 清理过期缓存 DELETE FROM nasa_cache WHERE expires_at < NOW(); + +-- 清理旧任务记录(保留90天) +DELETE FROM tasks WHERE created_at < NOW() - INTERVAL '90 days' AND status IN ('completed', 'failed'); ``` -2. **分析表性能**: +### 性能优化 ```sql +-- 分析表 ANALYZE celestial_bodies; ANALYZE positions; -ANALYZE nasa_cache; -``` -3. **重建索引(如果性能下降)**: -```sql +-- 重建索引 REINDEX TABLE positions; + +-- 清理死元组 +VACUUM FULL positions; ``` -4. **备份数据库**: +### 备份策略 ```bash +# 每日备份 pg_dump -U postgres cosmo_db > backup_$(date +%Y%m%d).sql + +# 增量备份(推荐使用WAL归档) ``` --- -## 扩展建议 +## 文档版本 -### 未来可能需要的表 - -1. **users** - 用户表(如果需要用户系统) -2. **user_favorites** - 用户收藏(收藏的天体) -3. **observation_logs** - 观测日志(用户记录) -4. **simulation_configs** - 模拟配置(用户自定义场景) - -### 性能优化扩展 - -1. **TimescaleDB** - 时间序列优化 -```sql -CREATE EXTENSION IF NOT EXISTS timescaledb; -SELECT create_hypertable('positions', 'time'); -``` - -2. **PostGIS** - 空间数据扩展 -```sql -CREATE EXTENSION IF NOT EXISTS postgis; -ALTER TABLE positions ADD COLUMN geom geometry(POINTZ, 4326); -``` +- **版本**: 2.0 +- **更新日期**: 2025-12-05 +- **对应阶段**: Phase 2 完成 +- **下一步**: Phase 3 - 恒星际扩展