Flaskz介绍5--基于Flaskz的管理系统开发模板

模板简介

Flaskz-admin 是基于Flaskz的管理系统开发模板,提供了基础的代码框架和文件目录。 基于admin模板,可以快速搭建系统开发框架和进行业务模块的开发。前面几篇文章介绍的Flaskz知识点基本上都能在admin模板中找到。

框架主要功能模块如下:

  • 提供了开发、测试和产品三种不同的环境配置,可根据场景进行灵活切换
  • 基于Alembic的数据库迁移,可以方便地对数库表进行增删改等迁移操作
  • 基于Flask-Login的用户登录管理,提供了session/token等登录方式
  • 基于角色的权限管理功能(RBAC),可以直接使用,也可以根据需求进行扩展或改写
  • 系统数据初始化功能,可以通过cli将菜单/角色/用户等数据初始化到数据库中
  • 逐步完善的各种业务场景和系统功能

接下来会对以上功能和模块进行讲解。

目录结构

先看一下模板的目录结构,以及各个目录的用途

                
                    -_cli               #命令行工具,可用于数据初始化
                    -_doc               #文档目录
                    -_syslog/           #系统日志存放目录
                    -tests/             #单元测试目录
                    -app/               #程序主目录
                    ---_ext/            #扩展功能目录,删除/复制到其他目录使用
                    ---api/             #API统一封装目录
                    ---app_page/        #前端页面文件目录,用于存放页面静态文件,也可以通过nginx等代理软件实现文件服务功能
                    ---main/            #页面服务和应用异常处理目录
                    ---modules/         #系统模块/模型类封装目录
                    ---sys_init/        #系统初始化目录,例如)国际化
                    ---sys_mgmt/        #默认提供的基于角色的权限控制目录
                    ------license/      #-license功能目录
                    ---util/            #系统工具类目录
                    ------_utils/       #系统内置工具类目录
                    ---__init__.py      #程序创建
                    -migrations/        #alembic数据库迁移目录,用于存放数据库迁移文件
                    ---versions/        #-数据库迁移版本目录
                    -admin_app.py       #应用程序主入口
                    -alembic.ini        #alembic配置文件
                    -config.py          #系统环境配置
                    -config.ini         #系统环境配置文件,可以省略,config.ini中属性的优先级>config.py中定义的属性
                    -requirements.txt   #依赖lib列表
                
            

以上就是模板提供的基础代码框架和文件目录,兼顾了标准化和可扩展性,可在此基础上构建业务应用。

系统环境配置(config.py/config.ini)

模板 config.py 文件中默认提供了4种环境配置, 程序启动时会加载指定环境的配置信息,可根据使用场景进行切换

  • DevelopmentConfig --开发环境,系统开发时使用
  • UnittestConfig --单元测试环境,单元测试时使用
  • TestConfig --测试环境,系统测试时使用
  • ProductionConfig --产品环境,系统上线时使用

如果不指定,默认使用开发环境配置,可以修改默认环境或通过环境变量APP_CONFIG进行配置。

除了通过py文件,模板还支持通过配置文件 config.ini 进行环境变量配置,示例如下

                
                    [DEFAULT]
                    ; 对应Config类配置
                    SECRET_KEY = hard to guess string

                    [DEVELOPMENT]
                    ; 对应DevelopmentConfig类配置
                    FLASKZ_DATABASE_URI = sqlite:///D:/PyDev/WorkSpace/flaskz-admin/_sqlite/flaskz-admin.db

                    [UNITTEST]
                    ;对应UnittestConfig类配置

                    [TEST]
                    ; 对应TestConfig类配置
                    FLASKZ_DATABASE_URI = sqlite:///D:/PyDev/WorkSpace/flasky/_sqlite/flasky_test.db

                    [PRODUCTION]
                    ; 对应ProductionConfig类配置
                    FLASKZ_DATABASE_URI = mysql+pymysql://{username}:{password}@{url}:{port}/{db}
                
            

config.ini中配置的优先级大于config.py中的配置

