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中的配置
配置项分为以下几类
- 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)
- 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"
- 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/)
基于 ModelBase 和 ModelMixin 创建数据模型类, 模型类创建或发生变化时,需要通过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 以管理用户登录,支持以下登录模式
- Session/Cookie -支持保持登录功能(remember=True),Flask默认使用客户端类型会话
- Token
- 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 # 当角色/用户被误删除导致不能登录时,修复角色/用户数据
开发步骤
下面介绍一下一般的开发步骤
- 初始化venv环境&安装依赖包
相关命令如下
> python -m venv venv # 创建venv环境 > source venv/bin/activate # 激活venv环境 > pip install -r requirements.txt # 安装依赖包
- 修改系统配置&初始化alembic
- 修改config.py中开发/测试/上线相关配置信息,尤其是数据库URI-FLASKZ_DATABASE_URI
- 修改alembic.ini文件中的数据库URI-sqlalchemy.url
- 初始化alembic
> alembic revision -m "migration init" # 初始化alembic版本 > alembic upgrade head # 初始化数据库alembic相关库表
- 初始化数据库表和数据
如果不使用默认提供的用户/角色权限管理模块,省略此步操作
> 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 # 修复角色/用户
- 创建数据模型类
参考 数据模型&更新数据库 章节
- 生成alembic数据库脚本,并创建数据库表
> alembic revision --autogenerate -m "add template" # 生成版本文件 > alembic upgrade head # 推送到数据库
- 生成API
- 注册路由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应用上
- 启动服务器
通过以下命令启动服务
# 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示例这样不太很复杂的模块,一天可以开发➓个。