Flog源码解读第一期
从今天开始,这个专栏不定期更新Flog的源码解读。
众所周知,Flog是一个庞大的Python项目,其现在拥有大约7000行的纯手写代码,以及6000行的其余文件(包括API文档、单元测试、数据库迁移代码以及项目依赖项文件等,这些文件都非常重要)。由于项目今天的版本过于庞大,我选择一个较早期的Flog版本(2020/11/1日版本)给大家解读,不过其中有一些糟粕,需要去掉。除此之外,会有一些数据模型的思想新出现在今天的Flog中,也会提到。
首先,我先从开发者的角度介绍一下Flog:Flog是一个用Flask写成,包括Web API、前端页面,支持两种语言的多人博客网站,其前端页面使用著名的Bootstrap写成。其权限系统分为管理员、协管员、普通用户以及被封禁用户,数据库使用SQL数据库。
阅读这份源码解读之前,首先要对Flask及Python的基本内容有一定了解。如果想要快速入门一下Flask的话,可以考虑从李辉的Flask入门教程学起。
第一期内容——权限系统
好了,今天,我们先来介绍Flog的权限系统。
我们首先来定义一个Permission类,用来表示一种具体的权限,至于为什么用2的指数来表示是待会的重点内容:
class Permission: FOLLOW = 1 # 关注权限 COMMENT = 2 # 评论权限 WRITE = 4 # 写作权限 MODERATE = 8 # 协管权限 ADMIN = 16 # 管理权限
我们再来定义一个Role类,这个类表示一种角色所拥有的权限:
class Role(db.Model): # id是数据库的主键,是一个数据模型的标志 id = db.Column(db.Integer, primary_key=True) # 权限名 name = db.Column(db.String(64), unique=True) # 一个角色所具有的所有权限 permissions = db.Column(db.Integer) # 属于该角色的所有用户 users = db.relationship('User', back_populates='role') # 是否是默认角色(用户),这里括号中default=True或False都无所谓 default = db.Column(db.Boolean, default=False, index=True)
我们给这个类添加几个“方法”:
class Role(db.Model): # ... def add_permission(self, permission: int): # 如果自己没有这个权限的话,则在所有权限中添加这个权限 if not self.has_permission(permission): self.permissions += permission def remove_permission(self, permission: int): # 同理 if self.has_permission(permission): self.permissions = 0 def has_permission(self, permission: int): """重点内容!!!""" return (self.permissions & perm) == perm
注意,我们将会对has_permission
这个函数进行解释,这也是我们为什么在前面要使用2的指数作为权限表示法的原因:
还记得我们前几期提到的位运算吧:
来个简单的:
如何运算2 & 1?
2: 10 1: 01 ----- 0: 00
将两个数二进制的每一位进行比较,只有当两位均为1是,该位才为1,否则为0
再来一道:
如何运算7 & 5:
7: 111 5: 101 ------ 5: 101
7 & 5 = 5
在这里也是一样,比如假设某一用户的所有权限值为63(即管理员),若我们要知道他有没有评论权限,我们可以这样运算:
63: 111111 2 : 000010 ---------- 2 : 000010
所以只需判断所有权限值 & 所查询的权限值 是否与 该权限相等即可:
表现在Python代码中,即 (self.permissions & perm) == perm
在Flog的早期版本中,没有封禁账户的功能,所以只定义了User
、Moderator
、Administrator
三种角色,那么如何定义这三种角色呢?
我们建立一个映射,把一个角色所具有的权限映射到角色名上:
roles = { 'User': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE], 'Moderator': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE, Permission.MODERATE], 'Administrator': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE, Permission.MODERATE, Permission.ADMIN] }
用户具有关注、评论和创作的权限,
协管员则具有用户的所有权限和协管权限
管理员则具有协管员的所有权限和管理权限
我们把这个映射定义在静态方法insert_roles
中,这个方法用来进行对数据库中用户的角色初始化,不详细讲了:
@staticmethod def insert_roles(): roles = { 'User': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE], 'Moderator': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE, Permission.MODERATE], 'Administrator': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE, Permission.MODERATE, Permission.ADMIN] } default_role = 'User' for r in roles: role = Role.query.filter_by(name=r).first() if role is None: role = Role(name=r) role.reset_permissions() for perm in roles[r]: role.add_permission(perm) role.default = (role.name == default_role) db.session.add(role) db.session.commit()
附上今天讲解的全部代码:
class Permission: FOLLOW = 1 COMMENT = 2 WRITE = 4 MODERATE = 8 ADMIN = 16 class Role(db.Model): __tablename__ = 'roles' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(64), unique=True) default = db.Column(db.Boolean, default=False, index=True) permissions = db.Column(db.Integer) users = db.relationship('User', back_populates='role') def __init__(self, **kwargs): super(Role, self).__init__(**kwargs) if self.permissions is None: self.permissions = 0 def __repr__(self): return self.name def add_permission(self, perm): if not self.has_permission(perm): self.permissions += perm def remove_permission(self, perm): if self.has_permission(perm): self.permissions -= perm def reset_permissions(self): self.permissions = 0 def has_permission(self, perm): """ Check if a single permission is in a combined permission """ return self.permissions & perm == perm @staticmethod def insert_roles(): roles = { 'User': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE], 'Moderator': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE, Permission.MODERATE], 'Administrator': [Permission.FOLLOW, Permission.COMMENT, Permission.WRITE, Permission.MODERATE, Permission.ADMIN] } default_role = 'User' for r in roles: role = Role.query.filter_by(name=r).first() if role is None: role = Role(name=r) role.reset_permissions() for perm in roles[r]: role.add_permission(perm) role.default = (role.name == default_role) db.session.add(role) db.session.commit()
代码地址:flog/models.py
This post belongs to Column 「算法专栏」 .