运行流程

在前面几小节中,我们介绍了 starter 项目的结构和开发规范,还通过一个简单的 book 项目熟悉了整体开发流程。本小节将会详细说明Lin的运行流程,你会对整个项目有更加清晰的认识。

app 初始化

在前面我们提到,程序的入口文件是根目录下的starter.py文件,我们打开starter.py文件,里面的内容为:

  1. from app.app import create_app # 从app.app中导入create_app
  2. app = create_app()
  3. if __name__ == '__main__':
  4. app.run(debug=True)

内容十分简单,我们从 app.app 中导入create_app函数,该函数会创建一个 Flask app 的实例,而后调用实例的run方法。

  1. def run(self, host=None, port=None, debug=None,
  2. load_dotenv=True, **options):

run 方法的定义如上,你可以传入很多参数,例如:host,默认为127.0.0.1port,默认为5000。当你运行该文件时,会以debug模式打开一个本地的服务,默认监听127.0.0.1:5000。请注意,当你在应用上线时,请关闭 debug 模式。

create_app方法是 starter 项目中的一个自定义方法,它的定义我们可以在app/app.py中找到。如下:

  1. #**********************
  2. def create_app():
  3. app = Flask(__name__) # 1. 初始化Flask app实例
  4. app.config.from_object('app.config.setting')
  5. app.config.from_object('app.config.secure') # 2. 导入配置文件
  6. register_blueprints(app) # 3. 注册所有蓝图
  7. Lin(app) # 4. 初始化Lin,第一个初始化的扩展
  8. apply_cors(app) # 5. 应用跨域扩展,使app支持请求跨域
  9. # 创建所有表格
  10. create_tables(app)# 6. 创建所有数据表
  11. return app

在注释中,我们已经详细的描述了create_app函数每一行代码所发挥的作用。相信你能理解这个函数的作用,但是我们仍然必须罗列如下几点要求:

  • 一旦 app 创建,我们推荐第一时间导入配置。Flask 的扩展会依赖一些配置,因此你必须在第一时间导入配置以保证你的配置生效。
  • 请将 Lin 的初始化放在 Flask 扩展的第一位。Flask 有诸多扩展,如应用跨域的 flask-cors 扩展,因此你必须在初始化这些扩展之前,先初始化 Lin。
  • 请在函数的最后调用create_tables函数来创建所有数据库表,以保证你所有的模型类均能与数据库关联。

Lin 初始化

在上面 app 的初始化过程中,无疑 Lin 的初始化是最为重要的。接下来,我们来着重分析一下 Lin 初始化的详细流程。我们查看源码(第三方包 lin-cms 的源码),在lin/core.py中可以找到 Lin 的定义。

  1. class Lin(object):
  2. def __init__(self,
  3. app: Flask = None, # flask app , default None
  4. group_model=None, # group model, default None
  5. user_model=None, # user model, default None
  6. auth_model=None, # authority model, default None
  7. create_all=False, # 是否创建所有数据库表, default false
  8. mount=True, # 是否挂载默认的蓝图, default True
  9. handle=True, # 是否使用全局异常处理 , default True
  10. json_encoder=True, # 是否使用自定义的json_encoder , default True
  11. ):
  12. self.app = app
  13. self.manager = None
  14. if app is not None:
  15. self.init_app(app, group_model, user_model, auth_model, create_all, mount, handle, json_encoder)

Lin 的构造函数共有 8 个参数,它们作用如下:

name说明类型作用
appFlask app 实例object传入 app 实例
group_model权限组模型object默认为 None,使用 Lin 中默认的权限组模型;若传入模型,则使用传入的模型
user_model用户模型object默认为 None,使用 Lin 中默认的用户模型;若传入模型,则使用传入的模型
auth_model权限模型object默认为 None,使用 Lin 中默认的权限模型;若传入模型,则使用传入的模型
create_all是否创建所有数据表boolean
mount是否挂载默认蓝图boolean
handle是否处理全局异常boolean**
json_encoder是否使用 自定义的 json 的编码器boolean改变 Flask 的默认 json 编码器

