You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

7.3 KiB

初始化数据库迁移系统

日期: 2026-01-27
类型: 数据库迁移
影响范围: 全新环境部署

概述

创建完整的初始化数据库迁移脚本,支持全新环境一键创建所有表结构。

背景

之前的基线迁移(baseline)只是标记现有数据库状态,不包含实际的建表逻辑。这导致全新环境部署时无法自动创建表结构。

变更内容

1. 删除旧基线迁移

删除文件:

  • server/alembic/versions/20260127_1412_dc0e4f0a740c_baseline_标记现有数据库状态.py

2. 生成完整初始化迁移

新文件:

  • server/alembic/versions/20260127_1931_3a3a2a1417de_initial_schema_初始化所有表结构.py
  • 迁移版本: 3a3a2a1417de

包含所有表的完整建表逻辑:

  • 用户系统(users, user_sessions)
  • 文件夹系统(folders, folder_members)
  • 项目系统(projects, project_members, project_shares, project_versions)
  • AI 服务(ai_models, ai_jobs, ai_quotas, ai_usage_logs)
  • 积分系统(credit_transactions, credit_consumption_logs, credit_gifts, credit_packages, credit_pricing)
  • 充值系统(recharge_orders, payment_callbacks)
  • 短信服务(sms_verification_codes)

3. 自动创建 UUID v7 函数

迁移脚本在 upgrade() 函数开头自动创建 UUID v7 函数:

def upgrade() -> None:
    """升级数据库"""
    # 1. 创建 UUID v7 函数(必须在创建表之前)
    op.execute("""
        CREATE OR REPLACE FUNCTION gen_uuid_v7() RETURNS uuid AS $$
        DECLARE
          unix_ts_ms BIGINT;
          uuid_bytes BYTEA;
        BEGIN
          unix_ts_ms = (EXTRACT(EPOCH FROM CLOCK_TIMESTAMP()) * 1000)::BIGINT;
          uuid_bytes = SUBSTRING(INT8SEND(unix_ts_ms) FROM 3 FOR 6) || gen_random_bytes(10);
          RETURN ENCODE(
            SET_BYTE(SET_BYTE(uuid_bytes, 6, (GET_BYTE(uuid_bytes, 6) & 15) | 112), 8, (GET_BYTE(uuid_bytes, 8) & 63) | 128),
            'hex'
          )::UUID;
        END;
        $$ LANGUAGE plpgsql VOLATILE;
    """)
    
    # 2. 创建所有表结构
    # ...

downgrade() 函数结尾自动删除:

def downgrade() -> None:
    """回滚数据库"""
    # 删除所有表...
    
    # 3. 删除 UUID v7 函数
    op.execute("DROP FUNCTION IF EXISTS gen_uuid_v7()")

4. 修复枚举值问题

修复了 server/app/models/recharge/order.py 中的索引定义:

  • 问题: 索引条件中使用了枚举对象 PaymentStatus.PENDING
  • 修复: 改为整数值 1(PENDING 的枚举值)
  • 原因: Alembic autogenerate 无法将枚举对象渲染为 SQL 字面值
# 修复前
Index('idx_recharge_orders_payment_status_pending', 'payment_status', 
      postgresql_where=Column('payment_status') == PaymentStatus.PENDING)

# 修复后
Index('idx_recharge_orders_payment_status_pending', 'payment_status', 
      postgresql_where=Column('payment_status') == 1)  # 1 = PENDING

4. 修复枚举值问题

  • 禁止外键约束(应用层验证)
  • 枚举类型使用 SMALLINT
  • UUID v7 主键
  • 时间戳字段(created_at, updated_at, deleted_at)
  • 索引优化

部署指南

全新环境部署

# 1. 启动容器(自动执行迁移)
./start_docker.sh --clean --build

⚠️ 注意

  • 脚本会自动检测首次部署并执行数据库迁移
  • 迁移脚本会自动创建 UUID v7 函数和所有表
  • 迁移成功后会创建标记文件 .migration_initialized

详见:首次部署自动执行数据库迁移

现有环境更新

如果你的数据库已经有表结构:

# 标记为已应用最新迁移(不执行建表)
docker exec jointo-server-app alembic stamp head

# 验证
docker exec jointo-server-app alembic current

验证结果

检查迁移状态

$ docker exec jointo-server-app alembic current
3a3a2a1417de (head)

检查表结构

$ docker exec jointo-server-postgres psql -U jointoAI -d jointo -c "\dt"
                  List of relations
 Schema |          Name           | Type  |  Owner   
--------+-------------------------+-------+----------
 public | ai_jobs                 | table | jointoAI
 public | ai_models               | table | jointoAI
 public | ai_quotas               | table | jointoAI
 public | ai_usage_logs           | table | jointoAI
 public | alembic_version         | table | jointoAI
 public | credit_consumption_logs | table | jointoAI
 public | credit_gifts            | table | jointoAI
 public | credit_packages         | table | jointoAI
 public | credit_pricing          | table | jointoAI
 public | credit_transactions     | table | jointoAI
 public | folder_members          | table | jointoAI
 public | folders                 | table | jointoAI
 public | payment_callbacks       | table | jointoAI
 public | project_members         | table | jointoAI
 public | project_shares          | table | jointoAI
 public | project_versions        | table | jointoAI
 public | projects                | table | jointoAI
 public | recharge_orders         | table | jointoAI
 public | sms_verification_codes  | table | jointoAI
 public | user_sessions           | table | jointoAI
 public | users                   | table | jointoAI
(21 rows)

优势

1. 全新环境支持

  • 一键创建所有表结构
  • 无需手动执行 SQL
  • 自动应用索引和约束

2. 版本化管理

  • 完整的迁移历史
  • 支持 upgrade 和 downgrade
  • 自动依赖管理

3. 团队协作

  • 统一的数据库结构
  • 可重复的部署流程
  • 易于代码审查

注意事项

1. 现有环境

如果你的数据库已经有表结构,不要执行 alembic upgrade,而是使用 alembic stamp head 标记为已应用。

2. 数据备份

在生产环境执行迁移前,务必备份数据库:

docker exec jointo-server-postgres pg_dump -U jointoAI jointo > backup_$(date +%Y%m%d_%H%M%S).sql

3. 迁移测试

在开发环境测试迁移的 upgrade 和 downgrade:

# 测试升级
docker exec jointo-server-app alembic upgrade head

# 测试回滚
docker exec jointo-server-app alembic downgrade base

# 重新升级
docker exec jointo-server-app alembic upgrade head

相关文档

问题排查

问题 1:表已存在

症状:执行 alembic upgrade 时报错 "relation already exists"

解决:使用 alembic stamp head 标记为已应用

问题 2:迁移版本不一致

症状alembic current 显示旧版本或无版本

解决

# 查看当前版本
docker exec jointo-server-app alembic current

# 标记为最新版本
docker exec jointo-server-app alembic stamp head

问题 3:容器未运行

症状:命令执行失败

解决

# 启动容器
docker compose up -d

# 检查容器状态
docker ps | grep jointo-server

总结

完成了从标记式基线迁移到完整初始化迁移的转换,现在支持:

  • 全新环境一键部署
  • 现有环境平滑升级
  • 完整的版本化管理
  • 符合 Jointo 技术栈规范