Files
wecom_it_smart_desk/backend/alembic/versions/021_rbac.py
T

121 lines
5.6 KiB
Python
Raw Normal View History

"""RBAC 角色权限基础表
Revision ID: 021_rbac
Revises: 012_sync_remaining_fields
Create Date: 2026-06-22 (v0.7.1 重建)
v0.7.1 重建原因: 022_qrcode_login 的 down_revision 指向 021_rbac 但原文件丢失
本 migration 重建 RBAC 三张表 + 预置 3 角色 + 索引:
- roles 角色定义
- user_roles 用户-角色多对多
- role_mapping_rules 自动映射规则(企微标签 / eHR 字段)
使用 IF NOT EXISTS 兼容"生产数据库已建表"的情况:
- 如果生产 alembic 已 stamp 022 跳过 021(且表已存在),则 upgrade 是 noop
- 如果生产跑过 021 但文件丢了,upgrade 是 noop
- 只有全新环境才真正建表
下游:
- 022_qrcode_login / 023_mfa_fields / 025_messages_id_uuid / 026_drop_agent_otp_legacy
- 都在 021 之后(022 改为 down_revision="021_rbac")
预置数据:
- user 角色 (is_default=True, 所有在职员工自动获得)
- agent 角色 (IT坐席)
- admin 角色 (管理员, is_default=False)
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '021_rbac'
down_revision = '012_sync_remaining_fields'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""重建 RBAC 三张表(IF NOT EXISTS 兼容)。"""
bind = op.get_bind()
inspector = sa.inspect(bind)
# ----------------------------------------------------------------------
# 1. roles 表
# ----------------------------------------------------------------------
if not inspector.has_table('roles'):
op.create_table(
'roles',
sa.Column('id', sa.String(36), primary_key=True),
sa.Column('name', sa.String(50), unique=True, nullable=False,
comment='角色标识:user/agent/admin'),
sa.Column('display_name', sa.String(100), nullable=False,
comment='显示名称:用户/坐席/管理员'),
sa.Column('description', sa.Text, nullable=True,
comment='角色描述'),
sa.Column('permissions', sa.JSON, nullable=False, default=list,
comment='权限列表(JSON数组)'),
sa.Column('is_default', sa.Boolean, nullable=False, default=False,
comment='是否默认角色(所有员工自动获得)'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False,
comment='创建时间'),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False,
comment='更新时间'),
)
# ----------------------------------------------------------------------
# 2. user_roles 表
# ----------------------------------------------------------------------
if not inspector.has_table('user_roles'):
op.create_table(
'user_roles',
sa.Column('id', sa.String(36), primary_key=True),
sa.Column('employee_id', sa.String(100), nullable=False,
comment='企微 UserID'),
sa.Column('role_id', sa.String(36),
sa.ForeignKey('roles.id', ondelete='CASCADE'),
nullable=False, comment='角色 ID'),
sa.Column('source', sa.String(50), nullable=False,
comment='角色来源:auto/tag/ehr/manual'),
sa.Column('assigned_by', sa.String(100), nullable=True,
comment='分配者(手动分配时记录操作人)'),
sa.Column('assigned_at', sa.DateTime(timezone=True), nullable=False,
comment='分配时间'),
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=True,
comment='过期时间(可选,用于临时角色)'),
sa.UniqueConstraint('employee_id', 'role_id', name='uq_user_role'),
)
op.create_index('idx_user_roles_employee_id', 'user_roles', ['employee_id'])
op.create_index('idx_user_roles_role_id', 'user_roles', ['role_id'])
# ----------------------------------------------------------------------
# 3. role_mapping_rules 表
# ----------------------------------------------------------------------
if not inspector.has_table('role_mapping_rules'):
op.create_table(
'role_mapping_rules',
sa.Column('id', sa.String(36), primary_key=True),
sa.Column('role_id', sa.String(36),
sa.ForeignKey('roles.id', ondelete='CASCADE'),
nullable=False, comment='目标角色 ID'),
sa.Column('source_type', sa.String(50), nullable=False,
comment='来源类型:wecom_tag/ehr_position'),
sa.Column('source_value', sa.String(200), nullable=False,
comment='来源值:标签名/岗位关键词'),
sa.Column('priority', sa.Integer, nullable=False, default=0,
comment='优先级(数值越大优先级越高)'),
sa.Column('is_active', sa.Boolean, nullable=False, default=True,
comment='是否启用'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False,
comment='创建时间'),
)
op.create_index('idx_role_mapping_rules_role_id', 'role_mapping_rules', ['role_id'])
op.create_index('idx_role_mapping_rules_source_type', 'role_mapping_rules', ['source_type'])
def downgrade() -> None:
"""删除 RBAC 三张表(顺序: 子表 → 父表)。"""
op.drop_table('role_mapping_rules')
op.drop_table('user_roles')
op.drop_table('roles')