Flaskz介绍3--API封装、访问权限控制和日志

开发过程中,最多的路由API就是对于数据的增删改查操作,Flaskz提供了封装函数,用于生成数据模型的CRUD等路由函数,可以方便地生成数据操作的相关API,并提供了访问权限控制和日志记录功能。

注册数据模型路由

通过register_model_route函数,可以生成数据库模型的CRUD等操作对应的API,下面先介绍一下这个函数。

register_model_route(app, model, rule, module=None, types=None, multi_models=None, to_json_option=None, strict_slash=True)

  • 参数:
    • {Blueprint/Flask}app --指定的Flask应用/Blueprint实例,用于注册API
    • {BaseModelMixin}model --数据模型类,例如User/Device等
    • {str}rule --路由的URL路径,例如"users","/devices"等
    • {str}module --API所属的模块,比如"users"/"devices",主要用于权限控制和记录日志
      • 如果API没有所属模块,可以设置module为None
      • 如果设置module为False,则API没有任何权限控制(不需要登录也可以访问)
    • {list}types --生成的路由列表,默认会生成以下路由API
      类型 选项 Method URL 说明
      添加 add POST http://{{flask_server_url}}/api/v1.0/{rule}/ 通过POST方法,添加模型数据
      删除 delete DELETE http://{{flask_server_url}}/api/v1.0/{rule}/[pk_value]/ 通过DELETE方法,删除指定主键值对应的模型数据
      修改 update PATCH http://{{flask_server_url}}/api/v1.0/{rule}/ 通过PATCH方法,修改指定的模型数据
      修改或添加 upsert POST http://{{flask_server_url}}/api/v1.0/{rule}/upsert/ 通过POST方法,数据存在则进行修改,不存在则添加
      查询 query GET http://{{flask_server_url}}/api/v1.0/{rule}/ 查询模型类所有的数据
      条件查询 pss POST http://{{flask_server_url}}/api/v1.0/{rule}/pss/ 根据指定条件(分页+搜索+排序)查询符合条件的模型数据
      多表查询 multi GET http://{{flask_server_url}}/api/v1.0/{rule}/multi/ 一次查询多个模型/数据库表的所有数据
    • {dict|None}to_json_option --模型类instance转换为json的选项, 请参考 model_to_dict 函数和 BaseModelMixin.to_dict 方法
    • {dict|None}multi_models --用于生成多表查询API,一次查询多个模型类的数据,如果没有multi_models参数,则不会生成多表查询路由API, 请参考 query_multiple_model 函数
  • 示例:
                            
                                # 生成User相关的API
                                # -查询所有数据          [GET] http://{{flask_server_url}}/api/v1.0/users/
                                # -增加数据             [POST] http://{{flask_server_url}}/api/v1.0/users/
                                # -根据主键删除数据       [DELETE] http://{{flask_server_url}}/api/v1.0/users/[id]
                                # -更新数据             [Patch] http://{{flask_server_url}}/api/v1.0/users/
                                # -条件查询             [POST] http://{{flask_server_url}}/api/v1.0/user/pss/
                                # -多表查询             [GET] http://{{flask_server_url}}/sys_mgmt/user/multi/
                                register_model_route(api_bp, User, 'users', 'users',multi_models={
                                    'users': User,
                                    'roles': {
                                        'model': Role,
                                        'to_json_option': {
                                            # 'cascade': 2, # 级联关系层级
                                            # 'relationships': ['department'], # 指定输出的级联关系
                                            'include': ['id', 'name']
                                        }
                                    }
                                })
    
                                #其它示例
                                register_model_route(sys_mgmt_bp,ActionLog 'action-logs', 'action-logs', types=['pss']) # 不需要增删改操作,只生成query_pss查询API
                            
                        

    请参考API Postman示例

  • 说明:
    • 程序生成的API请参考API规范,和Rest规范相比,主要有以下两个差异差异
      1. 修改数据API中用到的主键值,放在了body数据中而不是url

        --[Patch] http://{{flask_server_url}}/api/v1.0/user/

      2. 另外为了更方便的组织查询条件数据,条件查询用的也是POST方法

        即所有的分页/过滤/排序参数都要放在请求body中,请参考 BaseModelMixin.query_pssparse_pss 函数

        --[POST] http://{{flask_server_url}}/api/v1.0/user/pss/

    • 在生成的API处理函数内部,通过flaskz_logger进行了系统日志记录,以方便程序的跟踪调试
      1. 查询相关API中,使用debug方法记录日志
      2. 其他API中,使用info方法记录日志
    • 除了记录系统日志,API内部还通过log_operation记录了操作日志(插入记录到数据库日志表中),以方便后续查询
    • 生成的API都添加了模块和操作权限控制,参考以下权限控制和操作日志记录章节

