Phase 2
parent
c0d82ddb97
commit
f9f804cb5f
|
|
@ -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,
|
||||
)
|
||||
```
|
||||
|
|
@ -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<string, number> = {
|
||||
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 + 本总结)
|
||||
289
DUPLICATE_FIX.md
289
DUPLICATE_FIX.md
|
|
@ -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` (新增)
|
||||
|
|
@ -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. 所有提交必须通过基本测试
|
||||
|
|
@ -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
|
||||
**状态**: ✅ 完成
|
||||
|
|
@ -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');
|
||||
}
|
||||
```
|
||||
151
STATUS.md
151
STATUS.md
|
|
@ -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 个天体的精确位置!
|
||||
|
|
@ -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 && (
|
||||
<div className="text-yellow-400 text-xs">
|
||||
<span>⚠️ 视觉位置已调整便于观察</span>
|
||||
<span>真实距离: {realDistance.toFixed(4)} AU</span>
|
||||
<span>约 {(realDistance * 149597870.7).toFixed(0)} 千米</span>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
2. **添加真实轨道线(虚线)**:
|
||||
```typescript
|
||||
// 在 Probe.tsx 中添加真实轨道路径
|
||||
{hasOffset && (
|
||||
<Line
|
||||
points={[
|
||||
new Vector3(realPos.x, realPos.y, realPos.z), // 真实位置
|
||||
new Vector3(visualPos.x, visualPos.y, visualPos.z) // 视觉位置
|
||||
]}
|
||||
color="yellow"
|
||||
lineWidth={1}
|
||||
dashed
|
||||
dashSize={0.1}
|
||||
gapSize={0.05}
|
||||
/>
|
||||
)}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- ✅ 保持当前的视觉清晰度
|
||||
- ✅ 通过文字和虚线提示真实位置
|
||||
- ✅ 实现简单,改动小
|
||||
|
||||
**缺点**:
|
||||
- ⚠️ 用户仍然无法直观看到真实距离
|
||||
- ⚠️ 需要额外的 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 添加切换按钮
|
||||
<button onClick={() => setVisualMode(mode === 'realistic' ? 'demo' : 'realistic')}>
|
||||
{visualMode === 'realistic' ? '真实距离' : '演示模式'}
|
||||
</button>
|
||||
|
||||
// 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`
|
||||
|
|
@ -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, -- <EFBFBD><EFBFBD><EFBFBD>体类型
|
||||
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, -- 文<EFBFBD><EFBFBD>大小(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 - 静态天文数据表
|
||||
|
||||
存储星座、星系、恒星等不需要动态计算的静态天文数据。
|
||||
存储星座、星系、恒星等不需要动态计算的静态天文数据<EFBFBD><EFBFBD><EFBFBD>
|
||||
|
||||
```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 - 恒星际扩展
|
||||
|
|
|
|||
Loading…
Reference in New Issue