配置项分为以下几类

  1. Flask应用配置

    相关配置如下

                            
                                # Flask参数,用于安全签署会话cookie的密钥,并可用于扩展应用程序的任何其他安全相关需求。它应该是一个长随机字符串
                                SECRET_KEY = 'hard to guess string'
                                # Flask参数,提供页面服务时,浏览器上文件的缓存时间,如果是None,则浏览器不会缓存页面文件,datetime.timedelta/秒数
                                SEND_FILE_MAX_AGE_DEFAULT = timedelta(hours=16)
                                # Flask参数,采用session实现用户登录管理时,session的过期时间,datetime.timedelta/秒数
                                PERMANENT_SESSION_LIFETIME = timedelta(hours=16)
                                # Flask-Login参数,选中保持登录/记住我时,cookie的过期时间,datetime.timedelta/秒数
                                REMEMBER_COOKIE_DURATION = timedelta(days=30)
                            
                        
  2. Flaskz配置

    相关配置如下,具体使用请参考 数据库配置系统日志配置

                            
                                # 数据库相关配置,请参考 -http://zhangyiheng.com/blog/articles/py_flaskz_model_init.html
                                FLASKZ_DATABASE_URI = None
                                FLASKZ_DATABASE_ECHO = False  # 如果为True,会打印sql语句,只适用于开发环境
                                FLASKZ_DATABASE_POOL_RECYCLE = int(timedelta(hours=2).total_seconds())  # recycle connections seconds
                                FLASKZ_DATABASE_POOL_PRE_PING = True  # engine.pool_pre_ping- DB操作之前先测试连接,如果不可用会重连(HA/数据库重启)
                                FLASKZ_DATABASE_ENGINE_KWARGS = None  # engine自定义属性 ex){'pool_timeout': 20, 'pool_size': 20, "poolclass": QueuePool, 'max_overflow': 20}
                                FLASKZ_DATABASE_SESSION_KWARGS = {'expire_on_commit': False}  # session自定义属性
    
                                FLASKZ_DATABASE_DEBUG = True  # 如果为True,会记录一个请求过程中的DB操作,并打印>slow_time和?times的操作,只适用于开发环境
                                FLASKZ_DATABASE_DEBUG_SLOW_TIME = -1
                                FLASKZ_DATABASE_DEBUG_ACCESS_TIMES = -1
    
                                # 系统日志相关配置,请参考 -http://zhangyiheng.com/blog/articles/py_flaskz_api.html
                                FLASKZ_LOGGER_FILENAME = None  # 日志文件名
                                FLASKZ_LOGGER_FILEPATH = '_syslog'  # 日志文件目录
                                FLASKZ_LOGGER_LEVEL = 'INFO'  # 'DEBUG'/'INFO'/'WARNING'/'WARN'/'ERROR'/'FATAL'/'CRITICAL'
                                FLASKZ_LOGGER_FORMAT = '%(asctime)s %(filename)16s[line:%(lineno)-3d] %(levelname)8s: \n%(message)s\n'
                                FLASKZ_LOGGER_WHEN = 'midnight'  # 每天midnight,将当日志文件按日期重命名保存,并生成一个新的日志文件
                                FLASKZ_LOGGER_BACKUP_COUNT = 90
                                FLASKZ_LOGGER_DISABLED = False
                                FLASKZ_WZ_LOGGER_DISABLED = True
    
                                # 请求响应相关配置,请参考 -http://zhangyiheng.com/blog/articles/py_flaskz_utils.html#toc-res
                                FLASKZ_RES_SUCCESS_STATUS = "success"
                                FLASKZ_RES_FAIL_STATUS = "fail"
                            
                        
  3. APP应用配置

    相关配置如下

                            
                                # 采用token管理用户登录时,token的名字,用于从header中获取对应的token值
                                APP_TOKEN_AUTHORIZATION = 'Authorization'
                                # 采用token管理用户登录时,token的过期时间
                                APP_TOKEN_EXPIRES_IN = int(timedelta(hours=16).total_seconds())
                                # 静态文件目录(前端页面)
                                APP_PAGE_STATIC_FOLDER = './app/app_page/'
                                APP_PAGE_STATIC_STATIC_URL_PATH = '/'
    
                                # 其他相关配置
                                # APP_UPLOAD_FOLDER = 'uploads' # 上传文件目录
                                # 允许的上传文件格式
                                APP_UPLOAD_FILE_ALLOWED_EXTENSIONS = {'txt', 'dat', 'xlsx', 'xls', 'csv', 'xml', 'json', 'yaml'}
    
                                # license公钥目录
                                APP_LICENSE_PUBLIC_KEY_FILEPATH = './_license/public.key'
                                # License菜单path(如果启用列license功能&没有上传license,只返回license菜单)
                                APP_LICENSE_MENU_PATH = 'licenses'
    
                                # 静态文件目录(前端页面)
                                APP_PAGE_STATIC_FOLDER = './app/app_page/'
                                APP_PAGE_STATIC_STATIC_URL_PATH = '/'
                            
                        

为了配置更加灵活&安全,也可以从系统环境变量中导入,例如SECRET_KEY/数据库密码等

                
                    SECRET_KEY = os.environ.get('APP_SECRET_KEY') or 'hard to guess string'
                    FLASKZ_DATABASE_URI = os.environ.get('FLASKZ_PRO_DATABASE_URI') or 'mysql+pymysql://{username}:{password}@{url}:{port}/{db}'
                
            

