flask项目代码解析

.
.
.
.

check_permit_role_api 装饰器函数


# 两层校验
# 既对token校验,也通过用户角色来判断,该用户是否有执行该api的权限
-----------------------------------------------

# 对token的校验,如果是允许的永久token,就不进行role_id的校验了,直接让通过
# 如果是普通的token,就进行正常的token校验,如果没有问题
# 再通过该token,获取对应的用户对象,然后得到该用户对象的role_id
# 如果该用户对象的role_id,在我们设置的强制允许通过的角色id列表中pass_role_id_list中,也直接让通过,不校验了

# 最后如果是普通token,我们不设强制允许通过的角色id列表,或用户对应的role_id不在强制允许通过的角色id列表中
# 那就只能老老实实的到角色与api多对多的表里面,查看该角色是否有对应的api关系,有就说明该角色有改api的权限,也让通过

-----------------------------------------------

def check_permit_role_api(api_id: int, pass_token = None, pass_role_id_list: list = None):
    def _check_permit_role_api(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            # 检查headers中是否传入token
            if 'token' not in request.headers:
                return jsonify_operation_msg(False, 'headers中没有找到token')

            if pass_token:
                # 判断pass_token是否是列表类型
                if isinstance(pass_token, list):
                    if request.headers['token'] in pass_token:
                        return func()

                # 默认传入为字符串,检查是否为可通过token,是则放行
                if request.headers['token'] == pass_token:
                    return func()

            # 根据请求头里token,获取session对象
            session: AuthSession = db.session.query(AuthSession).where(AuthSession.token == request.headers['token'],
                                                                       AuthSession.expire_time >= datetime.now()).first()

            # 检查是否存在有效期内的token
            if session is None:
                return jsonify_operation_msg(False, 'token无效,无法调用模块', code=999)

            # 对快到期的token进行检查,如果快到期则延长
            if session.expire_time <= (datetime.now() + timedelta(seconds=token_expire_time / 2)):
                try:
                    session.expire_time = datetime.now() + timedelta(seconds=token_expire_time)
                    db.session.commit()
                except:
                    db.session.rollback()

            # 根据session对象,获取用户对象
            user: AuthUser = db.session.query(AuthUser).where(AuthUser.id == session.user_id).first()

            if pass_role_id_list:

                # 如果用户本身就是根管理员,直接放行
                if user.role_id in pass_role_id_list:
                    return func()

            # 角色与api多对多的表里面,查看该角色是否有对应的api关系,有就说明该角色有改api的权限,也让通过
            permit_role_api: AuthPermitRoleApi = db.session.query(AuthPermitRoleApi) \
                .select_from(AuthPermitRoleApi) \
                .outerjoin(AuthUser, AuthPermitRoleApi.role_id == AuthUser.role_id) \
                .filter(AuthUser.id == user.id, AuthPermitRoleApi.api_id == api_id) \
                .first()

            if permit_role_api is None:
                return jsonify_operation_msg(False, '用户角色无该权限,无法调用模块')

            return func()

        wrapper.__name__ = func.__name__
        return wrapper

    return _check_permit_role_api



.
.
.
.
.
.
.
.

展示当前用户所能看到的api资源树,下每一个api对象信息,并且展示出要查的角色,有没有这些api的权限


from sqlalchemy.orm import aliased


# -----------------------------------------------------------

# 该递归函数
# 目的就是确定当前的api对象,有没有子api对象,如果没有,返回一个空列表,那么就不再递归了
# 如果有子api对象,那么就返回一个列表套字典的数据,列表套字典的数据里,每一个字典里,都包含一个子api对象的数据
# 并且同时判断要查的角色对象是否有  当前api的每一个子api对象的权限!!!
def find_children_detail_with_role_permit(self, role: AuthRole, user: AuthUser = None):
    # self 是查出来的api对象
    # 要查的角色对象与当前用户对象

    # 如果当前用户没有用SUPER_ADMIN_TOKEN,并且该用户不是超级管理员,走如下逻辑
    if user and user.role_id != 1:

        # 先给 AuthPermitRoleApi表起了两个别名
        user_api_permit: AuthPermitRoleApi = aliased(AuthPermitRoleApi)
        role_api_permit: AuthPermitRoleApi = aliased(AuthPermitRoleApi)

        # 返回值类型为 List[Tuple[AuthResourceApiExt, AuthPermitRoleApi]]
        # api表作为起始表,连接api角色多对多表,并且通过起别名的方式,连这个表连两次
        # .outerjoin(role_api_permit,(user_api_permit.api_id == role_api_permit.api_id) & (role_api_permit.role_id == role.id))
        # 再次使用外连接,将 user_api_permit 表与 role_api_permit 表连接,连接条件是它们的 api_id 字段相等,
        # 并且加了另一个连表条件 role_api_permit 表中的 role_id 字段等于某个 role 对象的 id 字段。 这样如果没连到role_api_permit表,行对象就是一个空了!!!

        # 对查询结果进行过滤,过滤出符合 api表的parent_id等于 当前api对象id,且api与角色多对多表里角色id等于用户对象的role_id 的api对象来
        # 也就是说过滤出当前api对象的子api对象,并且这些子api对象还要是,当前用户的角色所拥有的!!!
        # 而且还通过多连的那一张 角色与api多对多表,的表对象的有无,知道了要查的角色对象是否拥有该子api对象的权限!!!
        children_with_permit = db.session.query(AuthResourceApiExt, role_api_permit) \
            .select_from(AuthResourceApiExt) \
            .outerjoin(user_api_permit, AuthResourceApiExt.id == user_api_permit.api_id) \
            .outerjoin(role_api_permit,
                       (user_api_permit.api_id == role_api_permit.api_id) & (role_api_permit.role_id == role.id)) \
            .filter(AuthResourceApiExt.parent_id == self.id, user_api_permit.role_id == user.role_id) \
            .all()

    # 如果当前用户用的SUPER_ADMIN_TOKEN,或者用户的角色是超级管理员,走下面的逻辑
    # 返回值类型为 List[Tuple[AuthResourceApiExt, AuthPermitRoleApi]]
    else:
        children_with_permit = db.session.query(AuthResourceApiExt, AuthPermitRoleApi) \
            .select_from(AuthResourceApiExt) \
            .outerjoin(AuthPermitRoleApi,
                       (AuthResourceApiExt.id == AuthPermitRoleApi.api_id) & (AuthPermitRoleApi.role_id == role.id)) \
            .filter(AuthResourceApiExt.parent_id == self.id) \
            .all()

    # 先定义一个空列表,用于存储最终结果
    children_detail_with_role_permit = []

    # 处理上面查询获得的 children_with_permit列表套元组的数据
    for child_api, child_permit in children_with_permit:
        list_child_children = None
        if child_api.type != 1:
            # api子对象的type字段的值如果不为1,说明该子对象也是一个父对象,再递归调用自身函数
            list_child_children = child_api.find_children_detail_with_role_permit(role, user)

        if list_child_children:
            children_detail_with_role_permit.append({
                'id': child_api.id,
                'title': child_api.title,
                'type': child_api.type,
                'description': child_api.description,
                'permit': True if child_permit else False,
                # 可以这样改,这样改就不需要给api_role多对多表起别名,然后连这个表两次,一个用来过滤,
                # 一个用来判断permit,直接连一次过滤一下就行了
                # 但这样改,想要判断要查询的角色是否有,每一个子api的权限,
                # 那这个orm查询要执行好多次,效率有点低了!!!!!!!!
                # 'permit': True if db.session.query(AuthPermitRoleApi).filter(role_id==role.id,api_id==child_api).first() else False,
                'children': list_child_children
            })

        # 如果child_permit为None,说明api对象的type字段的值等于1,说明该api对象是个子对象,不再递归了!!!
        else:
            children_detail_with_role_permit.append({
                'id': child_api.id,
                'title': child_api.title,
                'type': child_api.type,
                'description': child_api.description,
                'permit': True if child_permit else False
            })

    # 返回一个列表套字典的数据
    return children_detail_with_role_permit


# -----------------------------------------------------------
# -----------------------------------------------------------


# 每一个能给当前用户展示的 api对象,调用该方法,获取一个字典数据
def find_detail_with_permit_and_children_and_children_permit(self, role: AuthRole, user: AuthUser = None):
    # self是查出来的api对象
    # 如果要查的角色对象的id为1,说明要查的角色是根管理员
    if role.id == 1:
        result = True

    # 如果要查的角色对象的id不为1,不是根管理员
    else:
        # 查询  角色与api多对多表里,api_id等于当前传进来的api对象id的,并且role_id等于要查的角色id的行对象
        # 如果有说明,该要查的角色也有该api对象的权限!!!
        # 相反如果没查出来,说明该要查的角色没有该api对象的权限!!!
        # 执行该ORM的目的就是,通过查询结果确定,要查的角色是否有该api对象的权限,据此给permit键赋值
        result: AuthPermitRoleApi = db.session.query(AuthPermitRoleApi) \
            .filter(AuthPermitRoleApi.api_id == self.id, AuthPermitRoleApi.role_id == role.id) \
            .first()

    # api对象调用find_children_detail_with_role_permit方法,并将要查的角色对象与当前用户对象作为参数传入
    # 目的就是确定当前的api对象,有没有子api对象,如果没有,返回一个空列表,那么就不再递归了
    # 如果有子api对象,那么就返回一个列表套字典的数据,列表套字典的数据里,每一个字典里,都包含一个子api对象的数据
    list_children_dict = self.find_children_detail_with_role_permit(role, user)

    # 也就是说到这一步,当前的api对象的所有数据已经确定了
    # 如果有子api对象,通过children键把子对象数据 list_children_dict [{},{},{}]挂上去
    # 没有子api对象,api对象的数据,就正常返回就行了!!!
    if list_children_dict == []:
        return {
            'id': self.id,
            'title': self.title,
            'type': self.type,
            'description': self.description,
            'permit': True if result else False
        }

    return {
        'id': self.id,
        'title': self.title,
        'type': self.type,
        'description': self.description,
        'permit': True if result else False,
        'children': list_children_dict
    }


# -----------------------------------------------------------

@blueprint.route('/resource_api_list_tree_with_role_permit', methods=['POST'])
@check_permit_role_api(118, SUPER_ADMIN_TOKEN, [1])
@body_need_keys(['role_id'])
@check_request_json_key_type([('role_id', int), ('api_parent_id', int)])
@token_required_with_user_allowed_pass_token(SUPER_ADMIN_TOKEN)
def resource_api_list_tree_with_role_permit(user: AuthUser):
    # 当前用户所能看到的API资源树下,要查的角色id对应的API资源是否允许查看

    # 先根据当前用户传过来的要查的role_id,查到对应的角色对象
    role: AuthRole = db.session.query(AuthRole).where(AuthRole.id == request.json['role_id']).first()

    # 判断一下有没有该角色对象
    if role is None:
        return jsonify_return_body(body_key_list=['code', 'msg', 'result'], result=False, msg='无此id用户角色')

    # 判断一下token_required_with_user_allowed_pass_token装饰器传给,视图函数的user是不是空,是空说明,用的SUPER_ADMIN_TOKEN登录的,
    # 此时只知道是超级管理员,不知道具体用户是谁, 如果user有值说明是普通的token,装饰器传给视图函数的user是该token对应的用户对象
    # 此时先判断该用户的角色是否为1,也就是判断是不是超级管理员角色
    if user and user.role_id != 1:
        # 如果没有用SUPER_ADMIN_TOKEN,并且该用户不是超级管理员,走如下逻辑
        # 如果传入了api_parent_id对应的值,查询api表里parent_id等于该值的子对象,并通过与api与角色多对多表连表,再次过滤出
        # api与角色多对多表中role_id等于当前用户的role_id的所有对象,也就是通过连表与两个过滤条件,过滤出的是
        # 同时满足api表中parent_id字段对应的值等于传入的要查的值,
        # 并且连表的api与角色多对多表中,这些api_id对应的role_id中,必须有当前用户所属的role_id,的所有子api对象
        # 这些对象就是当前用户,在parent_id对应的父组下,所能允许使用的api子对象!!!
        # 当前api表里面定义的   account的id为100   biz的id为200   node的id为300
        # 这3个既是根api对象的子对象,也同时是父对象,他们下面挂了很多对应的子api对象
        if 'api_parent_id' in request.json.keys():
            roots: List[AuthResourceApiExt] = db.session.query(AuthResourceApiExt) \
                .select_from(AuthResourceApiExt) \
                .outerjoin(AuthPermitRoleApi, AuthResourceApiExt.id == AuthPermitRoleApi.api_id) \
                .filter(
                AuthResourceApiExt.parent_id == request.json['api_parent_id'],
                AuthPermitRoleApi.role_id == user.role_id
            ).all()
        # 感觉过滤条件这样 AuthResourceApiExt.id == request.json['api_parent_id']  更合理  找出id是要查的api_parent_id的父api对象出来
        # 但是现在前端想的是,如果传入了api_parent_id对应的值,就直接过滤出该api_parent_id对应的子api对象出来,不想再展示这个父api对象的信息了
        # 所以这里才用 AuthResourceApiExt.parent_id == request.json['api_parent_id'] 这个过滤条件的

        else:
            # 如果不传api_parent_id对应的值,则过滤出parent_id=0的api对象也就是
            #  account/biz/node 这3个api对象
            # 并且在该基础上,继续过滤出连表的api与角色多对多表的role_id等于用户的role_id  的api对象出来
            roots: List[AuthResourceApiExt] = db.session.query(AuthResourceApiExt) \
                .select_from(AuthResourceApiExt) \
                .outerjoin(AuthPermitRoleApi, AuthResourceApiExt.id == AuthPermitRoleApi.api_id) \
                .filter(
                AuthResourceApiExt.parent_id == 0,
                AuthPermitRoleApi.role_id == user.role_id
            ).all()

    # 如果当前用户用的SUPER_ADMIN_TOKEN,或者用户的角色是超级管理员,走下面的逻辑
    else:
        # 如果请求中有api_parent_id
        # 过滤出parent_id等于对应值的所有子api对象
        if 'api_parent_id' in request.json.keys():
            roots: List[AuthResourceApiExt] = db.session.query(AuthResourceApiExt).filter(
                AuthResourceApiExt.parent_id == request.json['api_parent_id']).all()

        # 如果不传api_parent_id对应的值,则过滤出parent_id=0的所有子api对象
        # 也就是 account/biz/node 这3个api对象     这3个api对象既是子对象,也是父对象
        else:
            roots: List[AuthResourceApiExt] = db.session.query(AuthResourceApiExt).filter(
                AuthResourceApiExt.parent_id == 0).all()

    # roots 得到的是  能给当前用户展示的 api对象列表 可以是父api对象的列表,也可以是子api对象的列表
    # 取决于你有没有传api_parent_id对应的值,没传,我就给你parent_id等于0的子api对象  也就是account/biz/node  包含这3个父api对象的列表
    # 传了,比如你传api_parent_id=0  我就给你parent_id等于0的子api对象  那也就是 account/biz/node 包含这3个父api对象的列表
    # 如果你传api_parent_id=1  我就给你parent_id等于1的子api对象  那也就是包含 account父api对象下面所有的子对象的列表

    # 所有该视图函数代码走到这,实际上才干了一件事,根据当前用户是不是管理员或者普通用户,获取到该用户能看到的子api对象列表来
    # 前端如果不传过滤条件,api_parent_id, 那么就正常以parent_id == 0 过滤出该用户能看到的子api对象列表来
    # 前端传了过滤条件,api_parent_id=xxx, 那么就正常以parent_id == xxx 过滤出该用户能看到的子api对象列表来

    # 返回json格式的返回体,包括result, code, msg, data
    return jsonify_return_body(
        body_key_list=['result', 'code', 'msg', 'data'],
        result=True,
        data=[root.find_detail_with_permit_and_children_and_children_permit(role, user) for root in roots]
    )

# data=[root.find_detail_with_permit_and_children_and_children_permit(role, user) for root in roots]
# for循环出roots列表里面的每一个api对象出来,每个api对象去调用find_detail_with_permit_and_children_and_children_permit方法
# 并将上面要查的角色对象与当前用户对象作为参数传入
# 也就是说data变量是一个列表套字典,每一个字典里面还有可能通过child键再挂一个列表,


# 该接口最后所达到的效果就是,如果是token是SUPER_ADMIN_TOKEN,或者用户的角色是超级管理员,data字段里面展示所有的父api对象信息,[{},{},{}]
# 并且父api对象信息的字典里面的children键里面,再挂一个列表,列表里面是父api对象的所有子api对象信息,[{},{},{}]]
# 然后所有的这些api对象信息,无论是父api对象还是子api对象,都有一个permit字段,来表示你要查的这个角色对象是否拥有访问这个api对象的权限

# 如果token不是SUPER_ADMIN_TOKEN,或者用户的角色不是超级管理员,data字段里面展示的是你拥有的访问权限的api对象信息,[{},{},{}]]
# 并且父api对象信息的字典里面的children键里面,再挂一个列表,列表里面是父api对象的所有子api对象信息,[{},{},{}]]
# 然后所有的这些api对象信息,无论是父api对象还是子api对象,都有一个permit字段,来表示你要查的这个角色对象是否拥有访问这个api对象的权限



.
image
.
image
.
image
.
.

posted @   tengyifan  阅读(21)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
点击右上角即可分享
微信分享提示