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的早期版本中,没有封禁账户的功能,所以只定义了UserModeratorAdministrator三种角色,那么如何定义这三种角色呢?

我们建立一个映射,把一个角色所具有的权限映射到角色名上:

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 「算法专栏」 .

0 comments
latest

No comments yet.