diff --git a/.DS_Store b/.DS_Store index 5d83e98..d378f88 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/backend/scripts/UPGRADE_GUIDE.md b/backend/scripts/UPGRADE_GUIDE.md deleted file mode 100644 index c5e82bf..0000000 --- a/backend/scripts/UPGRADE_GUIDE.md +++ /dev/null @@ -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 :/tmp/ -docker exec -it 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`: 120(NASA 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. 脚本执行输出 diff --git a/backend/scripts/UPGRADE_GUIDE_FINAL.md b/backend/scripts/UPGRADE_GUIDE_FINAL.md new file mode 100644 index 0000000..fb26fcf --- /dev/null +++ b/backend/scripts/UPGRADE_GUIDE_FINAL.md @@ -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. ✅ 创建/更新 roles(admin、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. 数据一致性验证输出 diff --git a/backend/scripts/check_roles.sql b/backend/scripts/check_roles.sql new file mode 100644 index 0000000..1586533 --- /dev/null +++ b/backend/scripts/check_roles.sql @@ -0,0 +1,2 @@ +-- 检查 roles 表 +SELECT * FROM roles ORDER BY id; \ No newline at end of file diff --git a/backend/scripts/upgrade_final.sh b/backend/scripts/upgrade_final.sh new file mode 100755 index 0000000..3c1a204 --- /dev/null +++ b/backend/scripts/upgrade_final.sh @@ -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 diff --git a/backend/scripts/upgrade_production.sql b/backend/scripts/upgrade_production_final.sql similarity index 58% rename from backend/scripts/upgrade_production.sql rename to backend/scripts/upgrade_production_final.sql index 474825f..b209259 100644 --- a/backend/scripts/upgrade_production.sql +++ b/backend/scripts/upgrade_production_final.sql @@ -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; diff --git a/assembly_COSMO_frames.zip b/资源文件/assembly_COSMO_frames.zip similarity index 100% rename from assembly_COSMO_frames.zip rename to 资源文件/assembly_COSMO_frames.zip