1.0.3
parent
3c16839fa5
commit
898f207302
|
|
@ -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`: 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. 脚本执行输出
|
||||
|
|
@ -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. 数据一致性验证输出
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
-- 检查 roles 表
|
||||
SELECT * FROM roles ORDER BY id;
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
Loading…
Reference in New Issue