flask -服务端项目搭建

1. 服务端项目搭建

新建项目目录mofangapi,并创建虚拟环境

conda create -n mofang python=3.8

安装开发中使用的依赖模块

pip install flask==1.1.4 -i https://pypi.douban.com/simple
pip install flask-redis -i https://pypi.douban.com/simple
pip install flask-session -i https://pypi.douban.com/simple
pip install flask-script -i https://pypi.douban.com/simple
pip install flask-mysqldb -i https://pypi.douban.com/simple
pip install flask-sqlalchemy -i https://pypi.douban.com/simple

在pycharm中打开项目目录mofangapi编写manage.py启动项目的文件



2. 创建启动文件

from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'index'

if __name__ == '__main__':
    app.run()

manage.py终不能存放大量的开发代码, 在开发中应该体现的是一种分工精神,所以我们可以把flask中各种功能代码进行分类分文件存储.

创建项目目录结构:

项目根目录/
├── application/            # 项目主要逻辑代码保存目录
|   ├── settings/           # 项目配置存储目录
│   │   ├ dev.py            # 开发阶段的配置文件【本地开发者使用】
│   │   ├ prod.py           # 生产阶段的配置文件【线上服务器使用】
|   |   ├ __init__.py       # 项目公共配置文件
│   ├── __init__.py         # 项目初始化文件
├── manage.py               # 项目的终端管理脚本文件


3. 构建全局初始化函数创建app应用对象

把引导整个项目启动的全局初始化代码,保存到application/__init__py,代码:

from flask import Flask

def init_app():
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)

    return app

manage.py,中调用初始化函数,创建app应用对象,代码:

from application import init_app

app = init_app()

@app.route('/')
def index():
    return 'index'

if __name__ == '__main__':
    app.run()


4. 通过终端脚本启动项目

applicatiion/__init__py,代码:

from flask import Flask
from flask_script import Manager

def init_app():
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)

    # 初始化终端脚本工具
    manage = Manager()
    manage.app = app

    return manage

manage.py,代码:

from application import init_app

manage = init_app()
app = manage.app

@app.route('/')
def index():
    return 'index'

if __name__ == '__main__':
    manage.run()

运营项目的方式就要修改成如下:

python manage.py runserver -h0.0.0.0 -p5000
python manage.py runserver -h0 -p5000

终端运行效果:



3. 项目加载配置

在application/utils/config.py中准备加载配置的函数代码:

def init_config(app, config_path):
    """先加载默认配置,然后加载当前指定配置"""
    init_path = ".".join( config_path.split(".")[:-1] )
    app.config.from_object(init_path)
    app.config.from_object(config_path)

编写项目默认配置文件, application/settings/__init__.py代码:

"""公共配置"""
# 调试模式
DEBUG = True

当然, 项目开发过程完成以后肯定会项目上线,所以针对配置文件,我们可以准备不同环境下的配置

application/settings/dev.py,代码:

"""本地配置"""
# 调试模式
DEBUG = True
# 语言
LANGUAGE = "zh_hans"

application/settings/prod.py,代码:

"""生产配置"""
# 调试模式
DEBUG = False

在项目入口文件application/__init__py中加载配置,代码:

from flask import Flask
from flask_script import Manager

from application.utils.config import init_config

def init_app(config_path):
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)

    # 加载配置
    init_config(app, config_path)

    # 初始化终端脚本工具
    manage = Manager()
    manage.app = app

    return manage

在创建app对象的项目启动文件manage.py中,设置配置

from application import init_app

manage = init_app("application.settings.dev")
app = manage.app

@app.route('/')
def index():
    return 'index'

if __name__ == '__main__':
    manage.run()


4. 数据库初始化

4.1 SQLAlchemy初始化

默认项目配置文件中增加配置选项,application/settings/__init__.py,代码:

"""公共配置"""

"""调试模式"""
DEBUG = True

"""数据库配置"""
# 数据库连接
SQLALCHEMY_DATABASE_URI = ""
# 动态追踪修改设置
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = False

开发配置dev.py中,配置数据库连接信息,代码:

"""本地配置"""
"""调试模式"""
DEBUG = True

"""数据库配置"""
# 动态追踪修改设置
SQLALCHEMY_DATABASE_URI = "mysql://mofanguser:mofang@127.0.0.1:3306/mofang?charset=utf8mb4"
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True

在mysql终端下, 创建属于当前项目的数据库管理用户, 命令如下:

create database mofang charset=utf8mb4;
# 针对当前数据库配置账户信息
# mysql8.0之前
create user mofanguser identified by 'mofang'; 
grant all privileges on mofang.* to 'mofanguser'@'%';
flush privileges;

# mysql8.0之后
create user 'mofanguser'@'%' identified with mysql_native_password by 'mofang';
GRANT ALL ON mofang.* TO 'mofanguser'@'%';

在项目全局引导文件中,对数据库功能进行初始化,application/__init__.py,代码:

# 先内置,后官方,然后第三方,接着是自己的。
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy

from application.utils.config import init_config

# 终端脚本工具初始化
manage = Manager()

# SQLAlchemy初始化
db = SQLAlchemy()

def init_app(config_path):
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)

    # 加载配置
    init_config(app, config_path)

    # SQLAlchemy加载配置
    db.init_app(app)

    # 终端脚本工具加载配置
    manage.app = app

    return manage