数据模型&维护数据库表结构(modules/)

基于 ModelBaseModelMixin 创建数据模型类, 模型类创建或发生变化时,需要通过alembic将变化推送到数据库,创建或更新对应的表结构。

为了便于统一管理,数据模型类应该放在app/modules目录中。示例代码如下

               
                    from datetime import datetime

                    from flaskz.models import ModelBase, ModelMixin
                    from sqlalchemy import Column, Integer, String, DateTime


                    class TemplateModel(ModelBase, ModelMixin):
                        __tablename__ = 'templates'  # 指定数据库中的表名,用于创建和关联数据库表

                        id = Column(Integer, primary_key=True, autoincrement=True)  # 主键,必需
                        name = Column(String(32), unique=True, nullable=False)
                        age = Column(Integer)
                        email = Column(String(255), nullable=False)
                        description = Column(String(255))
                        created_at = Column(DateTime(), default=datetime.now, info={'auto': True})
                        updated_at = Column(DateTime(), default=datetime.now, onupdate=datetime.now)
                        # system_default = Column('default', Boolean, default=False, info={'field': 'system_default'})
                        like_columns = ['name', description]
                        auto_columns = ['id', updated_at]
                
            

请注意SQLAlchemy ORM要求数据模型必须要有主键,以区分同一个模型中的不同数据。

创建好模型类以后,就可以通过alembic的revision和upgrade命令将模型类对应的数据库结构推送到数据库中,根据定义的列,创建相应的表。

对数据模型进行修改以后,也需要执行类似操作,将变化推送到数据库中以更新数据库表结构。

封装API(api/)

创建好模型类以后,接下来要做的就是把模型类的增删改查操作 封装成API 以供客户端调用, 同时要对操作过程中的系统日志和操作日志进行记录,以方便查询和调试。

为了便于统一管理,数据模型类应该放在app/api目录中。示例代码如下

               
                    # 生成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,
                                'include': ['id', 'name']
                            }
                        }
                    })

                    # 生成单个路由API
                    register_model_route(sys_mgmt_bp,ActionLog 'action-logs', 'action-logs', types=['pss']) # 不需要增删改操作,只生成query_pss查询API
                
            

当然也可以根据使用场景,请参考自定义API

用户登录管理(sys_mgmt/)

框架集成了 Flask-Login 以管理用户登录,支持以下登录模式

  1. Session/Cookie -支持保持登录功能(remember=True),Flask默认使用客户端类型会话
  2. Token
  3. Basic Auth

系统按照上述顺序,依次检查用户登录信息,所以也支持混用。

通过以下代码,启用用户登录管理,可以在模块中通过current_user访问当前登录用户信息

               
                    def _init_login(app):
                        """初始化login模块,按需使用"""
                        from flask_login import LoginManager
                        login_manager = LoginManager()
                        # 用户加载回调函数,可以通过id查找对应的用户(Session/Cookie)
                        login_manager.user_loader(auth.load_user_by_id)
                        # 根据request校验用户,支持Token和Basic Auth
                        login_manager.request_loader(auth.load_user_by_request)
                        # 初始化flask应用的用户登录管理
                        login_manager.init_app(app)
                
            

基于角色的权限控制(sys_mgmt/)

框架内置了基于角色的权限控制模块,可以直接使用或根据需求进行改写,实现思路如下

  • 角色定义菜单权限和控制权限
  • 用户权限基于所选择的角色

后台服务和前端页面都要实现相应的控制功能

  • 后台服务API要对客户访问进行模块和操作权限校验,只有拥有对应的权限才可以调用API
  • 前端页面导航菜单应根据权限动态生成(菜单权限)
  • 前端页面应根据模块的操作权限进行调整,例如隐藏按钮/操作列等(操作权限)

通过以下代码,就实现了权限控制和日志管理功能

               
                    def _init_model_rest(app):
                        """初始化model rest api权限控制和操作日志记录,按需使用"""
                        from flaskz.rest import ModelRestManager
                        model_rest_manager = ModelRestManager()
                        # 检查用户是否login
                        model_rest_manager.login_check(auth.login_check)
                        # 检查用户是否有菜单&操作权限
                        model_rest_manager.permission_check(auth.permission_check)
                        # 日志记录函数
                        model_rest_manager.logging(sys_mgmt.log_operation)
                        model_rest_manager.init_app(app)
                
            

License管理(sys_mgmt/license/)

框架内置了License管理模块,可以按需使用