关于三个模型参数的使用,我们将在后面单独的一个小节详细介绍。在 Lin 的构造函数中,它本身还调用了自己的一个方法init_app,如下:

  1. def init_app(self,***
  2. ***):
  3. # default config
  4. app.config.setdefault('PLUGIN_PATH', {})# 默认的PLUGIN_PATH配置,插件路径的配置
  5. # 默认蓝图的前缀
  6. app.config.setdefault('BP_URL_PREFIX', '')# 默认的BP_URL_PREFIX配置,默认蓝图的url前缀
  7. json_encoder and self._enable_json_encoder(app) # 使用 自定义的 json 的编码器
  8. self.app = app
  9. # 初始化 manager ,manager主要负责基础库中模型的使用
  10. self.manager = Manager(app.config.get('PLUGIN_PATH'),
  11. group_model,
  12. user_model,
  13. auth_model)
  14. self.app.extensions['manager'] = self.manager # 将manager注册到app的extensions中
  15. db.init_app(app) # 初始化flask-sqlalchemy扩展
  16. create_all and self._enable_create_all(app) # 创建所有数据库表,不建议在此处调用
  17. jwt.init_app(app) # 初始化flask-jwt扩展
  18. mount and self.mount(app) # 挂载默认的CMS蓝图
  19. handle and self.handle_error(app) # 使用全局异常处理

因为 manager 主要负责 Lin 的数据模型的操作,这会在其它小节和上述的三个模型参数一起介绍。

关于dbjwt这两个 Flask 的扩展,Lin 默认就已经集成这两个扩展,如果你希望对它们有更加深入的了解,请阅读相应的官方文档。Flask-JWT-Extended运行流程分析 - 图1Flask-SQLAlchemy运行流程分析 - 图2

视图函数挂载

接下来我们着重分析mount这个函数,它会将默认的视图函数以及 Lin 插件的视图函数挂载到 app 中,当用户访问相应的 url 时便可得到相应的回应。

  1. def mount(self, app):
  2. # 加载默认的路由
  3. bp = Blueprint('plugin', __name__) # 得到默认的CMS蓝图
  4. # 加载插件的路由
  5. for plugin in self.manager.plugins.values():
  6. for controller in plugin.controllers.values():
  7. controller.register(bp) # 在Lin的插件的视图函数挂载到默认蓝图中
  8. app.register_blueprint(bp, url_prefix=app.config.get('BP_URL_PREFIX')) # 将默认的蓝图注册到app中
  9. # 将每个需要权限管理的视图函数加入到ep_meta中,注:此处我们将在后续介绍,你只需要知道它会把某个视图函数的信息加入到manager中,供权限调度
  10. for ep, func in app.view_functions.items():
  11. info = route_meta_infos.get(func.__name__, None)
  12. if info:
  13. self.manager.ep_meta.setdefault(ep, info)

到此,你或许有些疑惑,Lin 还有插件?是的,Lin 是有自己的插件机制的,Lin 的插件(plugin)跟 Flask 的扩展(extension)很像,但它们压根不是同一种东西!!!

WARNING

你可以如此区分,Lin 是 Flask 的一个扩展,而 Lin 本身又带有诸多插件,Lin 的插件可以认为是 Flask 扩展的一部分。关于插件,Lin 在第一个版本并没有引入,这将会在下个版本进行支持。如果你有兴趣,可以通过源代码提前了解。

  1. bp = Blueprint('plugin', __name__)

在上段代码中,我们定义了一个默认的插件蓝图,这个蓝图并没有停止挂载红图。而是依次遍历每个插件,并且将插件的 controller(控制类,实则就是一个红图)挂载到这个蓝图上。所以,这个蓝图上就有默认基础库插件的所有的视图函数。

至此,程序跑起来后,你就可以通过相应的 url 访问所有视图函数对应的 API。

小节

在本小节中,我们分析了项目的运行流程。当然我们没有叙述 app 的启动流程,而是介绍了 Lin 的初始化和视图函数的挂载流程,并且着重区分了 Flask 的扩展以及 Lin 的插件。当然这里面有很重要的一环——插件的加载。

WARNING

在 Lin 的 1.0.0 的版本之前,插件机制虽然存在,但却未被真正使用。因此如果你对此有所疑惑,没关系,我们会在插件彻底上线后,着重解析插件的使用和原理。