在前两篇教程中,我们从零开始搭建了 Flask 开发环境,掌握了路由、视图、模板、表单和数据库操作等核心技能。现在,你已经能够独立开发一个功能完整的 Web 应用。但一个成熟的应用不仅需要业务逻辑正确,还需要良好的可维护性、可扩展性以及生产环境的稳定性。
本篇将带你进入 Flask 的高级领域,内容包括:
在 Web 开发中,中间件指的是在请求到达视图函数之前或响应返回给客户端之后执行的一些代码。Flask 提供了多种装饰器来实现这种“钩子”功能,同时也支持标准的 WSGI 中间件。
Flask 内置了以下常用钩子:
@app.before_request@app.after_request@app.teardown_request@app.errorhandler示例:记录请求耗时
import time
from flask import g
@app.before_request
defstart_timer():
g.start = time.time()
@app.after_request
deflog_request(response):
elapsed = time.time()- g.start
app.logger.info(f'{request.method}{request.path} took {elapsed:.4f}s')
return response
示例:权限验证
@app.before_request
defcheck_auth():
if request.endpoint and request.endpoint notin['login','static']:
ifnot session.get('user_id'):
return redirect(url_for('auth.login'))
Flask 应用本身是一个 WSGI 应用,因此可以包裹在任意 WSGI 中间件中。例如,使用 werkzeug.middleware.profiler.ProfilerMiddleware 进行性能分析:
from werkzeug.middleware.profiler import ProfilerMiddleware
app.wsgi_app = ProfilerMiddleware(app.wsgi_app, restrictions=[30])
常见的 WSGI 中间件包括:ProxyFix(处理反向代理头)、DispatcherMiddleware(多应用分发)等。
Flask 的“微型”哲学体现在核心只提供基础功能,而扩展则按需选择。以下是一些最常用、最成熟的扩展。
Flask-Login 管理用户会话,提供 login_user、logout_user、@login_required 等便捷功能。
安装:
pip install flask-login
初始化:
from flask_login import LoginManager
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view ='auth.login'# 未认证时重定向
用户模型:
from flask_login import UserMixin
classUser(UserMixin, db.Model):
id= db.Column(db.Integer, primary_key=True)
# ... 其他字段
加载用户回调:
@login_manager.user_loader
defload_user(user_id):
return User.query.get(int(user_id))
在视图中使用:
from flask_login import login_user, logout_user, login_required, current_user
@app.route('/login', methods=['POST'])
deflogin():
user = User.query.filter_by(email=form.email.data).first()
if user and check_password_hash(user.password_hash, form.password.data):
login_user(user)
return redirect(url_for('index'))
# ...
@app.route('/logout')
@login_required
deflogout():
logout_user()
return redirect(url_for('index'))
在模板中,可以通过 current_user 访问当前用户信息。
安装:
pip install flask-mail
配置:
app.config['MAIL_SERVER']='smtp.gmail.com'
app.config['MAIL_PORT']=587
app.config['MAIL_USE_TLS']=True
app.config['MAIL_USERNAME']='your-email@gmail.com'
app.config['MAIL_PASSWORD']='your-password'
发送邮件:
from flask_mail import Mail, Message
mail = Mail(app)
msg = Message('Hello', sender='noreply@example.com', recipients=['user@example.com'])
msg.body ='This is a test email.'
mail.send(msg)
安装:
pip install flask-caching
配置:
app.config['CACHE_TYPE']='SimpleCache'# 开发环境使用内存缓存
# 生产环境推荐 Redis: 'RedisCache'
使用:
from flask_caching import Cache
cache = Cache(app)
@cache.cached(timeout=300)
defget_expensive_data():
# 耗时操作
return result
在视图中也可以缓存整个响应:
@app.route('/')
@cache.cached(timeout=60)
defindex():
return render_template('index.html')
安装:
pip install flask-rq2
初始化:
from flask_rq2 import RQ
rq = RQ(app)
定义任务:
@rq.job
defsend_welcome_email(user_id):
# 发送邮件的耗时逻辑
pass
调用任务:
send_welcome_email.delay(user.id)
flask 命令)将 Flask 应用部署到生产环境时,需要考虑性能、安全、可靠性。以下介绍几种主流方式。
pip install gunicorn
假设入口文件是 run.py,其中包含 app 对象:
gunicorn -w4-b127.0.0.1:8000 run:app
-w 4-b安装 Nginx:
sudoaptinstall nginx
创建站点配置 /etc/nginx/sites-available/myapp:
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /static {
alias /path/to/your/project/app/static;
expires 30d;
}
}
启用配置并重启:
sudoln-s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled
sudo nginx -t
sudo systemctl restart nginx
安装 Supervisor:
sudoaptinstall supervisor
创建配置文件 /etc/supervisor/conf.d/myapp.conf:
[program:myapp]
command=/path/to/venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 run:app
directory=/path/to/project
user=www-data
autostart=true
autorestart=true
stdout_logfile=/var/log/myapp.log
stderr_logfile=/var/log/myapp.err
启动并管理:
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start myapp
使用 Docker 可以简化环境一致性。创建 Dockerfile:
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "-b", "0.0.0.0:5000", "run:app"]
构建镜像并运行:
docker build -t myapp .
docker run -d-p5000:5000 --name myapp myapp
配合 Docker Compose 可以整合数据库等服务。
Procfile:web: gunicorn run:appheroku local 测试git push heroku mainapp.run(debug=False) 或通过环境变量 FLASK_ENV=production。import os
app.config['SECRET_KEY']= os.environ.get('SECRET_KEY')
SQLALCHEMY_POOL_SIZE。下面我们整合前两篇以及本篇所学,构建一个简易博客系统。功能包括:
blog/
├── app/
│ ├── __init__.py # 应用工厂
│ ├── models.py # 数据库模型
│ ├── forms.py # 表单类
│ ├── auth/
│ │ ├── __init__.py
│ │ └── routes.py # 认证相关路由
│ ├── main/
│ │ ├── __init__.py
│ │ └── routes.py # 主要业务路由
│ ├── templates/ # 模板文件
│ └── static/ # 静态文件
├── config.py # 配置类
├── run.py # 启动入口
├── requirements.txt
└── .env # 环境变量(不提交到版本库)
config.pyimport os
from dotenv import load_dotenv
load_dotenv()
classConfig:
SECRET_KEY = os.environ.get('SECRET_KEY')or'dev-key'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL')or'sqlite:///blog.db'
SQLALCHEMY_TRACK_MODIFICATIONS =False
MAIL_SERVER = os.environ.get('MAIL_SERVER')
MAIL_PORT =int(os.environ.get('MAIL_PORT')or25)
MAIL_USE_TLS = os.environ.get('MAIL_USE_TLS','true').lower()in['true','on','1']
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
app/__init__.pyfrom flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from flask_mail import Mail
from flask_caching import Cache
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
mail = Mail()
cache = Cache()
defcreate_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
mail.init_app(app)
cache.init_app(app)
login_manager.login_view ='auth.login'
login_manager.login_message ='请先登录'
from app.auth import auth_bp
from app.main import main_bp
app.register_blueprint(auth_bp)
app.register_blueprint(main_bp)
return app
app/models.pyfrom app import db
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
classUser(UserMixin, db.Model):
id= db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', backref='author', lazy='dynamic')
defset_password(self, password):
self.password_hash = generate_password_hash(password)
defcheck_password(self, password):
return check_password_hash(self.password_hash, password)
classPost(db.Model):
id= db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
body = db.Column(db.Text, nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
app/forms.pyfrom flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, TextAreaField
from wtforms.validators import DataRequired, Email, Length, EqualTo
classRegistrationForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired(), Length(min=2,max=64)])
email = StringField('邮箱', validators=[DataRequired(), Email()])
password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
confirm = PasswordField('确认密码', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('注册')
classLoginForm(FlaskForm):
email = StringField('邮箱', validators=[DataRequired(), Email()])
password = PasswordField('密码', validators=[DataRequired()])
submit = SubmitField('登录')
classPostForm(FlaskForm):
title = StringField('标题', validators=[DataRequired()])
body = TextAreaField('内容', validators=[DataRequired()])
submit = SubmitField('发布')
app/auth/routes.pyfrom flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, current_user
from app import db, mail
from app.forms import RegistrationForm, LoginForm
from app.models import User
from flask_mail import Message
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/register', methods=['GET','POST'])
defregister():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
# 发送欢迎邮件
msg = Message('欢迎注册博客', recipients=[user.email])
msg.body =f'你好 {user.username},感谢注册!'
mail.send(msg)
flash('注册成功,请登录','success')
return redirect(url_for('auth.login'))
return render_template('auth/register.html', form=form)
@auth_bp.route('/login', methods=['GET','POST'])
deflogin():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user and user.check_password(form.password.data):
login_user(user)
next_page = request.args.get('next')
return redirect(next_page)if next_page else redirect(url_for('main.index'))
else:
flash('邮箱或密码错误','danger')
return render_template('auth/login.html', form=form)
@auth_bp.route('/logout')
deflogout():
logout_user()
return redirect(url_for('main.index'))
app/main/routes.pyfrom flask import Blueprint, render_template, redirect, url_for, flash, abort
from flask_login import login_required, current_user
from app import db, cache
from app.forms import PostForm
from app.models import Post
main_bp = Blueprint('main', __name__)
@main_bp.route('/')
@cache.cached(timeout=60)
defindex():
page = request.args.get('page',1,type=int)
posts = Post.query.order_by(Post.created_at.desc()).paginate(page=page, per_page=10)
return render_template('main/index.html', posts=posts)
@main_bp.route('/post/<int:id>')
defpost_detail(id):
post = Post.query.get_or_404(id)
return render_template('main/post_detail.html', post=post)
@main_bp.route('/create', methods=['GET','POST'])
@login_required
defcreate_post():
form = PostForm()
if form.validate_on_submit():
post = Post(title=form.title.data, body=form.body.data, author=current_user)
db.session.add(post)
db.session.commit()
flash('文章发布成功','success')
return redirect(url_for('main.index'))
return render_template('main/create_post.html', form=form)
@main_bp.route('/edit/<int:id>', methods=['GET','POST'])
@login_required
defedit_post(id):
post = Post.query.get_or_404(id)
if post.author != current_user:
abort(403)
form = PostForm()
if form.validate_on_submit():
post.title = form.title.data
post.body = form.body.data
db.session.commit()
flash('文章已更新','success')
return redirect(url_for('main.post_detail',id=post.id))
elif request.method =='GET':
form.title.data = post.title
form.body.data = post.body
return render_template('main/edit_post.html', form=form, post=post)
@main_bp.route('/delete/<int:id>', methods=['POST'])
@login_required
defdelete_post(id):
post = Post.query.get_or_404(id)
if post.author != current_user:
abort(403)
db.session.delete(post)
db.session.commit()
flash('文章已删除','success')
return redirect(url_for('main.index'))
pip install -r requirements.txtflask db init && flask db migrate && flask db upgrade.env 中):FLASK_APP=run.py
SECRET_KEY=your-secret-key
flask runhttp://127.0.0.1:5000 体验功能。经过三篇教程的学习,你已经从零开始构建了一个完整的 Flask 应用,并掌握了以下核心技能:
Flask 的生态系统非常庞大,你可以继续探索以下方向:
记住,实践是最好的老师。尝试自己动手扩展这个博客系统,比如添加评论功能、标签分类、用户头像上传等,在解决问题的过程中你会获得更深刻的理解。
资源推荐:
欢迎关注公众号,感谢对文章的点赞分享喜欢,冉成未来会持续更新前后端开发技术、人工智能技术、IT相关的文章及学习经验、知识分享,未来虽然充满着不确定性,但我们可以不断提升自己,不断为未来做准备,让未来更好的自己成就更美好的未来。
Python基础系列 | Python之PyQt5基础知识(五)
Python基础系列 | Python之PyQt5基础知识(四)
Python基础系列 | Python之PyQt5基础知识(三)
Python基础系列 | Python之PyQt5基础知识(二)
Python基础系列 | Python之PyQt5基础知识(一)