4.2 Redis数据库初始化

默认配置文件,application/settings/__init__py,代码:

"""公共配置"""

"""调试模式"""
DEBUG = True

"""数据库配置"""
# 数据库连接
SQLALCHEMY_DATABASE_URI = ""
# 动态追踪修改设置
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = False

"""redis配置"""
REDIS_URL = "redis://@127.0.0.1:6379/0"

开发环境配置文件,application/settings/dev.py,代码:

"""本地配置"""
"""调试模式"""
DEBUG = True

"""数据库配置"""
# 动态追踪修改设置
SQLALCHEMY_DATABASE_URI = "mysql://mofanguser:mofang@127.0.0.1:3306/mofang?charset=utf8mb4"
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True

"""redis配置"""
# 默认缓存数据
REDIS_URL = "redis://:@127.0.0.1:6379/0"
# 验证相关缓存
CHECK_URL  = "redis://:@127.0.0.1:6379/1"

在全局引导文件中, 对redis进行初始化,applicaiton/__init__.py,代码:

# 先内置,后官方,然后第三方,接着是自己的。
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis

from application.utils.config import init_config

# 终端脚本工具初始化
manage = Manager()

# redis初始化
redis_cache = FlaskRedis(config_prefix="REDIS")
redis_check = FlaskRedis(config_prefix="CHECK")

# SQLAlchemy初始化
db = SQLAlchemy()

def init_app(config_path):
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)

    # 加载配置
    init_config(app, config_path)

    # SQLAlchemy加载配置
    db.init_app(app)

    # redis加载配置
    redis_cache.init_app(app)
    redis_check.init_app(app)

    # 终端脚本工具加载配置
    manage.app = app

    return manage

4.3 Session存储到redis数据库中

配置文件中,添加session相关配置,并在redis的配置项中新增一个redis存储对象,指向2号数据库。

application/settings/dev.py,代码:

"""本地配置"""
"""调试模式"""
DEBUG = True

"""数据库配置"""
# 动态追踪修改设置
SQLALCHEMY_DATABASE_URI = "mysql://mofanguser:mofang@127.0.0.1:3306/mofang?charset=utf8mb4"
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True

"""redis配置"""
# 默认缓存数据
REDIS_URL = "redis://:@127.0.0.1:6379/0"
# 验证相关缓存
CHECK_URL  = "redis://:@127.0.0.1:6379/1"
# redis存储session
SESSION_URL = "redis://:@127.0.0.1:6379/2"

"""session存储配置"""
# session存储方式配置
SESSION_TYPE = "redis"
# 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
SESSION_PERMANENT = False
# 设置session_id在浏览器中的cookie有效期
PERMANENT_SESSION_LIFETIME = 24 * 60 * 60  # session 的有效期,单位是秒
# 是否对发送到浏览器上session的cookie值进行加密
SESSION_USE_SIGNER = True
# 保存到redis的session数的名称前缀
SESSION_KEY_PREFIX = "session:"

默认配置文件application/settings/__init__.py中, 添加秘钥这个公共配置项:

"""公共配置"""

"""调试模式"""
DEBUG = True

"""加密秘钥"""
# 设置密钥,可以通过 base64.b64encode(os.urandom(48)) 来生成一个指定长度的随机字符串
SECRET_KEY = "RvDsY4u6GRJm4srpe2NUbpxS3R8+5yZRNTcYkx7NtzHpumOK4Wq/+f+qplfxgGld"

"""数据库配置"""
# 数据库连接
SQLALCHEMY_DATABASE_URI = ""
# 动态追踪修改设置
SQLALCHEMY_TRACK_MODIFICATIONS = False
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = False

"""redis配置"""
REDIS_URL = "redis://@127.0.0.1:6379/0"

在本地开发配置中,设置session存储指定的redis库中,application/settings/dev.py,代码:

"""本地配置"""
"""调试模式"""
DEBUG = True

"""数据库配置"""
# 动态追踪修改设置
SQLALCHEMY_DATABASE_URI = "mysql://mofanguser:mofang@127.0.0.1:3306/mofang?charset=utf8mb4"
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True

"""redis配置"""
# 默认缓存数据
REDIS_URL = "redis://:@127.0.0.1:6379/0"
# 验证相关缓存
CHECK_URL  = "redis://:@127.0.0.1:6379/1"
# redis存储session
SESSION_URL = "redis://:@127.0.0.1:6379/2"

"""session存储配置"""
# session存储方式配置
SESSION_TYPE = "redis"
# 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
SESSION_PERMANENT = False
# 设置session_id在浏览器中的cookie有效期
PERMANENT_SESSION_LIFETIME = 24 * 60 * 60  # session 的有效期,单位是秒
# 是否对发送到浏览器上session的cookie值进行加密
SESSION_USE_SIGNER = True
# 保存到redis的session数的名称前缀
SESSION_KEY_PREFIX = "session:"

在项目入口文件application/__init__.py中对session存储进行初始化,代码:

# 先内置,后官方,然后第三方,接着是自己的。
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session

from application.utils.config import init_config

# 终端脚本工具初始化
manage = Manager()

# redis初始化
redis_cache = FlaskRedis(config_prefix="REDIS")
redis_check = FlaskRedis(config_prefix="CHECK")
redis_session = FlaskRedis(config_prefix="SESSION")