后端逻辑实现

  • license存放在数据库中,以方便license上传处理和HA多机部署
  • 多license并存,符合当前日期的最新的license会起作用,同时会合并计算到期时间(前端使用)
  • 同一个license只允许上传一次
  • license缓存,默认每10分钟重新加载一次,以减少license相关损耗,提高效率
  • request请求回调中对license进行校验(根据项目情况定制校验),如果校验失败则返回相关信息
  • 如果没有符合的license,只返回"License管理"菜单(需要有菜单访问权限)

前端逻辑实现

  • 加载的账号信息中包含license相关信息
  • 如果没有或没有符合当前日期的license,前端只显示"License管理"菜单(后端返回)
  • 如果license即将到期,前端会进行告警提示
  • license管理模块,只提供上传功能,不提供更改/删除等操作

使用代码如下

               
                    def _init_license(app):
                        """
                        初始化license模块,按需使用
                        pip install pycryptodome
                        """
                        from flaskz.utils import get_app_path
                        from .sys_mgmt import license
                        from .sys_mgmt.license import router  # enable api
                        license_manager = license.LicenseManager()
                        license_manager.load_license(license.load_license)
                        license_manager.request_check(license.request_check_by_license)
                        with open(get_app_path("_license/public.key"), "r") as f:
                            public_key = f.read()
                            license_manager.init_app(app, public_key)
                
            

系统cli(cli.py)

框架内置了 cli 模块,可以通过命令进行数据库和管理数据的初始化,如果不需要系统默认提供的基于角色的权限控制功能,不需要执行以下操作

               
                    > set FLASK_APP=admin_app.py    # windows设置环境变量
                    > export FLASK_APP=admin_app.py # mac/linux设置环境变量

                    > flask admin db help           # 帮助功能,为避免误操作,会提示输出密码: taozh
                    > flask admin db upgrade        # 初始化数据库表
                    > flask admin db init           # 初始化菜单/角色/用户
                    > flask admin db radmin         # 当角色/用户被误删除导致不能登录时,修复角色/用户数据
                
            

开发步骤

下面介绍一下一般的开发步骤

  1. 初始化venv环境&安装依赖包

    相关命令如下

                           
                                > python -m venv venv                   # 创建venv环境
                                > source venv/bin/activate              # 激活venv环境
                                > pip install -r requirements.txt       # 安装依赖包
                            
                        
  2. 修改系统配置&初始化alembic
    • 修改config.py中开发/测试/上线相关配置信息,尤其是数据库URI-FLASKZ_DATABASE_URI
    • 修改alembic.ini文件中的数据库URI-sqlalchemy.url
    • 初始化alembic
                                     
                                          > alembic revision -m "migration init"  # 初始化alembic版本
                                          > alembic upgrade head                  # 初始化数据库alembic相关库表
                                      
                                  
  3. 初始化数据库表和数据

    如果不使用默认提供的用户/角色权限管理模块,省略此步操作

                           
                                > set FLASK_APP=flaskz-admin.py     # 设置环境变量(Windows)
                                > export FLASK_APP=flaskz-admin.py  # 设置环境变量(Mac/Linux)
                                > flask admin db help               # 帮助功能,为避免误操作,会提示输出密码: taozh
                                > flask admin db upgrade            # 初始化数据库表
                                > flask admin db init               # 初始化菜单/角色/用户
                                > flask admin db radmin             # 修复角色/用户
                            
                        
  4. 创建数据模型类

    参考 数据模型&更新数据库 章节

  5. 生成alembic数据库脚本,并创建数据库表
                           
                                > alembic revision --autogenerate -m "add template"     # 生成版本文件
                                > alembic upgrade head                                  # 推送到数据库
                            
                        
  6. 生成API

    请参考 封装API自定义API

  7. 注册路由API
                           
                                from . import template      # api/__init__.py,将template相关api添加到blueprint上
                                app.register_blueprint(api_blueprint, url_prefix='/api/v1.0') # admin-app.py,将blueprint注册到app应用上
                           
                        
  8. 启动服务器

    通过以下命令启动服务

                           
                                # Linux
                                > export FLASK_APP=admin-app.py                 # 设置环境变量
                                > nohup flask run --host=0.0.0.0 --port=666 &   # 启动服务并在后台运行
                                > ps -ef | grep flask                           # 查看flask进程
    
                                # Windows
                                > set FLASK_APP=admin_app.py                    # 设置环境变量
                                > flask run --host=0.0.0.0 --port=666           # 启动服务
                            
                        

    浏览器输入 http://127.0.0.1:666/ 访问服务


以上便是使用admin模板进行开发的步骤,只需要简单的几步,就可以完成环境的搭建,然后开始功能模块的的开发。

类似TemplateModel示例这样不太很复杂的模块,一天可以开发➓个。