一、准备工作
- 经过分析,项目需要用f以下技术
- lask框架前后端分离
- mysql数据库
- redis数据库
- 工程日志
- 前端静态文件
- …
1.创建虚拟环境
pipenv shell
2、安装Flask框架及相关包
- 需要用到以下安装包
- Flask框架
- flask-sqlalchemy 数据库连接
- 2.3 redis 数据库连接
- 2.4 flask-wtf :表单处理
- 2.5 flask-session :session 和 cookies应用
pip install flask
pip install flask-sqlalchemy
pip install redis
pip install flask-wtf
pip install flask_session
3、创建Flask工程
二、配置环境
-
根据项目分析,项目需要准备以下内容
-
- flask框架
-
2、mysql
-
3、redis
-
4、session:参数SESSION_TYPE和SESSION_REDIS保存到redis数据库中,并对session签名
-
form验证表单需要加密
-
flask框架入口文件一般用manage.py,现在把所有可能用的到东西都写到一个入口文件,再进行拆分
1、入口文件准备
- 入口文件需要准备以下内容:
1、配置参数:定义成一个配置类,保存配置参数 - mysql数据库配置参数
- 安装并启动mysql数据库,并定义相关配置参数
- redis数据库配置参数:注册时可能会用到图片和短信验证,保存到redis数据库中
- 安装redis数据库
- session保存redis数据库中时,需要配置
2、加载配置文件
3、创建mysql数据库
4、创建redis数据库
5、session绑定app
6、form验证表单 wtf csrf
# manage.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_session import Session
import redis
from flask_wtf import CSRFProtect
app=Flask(__name__)
class Config():
"""配置信息"""
SECRET_KEY="ASDFLKASDKJF%AKLSDXO$IPPIUF" # 字符串随便设,用于session签名使用
# mysql数据库配置
# 连接数据库
MYSQL_HOSTNAME = '127.0.0.1'
# 数据库
MYSQL_DATABASE = 'testdb'
# 端口
MYSQL_PORT = 3306
# 用户名和密码
MYSQL_USERNAME = 'root'
MYSQL_PASSWORD = 'root'
# mysql+pymysql://用记名:密码@址址:端口/数据库
DB_URL = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(MYSQL_USERNAME, MYSQL_PASSWORD, MYSQL_HOSTNAME, MYSQL_PORT, MYSQL_DATABASE)
# redis 配置
REDIS_HOSTNAME="127.0.0.1"
REDIS_PORT=6379
# flask-session
SESSION_TYPE = 'redis'
SESSION_REDIS = redis.Redis(host=REDIS_HOSTNAME, port=REDIS_PORT)
SESSION_USE_SIGNER = True # session 签名
PERMANENT_SESSION_LIFETIME = 86400 # session生命周期,单位是秒,1天=60*60*24
# 加载配置参数
app.config.from_object(Config)
# 建立mysql数据库对象
db=SQLAlchemy(app)
# 创建Redis
redis_store = redis.Redis(host=Config.REDIS_HOSTNAME, port=Config.REDIS_PORT)
# session
Session(app)
# post请求 wtf csrf
CSRFProtect(app)
app.route("/")
def index():
return "hello world!"
if __name__=="__main__":
app.run()
2、入口文件拆分
2.1 拆分配置文件
- 项目配置文件一般都是单独的文件,故把 配置类拆分到配置文件中
- 项目一般都会分为开发环境和生产环境,所以再创建两个类,用于区分开发和生产环境,有些参数都是共用,所以两个类共用公共部分基类
- 加载配置文件时,怎么区分哪个是生产环境哪个是开发环境,有两种方式,一般对app定义采用工厂模式,传递参数,在函数内进行区分
- 方式一:用if判断进行区分
- 方式二:用字典引用
2.1.1 拆分后的配置文件
import redis
class Config():
"""配置信息"""
SECRET_KEY = 'ASDAXCWE5ERTFG%%DAS34' # 这个字符串自己随便写,用于session加密
# mysql配置
MYSQL_HOSTNAME = '127.0.0.1'
MYSQL_DATABASE = 'testdb'
MYSQL_PORT = 3306
MYSQL_USERNAME = 'root'
MYSQL_PASSWORD = 'root'
# mysql+pymysql://用记名:密码@址址:端口/数据库
DB_URL = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(MYSQL_USERNAME, MYSQL_PASSWORD, MYSQL_HOSTNAME, MYSQL_PORT,MYSQL_DATABASE)
# redis 配置
REDIS_HOSTNAME = "127.0.0.1"
REDIS_PORT = 6379
# flask-session session保存到redis中
SESSION_TYPE = 'redis'
SESSION_REDIS = redis.Redis(host=REDIS_HOSTNAME, port=REDIS_PORT)
SESSION_USE_SIGNER = True # 对session签名,即加密
PERMANENT_SESSION_LIFETIME = 86400 # session生命周期, 单位是秒, 60*60*24=86400
class DevConfig(Config):
"""开发环境"""
DEBUG = True
class ProConfig(Config):
"""生产环境"""
config_map = {
'dev': DevConfig,
'pro': ProConfig
}
2.1.2 对manage的改造
# manage.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_session import Session
from flask_wtf import CSRFProtect
from config import config_map
# 工厂模式
def create_app(config_name):
app = Flask(__name__)
# if config_name == "dev":
# from config import DevConfig
# app.config.from_object(DevConfig)
# else:
# from config import ProConfig
# app.config.from_object(ProConfig)
config_class = config_map.get(config_name) # 获取配置文件,用字典get方法可防止config_name不存时报错
app.config.from_object(config_class) # 加载配置文件
return app
app=create_app("dev")
# 建立mysql数据库对象
db=SQLAlchemy(app)
# 创建Redis
redis_store = redis.Redis(host=Config.REDIS_HOSTNAME, port=Config.REDIS_PORT)
# session
Session(app)
# post请求 wtf csrf
CSRFProtect(app)
app.route("/")
def index():
return "hello world!"
if __name__=="__main__":
app.run()
2.2 对应用进行分模块
- 将业务逻辑蓝图放到相应的包中:建api_1_0包,即V1.0蓝图包
- 建立静态文件目录: static
- 建立自已定义的工具包:utils
- 建立第三工具目录:libs
- 建立日志目录:logs
- 为进一步区分入口文件、公共文件及目录和业务逻辑目录,需要建立一个业务逻辑包lghome,将业务逻辑相关内容放到此包中,即将 api_1_0、static、utils、libs都移到此目录下
2.3 对入口文件进一步拆分
- 建立了业务逻辑包以后,入口文件中,业务逻辑相关的功能,都将移到相应的包中
- 1、工厂模式定义的create_app、数据库定义、session、csrf 都移到lghome包的__init__中
- 2、app路由移到api_1_0包中的一个文件里,以后再完善
- 3、简化manage入口文件
2.3.1 将业务逻辑相关的代码移到lghome包的__init__中
- 将相关代码移到__init__.py后,暂不处理报错,以后再优化
# ./lghome/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_session import Session
from flask_wtf import CSRFProtect
from config import config_map
# 工厂模式
def create_app(config_name):
app = Flask(__name__)
# if config_name == "dev":
# from config import DevConfig
# app.config.from_object(DevConfig)
# else:
# from config import ProConfig
# app.config.from_object(ProConfig)
config_class = config_map.get(config_name) # 获取配置文件,用字典get方法可防止config_name不存时报错
app.config.from_object(config_class) # 加载配置文件
return app
# 建立mysql数据库对象
db=SQLAlchemy(app)
# 创建Redis
redis_store = redis.Redis(host=Config.REDIS_HOSTNAME, port=Config.REDIS_PORT)
# session
Session(app)
# post请求 wtf csrf
CSRFProtect(app)
2.3.2 将路由代码暂时移到蓝图api_1_0包的demo.py中
# ./lghome/api_1_0/demo.py
# app.route("/")
# def index():
# return "hello world!"
2.3.3 优化入口文件manage.py
from lghome import create_app
app = create_app('dev')
if __name__ == '__main__':
app.run()
3、初步优化lghome包__init__.py
上面将代码移到__init__.py时,数据库定义、session、csrf 未处理
3.1 完善数据库定义
- 将 db和redis_store 定义放到 create_app函数前面,但不绑定,方便其它项目引用,当app初始化后,再进行绑定
# mysql数据库定义
db = SQLAlchemy()
# 创建Redis
redis_store = None
- app在create_app函数中定义,故db 和 redis_store放在create_app中绑定
- redis_store 定义为None ,所以绑定时重新赋值即可,即在函数内用global 重新赋值
# 使用app初始化db
db.init_app(app)
# redis配置
global redis_store
redis_store = redis.Redis(host=config_class.REDIS_HOST, port=config_class.REDIS_PORT)
3.2 完善session、csrf 定义
- session 和 csrf 无需单独定义,直接在create_app中定义即可
# session
Session(app)
# post请求 wtf csrf
CSRFProtect(app)
3.3 初步优化后的__ini__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_session import Session
from flask_wtf import CSRFProtect
from config import config_map
import redis
db = SQLAlchemy()
# 创建Redis
redis_store = None
# 工厂模式
def create_app(config_name):
app = Flask(__name__)
config_class = config_map.get(config_name)
app.config.from_object(config_class)
# 使用app初始化db
db.init_app(app)
# redis配置
global redis_store
redis_store = redis.Redis(host=config_class.REDIS_HOST, port=config_class.REDIS_PORT)
# session
Session(app)
# post请求 wtf csrf
CSRFProtect(app)
4、优化数据库参数设置
- 因数据库参数化时未定义 SQLALCHEMY_DATABASE_URI
- 未设置 SQLALCHEMY_TRACK_MODIFICATIONS
- 故修改配置文件即可
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(MYSQL_USERNAME, MYSQL_PASSWORD, MYSQL_HOSTNAME, MYSQL_PORT,MYSQL_DATABASE)
SQLALCHEMY_TRACK_MODIFICATIONS = False
完整代码如下:
import redis
class Config():
"""配置信息"""
SECRET_KEY = 'ASDAXCWE5ERTFG%%DAS34' # 这个字符串自己随便写,用于session加密
# mysql配置
MYSQL_HOSTNAME = '127.0.0.1'
MYSQL_DATABASE = 'testdb'
MYSQL_PORT = 3306
MYSQL_USERNAME = 'root'
MYSQL_PASSWORD = 'root'
# mysql+pymysql://用记名:密码@址址:端口/数据库
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://{}:{}@{}:{}/{}'.format(MYSQL_USERNAME, MYSQL_PASSWORD, MYSQL_HOSTNAME, MYSQL_PORT,MYSQL_DATABASE)
SQLALCHEMY_TRACK_MODIFICATIONS = False
# redis 配置
REDIS_HOSTNAME = "127.0.0.1"
REDIS_PORT = 6379
# flask-session session保存到redis中
SESSION_TYPE = 'redis'
SESSION_REDIS = redis.Redis(host=REDIS_HOSTNAME, port=REDIS_PORT)
SESSION_USE_SIGNER = True # 对session签名,即加密
PERMANENT_SESSION_LIFETIME = 86400 # 单位是秒,session生命周期, 60*60*24=86400
class DevConfig(Config):
"""开发环境"""
DEBUG = True
class ProConfig(Config):
"""生产环境"""
config_map = {
'dev': DevConfig,
'pro': ProConfig
}
5、入口文件中进行数据库迁移
- flask框架使用数据库,需要对数据进行迁移,并将app启动改为manage启动
-
- 数据库迁移时,注意导入db
from lghome import db
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
# 数据库迁移
manage = Manager(app)
Migrate(app, db)
manage.add_command('db', MigrateCommand)
- manage.py完善后代码
from lghome import create_app, db
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
app = create_app('dev')
# 数据库迁移
manage = Manager(app)
Migrate(app, db)
manage.add_command('db', MigrateCommand)
if __name__ == '__main__':
manage.run()
6、初步完善视图文件
6.1 定义视图文件
- 定义蓝图,并初步定义首页路由
-
- @app.route("/")改为 @api.route("/")
./lghome/api_1_0/demo.py
from flask import Blueprint
api = Blueprint('api_1_0', __name__, url_prefix='/api/v1.0')
@api.route("/")
def index():
return "index page"
6.2 将 蓝图定义移到api_1_0包的__init__.py中
- 因为蓝图定义,在整个包中都会使用,故需要移到__init__.py 中,方便引用
- 在视图文件中引用蓝图api
-
在__init__.py中将蓝图定义与路由关联起来(注意:如果不执行此步骤,系统不会报错,但无法正确显示页面)
# ./lghome/api_1_0/__init__.py
from flask import Blueprint
api = Blueprint('api_1_0', __name__, url_prefix='/api/v1.0')
from . import dmeo
6.3 将蓝图与app绑定
- 蓝图定义后,与app绑定后,才能影响前端的请求,即在create_app函数中将蓝图与app绑定
- app.register_blueprint(蓝图的实例名)
# ./lghome/__init__.py
...
def create_app(config_name)
... ...
# 注册蓝图
from lghome import api_1_0
app.register_blueprint(api_1_0.api)
7.到此为止,项目入口文件已拆分完成
- 运行系统即可访问: http://127.0.0.1/api/v1.0/index
运行结果如下:
8.可能遇到的坑
- 可能会出现循环导入,例:对db引用:
-
manage.py中需要导入db
-
lghome/init.py 中定义了db ,且引用了 lghome/api_1_0/init.py 中的api ,此时db 未创建
-
lghome/api_1_0/init.py中,引用了lghome/api_1_0/demo.py
-
lghome/api_1_0/demo.py 定义了路由,需要访问数据库,引了 db ,但db未创建,至此形成了循环引用。
-
解决办法:
-
- 在 lghome/init.py 中 工厂模式create_app函数内,注册蓝图时再引用api,如下图:
- 即:
- manage.py中需要导入db
- lghome/init.py 中定义了db ,必入时未执行create_app函数,就未引用了 lghome/api_1_0/init.py 中的api ,可顺利完成引用, 随后db 已创建,完成引用
- 当manage.py中调用create_app函数时,引用了 lghome/api_1_0/init.py 中的api
- lghome/api_1_0/init.py中,引用了lghome/api_1_0/demo.py
- lghome/api_1_0/demo.py 定义了路由,需要访问数据库,引了 db ,但db已创建,完美的避开了循环引用。
三、项目日志配置
- 项目框架搭建时,需要搭建日志记录功能模块,支持系统各模块记录日志使用
1、创建日志模块方法
# 日志
def setup_log():
# 设置日志的的登记 DEBUG调试级别
logging.basicConfig(level=logging.DEBUG)
# 创建日志记录器,设置日志的保存路径和每个日志的大小和日志的总大小
# log 100M log1 log
file_log_handler = RotatingFileHandler("logs/log.log", maxBytes=1024*1024*100,backupCount=100)
# 创建日志记录格式,日志等级,输出日志的文件名 行数 日志信息
formatter = logging.Formatter("%(levelname)s %(filename)s: %(lineno)d %(message)s")
# 为日志记录器设置记录格式
file_log_handler.setFormatter(formatter)
# 为全局的日志工具对象(flaks app使用的)加载日志记录器
logging.getLogger().addHandler(file_log_handler)
2、启动日志
- 在create_app()时,启动日志
3、记录日志
- 在需要记录日志的地方,调用logger即可
# 创建日志记录器
logger = logging.getLogger('django')
# 输出日志
logger.debug('测试logging模块debug')
logger.info('测试logging模块info')
logger.error('测试logging模块error')
4、日志更多操作详见《Flask中日志logging的使用》
四、通过蓝图方式调用静态html提供前端界面
-
本项目是前后端分离的开发模式,为开发调试方便,通过蓝图方式提供调用静页面实现前端界面
-
这里创建的蓝图与api_1_0的蓝图没有关系。
-
例本项目将:静态文件保存到lghome/static/目录下
-
可以这样访问:
-
http://127.0.0.1:5000/static/html/index.html
-
但直接将服务器的路径都暴露了
-
-
建议访问方式:
- 127.0.0.1:5000/ 访问首页
- 127.0.0.1:5000/index.html 访问首页
- 127.0.0.1:5000/register.html 访问注册
-
为发达到这个效果,需要进行路由转换,自定义一个路由转换器
1、自定义一个路由转换器
- 在lghome/utils下创建 路由转换器
from werkzeug.routing import BaseConverter
class ReConverter(BaseConverter):
def __init__(self,map,regex):
super().__init__(map)
self.regex=regex
2、将自定义的路由转换器添加到
- 路由转换器定义后,需要添加到flask框加中
- 在注册app 时,将自定义路由转换器添加到 app.url_map.converters字典中
app.url_map.converters["re"]=ReConverter
3、定义静态html蓝图及路由
- 在lghome下创建蓝图文件commons.py
- 定义蓝图
- 定义通用路由:根据自定路由转换器
- 静态html文件在static/home下面,少了一级目录,所以把home加上即可
- 如果没有输入页面(没有传递参数),应该访问主页,判断一下改成index.html
# /lghome/commons.py
from flask import Blueprint,current_app
html=Blueprint("web_html",__name__)
@html.route("/<re('.*'):html_file_name>")
def get_html(html_file_name):
# 访问首页
if not html_file_name:
html_file_name="index.html"
html_file_name = 'html/' + html_file_name
return current_app.send_static_file(html_file_name)
4、注册静态蓝图
- 在create_app函数中注册静态蓝图
# 注册静态蓝图
from lghome import web_html
app.register_blueprint(web_html.html)
五、数据库模型准备
- 数据库设计需要根据实际需求来设计
1、用户表user
- 通地注册界面分析,user表应该有:id、mobile、password
- 通过个人信息分析,user表应该还有:name、real_name、id_card、avater_url
2、房屋表house
- house_id 、user_id、title、price、area_id
- 房屋所在区域独立建表
- 房屋轮播图信息也独立建表
- 房屋标签信息也独立建表
3、区域表 area
- area_id、name
4、房屋轮播图 house_image
- url 、house_id
5、房屋标签信息 faciliy
- faciliy_id、name
6 定单表 order
- id 、user_id、house_id、create_time、start_time、end_time、price、status、days、amount、comment
六、数据库迁移
1、 db init
2、 db migrate
3、 db upgrade
4、检查
5、可能遇到的问题
5.1 执行db migrate报错,提示: ModuleNotFoundError:No module named ‘pymysql’
- 解决办法:pip install pymysql
5.2 执行db upgrade报错,提示:
- 解决办法:在蓝图进行引用(代码编写后,未在系统中进行引用)
七、注意事项:
1、 系统运行前,需要启动以下数据库,并保证参数正确:
- mysql
- redis