# SQLAlchemy初始化
db = SQLAlchemy()

# session存储配置初始化
session_store = Session()

def init_app(config_path):
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)

    # 加载配置
    init_config(app, config_path)

    # SQLAlchemy加载配置
    db.init_app(app)

    # redis加载配置
    redis_cache.init_app(app)
    redis_check.init_app(app)
    redis_session.init_app(app)
    # session保存数据到redis时启用的链接对象
    app.config["SESSION_REDIS"] = redis_session
    # session存储配置类加载配置
    session_store.init_app(app)
    
    # 终端脚本工具加载配置
    manage.app = app

    return manage

完成上面的项目构建步骤以后,此时目录结构如下:

mofangapi/                  # 项目根目录
├── docs/                   # 项目开发文档/接口等备份资料存储目录
├── logs/                   # 项目日志存储目录
├── application/            # 项目主要逻辑代码保存目录
|   ├── settings/           # 项目配置存储目录
│   │   ├ dev.py            # 开发阶段的配置文件
│   │   ├ prod.py           # 生产阶段的配置文件
|   |   ├ __init__.py       # 项目公共配置文件
|   ├── utils/              # 工具函数库/类库
│   │   ├ config.py         # 配置相关的辅助函数
│   ├── __init__.py         # 项目初始化文件[入口程序,APP工厂函数]
└── manage.py               # 项目的终端管理脚本文件


5. 日志初始化

flask中本身内置了基于loging模块封装的日志功能的,我们在使用的时候, 一般日志如果不是核心重点,则通过由python内置的logging模块进行配置集成使用即可, 如果项目中日志发挥作用比较重要, 则一般安装部署ELK日志分析系统.

5.1 日志的等级

从高到低,依次:
	FATAL/CRITICAL = 致命的,危险的
	ERROR = 错误
	WARNING = 警告
	INFO = 信息
	DEBUG = 调试

flask日志功能的基本使用

app.logger.fatal("致命")    # [2021-05-28 17:44:08,524] CRITICAL in manage: 致命
app.logger.error("错误")    # [2021-05-28 17:42:41,597] ERROR in manage: 错误
app.logger.warning("警告")  # [2021-05-28 17:43:34,401] WARNING in manage: 警告

# debug模式关闭了以后,info和debug等级的日志记录不会输出了
app.logger.info("运行信息")  # [2021-05-28 17:44:39,914] INFO in manage: 运行信息
app.logger.debug("调试信息") # [2021-05-28 17:45:01,131] DEBUG in manage: 调试信息

在项目运行时,框架本身会不断记录代码程序运行的日志错误,但是我们将来编写的业务逻辑也可能存在抛出异常的情况,这些异常和框架代码本身无关,如果都混在一起的话,对于找出错误,是没有帮助的。所以,我们在这里新建一个日志记录器,专门只是记录我们业务逻辑的相关日志。


5.2 构建日志模块

把日志初始化相关的代码封装成一个日志类,application/utils/logger.py,代码:

import logging
from logging.handlers import TimedRotatingFileHandler
class Log():
    """日志模块"""
    def __init__(self, app=None):
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        self.app = app
        return self.setup()

    def setup(self):
        """集成日志功能到flask项目中"""
        # 设置日志的记录等级
        logging.basicConfig(level=self.app.config.get("LOG_LEVEL"))  # 调试debug级

        # 创建日志记录器,指明日志保存的路径、每个日志文件的最大大小、保存的日志文件个数上限
        file_log_handler = TimedRotatingFileHandler(
            filename=self.app.BASE_DIR + self.app.config.get("LOG_DIR"),
            when="midnight", # 每天新增日志的时间,午夜
            backupCount=self.app.config.get("LOG_BACKPU_COUNT")
        )

        # 创建日志记录的格式 日志记录器 日志等级 发生时间 输入日志信息的文件名 行数 日志信息
        formatter = logging.Formatter('%(name)s: %(levelname)s %(asctime)s %(filename)s:%(lineno)d %(message)s')
        # 为刚创建的日志记录器设置日志记录格式
        file_log_handler.setFormatter(formatter)
        # 为全局的日志工具对象(flaskapp使用的)添加日志记录器
        logging.getLogger(self.app.config.get("LOG_NAME")).addHandler(file_log_handler)
        # 返回日志器对象提供给业务开发
        logger = logging.getLogger(self.app.config.get("LOG_NAME"))
        # 给flask默认的日志记录器增加写入日志到logs
        self.app.logger.addHandler(file_log_handler)
        return logger

application/settings/dev.py代码:

"""本地配置"""
"""调试模式"""
DEBUG = True

"""数据库配置"""
# 动态追踪修改设置
SQLALCHEMY_DATABASE_URI = "mysql://mofanguser:mofang@127.0.0.1:3306/mofang?charset=utf8mb4"
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True

"""redis配置"""
# 默认缓存数据
REDIS_URL = "redis://:@127.0.0.1:6379/0"
# 验证相关缓存
CHECK_URL  = "redis://:@127.0.0.1:6379/1"
# redis存储session
SESSION_URL = "redis://:@127.0.0.1:6379/2"