除了register_model_route,还有以下路由注册函数,具体使用请参考函数文档。

  • register_model_add_route --可用于生成指定数据模型的添加路由函数
  • register_model_delete_route --可用于生成指定数据模型的删除路由函数
  • register_model_update_route --可用于生成指定数据模型的更新路由函数
  • register_model_upsert_route --可用于生成指定数据模型的添加/更新路由函数
  • register_model_query_route --可用于生成指定数据模型的全量查询路由函数
  • register_model_query_pss_route --可用于生成指定数据模型的条件查询(分页+搜索+排序)路由函数
  • register_multi_models_query_route --可用于生成多个数据模型的全量查询路由函数

查询API添加过滤

在查询相关API中,可以对结果数据进行过滤

  • 全量查询 / [GET]http://{{flask_server_url}}/api/v1.0/{url_prefix}/
                            
                                register_model_route(api_bp, EmployeeModel, 'ex-employees', 'ex-employees', to_json_option={    # **在to_json_option选项中添加filter函数
                                    'filter': lambda ins: ins.age >= 20 # filter过滤函数, 判断结果==True的才会返回
                                })
                            
                        
  • 条件查询 / [POST]http://{{flask_server_url}}/api/v1.0/{rule}/pss/
                            
                                def append_search_config(request_json):
                                    if request_json:
                                        request_json.get('search', {})['age'] = {   # 在request_json中附加查询条件
                                            ">": 20
                                        }
                                    return request_json
    
    
                                register_model_query_pss_route(api_bp, EmployeeModel, 'ex-employees', 'ex-employees', get_pss_config=append_search_config)  # 通过设置get_pss_config回调函数以自定义pss查询参数
                            
                        
  • 多表查询 / [GET]http://{{flask_server_url}}/api/v1.0/{rule}/multi/
                            
                                register_model_route(api_bp, EmployeeModel, 'ex-employees', 'ex-employees'
                                                     multi_models={
                                                         'employees': {
                                                             'model': EmployeeModel,
                                                             'option': {
                                                                 'filter': lambda ins: ins.age >= 20    # **在multi_models的option选项中设置filter函数函数
                                                             }
                                                         },
                                                         'departments': {
                                                             'model': DepartmentModel,
                                                             'option': {
                                                                 'include': ['id', 'name']
                                                             }
                                                         }
                                                     })
                            
                        

注册数据模型批量操作路由

通过register_model_bulk_route函数,可以生成数据库模型的批量增删改操作对应的API,下面先介绍一下这个函数。

register_model_bulk_route(app, model, rule, module=None, types=None, with_relationship=False, strict_slash=True)

  • 参数:
    • {Blueprint/Flask}app --指定的Flask应用/Blueprint实例,用于注册API
    • {BaseModelMixin}model --数据模型类,例如User/Device等
    • {str}rule --路由的URL路径,例如"users","/devices"等
    • {str}module --API所属的模块,比如"users"/"devices",主要用于权限控制和记录日志
      • 如果API没有所属模块,可以设置module为None
      • 如果设置module为False,则API没有任何权限控制(不需要登录也可以访问)
    • {list}types --生成的路由列表,默认会生成以下路由API
      类型 选项 Method URL 说明
      批量添加 bulk_add POST http://{{flask_server_url}}/api/v1.0/{rule}/bulk/ 通过POST方法,批量添加模型数据
      批量删除 bulk_delete DELETE http://{{flask_server_url}}/api/v1.0/{rule}/bulk/[pks]/ 通过DELETE方法,批量删除通过请求的URL或body中指定的主键列表
      批量修改 bulk_update PATCH http://{{flask_server_url}}/api/v1.0/{rule}/bulk/ 通过PATCH方法,批量修改指定的模型数据
    • {boolean}with_relationship --如果with_relationship=True,则通过API进行批量操作时,会更新关系数据
  • 示例:
                            
                                # 生成User批量操作的API
                                # -批量增加数据       [POST] http://{{flask_server_url}}/api/v1.0/users/bulk/
                                # -批量删除数据       [DELETE] http://{{flask_server_url}}/api/v1.0/users/bulk/[ids]/
                                # -批量更新数据       [Patch] http://{{flask_server_url}}/api/v1.0/users/bulk/
                                register_model_bulk_route(api_bp, User, 'users', 'users')
    
                                #其它示例
                                register_model_bulk_route(sys_mgmt_bp,User 'users', 'users', types=['bulk_add']) # 不需要删改操作,只生成批量添加API
                            
                        

    请参考API Postman示例

