main
mula.liu 2025-12-06 00:39:27 +08:00
parent c0d82ddb97
commit f9f804cb5f
9 changed files with 883 additions and 2040 deletions

View File

@ -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
#### 阶段4API重构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,
)
```

View File

@ -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 + 本总结)

View File

@ -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 个 UPSERT1 个 INSERT9 个 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` (新增)

View File

@ -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. 所有提交必须通过基本测试

View File

@ -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
**状态**: ✅ 完成

331
RAODMAP.md 100644
View File

@ -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
View File

@ -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 个天体的精确位置!

View File

@ -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 AU38万公里
- ❌ 但在视觉上被推到距离地球表面 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`

View File

@ -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 '父菜单IDNULL表示根菜单';
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 - 恒星际扩展