"""session存储配置"""
# session存储方式配置
SESSION_TYPE = "redis"
# 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
SESSION_PERMANENT = False
# 设置session_id在浏览器中的cookie有效期
PERMANENT_SESSION_LIFETIME = 24 * 60 * 60  # session 的有效期,单位是秒
# 是否对发送到浏览器上session的cookie值进行加密
SESSION_USE_SIGNER = True
# 保存到redis的session数的名称前缀
SESSION_KEY_PREFIX = "session:"


"""日志配置"""
LOG_LEVEL = "INFO"              # 日志输出到文件中的最低等级
LOG_DIR = "/logs/mofang.log"    # 日志存储目录
LOG_BACKPU_COUNT = 20           # 日志文件的最大备份数量
LOG_NAME = "mofang"             # 日志器的名字,flask的名为:flask.app

application/__init__.py文件中的init_app 方法中调用日志初始化。

# 先内置,后官方,然后第三方,接着是自己的。
import os,sys

from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session

from application.utils.config import init_config
from application.utils.logger import Log
# 终端脚本工具初始化
manage = Manager()

# redis初始化
redis_cache = FlaskRedis(config_prefix="REDIS")
redis_check = FlaskRedis(config_prefix="CHECK")
redis_session = FlaskRedis(config_prefix="SESSION")

# SQLAlchemy初始化
db = SQLAlchemy()

# session存储配置初始化
session_store = Session()

# 自定义日志初始化
logger = Log()

def init_app(config_path):
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)
    # 当前项目根目录
    app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    # 加载配置
    init_config(app, config_path)

    # SQLAlchemy加载配置
    db.init_app(app)

    # redis加载配置
    redis_cache.init_app(app)
    redis_check.init_app(app)
    redis_session.init_app(app)

    # session保存数据到redis时启用的链接对象
    app.config["SESSION_REDIS"] = redis_session
    # session存储配置类加载配置
    session_store.init_app(app)


    # 日志加载配置
    log = logger.init_app(app)
    app.log = log
    # 终端脚本工具加载配置
    manage.app = app

    return manage

新增日志以后的项目目录结构

.
├── application
│   ├── __init__.py
│   ├── settings
│   │   ├── dev.py
│   │   ├── __init__.py
│   │   ├── prod.py
│   └── utils
│       ├── config.py
│       ├── __init__.py
│       ├── logger.py      # 日志相关模块代码
│       └── session.py
├── docs
├── logs                    # 日志文件存储目录
│   └── mofang.log
└── manage.py

经过上面的改造,我们接下来就可以开始创建蓝图了。



6. 蓝图初始化

在application下创建apps目录,apps以后专门用于保存项目的每一个蓝图,并在apps创建home蓝图目录,并在__init__.py文件中创建蓝图对象

通过自定义终端命令, 创建一个自动生成蓝图目录的命令.application/utils/commands.py,代码:

import os
from flask_script import Command, Option

class BlueprintCommand(Command):
    """生成蓝图"""
    name = "blue"
    option_list = [
        Option('--name', '-n', dest='name'),
    ]

    def run(self, name):
        # 生成蓝图名称对象的目录
        os.mkdir(name)
        open("%s/__init__.py" % name, "w")
        open("%s/views.py" % name, "w")
        open("%s/models.py" % name, "w")
        open("%s/urls.py" % name, "w")
        open("%s/test.py" % name, "w")
        open("%s/tasks.py" % name, "w")
        open("%s/marshmallow.py" % name, "w")
        open("%s/socket.py" % name, "w")
        open("%s/api.py" % name, "w")

        print("蓝图%s创建完成...." % name)

上面的命令就可以帮我们完成项目中生成蓝图的功能,就下来我们就可以直接把命令注册到manage对象中就可以使用了.

但是, 我们往后的开发中肯定还会继续的需要进行自定义终端命令,所以我们声明一个load_command的自动注册函数,可以自动帮我们完成加载注册自定义终端命令的过程.

application/utils/commands.py,代码:

import os,inspect

from flask_script import Command, Option
from importlib import import_module

def load_commands(manager, command_path=None):
    """根据指定导包路径注册自定义命令到manage中"""
    if command_path is None:
        command_path = "application.utils.commands"

    # 根据路径导包
    module = import_module(command_path)
    # 提取模块中所有成员,条件是,这个成员必须是类
    class_list = inspect.getmembers(module, inspect.isclass)
    # print( f"class_list={class_list}" )
    """
    [
        ('BlueprintCommand', <class 'application.utils.commands.BlueprintCommand'>), 
        ('Command', <class 'flask_script.commands.Command'>), 
        ('Option', <class 'flask_script.commands.Option'>)
    ]
    """

    for class_item in class_list:
        if issubclass(class_item[1], Command) and class_item[0] != "Command":
            # 注册命令类到manage中
            manager.add_command(class_item[1].name, class_item[1])

class BlueprintCommand(Command):
    """生成蓝图"""
    name = "blue"
    option_list = [
        Option('--name', '-n', dest='name'),
    ]

    def run(self, name):
        # 生成蓝图名称对象的目录
        os.mkdir(name)
        open("%s/__init__.py" % name, "w")
        open("%s/views.py" % name, "w")
        open("%s/models.py" % name, "w")
        open("%s/urls.py" % name, "w")
        open("%s/test.py" % name, "w")
        open("%s/tasks.py" % name, "w")
        open("%s/marshmallow.py" % name, "w")
        open("%s/socket.py" % name, "w")
        open("%s/api.py" % name, "w")

        print("蓝图%s创建完成...." % name)