register_model_bulk_route,还有以下路由注册函数,具体使用请参考函数文档。

  • register_model_bulk_add_route --可用于生成指定数据模型的批量添加路由函数
  • register_model_bulk_delete_route --可用于生成指定数据模型的批量删除路由函数
  • register_model_bulk_update_route --可用于生成指定数据模型的批量更新路由函数

创建数据模型API

建议使用register_model_route函数生成模型路由

通过init_model_rest_blueprint函数,可以将数据库模型的增删改查操作封装成API,下面先介绍一下这个函数。

init_model_rest_blueprint(model_cls, api_blueprint, url_prefix, module, routers=None, to_json_option=None, multiple_option=None)

  • 参数:
    • {BaseModelMixin}model_cls --数据模型类,例如User/Device等
    • {Blueprint}api_blueprint --指定的API Blueprint实例,用于注册API
    • {string}url_prefix --API对应的URL前缀,用于生成API的URL,例如"/user","/device"等
    • {string}module --API所属的模块,比如"user"/"device",主要用于权限控制和记录日志
      • 如果API没有所属模块,可以设置module为None
      • 如果设置module为False,则API没有任何权限控制(不需要登录也可以访问)
    • {list}routers --需要生成的API列表,默认会生成以下API
      类型 选项 Method URL 说明
      添加 add POST http://{{flask_server_url}}/api/v1.0/{url_prefix}/ 通过POST方法,添加模型数据
      删除 delete DELETE http://{{flask_server_url}}/api/v1.0/{url_prefix}/[pk_value] 通过DELETE方法,删除指定主键值对应的模型数据
      修改 update PATCH http://{{flask_server_url}}/api/v1.0/{url_prefix}/ 通过PATCH方法,修改指定的模型数据
      修改或添加 upsert POST http://{{flask_server_url}}/api/v1.0/{url_prefix}/upsert/ 通过POST方法,数据存在则进行修改,不存在则添加
      查询 query GET http://{{flask_server_url}}/api/v1.0/{url_prefix}/ 查询模型类所有的数据
      条件查询 query_pss POST http://{{flask_server_url}}/api/v1.0/{url_prefix}/query_pss/ 根据条件查询符合条件的模型数据
      多表查询 query_multiple GET http://{{flask_server_url}}/sys_mgmt/{url_prefix}/query_multiple/ 一次查询多个模型/数据库表的所有数据
    • {dict|None}to_json_option --模型类instance转换为json的选项, 请参考 model_to_dict 函数和 BaseModelMixin.to_dict 方法
    • {dict|None}multiple_option --主要用于生成query_multiple API,用于一次查询多个模型类的数据,如果没有multiple_option参数,则不会生成query_multiple API, 请参考 query_multiple_model 函数
  • 示例:
                            
                                # 生成User相关的API
                                # -查询所有User数据      [GET] http://{{flask_server_url}}/api/v1.0/user/
                                # -增加数据              [POST] http://{{flask_server_url}}/api/v1.0/user/
                                # -根据id删除数据        [DELETE] http://{{flask_server_url}}/api/v1.0/user/[id]
                                # -更新数据              [Patch] http://{{flask_server_url}}/api/v1.0/user/
                                # -根据条件查询数据      [POST] http://{{flask_server_url}}/api/v1.0/user/query_pss/
                                # -查询User和Role数据    [GET] http://{{flask_server_url}}/sys_mgmt/user/query_multiple/
                                init_model_rest_blueprint(User, api_bp, '/user', 'user',multiple_option={
                                    'users': User,
                                    'roles': {
                                        'model_cls': Role,
                                        'option': {
                                            # 'cascade': 2,
                                            'include': ['id', 'name']
                                        }
                                    }
                                })
    
                                #其它示例
                                init_model_rest_blueprint(OPLog, sys_mgmt_bp, '/op_log', 'op_log', routers=['query_pss']) # 不需要增删改操作,只生成query_pss查询API
                            
                        

    请参考API Postman示例

  • 说明:
    • 程序生成的API请参考API规范,和Rest规范相比,主要有以下两个差异差异
      1. 修改数据API中用到的主键值,放在了body数据中而不是url

        --[Patch] http://{{flask_server_url}}/api/v1.0/user/

      2. 另外为了更方便的组织查询条件数据,条件查询用的也是POST方法

        即所有的分页/过滤/排序参数都要放在请求body中,请参考 BaseModelMixin.query_pssparse_pss 函数

        --[POST] http://{{flask_server_url}}/api/v1.0/user/query_pss/

    • 在生成的API处理函数内部,通过flaskz_logger进行了系统日志记录,以方便程序的跟踪调试
      1. 查询相关API中,使用debug方法记录日志
      2. 其他API中,使用info方法记录日志
    • 除了记录系统日志,API内部还通过log_operation记录了操作日志(插入记录到数据库日志表中),以方便后续查询
    • 生成的API都添加了模块和操作权限控制,参考以下权限控制和操作日志记录章节

