Files
wecom_it_smart_desk/backend/alembic/versions/007_role_system.py
T

105 lines
5.7 KiB
Python

"""role system — 统一入口角色系统迁移
新增 roles 表(角色定义)。
新增 user_roles 表(用户角色关联)。
新增 role_mapping_rules 表(角色映射规则)。
预置三个基础角色:user、agent、admin。
Revision ID: 007_role_sys
Revises: 006_admin_ext
Create Date: 2026-06-12 23:00:00.000000
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '007_role_sys'
down_revision = '006_admin_ext'
branch_labels = None
depends_on = None
def upgrade() -> None:
"""执行角色系统迁移。"""
# 1. 创建 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, server_default='[]', comment='权限列表'),
sa.Column('is_default', sa.Boolean, nullable=False, server_default='0', comment='是否默认角色'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), comment='创建时间'),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), comment='更新时间'),
)
# 2. 创建 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, server_default=sa.func.now(), 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 表
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, server_default='0', comment='优先级'),
sa.Column('is_active', sa.Boolean(), nullable=False, server_default='1', comment='是否启用'),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now(), 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'])
# 4. 预置三个基础角色
# 注意:使用 op.execute 直接插入数据,因为 server_default 不适用于 Python 端生成的 UUID
# PostgreSQL 使用 NOW() 替代 SQLite 的 datetime('now')
op.execute("""
INSERT INTO roles (id, name, display_name, description, permissions, is_default, created_at, updated_at) VALUES
('role_user_001', 'user', '用户', '所有在职员工默认角色,可提交工单、查看进度、浏览知识库', '["ticket.create", "ticket.view", "knowledge.view"]', TRUE, NOW(), NOW()),
('role_agent_001', 'agent', '坐席', 'IT支持人员,可处理会话、使用AI辅助、管理工单', '["conversation.manage", "ticket.assign", "knowledge.edit", "ai.wingman"]', FALSE, NOW(), NOW()),
('role_admin_001', 'admin', '管理员', '系统管理员,可配置系统、管理权限、查看数据分析', '["system.config", "user.manage", "role.manage", "analytics.view"]', FALSE, NOW(), NOW())
""")
# 5. 预置默认映射规则(企微标签 → agent 角色)
op.execute("""
INSERT INTO role_mapping_rules (id, role_id, source_type, source_value, priority, is_active, created_at) VALUES
('rule_agent_tag_001', 'role_agent_001', 'wecom_tag', 'IT坐席', 10, TRUE, NOW()),
('rule_agent_ehr_001', 'role_agent_001', 'ehr_position', 'IT支持', 10, TRUE, NOW()),
('rule_agent_ehr_002', 'role_agent_001', 'ehr_position', 'IT运维', 10, TRUE, NOW()),
('rule_agent_ehr_003', 'role_agent_001', 'ehr_position', '技术支持', 10, TRUE, NOW())
""")
def downgrade() -> None:
"""回滚角色系统迁移。"""
# 删除 role_mapping_rules 表索引和表
op.drop_index('idx_role_mapping_rules_source_type', table_name='role_mapping_rules')
op.drop_index('idx_role_mapping_rules_role_id', table_name='role_mapping_rules')
op.drop_table('role_mapping_rules')
# 删除 user_roles 表索引和表
op.drop_index('idx_user_roles_role_id', table_name='user_roles')
op.drop_index('idx_user_roles_employee_id', table_name='user_roles')
op.drop_table('user_roles')
# 删除 roles 表
op.drop_table('roles')