在项目入口文件application/__init__.py中, 调用load_command函数注册命令

# 先内置,后官方,然后第三方,接着是自己的。
import os,sys

from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session

from application.utils.config import init_config
from application.utils.logger import Log
from application.utils.commands import load_commands

# 终端脚本工具初始化
manage = Manager()

# redis初始化
redis_cache = FlaskRedis(config_prefix="REDIS")
redis_check = FlaskRedis(config_prefix="CHECK")
redis_session = FlaskRedis(config_prefix="SESSION")

# SQLAlchemy初始化
db = SQLAlchemy()

# session存储配置初始化
session_store = Session()

# 自定义日志初始化
logger = Log()

def init_app(config_path):
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)
    # 当前项目根目录
    app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    # 加载配置
    init_config(app, config_path)

    # SQLAlchemy加载配置
    db.init_app(app)

    # redis加载配置
    redis_cache.init_app(app)
    redis_check.init_app(app)
    redis_session.init_app(app)

    # session保存数据到redis时启用的链接对象
    app.config["SESSION_REDIS"] = redis_session
    # session存储配置类加载配置
    session_store.init_app(app)

    # 日志加载配置
    log = logger.init_app(app)
    app.log = log
    # 终端脚本工具加载配置
    manage.app = app
    # 自动注册自定义命令
    load_commands(manage)
    return manage

接下来就可以在终端下,通过命令生成蓝图目录了. 命令:

cd application/apps
python ../../manage.py blue -nhome

效果:

有了蓝图以后,接下来我们就可以把视图代码,模型代码,路由代码等存储到蓝图目录下了,但是我们需要把蓝图注册到app应用对象下,想想以后,是不是会出现很多的蓝图?

所以我们能不能像上面的自动注册命令那样去自动注册蓝图?可以的,我们也可以封装注册蓝图的功能到一个函数中.让程序自动识别并注册.

项目中的蓝图可以有很多,但是有些蓝图可能并不能提供给客户端访问,所以我们需要在配置文件中声明一个蓝图注册列表, 在蓝图自动注册的函数中只注册列表中填写的蓝图。

application/settings/dev.py,代码:

"""本地配置"""
"""调试模式"""
DEBUG = True

"""数据库配置"""
# 动态追踪修改设置
SQLALCHEMY_DATABASE_URI = "mysql://mofanguser:mofang@127.0.0.1:3306/mofang?charset=utf8mb4"
# 查询时会显示原始SQL语句
SQLALCHEMY_ECHO = True

"""redis配置"""
# 默认缓存数据
REDIS_URL = "redis://:@127.0.0.1:6379/0"
# 验证相关缓存
CHECK_URL  = "redis://:@127.0.0.1:6379/1"
# redis存储session
SESSION_URL = "redis://:@127.0.0.1:6379/2"

"""session存储配置"""
# session存储方式配置
SESSION_TYPE = "redis"
# 如果设置session的生命周期是否是会话期, 为True,则关闭浏览器session就失效
SESSION_PERMANENT = False
# 设置session_id在浏览器中的cookie有效期
PERMANENT_SESSION_LIFETIME = 24 * 60 * 60  # session 的有效期,单位是秒
# 是否对发送到浏览器上session的cookie值进行加密
SESSION_USE_SIGNER = True
# 保存到redis的session数的名称前缀
SESSION_KEY_PREFIX = "session:"


"""日志配置"""
LOG_LEVEL = "INFO"              # 日志输出到文件中的最低等级
LOG_DIR = "/logs/mofang.log"    # 日志存储目录
LOG_BACKPU_COUNT = 20           # 日志文件的最大备份数量
LOG_NAME = "mofang"             # 日志器的名字,flask的名为:flask.app

"""蓝图列表"""
INSTALL_BLUEPRINT = [
    "application.apps.home",
]

6.1 注册蓝图

application.utils.blueprint.py模块中声明一个resgister_blueprint函数,函数中针对注册到项目配置文件中的INSTALL_BLUEPRINT蓝图列表选项的蓝图内容实现自动注册到app应用对象里面。

application/utils/blueprint.py,代码:

from flask import Blueprint
def register_blueprint(app):
    """自动注册蓝图"""
    # 从配置文件中读取需要注册到项目中的蓝图路径信息
    blueprint_path_list = app.config.get("INSTALL_BLUEPRINT")
    # 遍历蓝图路径列表,对每一个蓝图进行初始化
    for blueprint_path in blueprint_path_list:
        # 获取蓝图路径中最后一段的包名作为蓝图的名称
        blueprint_name = blueprint_path.split(".")[-1]
        # 创建蓝图对象
        blueprint = Blueprint(blueprint_name,blueprint_path)
        # 把蓝图对象注册到app实例对象
        # todo url_prefix 是地址前缀,将来我们将来实现一个总路由来声明它
        app.register_blueprint(blueprint,url_prefix="")

项目入口文件application.__init__中, 调用register_blueprint方法, 自动注册蓝图.application/__init__.py,代码:

# 先内置,后官方,然后第三方,接着是自己的。
import os,sys

