Flask_03

Flask_03

一、蓝图

1、简介

一个Web项目,会有很多个模块,如用户管理、部门管理、账号管理等模块 ,如果把所有的这些模块都放在一个app.py文件之中,那么最后app.py文件必然臃肿不堪,并且极难维护 。因此flask中便有了blueprint的概念。

在Django中使用app来对项目进行模块化管理,解耦合,利于项目管理和维护

2、如何搭建项目结构

在这里插入图片描述
在这里插入图片描述
❶ 在project的根目录下创建包app,容纳各个项目模块(admin/employee)+static+templates

❷ 每个模块定义自己的蓝图,将项目的不同模块分在不同的package中(admin/employee)

❸ 在app包的init文件中初始化Flask环境 : 如Flask对象和SQLAlchemy对象和Session对象

❹ app目录成为template和static的root_path,所以将static和templates目录放在app目录下

❺ models.py依然作为项目的模型文件,也放在app目录下

❻ config.py作为整个project的配置文件,存放配置信息:mysql连接参数,session配置参数等

❼ run.py作为启动文件,负责:app.run()

#-- app.py --#
from app import app

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()
#-- config.py --#
class Config:
    SQLALCHEMY_DATABASE_URI = "mysql://root:123456@localhost/flask_db"
    SQLALCHEMY_TRACK_MODIFICATIONS = False
#-- app - init.py --#
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

from config import Config

app = Flask(__name__)

app.config.from_object(Config)

db = SQLAlchemy(app)
3、蓝图的使用
3.1 view中声明

在每个模块中,定义自己的蓝图

from flask import Blueprint, render_template
#                      蓝图名   模块名   前缀
admin_blue = Blueprint("admin",__name__,url_prefix='/admin')   # 声明蓝图
	

@admin_blue.route("/login/")      # 使用蓝图对象配置路由  不再使用app.route
def login():
    return render_template("admin/login.html")


@admin_blue.route("/login_logic/",methods=['post'])
def login_logic():
    return ""
3.2 注册蓝图

在应用程序的初始化文件中,加入以下代码:

# -- app/__init__.py --#
...
...
from app.admin.views import admin_blue    # 引入各个模块中的蓝图
from app.emp.views import emp_blue
app.register_blueprint(admin_blue)    # 注册蓝图
app.register_blueprint(emp_blue)

二、状态保持

1、Cookie
1.1 简介

Http协议是一种无状态的协议,什么叫无状态呢,就是本次的客户端请求不会保留上一次客户端请求的状态,简单点说就是这样会要求我们每次在浏览器中点开一个网站的链接都会输一次账户和密码。cookie就是用来解决这个问题的。

1.2 Cookie的使用
  • 在服务器生成cookie,随着响应到达浏览器随之存储在浏览器的小段数据
  • 保持数据,使得数据在一段时间之内,可以反复使用**(记住我)**
  • cookie会随着后续的每一个请求,再次回到服务器
  • 向服务器发送请求时,会自动携带来自该服务器的所有cookie
@app.route("/cook/")
def testcookie(): #写
    response = make_response(render_template("xxx.html")) # 获得response
    # response = make_response("Hello World~~") # 获得response
    # 设置cookie,如果不设置max_age,则为会话cookie
    response.set_cookie("name","Mr_lee",max_age=60*10) #设置一个10min的cookie
    return response

@app.route("/cook2/")
def testcookie2(): #读
    print(request.cookies.get("name")) #获取cookie
    return "aaa"

@app.route("/cook3/")
def testcookie3():#删
    response = make_response("hello") #获得response
    # response.delete_cookie('name') #删除cookie
    response.set_cookie("name",max_age=0) #删除cookie
    return response
2、Session

Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。

2.1 客户端session

flask默认的session处理是将session存在cookie中:“客户端session”。

import os
os.urandom(20) # 获得一个随机的key
from flask import session
app.secret_key=b'\x19M\xf1\xb2<&\xe2\x16l\x81\xa7G\xe2\xf2"\x82\xe2 d,' # 设置加密cookie的密钥
...

@app.route('/test/<name>')
def hello_world(name):
    session['name']=name # 数据会加密后存在客户端的cookie中
    
    return 'Hello World!'
2.2 服务器session

(1)准备工作

安装flask扩展 pip install flask-session 帮助实现server-session(mysql、redis等)

(2)session-mysql

  • mysql配置
from flask_session import Session
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:222222@localhost/test"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"]=True #避免警告信息
db = SQLAlchemy(app) #构建SQLAlchemy对象
  • session配置