API权限控制和操作日志

系统提供了 ModelRestManager 类对API进行权限控制和操作日志记录。

初始化

不同项目中,对于权限控制和记录操作日志实现差异较大,所以默认不提供,如果需要,可以通过设置回调函数的方式来实现具体的功能逻辑,请参考Flaskz-admin管理系统模板。

示例代码如下

               
                    model_rest_manager = ModelRestManager()                     # 创建ModelRestManager实例
                    model_rest_manager.login_check(auth.login_check)            # 设置用户是否登录检查回调函数,对应rest_login_required装饰器
                    model_rest_manager.permission_check(auth.permission_check)  # 设置权限检查回调函数,对应rest_permission_required装饰器
                    model_rest_manager.logging(sys_mgmt.log_operation)          # 设置操作日志记录函数,对应log_operation函数

                    model_rest_manager.init_app(app)                            # 将ModelRestManager实例绑定到应用上
                
            

权限控制

设置好model_rest_manager以后,就可以通过封装好的函数装饰器来设置权限控制功能了

  • rest_login_required 函数装饰器 --控制API只有登录用户才可以访问
  • rest_permission_required(module,operation) 函数装饰器 --控制API只能被有模块访问权限&模块对应操作权限的用户才可以访问

示例代码

               
                    @sys_mgmt_bp.route('/auth/account/', methods=['GET', 'POST'])
                    @rest_login_required()                      # *只有已登录用户,才可以调用/auth/account/接口API加载当前账户信息
                    def sys_auth_account_query():
                        pass

                    @sys_mgmt_bp.route('/role/', methods=['POST'])
                    @rest_permission_required('role', 'add')    # *只有已登录用户,并且拥有模块访问&添加权限,才可以调用API添加角色数据
                    def sys_role_add():
                        #...
                        log_operation('role', 'add', result[0], req_log_data, res_log_data)                     # 记录操作日志-->数据库
                        flaskz_logger.info(get_rest_log_msg('Add role', req_log_data, result[0], res_log_data)) # 记录系统日志-->文件/控制台
                        pass
                
            

操作日志

初始化model_rest_manager以后,就可以通过封装好的函数来记录操作日志日志

  • log_operation(*args, **kwargs) 函数 --记录操作日志

示例代码

               
                    @sys_mgmt_bp.route('/role/', methods=['POST'])
                    @rest_permission_required('role', 'add')
                    def sys_role_add():
                        #...
                        log_operation('role', 'add', result[0], req_log_data, res_log_data)                     # 记录操作日志-->数据库
                        flaskz_logger.info(get_rest_log_msg('Add role', req_log_data, result[0], res_log_data)) # 记录系统日志-->文件/控制台
                        pass
                
            

自定义API

除了通过init_model_rest_blueprint函数生成模型对应的API以外,也可以根据需求和场景自定义API,下面通过一个示例,演示如何自定义API

               
                    @sys_mgmt_bp.route('/role/', methods=['PUT', 'PATCH'])  # 向blueprint注册api,请求url为/role/,请求方法为PUT或PATCH
                    @rest_permission_required('role', 'update')             # 模块权限和操作权限控制
                    def sys_role_update():                                  # 路由处理函数
                        """
                        Update the specified role.
                        :return:
                        """
                        request_json = request.json
                        req_log_data = json.dumps(request_json)

                        result = Role.update(Role.to_server_json(request_json))     # 更新数据库
                        res_data = model_to_dict(result[1], {'cascade': 1})         # 将更新以后的数据转换为字典属性对象
                        if result[0] is True:                                       # 对操作成功/失败进行判断
                            res_data = Role.to_client_json(res_data)

                        res_log_data = get_log_data(res_data)
                        log_operation('role', 'update', result[0], req_log_data, res_log_data)                      # 记录操作日志
                        flaskz_logger.info(get_rest_log_msg('Update role', req_log_data, result[0], res_log_data))  # 记录系统日志

                        return create_response(result[0], res_data)         # 将结果返回到请求客户端
                
            