from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session

from application.utils.config import init_config
from application.utils.logger import Log
from application.utils.commands import load_commands
from application.utils.blueprint import register_blueprint
# 终端脚本工具初始化
manage = Manager()

# redis初始化
redis_cache = FlaskRedis(config_prefix="REDIS")
redis_check = FlaskRedis(config_prefix="CHECK")
redis_session = FlaskRedis(config_prefix="SESSION")

# SQLAlchemy初始化
db = SQLAlchemy()

# session存储配置初始化
session_store = Session()

# 自定义日志初始化
logger = Log()
from flask import request
def init_app(config_path):
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)
    # 当前项目根目录
    app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    # 加载配置
    init_config(app, config_path)

    # SQLAlchemy加载配置
    db.init_app(app)

    # redis加载配置
    redis_cache.init_app(app)
    redis_check.init_app(app)
    redis_session.init_app(app)

    # session保存数据到redis时启用的链接对象
    app.config["SESSION_REDIS"] = redis_session
    # session存储配置类加载配置
    session_store.init_app(app)

    # 日志加载配置
    log = logger.init_app(app)
    app.log = log

    # 自动注册蓝图
    register_blueprint(app)

    # 终端脚本工具加载配置
    manage.app = app
    # 自动注册自定义命令
    load_commands(manage) # 导入默认的那个命令文件
    return manage

注册了蓝图对象以后,蓝图下面的视图方法和视图对应的路由关系也要进行注册!


6.2 注册蓝图的路由和视图

所以, 在蓝图home下面的urls.py文件中,通过path方法把url地址和视图方法进行处理成字典,然后把字典作为成员添加到路由列表的固定变量urlpatterns列表中.

application/utils/blueprint.py,生成path函数,代码:

from flask import Blueprint
def register_blueprint(app):
    """自动注册蓝图"""
    # 从配置文件中读取需要注册到项目中的蓝图路径信息
    blueprint_path_list = app.config.get("INSTALL_BLUEPRINT")
    # 遍历蓝图路径列表,对每一个蓝图进行初始化
    for blueprint_path in blueprint_path_list:
        # 获取蓝图路径中最后一段的包名作为蓝图的名称
        blueprint_name = blueprint_path.split(".")[-1]
        # 创建蓝图对象
        blueprint = Blueprint(blueprint_name,blueprint_path)
        # 把蓝图对象注册到app实例对象
        # todo url_prefix 是地址前缀,将来我们将来实现一个总路由来声明它
        app.register_blueprint(blueprint,url_prefix="")

def path(rule, view_func,**kwargs):
    """绑定url地址和视图的映射关系"""
    return {"rule":rule,"view_func":view_func,**kwargs}

application.__init__入口文件中,导入path路由映射函数,将来方便再其他地方调用该函数,application.__init__,文件添加代码如下:

from application.utils.blueprint import register_blueprint,path

在home蓝图下的urls.py中,使用path函数绑定视图和路由的映射关系并添加urlpatterns中,home.urls.py,代码:

from application import path
from . import views
urlpatterns = [
    path("/", views.index,methods=["POST"]),
    path("/demo", views.demo),
]

为了方便测试,先到当前home蓝图下视图文件views.py中,添加2个测试的视图,代码:

def index():
    return "ok!!!!"

def demo():
    return "demo"

完成上面步骤,将来即便有新的蓝图也可以参考上面的写法,把当前蓝图中所有的路由全部集中在urlpatterns中,接着下来,我们就可以在application.utils.blueprint.register_blueprint蓝图注册函数中, 生成蓝图对象以后添加自动加载并注册蓝图的路由和视图了。application/utils/blueprint.py,代码:

from flask import Blueprint
from importlib import import_module
def register_blueprint(app):
    """自动注册蓝图"""
    # 从配置文件中读取需要注册到项目中的蓝图路径信息
    blueprint_path_list = app.config.get("INSTALL_BLUEPRINT")
    # 遍历蓝图路径列表,对每一个蓝图进行初始化
    for blueprint_path in blueprint_path_list:
        # 获取蓝图路径中最后一段的包名作为蓝图的名称
        blueprint_name = blueprint_path.split(".")[-1]
        # 创建蓝图对象
        blueprint = Blueprint(blueprint_name,blueprint_path)

        # 导入子路由关系,blueprint_url_path就是当前蓝图下的urls模块的导包路径
        blueprint_url_path = blueprint_path + ".urls"
        urls_module = import_module(blueprint_url_path)
        # 获取urls模块下路由列表urlpatterns
        urlpatterns = urls_module.urlpatterns
        # 把urlpatterns的每一个路由信息添加注册到蓝图对象里面
        for url in urlpatterns:
            blueprint.add_url_rule(**url)

        # 把蓝图对象注册到app实例对象
        # todo url_prefix 是地址前缀,将来我们将来实现一个总路由来声明它
        app.register_blueprint(blueprint,url_prefix="")

def path(rule, view_func,**kwargs):
    """绑定url地址和视图的映射关系"""
    return {"rule":rule,"view_func":view_func,**kwargs}

此时,运行项目,就可以通过url地址访问蓝图下的视图方法了.

上面蓝图注册到app示例对象时, 没有设置url_prefix路由前缀, 接下来我们可以单独设置一个总路由application/urls.py,进行路由前缀的设置.

