main
mula.liu 2025-12-27 18:28:15 +08:00
parent 3c16839fa5
commit 898f207302
7 changed files with 548 additions and 227 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,177 +0,0 @@
# 生产环境数据库升级指南
## 概述
此升级脚本包含以下变更:
1. 在 `celestial_bodies` 表增加 `short_name` 字段
2. 完整导入 `menus``role_menus`
3. 清空 `celestial_events` 表(将由定时任务重新生成)
4. 完整导入 `scheduled_jobs`
5. 导入/更新 `system_settings`
6. 保留 `user_follows` 表的现有数据
## 升级前准备
### 1. 备份数据库
```bash
# 在生产服务器上执行
pg_dump -U postgres -d cosmo_db > backup_$(date +%Y%m%d_%H%M%S).sql
```
### 2. 测试升级脚本(推荐)
```bash
# 在测试环境先运行
psql -U postgres -d cosmo_db_test < upgrade_production.sql
```
## 执行升级
### 方式1直接执行SQL文件
```bash
psql -U postgres -d cosmo_db < upgrade_production.sql
```
### 方式2通过Docker容器执行
```bash
docker cp upgrade_production.sql <container_name>:/tmp/
docker exec -it <container_name> psql -U postgres -d cosmo_db -f /tmp/upgrade_production.sql
```
### 方式3交互式执行推荐便于观察
```bash
psql -U postgres -d cosmo_db
\i upgrade_production.sql
```
## 升级后验证
脚本会自动输出验证信息,检查以下内容:
1. **celestial_bodies.short_name 字段**:应该存在
2. **menus 数量**:应该是 14 条
3. **role_menus 数量**:应该是 16 条
4. **scheduled_jobs 数量**:应该是 2 条
5. **system_settings 数量**:应该至少 3 条
### 手动验证命令
```sql
-- 检查 short_name 字段
\d celestial_bodies
-- 检查菜单数据
SELECT id, name, title, path FROM menus ORDER BY parent_id NULLS FIRST, sort_order;
-- 检查角色菜单关联
SELECT r.name as role, m.title as menu
FROM role_menus rm
JOIN roles r ON rm.role_id = r.id
JOIN menus m ON rm.menu_id = m.id
ORDER BY r.name, m.sort_order;
-- 检查定时任务
SELECT id, name, is_active, predefined_function FROM scheduled_jobs;
-- 检查系统设置
SELECT key, value, value_type FROM system_settings;
```
## 升级详情
### 1. celestial_bodies 表升级
- 增加 `short_name VARCHAR(50)` 字段
- 如果字段已存在,则跳过
### 2. menus 和 role_menus 导入
- **重要**:会清空现有菜单数据
- 导入 14 条菜单记录
- 导入 16 条角色-菜单关联记录
- 管理员可访问所有菜单
- 普通用户只能访问:个人资料、我的天体
### 3. celestial_events 清空
- 清空所有现有天体事件
- 数据会由定时任务 `calculate_planetary_events` 自动重新生成
### 4. scheduled_jobs 导入
导入2个定时任务
- **每日更新天体位置数据**(已禁用)
- Cron: `0 2 * * *`每天凌晨2点
- 可通过后台管理界面手动执行
- **获取主要天体事件**(已启用)
- Cron: `0 3 1 * *`每月1日凌晨3点
- 自动计算未来一年的天文事件
### 5. system_settings 导入
导入3个系统设置
- `view_mode`: solar默认视图模式
- `nasa_api_timeout`: 120NASA API超时时间
- `auto_download_positions`: False自动下载位置数据开关
使用 `ON CONFLICT` 策略,如果键已存在则更新值。
### 6. user_follows 保留
- **不会修改此表**
- 保留所有用户关注数据
## 回滚方案
如果升级失败,使用备份恢复:
```bash
# 方式1完整恢复
psql -U postgres -d cosmo_db < backup_YYYYMMDD_HHMMSS.sql
# 方式2选择性回滚
# 如果只是某些表有问题,可以只恢复特定表
pg_restore -U postgres -d cosmo_db -t menus -t role_menus backup.dump
```
## 注意事项
1. **事务安全**:整个脚本在一个事务中执行,失败会自动回滚
2. **外键约束**menus 表有自引用外键,脚本已处理
3. **数据清空**menus、role_menus、celestial_events、scheduled_jobs 会被清空
4. **用户数据**user_follows 不会被修改
5. **定时任务**:位置数据下载任务默认禁用,需要手动执行或启用
## 升级后操作
1. **重启应用服务**
```bash
# 重启后端服务
systemctl restart cosmo-backend
# 或 docker restart cosmo-backend
```
2. **手动执行位置数据下载**(如需要)
- 登录后台管理系统
- 进入"定时任务设置"
- 找到"每日更新天体位置数据"
- 点击"立即执行"
3. **验证前端功能**
- 登录系统
- 检查菜单是否正确显示
- 测试个人资料页面
- 测试我的天体页面
## 常见问题
### Q: 升级过程中断怎么办?
A: 由于使用了事务,中断会自动回滚。使用备份重新开始。
### Q: 如何只导入某个表?
A: 从脚本中复制对应表的部分,单独执行。
### Q: 线上已有自定义菜单怎么办?
A: 脚本会清空菜单,请在升级前导出自定义菜单,升级后手动添加。
### Q: 定时任务什么时候开始执行?
A: 天体事件任务会在下个月1日凌晨3点执行。位置数据任务需手动启用或执行。
## 联系支持
如遇问题,请检查:
1. 数据库日志
2. 应用程序日志
3. 脚本执行输出

