diff --git a/alembic/versions/049_wechat_login_phone.py b/alembic/versions/049_wechat_login_phone.py index 628dd6f9f..302caa573 100755 --- a/alembic/versions/049_wechat_login_phone.py +++ b/alembic/versions/049_wechat_login_phone.py @@ -1,17 +1,18 @@ -"""#558 - 微信登录:用户手机号字段 + 验证码表 +"""#558 - 微信登录:手机号绑定字段 + 验证码表 Revision ID: 049 Revises: 048 Create Date: 2026-07-21 Changes: -1. users 表新增 phone / phone_verified / binding_completed_at 字段 -2. 新建 verification_codes 表(统一管理邮箱+手机验证码) +1. users 表新增 phone_verified / binding_completed_at 字段(phone 字段已在 029 中添加) +2. users 表 phone 字段添加唯一索引(幂等) +3. 新建 verification_codes 表(统一管理邮箱+手机验证码) """ import sqlalchemy as sa -from alembic import op +from alembic import context, op revision = "049_wechat_login_phone" down_revision = "048_cleanup_result_count" @@ -19,26 +20,59 @@ branch_labels = None depends_on = None -def upgrade() -> None: - # 1. users 表新增手机号相关字段 - op.add_column("users", sa.Column("phone", sa.String(32), nullable=True)) - op.add_column( - "users", - sa.Column( - "phone_verified", - sa.Boolean, - nullable=False, - server_default=sa.text("false"), +def _column_exists(table: str, column: str) -> bool: + """检查列是否已存在。离线模式下返回 False。""" + if context.is_offline_mode(): + return False + conn = op.get_bind() + result = conn.execute( + sa.text( + "SELECT 1 FROM information_schema.columns " + "WHERE table_name = :table AND column_name = :column" ), + {"table": table, "column": column}, ) - op.add_column( - "users", - sa.Column("binding_completed_at", sa.DateTime, nullable=True), - ) - # phone 唯一索引(已验证的手机号唯一,未验证的不做唯一约束) - op.create_index("ix_users_phone", "users", ["phone"], unique=True) + return result.first() is not None - # 2. verification_codes 表 + +def _index_exists(index_name: str) -> bool: + """检查索引是否已存在。离线模式下返回 False。""" + if context.is_offline_mode(): + return False + conn = op.get_bind() + result = conn.execute( + sa.text( + "SELECT 1 FROM pg_indexes WHERE indexname = :index_name" + ), + {"index_name": index_name}, + ) + return result.first() is not None + + +def upgrade() -> None: + # 1. users 表新增手机号验证状态字段(幂等) + if not _column_exists("users", "phone_verified"): + op.add_column( + "users", + sa.Column( + "phone_verified", + sa.Boolean, + nullable=False, + server_default=sa.text("false"), + ), + ) + + if not _column_exists("users", "binding_completed_at"): + op.add_column( + "users", + sa.Column("binding_completed_at", sa.DateTime, nullable=True), + ) + + # 2. phone 字段唯一索引(幂等 - 029 加了字段但没加索引) + if not _index_exists("ix_users_phone"): + op.create_index("ix_users_phone", "users", ["phone"], unique=True) + + # 3. verification_codes 表 op.create_table( "verification_codes", sa.Column("id", sa.String(36), primary_key=True), @@ -60,7 +94,9 @@ def upgrade() -> None: def downgrade() -> None: op.drop_table("verification_codes") - op.drop_index("ix_users_phone", table_name="users") - op.drop_column("users", "binding_completed_at") - op.drop_column("users", "phone_verified") - op.drop_column("users", "phone") + if _index_exists("ix_users_phone"): + op.drop_index("ix_users_phone", table_name="users") + if _column_exists("users", "binding_completed_at"): + op.drop_column("users", "binding_completed_at") + if _column_exists("users", "phone_verified"): + op.drop_column("users", "phone_verified")