在项目默认配置文件中,application/settings/__init__.py,新增总路由的配置项URL_PATH

"""总路由"""
URL_PATH = "application.urls"

接下来,在application.utils.blueprint.register_blueprint蓝图注册函数中include函数,负责把路由前缀和蓝图进行绑定映射,application.utils.blueprint,代码:

def include(url_prefix, blueprint_url_subffix):
    """
    绑定蓝图和路由前缀的关系
    :param url_prefix: 路由前缀
    :param blueprint_url_subffix: 蓝图名称,
           格式:蓝图包名.路由模块名
           例如:蓝图目录是home, 路由模块名是urls,则参数:home.urls
    :return:
    """
    return {"url_prefix": url_prefix, "blueprint_url_subffix": blueprint_url_subffix }

创建总路由文件并注册蓝图和路由前缀的关系, application/urls.py,代码:

from application import include
urlpatterns = [
    include("", "home.urls")
]

接下来,就可以在注册蓝图时,把路由前缀随着蓝图对象一起注册到app实例对象中。application.utils.blueprint,代码:

from flask import Blueprint
from importlib import import_module
def register_blueprint(app):
    """
    自动注册蓝图
    :param app: 当前flask的app实例对象
    :return:
    """
    # 从配置文件中读取需要注册到项目中的蓝图路径信息
    blueprint_path_list = app.config.get("INSTALL_BLUEPRINT")
    # 从配置文件中读取总路由模块
    app_urls_path = app.config.get("URL_PATH")
    # 总路由模块
    app_urls_module = import_module(app_urls_path)
    # 总路由列表
    app_urlpatterns = app_urls_module.urlpatterns

    # 遍历蓝图路径列表,对每一个蓝图进行初始化
    for blueprint_path in blueprint_path_list:
        # 获取蓝图路径中最后一段的包名作为蓝图的名称
        blueprint_name = blueprint_path.split(".")[-1]
        # 创建蓝图对象
        blueprint = Blueprint(blueprint_name,blueprint_path)

        # 蓝图路由的前缀
        url_prefix = ""
        # 蓝图下的子路由列表
        urlpatterns = []

        # 获取蓝图的父级目录
        blueprint_father_path = ".".join( blueprint_path.split(".")[:-1] )
        for item in app_urlpatterns:
            if blueprint_name in item["blueprint_url_subffix"]:
                # 导入蓝图下的子路由模块
                urls_module = import_module(blueprint_father_path +"."+ item["blueprint_url_subffix"])
                # 获取urls模块下路由列表urlpatterns
                urlpatterns = urls_module.urlpatterns
                # 提取蓝图路由的前缀
                url_prefix = item["url_prefix"]
                # 从总路由中查到当前蓝图对象的前缀就不要继续往下遍历了
                break

        # 把urlpatterns的每一个路由信息添加注册到蓝图对象里面
        for url in urlpatterns:
            blueprint.add_url_rule(**url)

        # 把蓝图对象注册到app实例对象
        app.register_blueprint(blueprint,url_prefix=url_prefix)

def path(rule, view_func, **kwargs):
    """
    绑定url地址和视图的映射关系
    :param rule: 路由url
    :param view_func: 视图
    :param kwargs: 其他参数,如: methods=["POST"]
    :return:
    """
    return {"rule":rule,"view_func":view_func,**kwargs}

def include(url_prefix, blueprint_url_subffix):
    """
    绑定蓝图和路由前缀的关系
    :param url_prefix: 路由前缀
    :param blueprint_url_subffix: 蓝图名称,
           格式:蓝图包名.路由模块名
           例如:蓝图目录是home, 路由模块名是urls,则参数:home.urls
    :return:
    """
    return {"url_prefix": url_prefix, "blueprint_url_subffix": blueprint_url_subffix }

6.3 注册蓝图的模型

在蓝图home下的models.py中声明模型,例如:

from application import db
class User(db.Model):
    __tablename__ = "mf_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), unique=True, comment="账户名")
    password = db.Column(db.String(255), comment="登录密码")
    ip_address = db.Column(db.String(255), index=True, comment="登录IP")

    def __repr__(self):
        return self.name

完成上面模型声明以后,我们在入口文件application.__init__中通过 db的 create_all() 创建数据表。

application.__init__,代码:

# 先内置,后官方,然后第三方,接着是自己的。
import os,sys

from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from flask_redis import FlaskRedis
from flask_session import Session

from application.utils.config import init_config
from application.utils.logger import Log
from application.utils.commands import load_commands
from application.utils.blueprint import register_blueprint,path,include
# 终端脚本工具初始化
manage = Manager()

# redis初始化
redis_cache = FlaskRedis(config_prefix="REDIS")
redis_check = FlaskRedis(config_prefix="CHECK")
redis_session = FlaskRedis(config_prefix="SESSION")

# SQLAlchemy初始化
db = SQLAlchemy()

# session存储配置初始化
session_store = Session()