请求转发

可以将收到的API请求转发到其他系统,可以用于以下场景

  • 系统整合,将多个系统整合到一个系统
  • 跨域访问,解决有些系统不支持跨域,导致的浏览器跨域访问问题

实现如下

                
                    class RegexConverter(PathConverter):
                        def __init__(self, url_map, regex):
                            super(RegexConverter, self).__init__(url_map)
                            self.regex = regex

                    app.url_map.converters['regex'] = RegexConverter # 添加正则类型的路由参数类型

                    @api_bp.route('/<regex(".*"):path>', methods=HTTP_METHODS) # 处理所有/特定请求path
                    def remote(path):
                        return forward_request(base_url + path) # 转发请求并返回,原请求的'method', 'data', 'json', 'headers', 'cookies'都会被转发
                
            

系统日志

系统提供了系统日志记录功能,以监控程序的运行和跟踪调试,生成的日志记录器名字为"flaskz_logger"

配置&初始化

可以通过init_log函数进行配置,相关配置参数如下

  • FLASKZ_LOGGER_FILENAME

    系统日志的文件名,只有设置了FLASKZ_LOGGER_FILENAME,系统日志才会输出到文件中,否则系统日志会被输出到控制台,默认为空。

    示例,FLASKZ_LOGGER_FILENAME = 'syslog.txt',表示系统日志会被记录到syslog.txt文件中

    • 开发环境,可以把系统日志可以直接输出到控制台,以便于调试
    • 测试/产品环境,要把系统日志输出到文件中,以跟踪程序的运行状态和异常处理

    请参考Flaskz-admin中的不同环境的配置信息&系统日志查询功能

  • FLASKZ_LOGGER_FILEPATH

    系统日志的目录,如果指定了FLASKZ_LOGGER_FILENAME,默认会将日志保存到当前应用的syslog目录。

    示例,FLASKZ_LOGGER_FILEPATH = os.path.join(os.getcwd(), './syslog'),表示所有日志文件会被放到syslog目录中。

  • FLASKZ_LOGGER_LEVEL

    系统日志的level,可选值为CRITICAL/ERROR/WARNING/INFO/DEBUG/NOTSET,请参考 logging

    输出的日志,只有级别大于或等于设置的日志类型才会被记录

    Flaskz中,使用的日志记录规则如下

    • 用info方法记录增删改操作
    • 用debug方法记录查询操作
    • 用exception方法记录异常信息
  • FLASKZ_LOGGER_FORMAT

    日志内容输出格式,例如,FLASKZ_LOGGER_FORMAT = '%(asctime)s %(filename)16s[line:%(lineno)-3d] %(levelname)8s: \n%(message)s\n'

  • FLASKZ_LOGGER_WHEN

    文件型日志的rollover时间,例如,FLASKZ_LOGGER_WHEN = 'midnight'

  • FLASKZ_LOGGER_BACKUP_COUNT

    文件型日志最大的保存数量,例如,FLASKZ_LOGGER_BACKUP_COUNT = 90

  • FLASKZ_LOGGER_DISABLED

    是否禁用flaskz_logger日志管理器,如果禁用,flaskz_logger日志管理器则不会输出日志,默认不禁用

  • FLASKZ_WZ_LOGGER_DISABLED

    是否禁用werkzeug日志管理器,如果禁用,werkzeug日志管理器则不会输出日志,默认禁用

以上配置参数,请参考 Flaskz-admin 模板。

可以通过flaskz.flaskz_logger,使用日志记录器来记录日志,示例如下

               
                    log.init_log(app) # 初始化flaskz日志管理器,一般在创建应用时调用

                    flaskz_logger.info(get_rest_log_msg('Update role', req_log_data, result[0], res_log_data)) # 记录日志
                
            

系统日志vs操作日志

系统日志和操作日志是两个不同的概念,用途也不一样,主要区别如下

  • 系统日志 --供开发人员使用,用于程序的跟踪调试和监控程序的运行状态,一般保存到文件或打印到控制台
  • 操作日志 --供系统的使用者使用,用于对业务相关的跟踪和审计等功能,一般保存到数据库中