前言
之前我们简单规划了平台基本功能,我们先从角色列表的增删改查开始,这里涉及到角色绑定权限。
Models
这里我们用三张表来表示角色、权限、以及角色与权限之间的关系。
role
class Role(BaseModel):
__tablename__ = 'role'
id = Column(INT, primary_key=True, index=True, autoincrement=True)
created_at = Column(DATETIME, default=datetime.now, comment="创建时间")
updated_at = Column(DATETIME, default=datetime.now, onupdate=datetime.now, comment="更新时间")
name = Column(String(100), nullable=False)
status = Column(INT, default=1, comment="1:可用 0:不可用")
comment = Column(String(100), comment="备注")
这个表主要记录角色本身的信息,比如名字、状态
ability
class Ability(BaseModel):
__tablename__ = "ability"
id = Column(INT, primary_key=True, index=True, autoincrement=True)
created_at = Column(DATETIME, default=datetime.now, comment="创建时间")
updated_at = Column(DATETIME, default=datetime.now, onupdate=datetime.now, comment="更新时间")
name = Column(String(100))
handler = Column(String(100))
status = Column(INT, default=1, comment="1:可用 0:不可用")
这个表主要记录权限数据
role_bind_ability
class RoleBindAbility(BaseModel):
__tablename__ = 'role_bind_ability'
role_id = Column(INT)
ability_id = Column(INT)
这个表来记录角色与权限之间的关系,删除角色时,要将此表相关的数据也删除。
定义接口
无非就是增删改查。我们先定义接口,再逐个进行实现。
增
@router.post("/create")
def role_create(data: role_schema.Role):
pass
改
@router.post("/update/{role_id}")
def role_update(role_id: int, data: role_schema.Role):
pass
删
@router.delete("/delete/{role_id}")
def role_delete(role_id: int):
pass
查
@router.get("/list")
def role_list():
pass
实现接口
增
我们先想想逻辑:
- 为避免角色重复,先查询角色名称是否存在,如果存在,直接返回。
- 如果不存在,我们就提交数据库,保存到role表中
- 如果未勾选相关权限,返回添加的数据。这样就完成了创建一个角色
- 如果勾选的相关权限,此时我们还需要将关联数据保存到role_bind_ability表中
逻辑清晰了,就变为代码:
class RoleService(object):
@classmethod
def create(cls, name: str, comment: str, ability_ids: list = None):
try:
with SessionLocal() as session:
role = session.query(Role).filter(Role.name == name, Role.status != 0).first()
if role:
return None, '角色已存在'
r = Role(name=name, comment=comment)
session.add(r)
session.commit()
if ability_ids:
cls.bind_abilities(r.id, ability_ids)
return r, None
except Exception as e:
cls.log.error(f"创建角色失败:{str(e)}")
return None, str(r)
@classmethod
def bind_abilities(cls, _id, ability_ids: list):
try:
with SessionLocal() as session:
[session.delete(item) for item in session.query(RoleBindAbility).filter(RoleBindAbility.role_id == _id).all()]
if ability_ids:
[session.add(RoleBindAbility(role_id=_id, ability_id=abl_id)) for abl_id in ability_ids]
session.commit()
return 1, None
except Exception as e:
cls.log.error(f"角色绑定权限失败:{str(e)}")
return None, str(e)
可以看到与设想逻辑差不多,绑定权限时,先删除了之前已经绑定的权限,然后重新进行关联。
改
增加实现了,更改就简单了,无非就是多了role_id
- 根据role_id查询到角色
- 更改名称、别称、权限即可
class RoleService(object):
@classmethod
def update(cls, _id, name, comment, ability_ids):
try:
with SessionLocal() as session:
r = session.query(Role).filter(Role.id == _id).first()
r.name = name
r.comment = comment
cls.bind_abilities(_id, ability_ids)
session.add(r)
session.commit()
return 1, None
except Exception as e:
session.rollback()
cls.log.error(f"更新角色失败:{str(e)}")
return None, str(e)
可以看到,把绑定权限的方法单独抽离出来还是有好处的,更改时直接调用该方法即可。这里注意,在发生异常时,我们回滚之前所有的操作,取消对数据库的修改。
删
这里我们对role表做软删除,更改状态(和上面更新操作一样),对role_bind_ability做物理删除,直接删除数据
class RoleService(object):
@classmethod
def delete(cls, role_id):
try:
with SessionLocal() as session:
role = session.query(Role).filter(Role.id == role_id).first()
role.status = 0
session.add(role)
role_bind_ability = session.query(RoleBindAbility).filter(RoleBindAbility.role_id == role_id).all()
[session.delete(item) for item in role_bind_ability]
session.commit()
return 1, None
except Exception as e:
cls.log.error(f"删除角色失败:{str(e)}")
return None, "删除角色失败:{str(e)}"
查
查比较复杂一些,需要联表查询,role、ability、role_bind_ability联表查询才能得到想要的结果。
我们先手写sql:
select Role.id, Role.name, Role.comment, GROUP_CONCAT(Ability.id) AS ability_ids FROM role LEFT OUTER JOIN role_bind_ability ON role.id = role_bind_ability.role_id LEFT OUTER JOIN ability ON ability.id = role_bind_ability.ability_id
WHERE role.status = 1 GROUP BY Role.id, Role.name, Role.comment;
在这个查询中,我们使用了 GROUP_CONCAT 函数来将每个角色对应的多个权限拼接成一个字符串。通过使用 GROUP BY 子句,我们可以确保每个角色只出现一次。
sqlalchemy提供了select来拼接sql,当然直接用上面的sql也是可以执行的
class RoleService(object):
@classmethod
def _query(cls):
return select(Role.id, Role.name, Role.comment, func.group_concat(Ability.id).label('abilitys')
,func.group_concat(Ability.name).label('ability_names')).outerjoin(
RoleBindAbility, Role.id == RoleBindAbility.role_id).outerjoin(
Ability, Ability.id == RoleBindAbility.ability_id).group_by(Role.id, Role.name, Role.comment)
@classmethod
def query_all(cls):
conditions = [Role.status == 1]
sql = cls._query().where(*conditions)
try:
with SessionLocal() as session:
data = session.execute(sql).fetchall()
return data, None
except Exception as e:
return None, str(e)
到这里。就实现的角色增删改查的核心功能。
最后,在定义好的接口中调用这些方法,返回数据即可,举个例子
@router.post("/create")
def role_create(data: role_schema.Role):
data, err = RoleService.create(data.name, data.comment, data.ability_ids)
if err:
return resp_error(USER_ERROR_A0500, msg=err)
return resp_success()
其他接口也一样。
后续
1、这里实现每个方法都重复写with SessionLocal() as session:,有点懒了,之后想想怎么优化
2、查询时,未实现分页查询