# 自定义日志初始化
logger = Log()
from flask import request
def init_app(config_path):
    """用于创建app实例对象并完成初始化过程的工厂函数"""
    app = Flask(__name__)
    # 当前项目根目录
    app.BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    # 加载配置
    init_config(app, config_path)

    # SQLAlchemy加载配置
    db.init_app(app)

    # redis加载配置
    redis_cache.init_app(app)
    redis_check.init_app(app)
    redis_session.init_app(app)

    # session保存数据到redis时启用的链接对象
    app.config["SESSION_REDIS"] = redis_session
    # session存储配置类加载配置
    session_store.init_app(app)

    # 日志加载配置
    log = logger.init_app(app)
    app.log = log

    # 自动注册蓝图
    register_blueprint(app)

    # db创建数据表
    with app.app_context():
        db.create_all()

    # 终端脚本工具加载配置
    manage.app = app
    # 自动注册自定义命令
    load_commands(manage) # 导入默认的那个命令文件
    return manage

项目重启,发现flask根本不知道我们已经声明上面的模型,所以根本没有创建数据表,这个原因是,我们模型写在了蓝图目录下,我们需要把模型导入到要么和蓝图对象一起,要么和app实例对象一起。因为模型是被蓝图保存的,所以我们可以在蓝图对象创建中,导入模型,让flask识别到。

application/utils/blueprint.py,代码:

from flask import Blueprint
from importlib import import_module
def register_blueprint(app):
    """
    自动注册蓝图
    :param app: 当前flask的app实例对象
    :return:
    """
    # 从配置文件中读取需要注册到项目中的蓝图路径信息
    blueprint_path_list = app.config.get("INSTALL_BLUEPRINT")
    # 从配置文件中读取总路由模块
    app_urls_path = app.config.get("URL_PATH")
    # 总路由模块
    app_urls_module = import_module(app_urls_path)
    # 总路由列表
    app_urlpatterns = app_urls_module.urlpatterns

    # 遍历蓝图路径列表,对每一个蓝图进行初始化
    for blueprint_path in blueprint_path_list:
        # 获取蓝图路径中最后一段的包名作为蓝图的名称
        blueprint_name = blueprint_path.split(".")[-1]
        # 创建蓝图对象
        blueprint = Blueprint(blueprint_name,blueprint_path)

        # 蓝图路由的前缀
        url_prefix = ""
        # 蓝图下的子路由列表
        urlpatterns = []

        # 获取蓝图的父级目录
        blueprint_father_path = ".".join( blueprint_path.split(".")[:-1] )
        for item in app_urlpatterns:
            if blueprint_name in item["blueprint_url_subffix"]:
                # 导入蓝图下的子路由模块
                urls_module = import_module(blueprint_father_path +"."+ item["blueprint_url_subffix"])
                # 获取urls模块下路由列表urlpatterns
                urlpatterns = urls_module.urlpatterns
                # 提取蓝图路由的前缀
                url_prefix = item["url_prefix"]
                # 从总路由中查到当前蓝图对象的前缀就不要继续往下遍历了
                break

        # 把urlpatterns的每一个路由信息添加注册到蓝图对象里面
        for url in urlpatterns:
            blueprint.add_url_rule(**url)

        # 导入模型
        import_module(blueprint_path+".models")

        # 把蓝图对象注册到app实例对象
        app.register_blueprint(blueprint,url_prefix=url_prefix)

def path(rule, view_func, **kwargs):
    """
    绑定url地址和视图的映射关系
    :param rule: 路由url
    :param view_func: 视图
    :param kwargs: 其他参数,如: methods=["POST"]
    :return:
    """
    return {"rule":rule,"view_func":view_func,**kwargs}

def include(url_prefix, blueprint_url_subffix):
    """
    绑定蓝图和路由前缀的关系
    :param url_prefix: 路由前缀
    :param blueprint_url_subffix: 蓝图名称,
           格式:蓝图包名.路由模块名
           例如:蓝图目录是home, 路由模块名是urls,则参数:home.urls
    :return:
    """
    return {"url_prefix": url_prefix, "blueprint_url_subffix": blueprint_url_subffix }

项目能自动加载总路由也能加载蓝图下的子路由和视图模型以后的项目目录结构,如下:

项目根目录/
├── application/            # 项目主要逻辑代码保存目录
|   ├── settings/           # 项目配置存储目录
│   │   ├ __init__.py       # 项目默认初始化配置文件
│   │   ├ dev.py            # 开发阶段的配置文件
│   │   └ prod.py           # 生产阶段的配置文件
│   ├── __init__.py         # 项目初始化全局引导文件
|   ├── utils/              # 项目工具类库目录
│   │   ├ blueprint.py      # 蓝图注册相关的函数
│   │   ├ commands.py       # 自定义命令和加载命令的相关函数
│   │   ├ config.py         # 项目配置加载的辅助函数
│   │   ├ session.py        # 项目存储session相关的函数
│   │   └ logger.py         # 日志模块
│   ├── apps/               # 保存项目中所有蓝图的存储目录
│   │   ├── home            # 蓝图目录【这里是举例而已】
│   │   │   ├── __init__.py # 蓝图的初始化文件
│   │   │   ├── urls.py     # 蓝图的子路由文件
│   │   │   ├── models.py   # 蓝图的模型文件
│   │   │   └── views.py    # 蓝图的视图文件
│   │   ├── __init__.py
│   └── urls.py              # 总路由
├── manage.py               # 项目的终端管理脚本文件
原文地址:https://www.cnblogs.com/hsqKTm/p/14872724.html