app.config["SESSION_TYPE"]="sqlalchemy" #session存储类型
app.config["SESSION_SQLALCHEMY"]=db #session存储时,可以使用的SQLAlchemy对象
app.config["SESSION_SQLALCHEMY_TABLE"]='sessions125' #存储session数据的数据表
# app.config["SESSION_PERMANENT"]=True # 不设置时,默认为True为一个月 

# 如果会话一直没有活动,30分钟sesison失效,仅适用于SESSION_PERMANENT=True时
# app.config["PERMANENT_SESSION_LIFETIME"]=timedelta(60*30)

app.config["SESSION_USE_SIGNER"]=False #是否需要加密cookie
app.config["SESSION_KEY_PREFIX"]="xxx:" #sessionID的前缀(sessionid会存入数据库中会追加一个prefix)

Session(app) #构建Session,则之后,在项目中可以使用session了

def hello_world(name):
    session['name']="Mr_lee" #数据存入数据库
    return 'Hello World!'

def hello_world2(name):
    session['name'] #从session取数据
    return 'Hello World!'

注意,在使用session前,需要做一次 db.create_all() 保证sessions表被创建(ops:也可以自己手动建表)

from app import db
db.create_all()

(3)session-redis

app.config['SESSION_TYPE'] = 'redis'  # session类型为redis
app.config['SESSION_PERMANENT'] = False  # 关闭浏览器session就失效。

# 如果会话一直没有活动,30分钟sesison失效

# 在redis的解决方案中SESSION_PERMANENT=False和PERMANENT_SESSION_LIFETIME可以共存

app.config["PERMANENT_SESSION_LIFETIME"]=60*30
app.config['SESSION_USE_SIGNER'] = False  # 是否对发送到浏览器上session的cookie值进行加密
app.config['SESSION_KEY_PREFIX'] = 'Mr_lee:'  # 保存到session中的值的前缀

# 用于连接redis的配置  (ops: pip install redis)

app.config['SESSION_REDIS'] = redis.Redis(host='192.168.180.131', port='7000',db=1)  

Session(app) #构建Session

def hello_world(name):
    session['name']="Mr_lee" #数据存入redis
    return 'Hello World!'

三、静态资源

flask静态资源根目录是 Flask初始化的目录下的static目录:

<img src="/static/hello/app.png"/>

# /static/hello/app.png

<img src="{{ url_for('static',filename='hello/app.png')}}"/>

# 如下view时flask内置的veiw:用于反向解析到静态资源的路径

@app.rout("/static/<filename>")
def static(filename):
    ....

四、响应Json

import json
@emp.route("/all",methods=['get'])
def query_all():
    users = User.query.all()
    def xx(a):
        if isinstance(a,User):
            return {"id":a.id,"age":a.age}
    return  json.dumps(users,default=xx)  # content-type = "text/html"
    或者
    #response = make_response(json.dumps(users,default=xx))
    #response.headers['content-type'] = 'application/json;charset=utf-8'
    #return response
    
$.ajax({
    type:"get/post",
    url:".....",
    data:"xxx=xxx&xxx=xxx",
    #dataType:"json",  #如果响应头中有 content-type="application/json",则此参数可省略
    success:function(a){...} #a==xhr.responseText 或 解析后javascript对象
})
from flask import jsonify  #(了解)
@emp.route("/all",methods=['get'])
def query_all():
    # {"id":1,"name":"zhj"} ,自动设置了响应头:content-type=application/json
    return jsonify(id=1,name="Mr_lee") 
    #return jsonify({"id":1,"name":"Mr_lee"})