View File

@ -0,0 +1,154 @@
# 生产数据库升级指南 (最终版)
## 重要改进
本方案使用 PostgreSQL 的 `session_replication_role` 特性,**彻底解决外键约束问题**
### 优势
- ✅ **无需关心插入顺序** - 可以任意顺序导入数据
- ✅ **大幅提升速度** - 跳过外键检查,导入速度提升数倍
- ✅ **事务安全** - 失败自动回滚,数据一致性有保障
- ✅ **自动验证** - 完成后自动检查数据完整性
### 原理
```sql
-- 1. 开启"上帝模式":暂时忽略外键和触发器
SET session_replication_role = 'replica';
-- 2. 执行所有数据操作(不受外键限制)
BEGIN;
-- 随意导入数据
COMMIT;
-- 3. 恢复正常模式
SET session_replication_role = 'origin';
-- 4. 验证数据一致性
```
## 快速开始
### 方式1一键自动升级推荐
```bash
cd /Users/jiliu/WorkSpace/cosmo/backend/scripts
./upgrade_final.sh
```
### 方式2手动执行
```bash
# 1. 备份数据库
docker exec cosmo_postgres pg_dump -U postgres -d cosmo_db > backup_$(date +%Y%m%d_%H%M%S).sql
# 2. 执行升级Navicat 或命令行均可)
cat upgrade_production_final.sql | docker exec -i cosmo_postgres psql -U postgres -d cosmo_db
```
## 关于 display_name 字段
脚本已适配 `roles` 表的 `display_name` 字段。如果你的生产环境没有这个字段,请:
1. 打开 `upgrade_production_final.sql`
2. 找到第 20-27 行(带 display_name 的版本)
3. 注释掉它们
4. 取消注释第 29-36 行(不带 display_name 的版本)
## 权限要求
**需要 superuser 权限**才能使用 `session_replication_role`
如果你的数据库用户不是 superuser
```sql
-- 由 superuser 执行
ALTER USER your_user WITH SUPERUSER;
-- 升级完成后可以撤销
ALTER USER your_user WITH NOSUPERUSER;
```
## 升级内容
1. ✅ 创建/更新 rolesadmin、user
2. ✅ 添加 celestial_bodies.short_name 字段
3. ✅ 完整导入 14 条 menus 记录
4. ✅ 完整导入 16 条 role_menus 记录
5. ✅ 清空 celestial_events由定时任务重新生成
6. ✅ 导入 2 条 scheduled_jobs
7. ✅ 导入/更新 3 条 system_settings
8. ✅ 为现有用户分配角色
9. ✅ 自动验证数据完整性
## 数据完整性保障
脚本在恢复约束后会自动执行以下验证:
- ✅ 检查 role_menus 的 role_id 引用
- ✅ 检查 role_menus 的 menu_id 引用
- ✅ 检查 menus 的 parent_id 引用
如果发现无效引用,会输出 WARNING 信息。
## 回滚方案
```bash
# 如果升级失败,使用备份恢复
cat backup_YYYYMMDD_HHMMSS.sql | docker exec -i cosmo_postgres psql -U postgres -d cosmo_db
```
## 为什么不迁移到 MySQL
PostgreSQL 的优势:
- 更强的 JSON 支持(你的系统用到 JSONB
- 更好的 GIS 扩展PostGIS适合天文坐标
- 更完善的窗口函数和 CTE
- 更严格的数据完整性保障
使用 `session_replication_role` 后,外键约束不再是问题!
## 常见问题
### Q: 为什么需要 superuser 权限?
A: `session_replication_role` 是为数据库复制设计的特性PostgreSQL 限制只有 superuser 可以修改它,以防止普通用户破坏数据一致性。
### Q: 会影响生产环境吗?
A: 不会。这个设置只影响当前会话,不影响其他连接。而且在事务中执行,失败自动回滚。
### Q: 如果我没有 superuser 权限怎么办?
A:
1. 联系 DBA 临时授予权限
2. 或者使用之前的 `upgrade_production_smart.sql`(会慢一些)
### Q: 数据一致性如何保证?
A: 脚本在恢复约束后会自动验证所有外键引用。如果有问题会立即报告。
## 性能对比
| 方案 | 导入速度 | 复杂度 | 推荐度 |
|------|---------|--------|--------|
| 传统方案(严格顺序) | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐ |
| DISABLE TRIGGER | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| **session_replication_role** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
## 升级后操作
```bash
# 1. 重启后端服务
docker restart cosmo-backend
# 2. 验证前端功能
# - 登录系统
# - 检查菜单显示
# - 测试用户功能
# 3. 手动执行定时任务(可选)
# 在后台管理 -> 定时任务设置 -> 立即执行
```
## 联系支持
如有问题,请检查:
1. PostgreSQL 日志: `docker logs cosmo_postgres --tail 100`
2. 用户权限: `SELECT current_user, usesuper FROM pg_user WHERE usename = current_user;`
3. 数据一致性验证输出

View File

@ -0,0 +1,2 @@
-- 检查 roles 表
SELECT * FROM roles ORDER BY id;

View File

@ -0,0 +1,249 @@
#!/bin/bash
# ============================================================
# 生产数据库终极升级脚本
# ============================================================
# 使用 session_replication_role 绕过外键约束
# 大幅提升升级速度和成功率
# ============================================================
set -e
# 配置
CONTAINER="cosmo_postgres"
DB_NAME="cosmo_db"
DB_USER="postgres"
BACKUP_FILE="backup_$(date +%Y%m%d_%H%M%S).sql"
SCRIPT_FILE="upgrade_production_final.sql"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 颜色
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
print_info() { echo -e "${BLUE} ${1}${NC}"; }
print_success() { echo -e "${GREEN}${1}${NC}"; }
print_warning() { echo -e "${YELLOW}⚠️ ${1}${NC}"; }
print_error() { echo -e "${RED}${1}${NC}"; }
print_step() { echo -e "${CYAN}${1}${NC}"; }
# 检查容器
check_container() {
print_step "检查 Docker 容器状态..."
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER}$"; then
print_error "容器 ${CONTAINER} 未运行!"
docker ps --format "table {{.Names}}\t{{.Status}}"
exit 1
fi
print_success "容器运行正常"
}
# 检查脚本
check_script() {
print_step "检查升级脚本..."
if [ ! -f "${SCRIPT_DIR}/${SCRIPT_FILE}" ]; then
print_error "找不到 ${SCRIPT_FILE}"
exit 1
fi
print_success "脚本就绪"
}
# 检查权限
check_permissions() {
print_step "检查数据库权限..."
SUPERUSER=$(docker exec ${CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -t -c \
"SELECT usesuper FROM pg_user WHERE usename = current_user;" | tr -d ' ')
if [ "$SUPERUSER" != "t" ]; then
print_error "用户 ${DB_USER} 不是 superuser"
echo ""
print_warning "session_replication_role 需要 superuser 权限"
echo "解决方案:"
echo " 1. 使用 superuser 账号执行升级"
echo " 2. 或临时授予权限: ALTER USER ${DB_USER} WITH SUPERUSER;"
exit 1
fi
print_success "权限检查通过 (superuser)"
}
# 检查 display_name 字段
check_display_name() {
print_step "检查 roles 表结构..."
HAS_DISPLAY_NAME=$(docker exec ${CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -t -c \
"SELECT COUNT(*) FROM information_schema.columns
WHERE table_name = 'roles' AND column_name = 'display_name';" | tr -d ' ')
if [ "$HAS_DISPLAY_NAME" = "1" ]; then
print_info "检测到 display_name 字段(将使用对应版本)"
echo ""
print_warning "请确认 upgrade_production_final.sql 中:"
echo " - 第 20-27 行(带 display_name未注释"
echo " - 第 29-36 行(不带 display_name已注释"
echo ""
read -p "是否确认脚本已正确配置? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
print_info "升级已取消,请检查脚本配置"
exit 0
fi
else
print_info "未检测到 display_name 字段"
echo ""
print_warning "请确认 upgrade_production_final.sql 中:"
echo " - 第 20-27 行(带 display_name已注释"
echo " - 第 29-36 行(不带 display_name未注释"
echo ""
read -p "是否确认脚本已正确配置? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
print_info "升级已取消,请检查脚本配置"
exit 0
fi
fi
}
# 备份数据库
backup_database() {
print_step "备份数据库..."
if docker exec ${CONTAINER} pg_dump -U ${DB_USER} -d ${DB_NAME} > "${SCRIPT_DIR}/${BACKUP_FILE}"; then
SIZE=$(ls -lh "${SCRIPT_DIR}/${BACKUP_FILE}" | awk '{print $5}')
print_success "备份完成: ${BACKUP_FILE} (${SIZE})"
else
print_error "备份失败!"
exit 1
fi
}
# 执行升级
execute_upgrade() {
print_step "执行数据库升级..."
echo "========================================================"
if cat "${SCRIPT_DIR}/${SCRIPT_FILE}" | docker exec -i ${CONTAINER} psql -U ${DB_USER} -d ${DB_NAME}; then
echo "========================================================"
print_success "升级执行完成!"
return 0
else
echo "========================================================"
print_error "升级失败!"
return 1
fi
}
# 显示验证结果
show_verification() {
print_step "数据验证..."
echo ""
docker exec ${CONTAINER} psql -U ${DB_USER} -d ${DB_NAME} -c "
SELECT
'celestial_bodies.short_name' as item,
CASE WHEN EXISTS(
SELECT 1 FROM information_schema.columns
WHERE table_name='celestial_bodies' AND column_name='short_name'
) THEN '✓ 存在' ELSE '✗ 缺失' END as status
UNION ALL
SELECT
'roles',
COUNT(*)::text || ' 条记录'
FROM roles
UNION ALL
SELECT
'menus',
COUNT(*)::text || ' 条记录'
FROM menus
UNION ALL
SELECT
'role_menus',
COUNT(*)::text || ' 条记录'
FROM role_menus
UNION ALL
SELECT
'scheduled_jobs',
COUNT(*)::text || ' 条记录'
FROM scheduled_jobs
UNION ALL
SELECT
'system_settings',
COUNT(*)::text || ' 条记录'
FROM system_settings;
" -t
echo ""
}
# 显示回滚信息
show_rollback_info() {
echo ""
print_warning "如需回滚,执行:"
echo "cat ${SCRIPT_DIR}/${BACKUP_FILE} | docker exec -i ${CONTAINER} psql -U ${DB_USER} -d ${DB_NAME}"
echo ""
}
# 主函数
main() {
echo "============================================================"
echo " 生产数据库终极升级脚本"
echo " 使用 session_replication_role 技术"
echo "============================================================"
echo ""
# 检查
check_container
check_script
check_permissions
check_display_name
# 确认
echo ""
print_warning "即将执行以下操作:"
echo " 1. 备份当前数据库"
echo " 2. 使用 replica 模式绕过外键约束"
echo " 3. 导入所有数据(无需关心顺序)"
echo " 4. 恢复正常模式并验证数据完整性"
echo ""
echo "受影响的表:"
echo " • celestial_bodies - 添加 short_name 字段"
echo " • roles - 创建/更新记录"
echo " • menus - 清空并重新导入 (14条)"
echo " • role_menus - 清空并重新导入 (16条)"
echo " • celestial_events - 清空"
echo " • scheduled_jobs - 清空并重新导入 (2条)"
echo " • system_settings - 导入/更新 (3条)"
echo " • user_roles - 为现有用户分配角色"
echo ""
read -p "是否继续? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
print_info "升级已取消"
exit 0
fi
# 执行
echo ""
backup_database
if execute_upgrade; then
show_verification
print_success "🎉 数据库升级成功!"
show_rollback_info
echo ""
print_info "后续步骤:"
echo " 1. 重启后端服务: docker restart cosmo-backend"
echo " 2. 登录系统验证菜单显示"
echo " 3. 测试用户功能"
echo ""
exit 0
else
print_error "升级失败(已自动回滚)"
show_rollback_info
exit 1
fi
}
main

View File

@ -1,19 +1,49 @@
-- ============================================================
-- Production Database Upgrade Script
-- Production Database Upgrade Script (Final Version)
-- ============================================================
-- 使用 session_replication_role 方法绕过外键约束检查
-- 这是数据迁移的最佳实践,显著提升升级效率
-- ============================================================
-- This script upgrades the production database with the following changes:
-- 1. Add short_name to celestial_bodies
-- 2. Import menus and role_menus
-- 3. Import celestial_events
-- 4. Import scheduled_jobs
-- 5. Import system_settings
-- 6. Import user_follows
--
-- IMPORTANT: Run this script in a transaction and test on a backup first!
-- 优势:
-- 1. 无需关心插入顺序
-- 2. 大幅提升导入速度
-- 3. 事务安全,失败自动回滚
--
-- 注意:需要 superuser 权限
-- ============================================================
-- 开启"上帝模式":忽略外键约束和触发器
SET session_replication_role = 'replica';
BEGIN;
-- ============================================================
-- 0. Ensure roles exist (适配 display_name 字段)
-- ============================================================
-- 方式1: 如果 roles 表有 display_name 字段,使用这个
INSERT INTO roles (id, name, display_name, description, created_at, updated_at)
VALUES
(1, 'admin', '管理员', '管理员角色,拥有所有权限', NOW(), NOW()),
(2, 'user', '普通用户', '普通用户角色,只能访问基本功能', NOW(), NOW())
ON CONFLICT (id) DO UPDATE SET
display_name = EXCLUDED.display_name,
description = EXCLUDED.description,
updated_at = NOW();
-- 方式2: 如果没有 display_name 字段,注释掉上面的,使用下面的
-- INSERT INTO roles (id, name, description, created_at, updated_at)
-- VALUES
-- (1, 'admin', '管理员角色,拥有所有权限', NOW(), NOW()),
-- (2, 'user', '普通用户角色,只能访问基本功能', NOW(), NOW())
-- ON CONFLICT (id) DO UPDATE SET
-- description = EXCLUDED.description,
-- updated_at = NOW();
-- Reset sequence for roles
SELECT setval('roles_id_seq', (SELECT COALESCE(MAX(id), 0) FROM roles));
-- ============================================================
-- 1. Add short_name column to celestial_bodies
-- ============================================================
@ -35,14 +65,10 @@ END $$;
-- 2. Import menus and role_menus
-- ============================================================
-- Clear existing menus (will cascade to role_menus due to foreign key)
-- 清空现有数据(因为禁用了约束,可以直接 TRUNCATE
TRUNCATE TABLE menus CASCADE;
RAISE NOTICE 'Cleared existing menus and role_menus';
-- Disable triggers temporarily to handle circular foreign keys
ALTER TABLE menus DISABLE TRIGGER ALL;
-- Insert menus (parent menus first, then child menus)
-- 插入菜单数据(无需关心父子顺序!)
INSERT INTO menus (id, parent_id, name, title, icon, path, component, sort_order, is_active, description, created_at, updated_at) VALUES
(1, NULL, 'dashboard', '控制台', 'dashboard', '/admin/dashboard', 'admin/Dashboard', 1, true, '系统控制台', '2025-11-28 18:07:11.767382', '2025-11-28 18:07:11.767382'),
(2, NULL, 'data_management', '数据管理', 'database', '', '', 2, true, '数据管理模块', '2025-11-28 18:07:11.767382', '2025-11-28 18:07:11.767382'),
@ -59,48 +85,39 @@ INSERT INTO menus (id, parent_id, name, title, icon, path, component, sort_order
(12, 6, 'scheduled_jobs', '定时任务设置', 'ClockCircleOutlined', '/admin/scheduled-jobs', 'admin/ScheduledJobs', 5, true, '管理系统定时任务及脚本', '2025-12-10 17:42:38.031518', '2025-12-10 17:42:38.031518'),
(10, 6, 'system_tasks', '系统任务监控', 'schedule', '/admin/tasks', 'admin/Tasks', 30, true, '', '2025-11-30 16:04:59.572869', '2025-11-30 16:04:59.572869');
-- Re-enable triggers
ALTER TABLE menus ENABLE TRIGGER ALL;
RAISE NOTICE 'Imported menus data';
-- Reset sequence for menus
SELECT setval('menus_id_seq', (SELECT MAX(id) FROM menus));
-- Insert role_menus
-- 插入 role_menus无需担心 roles 是否存在!)
INSERT INTO role_menus (role_id, menu_id) VALUES
-- Admin role (role_id = 1) has access to all menus
(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 10), (1, 11), (1, 12), (1, 13), (1, 14), (1, 15),
-- User role (role_id = 2) has access to user menus only
(2, 14), (2, 15);
RAISE NOTICE 'Imported role_menus data';
-- ============================================================
-- 3. Import celestial_events
-- ============================================================
-- ============================================================
-- 3. Import celestial_events (will be truncated and re-imported)
-- ============================================================
TRUNCATE TABLE celestial_events;
RAISE NOTICE 'Cleared existing celestial_events (data will be regenerated by scheduled jobs)';
-- ============================================================
-- 4. Import scheduled_jobs
-- ============================================================
-- Clear existing scheduled_jobs
TRUNCATE TABLE scheduled_jobs CASCADE;
RAISE NOTICE 'Cleared existing scheduled_jobs';
-- Insert scheduled_jobs
TRUNCATE TABLE scheduled_jobs CASCADE;
INSERT INTO scheduled_jobs (id, name, cron_expression, python_code, is_active, last_run_at, last_run_status, next_run_at, description, created_at, updated_at, job_type, predefined_function, function_params) VALUES
(1, '每日更新天体位置数据', '0 2 * * *', NULL, false, NULL, NULL, NULL, '每天凌晨2点自动从NASA Horizons下载主要天体的位置数据', '2025-12-10 17:43:01.234567', '2025-12-10 17:43:01.234567', 'predefined', 'download_positions_task', '{"body_ids": ["10", "199", "299", "399", "301", "499", "599", "699", "799", "899"], "days_range": "3"}'),
(2, '获取主要天体的食、合、冲等事件', '0 3 1 * *', NULL, true, NULL, NULL, NULL, '每月1日凌晨3点计算未来一年的主要天文事件', '2025-12-10 17:43:01.234567', '2025-12-10 17:43:01.234567', 'predefined', 'calculate_planetary_events', '{"body_ids": ["199", "299", "499", "599", "699", "799", "899"], "days_ahead": "365", "clean_old_events": true, "threshold_degrees": "5", "calculate_close_approaches": true}');
-- Reset sequence
SELECT setval('scheduled_jobs_id_seq', (SELECT MAX(id) FROM scheduled_jobs));
RAISE NOTICE 'Imported scheduled_jobs data';
-- ============================================================
-- 5. Import system_settings
-- ============================================================
-- Use INSERT ... ON CONFLICT to avoid duplicates
INSERT INTO system_settings (key, value, value_type, category, label, description, is_public, created_at, updated_at) VALUES
('view_mode', 'solar', 'string', 'ui', '默认视图模式', '系统默认的3D场景视图模式solar或galaxy', true, NOW(), NOW()),
('nasa_api_timeout', '120', 'int', 'api', 'NASA API超时时间', 'NASA Horizons API请求超时时间', false, NOW(), NOW()),
@ -114,31 +131,107 @@ ON CONFLICT (key) DO UPDATE SET
is_public = EXCLUDED.is_public,
updated_at = NOW();
RAISE NOTICE 'Imported/updated system_settings data';
-- ============================================================
-- 6. Ensure existing users have roles assigned
-- ============================================================
DO $$
DECLARE
user_record RECORD;
user_role_id INTEGER := 2; -- user role
BEGIN
FOR user_record IN SELECT id FROM users LOOP
IF NOT EXISTS (
SELECT 1 FROM user_roles WHERE user_id = user_record.id
) THEN
INSERT INTO user_roles (user_id, role_id)
VALUES (user_record.id, user_role_id);
RAISE NOTICE 'Assigned user role to user %', user_record.id;
END IF;
END LOOP;
END $$;
-- ============================================================
-- 6. Import user_follows (keep existing data, don't truncate)
-- 提交事务
-- ============================================================
-- Note: user_follows should retain existing production data
-- This section is intentionally left empty to preserve user data
RAISE NOTICE 'Skipped user_follows import (preserving existing user data)';
-- ============================================================
-- Commit transaction
-- ============================================================
COMMIT;
-- ============================================================
-- 恢复正常模式(关键步骤!)
-- ============================================================
SET session_replication_role = 'origin';
-- ============================================================
-- 数据一致性验证(在恢复约束后执行)
-- ============================================================
-- 验证外键一致性
DO $$
DECLARE
invalid_count INTEGER;
BEGIN
-- 检查 role_menus 中是否有无效的 role_id
SELECT COUNT(*) INTO invalid_count
FROM role_menus rm
WHERE NOT EXISTS (SELECT 1 FROM roles r WHERE r.id = rm.role_id);
IF invalid_count > 0 THEN
RAISE WARNING 'Found % invalid role_id references in role_menus', invalid_count;
END IF;
-- 检查 role_menus 中是否有无效的 menu_id
SELECT COUNT(*) INTO invalid_count
FROM role_menus rm
WHERE NOT EXISTS (SELECT 1 FROM menus m WHERE m.id = rm.menu_id);
IF invalid_count > 0 THEN
RAISE WARNING 'Found % invalid menu_id references in role_menus', invalid_count;
END IF;
-- 检查 menus 中是否有无效的 parent_id
SELECT COUNT(*) INTO invalid_count
FROM menus m1
WHERE m1.parent_id IS NOT NULL
AND NOT EXISTS (SELECT 1 FROM menus m2 WHERE m2.id = m1.parent_id);
IF invalid_count > 0 THEN
RAISE WARNING 'Found % invalid parent_id references in menus', invalid_count;
END IF;
RAISE NOTICE 'Data integrity validation completed';
END $$;
-- ============================================================
-- Verification queries
-- ============================================================
\echo '============================================================'
\echo 'Upgrade completed successfully!'
\echo '============================================================'
\echo 'Verification:'
SELECT 'celestial_bodies.short_name exists:' as check,
EXISTS(SELECT 1 FROM information_schema.columns WHERE table_name='celestial_bodies' AND column_name='short_name') as result;
SELECT 'menus count:' as check, COUNT(*) as result FROM menus;
SELECT 'role_menus count:' as check, COUNT(*) as result FROM role_menus;
SELECT 'scheduled_jobs count:' as check, COUNT(*) as result FROM scheduled_jobs;
SELECT 'system_settings count:' as check, COUNT(*) as result FROM system_settings;
\echo '============================================================'
-- Check roles
SELECT id, name, description FROM roles ORDER BY id;
-- Check if short_name column exists
SELECT 'celestial_bodies.short_name' as "Item",
CASE WHEN EXISTS(
SELECT 1 FROM information_schema.columns
WHERE table_name='celestial_bodies' AND column_name='short_name'
) THEN '✓ EXISTS' ELSE '✗ MISSING' END as "Status";
-- Check record counts
SELECT 'roles' as "Table", COUNT(*)::text || ' records' as "Count" FROM roles
UNION ALL
SELECT 'menus', COUNT(*)::text || ' records' FROM menus
UNION ALL
SELECT 'role_menus', COUNT(*)::text || ' records' FROM role_menus
UNION ALL
SELECT 'scheduled_jobs', COUNT(*)::text || ' records' FROM scheduled_jobs
UNION ALL
SELECT 'system_settings', COUNT(*)::text || ' records' FROM system_settings;
-- Check user role assignments
SELECT u.id, u.username, COALESCE(array_agg(r.name), ARRAY[]::varchar[]) as roles
FROM users u
LEFT JOIN user_roles ur ON u.id = ur.user_id
LEFT JOIN roles r ON ur.role_id = r.id
GROUP BY u.id, u.username
ORDER BY u.id;