<think>好的,用户遇到了一个APScheduler任务在执行时抛出的异常,错误信息是“RuntimeError: Working outside of application context.”。我需要分析可能的原因和解决方法。 首先,错误信息明确指出是在应用上下文之外操作。用户的任务函数`calculate_overdue`中使用了Flask-SQLAlchemy的`BorrowRecords.query.all()`,而Flask-SQLAlchemy需要在应用上下文中才能访问数据库会话。问题可能出现在任务调度时没有正确绑定应用上下文。 接下来,我需要回忆Flask的应用上下文机制。当使用`app.app_context()`来包裹需要访问数据库的代码时,确保数据库操作在正确的上下文中执行。而用户的任务是在APScheduler中执行的,这可能是在一个独立的线程或进程中,此时Flask的应用上下文可能没有被自动激活。 可能的错误原因包括: 1. 任务调度器的初始化没有与Flask应用关联,导致任务执行时没有应用上下文。 2. 在定义任务函数时,没有手动创建应用上下文,导致数据库查询无法访问会话。 3. 应用实例在任务执行时尚未正确初始化或被正确引用。 接下来,我需要验证用户的代码结构。用户可能在创建APScheduler任务时,直接引用了模型类(如`BorrowRecords`),但在任务执行时,应用上下文不存在。例如,任务函数可能没有包裹在`with app.app_context():`中。 解决方法可能包括: - 在任务函数内部手动创建应用上下文。 - 确保调度器在应用初始化之后才启动,并且正确传递应用实例。 - 使用装饰器或上下文管理器来确保每次任务执行时都有应用上下文。 此外,还需要注意Flask应用的工厂模式是否影响了上下文的创建。如果应用是使用工厂函数动态创建的,确保在任务中正确获取应用实例。 最后,需要检查是否有其他资源(如数据库连接、配置)依赖于应用上下文,导致在任务中无法访问。确保所有需要上下文的操作都在正确的上下文中执行。</think>--- ### **错误原因分析** 该错误的核心原因是:**在APScheduler后台任务中直接调用了Flask-SQLAlchemy的数据库查询,但没有激活Flask应用上下文(Application Context)**。以下是详细分析: --- ### 1. **错误本质** - **关键报错**: `RuntimeError: Working outside of application context.` `This typically means that you attempted to use functionality that needed the current application.` - **问题根源**: Flask-SQLAlchemy的数据库操作(如`BorrowRecords.query.all()`)**必须在Flask应用上下文中执行**,而APScheduler任务默认运行在独立线程/进程中,无法自动获取上下文。 --- ### 2. **具体场景复现** 假设你的代码如下: ```python # scheduler.py from apscheduler.schedulers.background import BackgroundScheduler from models import BorrowRecords # 假设从模型导入 def calculate_overdue(): # 错误:直接调用查询,无应用上下文 records = BorrowRecords.query.all() # ❌ 引发RuntimeError scheduler = BackgroundScheduler() scheduler.add_job(calculate_overdue, 'cron', minute=57) scheduler.start() ``` --- ### 3. **解决方案** #### **方法1:在任务函数中手动激活应用上下文** ```python from flask import current_app def calculate_overdue(): # 手动绑定应用上下文 with current_app.app_context(): # ✅ records = BorrowRecords.query.all() # 后续数据库操作... ``` #### **方法2:在调度器初始化时传递应用对象** ```python # 初始化Flask应用时绑定调度器 def create_app(): app = Flask(__name__) app.config.from_object(Config) db.init_app(app) # 初始化调度器 scheduler = BackgroundScheduler() scheduler.add_job(calculate_overdue, 'cron', minute=57) scheduler.start() return app # 任务函数需接收app参数 def calculate_overdue(): with app.app_context(): # ✅ records = BorrowRecords.query.all() ``` #### **方法3(推荐):使用工厂模式延迟初始化** ```python # 延迟初始化APScheduler scheduler = BackgroundScheduler() def create_app(): app = Flask(__name__) app.config.from_object(Config) db.init_app(app) # 将调度器绑定到应用 scheduler.init_app(app) # 假设自定义扩展 scheduler.start() return app # 任务函数通过current_app访问上下文 def calculate_overdue(): with current_app.app_context(): # ✅ records = BorrowRecords.query.all() ``` --- ### 4. **根本原因总结** - **Flask上下文机制**: Flask的请求处理、数据库连接等依赖`应用上下文`(`app_context`)和`请求上下文`(`request_context`)。后台任务没有HTTP请求触发,因此需手动激活。 - **APScheduler与Flask的集成**: APScheduler任务线程默认不属于Flask的生命周期,需显式传递应用实例或通过`current_app`代理访问。 --- ### 5. **如何避免类似问题** 1. **遵循Flask上下文规则**: 任何在非请求处理流程中(如后台任务、CLI命令)的数据库操作,必须包裹在`with app.app_context()`中。 2. **依赖注入模式**: 在任务函数中通过`current_app`获取数据库对象,而非直接导入模型: ```python from flask import current_app def calculate_overdue(): with current_app.app_context(): records = current_app.extensions['sqlalchemy'].db.session.query(BorrowRecords).all() ``` 3. **使用专用扩展库**: 考虑使用`Flask-APScheduler`等集成库,自动处理上下文: ```python from flask_apscheduler import APScheduler scheduler = APScheduler() scheduler.init_app(app) scheduler.start() ``` --- ### 6. **扩展:调试建议** - **检查上下文状态**: 在任务函数中添加调试代码,确认上下文是否激活: ```python from flask import current_app def calculate_overdue(): print("Current app context:", current_app._get_current_object()) # 如果报错,则上下文未激活 ``` - **确保应用初始化完成**: APScheduler的启动应在Flask应用完全初始化之后(通常在`create_app()`或`__init__.py`中完成)。 --- 通过上述方法,你可以确保APScheduler任务在正确的Flask应用上下文中运行,从而避免`RuntimeError`异常。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值