八、上下文管理
在 Flask 框架中,上下文管理是一项关键特性,它允许开发者在应用程序的不同部分之间共享数据,并且确保在请求处理期间相关的资源(如数据库连接)能够正确地初始化和清理。Flask 中有两种主要的上下文类型:应用上下文(app context
)和请求上下文(request context
)。
8.1 请求上下文
在 Flask 中,请求上下文是一个关键概念,它包含了处理请求所需的所有信息。当 Flask 接收到一个 HTTP 请求时,会创建一个请求上下文,这个上下文会一直存在,直到请求处理完成。请求上下文的存在使得在整个请求处理过程中,不同的函数和模块都能方便地访问与该请求相关的数据。它有两个核心对象request
对象、session
对象,前边已经介绍了request
对象,这里重点介绍下session
对象。
1.session
对象
session
对象是flask.sessions.Session
类的实例,用于在不同请求之间存储特定于用户会话的数据。这对于维护用户状态,如用户登录状态、购物车内容等功能至关重要。
session
对象本质上是一个字典,Flask 使用加密的Cookie
在客户端和服务器之间传递 session
数据。当客户端发起请求时,Flask 检查请求中的 session Cookie
。如果 Cookie
存在且签名验证通过,Flask 会将Cookie
中的数据加载到 session
对象中,这样在视图函数中就可以访问和修改这些数据。当请求结束时,session
对象中的任何更改都会被序列化并存储回 session Cookie
,发送给客户端。
在使用 session
之前,需要设置一个 SECRET_KEY
。这个密钥用于对 session
Cookie 进行加密和签名,确保数据的安全性。可以在 Flask 应用初始化时设置:
from flask import Flask, session
app = Flask(__name__)
app.secret_key = 'your_secret_key'
请务必将 'your_secret_key'
替换为一个强密钥。在生产环境中,建议从环境变量或配置文件中读取密钥,而不是硬编码。
from flask import Flask, session, request, redirect, url_for
app = Flask(__name__)
app.secret_key = 'your_secret_key'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form.get('username')
# 设置会话变量,将用户名存储在session中
session['username'] = username
return redirect(url_for('profile'))
return '''
<form method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<input type="submit" value="登录">
</form>
'''
@app.route('/profile')
def profile():
# 从session中获取用户名
username = session.get('username')
if username:
return f'欢迎,{username}!这是你的个人资料页面。'
return redirect(url_for('login'))
if __name__ == '__main__':
app.run(debug=True)
2. 请求上下文的生命周期
-
创建:当 Flask 应用接收到一个 HTTP 请求时,会自动创建请求上下文。请求上下文被压入一个栈(
_request_ctx_stack
)中,这个栈确保每个线程都有自己独立的上下文栈,保证多线程环境下请求处理的隔离性。 -
使用:在请求处理过程中,例如在视图函数、中间件等地方,都可以通过
request
和session
对象访问请求上下文的信息。 -
销毁:一旦请求处理完成,无论是正常结束还是发生异常,请求上下文都会从栈中弹出并销毁。这意味着
request
和session
对象中的数据在请求处理完成后将不再可用,除非将相关数据保存到其他地方(如数据库)。from flask import Flask, session, request, redirect, url_for app = Flask(__name__) app.secret_key = 'your_secret_key' # 创建 @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': username = request.form.get('username') session['username'] = username return redirect(url_for('profile')) return ''' <form method="post"> <label for="username">用户名:</label> <input type="text" id="username" name="username" required> <input type="submit" value="登录"> </form> ''' # 使用 @app.route('/profile') def profile(): username = session.get('username') if username: return f'欢迎,{username}!这是你的个人资料页面。' return redirect(url_for('login')) # 销毁 @app.route('/logout') def logout(): # 从session中移除用户名 session.pop('username', None) return redirect(url_for('login')) if __name__ == '__main__': app.run(debug=True)
3. 手动操作请求上下文
在某些情况下,例如单元测试或者需要模拟请求的场景中,可能需要手动操作请求上下文。可以使用with
语句结合app.test_request_context()
来手动创建请求上下文。例如:
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
return f"请求路径: {request.path}"
with app.test_request_context('/test_path'):
result = index()
print(result)
- 在上述代码中,
app.test_request_context('/test_path')
创建了一个模拟的请求上下文,路径为/test_path
。在这个上下文环境中调用index
函数,就好像 Flask 真正接收到了一个指向/test_path
的请求一样。
8.2 应用上下文
在 Flask 中,应用上下文(Application Context)是一个重要的概念,它管理着与 Flask 应用实例相关的数据和状态。与请求上下文主要处理单个请求不同,应用上下文的作用域更侧重于整个应用程序,并且在应用的生命周期内可能会被多次激活和使用。
1. 应用上下文的作用
- 存储应用级数据:应用上下文提供了一个地方来存储与应用相关的全局数据,这些数据在整个应用的不同部分都可以访问。例如,可以在应用上下文中存储数据库连接池、配置对象等,这样在不同的视图函数、扩展或其他应用组件中都能方便地获取这些资源,而无需在每个地方重复创建或配置。
- 关联应用实例:它将当前的执行环境与特定的 Flask 应用实例相关联。在多应用(如在一个进程中运行多个 Flask 应用实例)或者在使用 Flask 扩展的情况下,应用上下文确保每个操作都与正确的应用实例相关联,避免混淆和错误。
2. 应用上下文对象
Flask 的应用上下文主要由两个对象组成:
current_app
:指向当前激活的 Flask 应用实例。通过这个对象,你可以访问应用的各种配置、属性和方法。例如,current_app.config
可以获取应用的配置信息,current_app.logger
可以使用应用的日志记录器。g
:这是一个全局对象,用于在请求处理过程中临时存储数据。与current_app
不同,g
的数据只在当前请求处理期间有效,且每个请求都有自己独立的g
对象。不过,由于应用上下文在请求处理期间也是激活的,所以可以在请求处理过程中通过g
来传递一些与当前请求相关,但又可能在不同函数间共享的数据。
3. 应用上下文的激活与销毁
- 自动激活:在处理请求时,Flask 会自动激活应用上下文和请求上下文。当一个请求进入应用,Flask 首先会激活应用上下文,然后再激活请求上下文。这确保了在请求处理过程中,应用级别的数据和当前请求相关的数据都可以被正确访问。
- 手动激活:在某些情况下,比如在自定义的命令行脚本或者扩展中,可能需要手动激活应用上下文。可以使用
app.app_context()
方法来手动创建并激活应用上下文。例如:
from flask import Flask
app = Flask(__name__)
with app.app_context():
# 在这个代码块内,应用上下文被激活
print(current_app.config['SECRET_KEY'])
在上述代码中,with app.app_context()
语句创建了一个应用上下文,并在 with
代码块内保持激活状态。在这个代码块中,可以使用 current_app
访问应用的配置信息。
- 销毁:当请求处理完成或者手动创建的应用上下文代码块结束时,应用上下文会被销毁。在销毁过程中,Flask 会自动清理与该应用上下文相关的资源,确保不会出现资源泄漏。
4. 应用上下文的使用场景
- 数据库连接管理:在应用启动时,可以在应用上下文中初始化数据库连接池,并在整个应用的生命周期内复用这些连接。不同的视图函数在处理请求时,可以从应用上下文中获取数据库连接,而无需每次都重新建立连接,提高了应用的性能和资源利用率。例如:
import sqlite3
from flask import Flask, g
app = Flask(__name__)
def get_db():
if 'db' not in g:
g.db = sqlite3.connect('example.db')
return g.db
@app.teardown_appcontext
def close_db(error):
db = g.pop('db', None)
if db is not None:
db.close()
@app.route('/')
def index():
db = get_db()
# 使用数据库连接执行查询等操作
return 'Hello, World!'
在上述代码中,get_db
函数在应用上下文中获取数据库连接,如果连接不存在则创建一个。@app.teardown_appcontext
装饰的 close_db
函数在应用上下文销毁时关闭数据库连接。此代码并不能直接运行,仅是一个示例,详细的数据库连接请看后面第九章数据库集成。
- 配置管理:应用上下文可以方便地访问应用的配置信息。例如,在视图函数或者扩展中,可以通过
current_app.config
获取配置参数,根据不同的配置执行不同的逻辑。
from flask import Flask, current_app
app = Flask(__name__)
app.config['DEBUG_MODE'] = True
@app.route('/')
def index():
if current_app.config['DEBUG_MODE']:
return '应用处于调试模式'
return '应用处于生产模式'
理解和正确使用应用上下文对于构建健壮、高效的 Flask 应用至关重要,它提供了一种有效的方式来管理应用级别的资源和数据,并确保这些资源在整个应用生命周期内的正确使用和清理。
九、会话管理
在 Web 应用开发中,会话管理是一项关键功能,它允许 Web 应用在多个请求之间跟踪用户状态和数据。在 Flask 框架里,会话管理通过 session
对象实现,该对象基于客户端的 Cookie 机制,提供了一种安全且便捷的方式来存储和访问用户特定的数据。
9.1 启用会话
Flask 中的会话默认是基于客户端的 Cookie 实现的。在应用中不需要额外的启用步骤,但需要设置一个密钥来对会话数据进行加密签名,以确保数据的安全性。
from flask import Flask
app = Flask(__name__)
app.secret_key ='my_secret_key' # 请使用更复杂和安全的密钥在实际应用中
9.2 存储和获取会话数据
可以像操作字典一样存储和获取会话数据。
@app.route('/set_session')
def set_session():
session['username'] = 'John'
session['logged_in'] = True
return 'Session data set'
@app.route('/get_session')
def get_session():
username = session.get('username')
logged_in = session.get('logged_in', False)
return f'Username: {username}, Logged In: {logged_in}'
9.3 删除会话数据
@app.route('/clear_session')
def clear_session():
session.pop('username', None)
session.pop('logged_in', None)
return 'Session data cleared'
9.4 会话有效期
1. 默认会话有效期
在 Flask 中,默认情况下,会话基于客户端的Cookie
实现。会话Cookie
在用户关闭浏览器时会被删除,这意味着会话数据的有效期默认到浏览器关闭为止。这种会话类型被称为 “非持久会话”。
2. 设置持久会话
如果你希望会话在浏览器关闭后仍然有效,可以通过将会话标记为持久化来实现。在 Flask 中,可以在设置会话数据时,通过设置 session.permanent = True
来实现。
python
from flask import Flask, session, redirect, url_for
app = Flask(__name__)
app.secret_key ='my_secret_key'
@app.route('/set_permanent_session')
def set_permanent_session():
session.permanent = True # 将会话标记为持久化
session['username'] = 'John'
session['logged_in'] = True
return redirect(url_for('get_session'))
@app.route('/get_session')
def get_session():
username = session.get('username', 'Guest')
logged_in = session.get('logged_in', False)
return f'Username: {username}, Logged In: {logged_in}'
if __name__ == '__main__':
app.run(debug = True)
3. 设置会话有效期时间
对于持久化会话,你还可以设置其确切的有效期。Flask 应用有一个 PERMANENT_SESSION_LIFETIME
配置变量,用于指定持久化会话的有效期。它是一个 datetime.timedelta
对象。
from flask import Flask, session, redirect, url_for
from datetime import timedelta
app = Flask(__name__)
app.secret_key ='my_secret_key'
# 设置持久化会话的有效期为 30 分钟或者设置day = 7,设置有效期为一周
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes = 30)
@app.route('/set_permanent_session')
def set_permanent_session():
session.permanent = True
session['username'] = 'John'
session['logged_in'] = True
return redirect(url_for('get_session'))# 重定向
@app.route('/get_session')
def get_session():
username = session.get('username', 'Guest')
logged_in = session.get('logged_in', False)
return f'Username: {username}, Logged In: {logged_in}'
if __name__ == '__main__':
app.run(debug = True)
-
在这个示例中,通过
app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes = 30)
将持久化会话的有效期设置为 30 分钟。超过这个时间后,会话数据将失效。 -
注意事项
- 虽然持久化会话提供了更长的有效期,但由于会话数据存储在客户端 Cookie 中,不要在会话中存储敏感信息,如密码等。
-
要确保
secret_key
的保密性和强度,因为它用于对会话数据进行加密签名,防止数据被篡改。如果secret_key
泄露,攻击者可能会伪造或篡改会话数据。
9.5 安全性考量
-
SECRET_KEY
安全:SECRET_KEY
必须足够复杂且保密,避免使用容易猜测的值。建议使用长且随机的字符串作为SECRET_KEY
。 -
数据验证:由于
session
数据存储在客户端,虽然经过签名验证,但仍不应存储敏感信息,如密码。在使用session
数据时,始终要进行必要的验证和过滤,防止潜在的安全漏洞。 -
Cookie 安全设置:可以通过配置 Flask 应用来设置
session
Cookie
的一些安全属性,如httponly
和secure
。httponly
确保 Cookie 不能通过 JavaScript 访问,减少 XSS 攻击获取 Cookie 的风险;secure
确保 Cookie 仅在 HTTPS 连接下发送,防止中间人攻击窃取 Cookie 数据。app.config['SESSION_COOKIE_HTTPONLY'] = True app.config['SESSION_COOKIE_SECURE'] = True
通过合理利用 Flask 的会话管理功能,并遵循安全最佳实践,可以有效地跟踪用户状态,提供个性化的用户体验,同时保障应用的安全性。
十、数据库集成
Flask 是一个轻量级的 Web 框架,本身不具备数据持久化能力。通过集成数据库,可以将应用产生的数据(如用户注册信息、文章内容等)保存下来,以便后续使用。这里只简单介绍数据库的基本应用,包括数据库的连接、配置、模型类定义、简单的数据插入、查询、更新、删除等操作。数据库集成高级应用请关注后续更新。
10.1 安装必要的库
要在 Flask 中连接 MySQL 数据库,需要安装 flask_sqlalchemy
和 mysql-connector-python
(用于连接 MySQL 数据库)。可以使用以下命令通过 pip
安装:
pip install flask_sqlalchemy
pip install mysql-connector-python
10.2 配置数据库
1. 创建数据库并添加用户表
MySQL数据库安装请参考以往文章,创建数据库test_db
并创建表user_info
,下边是SQL
语句:
-- 创建数据库 test_db,如果数据库已存在则不执行创建操作
CREATE DATABASE IF NOT EXISTS test_db;
-- 使用 test_db 数据库
USE test_db;
-- 在 test_db 数据库中创建 user_info 表
CREATE TABLE user_info (
user_id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(50) NOT NULL,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
phone_number VARCHAR(20),
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
2. 数据库URL配置
使用 app.config['SQLALCHEMY_DATABASE_URI']
配置数据库连接字符串。对于 MySQL 数据库,格式为 mysql+mysqlconnector://username:password@localhost:post/dbname
,其中包含数据库类型(mysql
)、驱动(mysqlconnector
)、用户名(username
)、密码(password
)、主机地址(localhost
)、端口号(post
)和数据库名(dbname
)等信息。
# 请将username、password、post(端口号)、数据库名更换为你的配置
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://username:password@localhost:3306/test_db'
注意:请勿在配置字符串中添加空格。
3. 其它关于数据库的配置
-
数据库连接URI配置
- MySQL:
app.config['SQLALCHEMY_DATABASE_URI'] ='mysql+mysqlconnector://username:password@localhost:3306/test_db'
- PostgreSQL:
app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@localhost:5432/test_db'
- SQLite:
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///test.db'
(文件型数据库,test.db
是数据库文件名,若不存在则会自动创建;如果使用内存数据库,可写成sqlite://
)
- MySQL:
-
跟踪修改配置
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
:默认值为True
,Flask - SQLAlchemy 会跟踪对象的修改并发送信号。这在调试和某些特定场景下可能有用,但会增加内存开销。一般在生产环境中建议设置为False
。
-
连接池相关配置
app.config['SQLALCHEMY_POOL_SIZE'] = 10
:设置数据库连接池的大小,即连接池中保持的数据库连接数量。此处表示连接池大小为 10 个连接。app.config['SQLALCHEMY_MAX_OVERFLOW'] = 20
:设置连接池最大溢出连接数。当连接池已满时,若还有连接请求,SQLAlchemy 会额外创建最多SQLALCHEMY_MAX_OVERFLOW
个连接。此处表示连接池可以额外溢出 20 个连接。app.config['SQLALCHEMY_POOL_TIMEOUT'] = 30
:设置从连接池中获取连接的超时时间(秒)。如果在这个时间内无法获取到连接,会抛出异常。此处表示获取连接的超时时间为 30 秒。
-
连接池回收时间配置
app.config['SQLALCHEMY_POOL_RECYCLE'] = 3600
:设置连接在连接池中的最长存活时间(秒)。超过这个时间,连接会被回收并重新创建,以防止数据库服务端关闭空闲连接导致的连接失效问题。此处表示连接在连接池中最长存活 1 小时。
-
引擎选项配置
SQLALCHEMY_ENGINE_OPTIONS
配置项允许你向 SQLAlchemy 的数据库引擎传递额外的选项。这些选项高度依赖于所使用的数据库类型,不同数据库支持的选项有所不同。-
MySQL
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'connect_args': { 'connect_timeout': 10, 'charset': 'utf8mb4' }, 'poolclass': QueuePool, 'pool_size': 5, 'pool_reset_on_return': 'rollback', 'echo_pool': True }
connect_args
:中设置了连接超时时间为 10 秒和字符集为utf8mb4
。poolclass
: 使用默认的QueuePool
连接池。pool_size
:设置连接池大小为5
。pool_reset_on_return
: 设置连接返回连接池时回滚事务。echo_pool
: 开启连接池活动日志记录。
-
PostgreSQL
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'strategy': 'threadlocal', 'connect_args': { 'application_name': 'MyFlaskApp', 'sslmode':'require' }, 'execution_options': {'autocommit': False} }
strategy
:设置为threadlocal
,为每个线程维护独立连接。connect_args
:中设置了应用程序名称为MyFlaskApp
且要求使用 SSL 连接。execution_options
:将autocommit
设置为False
,意味着需要手动管理事务。
-
SQLite
app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'connect_args': { 'isolation_level': None, 'detect_types': sqlite3.PARSE_DECLTYPES }, 'poolclass': QueuePool, 'pool_size': 2, 'echo_pool': False }
-
-
数据库会话相关
app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True
:如果设置为True
,Flask - SQLAlchemy 会在每次请求结束时自动提交数据库会话中的更改。这意味着在请求处理过程中对数据库所做的所有修改,在请求结束时会自动提交到数据库持久化保存。如果设置为False
(默认值),则需要手动调用db.session.commit()
来提交更改。- **注意事项:**虽然自动提交在某些场景下很方便,但在复杂的事务处理或需要精细控制数据库操作的情况下,手动提交更有助于保证数据的一致性和事务的完整性。
-
日志相关
app.config['SQLALCHEMY_ECHO'] = True
:当设置为True
时,SQLAlchemy 会将所有生成的 SQL 语句输出到标准输出(通常是控制台)。这在开发和调试过程中非常有用,能够帮助开发者查看实际执行的 SQL 语句,方便排查数据库相关的问题,比如查询语句是否正确、参数是否正确传递等。- **注意事项:**在生产环境中,由于性能和安全原因(可能会暴露敏感信息,如数据库用户名和密码在某些复杂查询日志中),一般不建议开启此选项。
-
元数据缓存相关
-
用于配置多个数据库连接(绑定)。如果你的应用需要连接多个不同的数据库(例如,一个主数据库用于主要业务数据,另一个数据库用于日志记录等),可以使用此配置。它是一个字典,键是绑定名称,值是对应的数据库连接 URI。
app.config['SQLALCHEMY_BINDS'] = { 'secondary_db':'sqlite:///secondary.db', }
在模型定义时,可以通过
__bind_key__
属性指定使用哪个绑定的数据库:class SecondaryModel(db.Model): __bind_key__ ='secondary_db' id = db.Column(db.Integer, primary_key = True) # 其他字段定义
-
-
连接池预Ping配置
app.config['SQLALCHEMY_POOL_PRE_PING'] = True
:设置为True
时,SQLAlchemy 在从连接池获取连接时,会先发送一个简单的查询(“预 ping”)来检查连接是否仍然有效。如果连接无效,会自动重新获取一个新的连接。这有助于避免使用已经失效的连接,特别是在数据库服务器可能会主动关闭长时间空闲连接的环境中。
10.3 数据库连接测试
-
run.py
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 配置数据库连接URL app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://root:root@localhost:3306/test_db' # 关闭 SQLAlchemy 对模型对象修改的跟踪。这样可以减少不必要的资源消耗,提高应用的性能。 app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # 将 SQLAlchemy 与特定的 Flask 应用关联起来,以便在应用中进行数据库操作,如创建模型、执行查询等。 db = SQLAlchemy(app)
-
test_db.py
使用
unittest
框架进行测试。import unittest from run import app,db class TestDBConnection(unittest.TestCase): # setUp方法会在每个测试方法执行前被调用 def setUp(self): # 获取Flask应用的上下文 self.app_context = app.app_context() # 推送应用上下文,确保后续的数据库操作等在Flask应用环境中执行 self.app_context.push() # tearDown方法会在每个测试方法执行后被调用 def tearDown(self): self.app_context.pop() # 定义测试数据库连接的方法 def test_db_connection(self): try: con = db.engine.connect() print(f"数据库连接成功!!连接对象是:{con}") except Exception as e: self.fail(f"数据库连接失败: {str(e)}") finally: con.close()
运行
test_db_connection()
执行测试。db.engine.connect()
是获取数据库连接对象的关键方法,用于从SQLAlchemy
的连接池中获取一个数据库连接对象。这个连接对象可以用来直接执行SQL
语句,与数据库进行交互,比如执行查询、插入、更新和删除等操作。- 在 Flask 应用中,通常通过定义模型类(继承自
db.Model
)来与数据库交互。但在某些情况下,当你需要执行一些复杂的原生 SQL 查询,或者执行数据库管理操作(如创建表、修改表结构)时,使用db.engine.connect()
获取连接并执行原生 SQL 语句会更加灵活直接,绕过了模型类的抽象层。
10.4 数据库模型定义
数据库模型类继承自 db.Model
,其中 db
是 SQLAlchemy
的实例。这种继承关系赋予了模型类与数据库交互的能力,使得我们可以通过操作模型类的实例来操作数据库表中的记录。
class UserInfo(db.Model):
pass
1. 表名定义
通过 __tablename__
类属性指定模型类对应的数据库表名。
class UserInfo(db.Model):
__tablename__ = 'user_info'
2. 字段定义
模型类的属性对应数据库表的列,通过 db.Column
来定义。db.Column
接受多个参数来指定列的特性:
-
数据库类型:指定列的数据类型,如
db.Integer
(整数)、db.String(50)
(长度为 50 的字符串)、db.TIMESTAMP
(时间戳)等。不同的数据类型适用于不同的数据存储需求,例如db.Integer
适合存储数字 ID,db.String
用于存储文本数据。 -
主键:通过设置
primary_key = True
来指定某列为主键。主键是表中唯一标识每一条记录的字段,例如:user_id = db.Column(db.Integer, primary_key=True)
-
自动递增:对于整数类型的主键,通常设置
autoincrement = True
使其值自动递增。每次向表中插入新记录时,该列的值会自动增加,无需手动指定:user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
-
唯一性:使用
unique = True
确保列中的值在表中是唯一的。例如,电子邮件地址通常需要唯一,以避免重复注册:email = db.Column(db.String(100), unique=True)
-
非空约束:通过
nullable = False
表示该列不允许为空值。在插入或更新记录时,如果没有为该列提供值,将会引发错误:user_name = db.Column(db.String(50), nullable=False)
-
默认值:可以使用
default
参数为列设置默认值。当插入记录时,如果没有显式指定该列的值,将使用默认值。例如,为记录创建时间设置默认值为当前时间:creation_date = db.Column(db.TIMESTAMP, default=datetime.now)
3. 完整示例
from datetime import datetime
# 创建SQLAlchemy实例,实际应用中可以不在这里创建,从其他地方导入
db = SQLAlchemy()
class UserInfo(db.Model):
# 指定该模型类对应的数据库表名为user_info。
__tablename__ = 'user_info'
user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
user_name = db.Column(db.String(50), nullable=False)
password = db.Column(db.String(255), nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
phone_number = db.Column(db.String(20))
creation_date = db.Column(db.TIMESTAMP, default=datetime.now)
10.5 数据库会话(Session)
1. 添加数据
-
添加单条数据
通过创建模型实例,然后使用
db.session.add()
方法将实例添加到会话中,最后调用db.session.commit()
提交事务。def test_add_user(self): user = UserInfo(user_name='admin', password='admin',email='153578@qq.com',phone_number='18565452652') try: db.session.add(user) db.session.commit() print("用户添加成功~!!") except Exception as e: db.session.rollback() print(f'用户数据添加错误: {str(e)}')
-
批量添加数据
批量添加使用
db.session.add_all()
将包含所有用户信息的列表一次性添加到数据库表中。def test_add_users(): users_data = [ {'username': 'user1', 'email': 'user1@example.com'}, {'username': 'user2', 'email': 'user2@example.com'} ] users = [User(username=user['username'], email=user['email']) for user in users_data] try: db.session.add_all(users) db.session.commit() print('用户数据添加成功!!') except Exception as e: db.session.rollback() print(f'用户数据添加错误: {str(e)}')
2. 查询数据
-
获取所有数据
查询表中所有信息,使用
db.session.query(<要查询的表的模型类>).all()
。def test_query_user(self): # 获取所有用户信息 users = db.session.query(UserInfo).all() for user in users: print(user.user_name)
-
获取单个用户信息
查询单个用户信息使用
filter_by().first()
通过某个字段值进行查询。def test_query_user(self): # 获取单个用户信息 user = db.session.query(UserInfo).filter_by(user_id = 5).first() print(user.user_name)
3. 更新用户信息
-
先查询到要更新的实例,修改其属性,然后提交事务。
def test_update_user(self): user = db.session.query(UserInfo).filter_by(user_id = 5).first() user.email = "jay_hnzz@163.com" db.session.commit() print("用户信息已更新!")
4. 删除数据
-
查询到要删除的实例,使用
db.session.delete()
方法,然后提交事务。def test_delete_user(self): user = db.session.query(UserInfo).filter_by(user_id = 5).first() db.session.delete(user) db.session.commit() print("该用户已注销!")
十一、Flask框架web应用完整示例
这是一个基于Flask框架实现用户注册与登录功能的完整示例,前端由HTML
构建注册与登录页面,CSS
美化样式,JavaScript
获取表单数据并通过fetch
以 JSON 格式向后端发送请求;后端借助flask_sqlalchemy
集成 MySQL
数据库,在app/__init__.py
配置连接,app/models.py
定义UserInfo
数据模型,run.py
通过/register
和/login
路由分别处理GET
请求以渲染页面、POST
请求以处理注册登录逻辑,含密码哈希、数据验证、唯一性检查及异常处理,完整呈现Web应用用户认证模块的实现流程。
11.1 项目结构
webproject/
│
├── app/
│ ├── __init__.py
│ ├── models.py
│ ├── static/
│ │ ├── login.js
│ │ ├── register.js
│ │ └── styles.css
│ └── templates/
│ ├── login.html
│ └── register.html
└── run.py
11.2 数据库搭建
-- 创建数据库 test_db,如果数据库已存在则不执行创建操作
CREATE DATABASE IF NOT EXISTS test_db;
-- 使用 test_db 数据库
USE test_db;
-- 在 test_db 数据库中创建 user_info 表
CREATE TABLE user_info (
user_id INT AUTO_INCREMENT PRIMARY KEY,
user_name VARCHAR(50) NOT NULL,
password VARCHAR(255) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
phone_number VARCHAR(20),
creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
11.3 前端页面
-
注册页面:
register.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device - width, initial - scale = 1.0"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <title>注册</title> </head> <body> <div class="container"> <h2>注册</h2> <form id="registerForm"> <label for="user_name">用户名:</label> <input type="text" id="user_name" name="user_name" required><br> <label for="password">密码:</label> <input type="password" id="password" name="password" required><br> <label for="email">邮箱:</label> <input type="email" id="email" name="email" required><br> <label for="phone_number">电话号码:</label> <input type="text" id="phone_number" name="phone_number"><br> <input type="submit" value="注册"> </form> <p>已有账号?<a href="{{ url_for('login') }}">登录</a></p> </div> <script src="{{ url_for('static', filename='register.js') }}"></script> </body> </html>
-
登录页面:
login.html
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device - width, initial - scale = 1.0"> <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> <title>登录</title> </head> <body> <div class="container"> <h2>登录</h2> <form id="loginForm"> <label for="user_name">用户名:</label> <input type="text" id="user_name" name="user_name" required><br> <label for="password">密码:</label> <input type="password" id="password" name="password" required><br> <input type="submit" value="登录"> </form> <p>没有账号?<a href="{{ url_for('register') }}">注册</a></p> </div> <script src="{{ url_for('static', filename='login.js') }}"></script> </body> </html>
-
CSS样式:
styles.css
body { font-family: Arial, sans-serif; background-color: #f4f4f4; display: flex; justify-content: center; align-items: center; min-height: 100vh; margin: 0; } .container { background-color: #fff; padding: 20px; border-radius: 5px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); width: 300px; } h2 { text-align: center; margin-bottom: 20px; } form label { display: block; margin-top: 10px; } form input { width: 90%; padding: 10px; margin-top: 5px; margin-bottom: 15px; border: 1px solid #ccc; border-radius: 3px; } form input[type="submit"] { background-color: #4CAF50; color: white; cursor: pointer; width: 50%; } form input[type="submit"]:hover { background-color: #45a049; } p { text-align: center; margin-top: 10px; } a { color: #4CAF50; text-decoration: none; } a:hover { text-decoration: underline; }
-
登录页面JS:
login.js
document.getElementById('loginForm').addEventListener('submit', function (e) { e.preventDefault(); const user_name = document.getElementById('user_name').value; const password = document.getElementById('password').value; fetch('{{ url_for("login")}}', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_name: user_name, password: password }) }) .then(response => response.json()) .then(data => { if (data.success) { alert('登录成功'); } else { alert('登录失败:' + data.message); } }); });
-
注册页面JS:
register.js
document.getElementById('registerForm').addEventListener('submit', function (e) { e.preventDefault(); const user_name = document.getElementById('user_name').value; const password = document.getElementById('password').value; const email = document.getElementById('email').value; const phone_number = document.getElementById('phone_number').value; fetch('{{ url_for("register")}}', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_name: user_name, password: password, email: email, phone_number: phone_number }) }) .then(response => response.json()) .then(data => { if (data.success) { alert('注册成功'); window.location.href = '/login'; } else { alert('注册失败:' + data.message); } }); });
11.4 后端业务
-
初始化配置模块:
__init__.py
from flask import Flask from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) # 配置数据库连接字符串,需根据实际情况修改用户名、密码和主机 app.config['SQLALCHEMY_DATABASE_URI'] ='mysql+mysqlconnector://username:password@localhost:3306/test_db' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False db = SQLAlchemy(app)
-
数据库模型类:
models.py
from app import db class UserInfo(db.Model): __tablename__ = 'user_info' user_id = db.Column(db.Integer, autoincrement=True, primary_key=True) user_name = db.Column(db.String(50), nullable=False) password = db.Column(db.String(255), nullable=False) email = db.Column(db.String(100), unique=True, nullable=False) phone_number = db.Column(db.String(20)) creation_date = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp())
-
路由及视图函数+主应用入口:
run.py
import hashlib from flask import request, jsonify, render_template from app import app, db from app.models import UserInfo # 对密码进行哈希处理的函数 def hash_password(password): # 使用SHA256算法对密码进行哈希,并将结果转换为十六进制字符串 return hashlib.sha256(password.encode()).hexdigest() # 根据用户名和哈希后的密码获取用户的函数 def get_user(user_name, hashed_password): # 在UserInfo表中查询符合条件的用户 return UserInfo.query.filter_by(user_name=user_name, password=hashed_password).first() # 注册路由,支持GET和POST请求 @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': # 获取POST请求中的JSON数据 data = request.get_json() # 从JSON数据中提取用户名 user_name = data.get('user_name') # 从JSON数据中提取密码 password = data.get('password') # 从JSON数据中提取邮箱 email = data.get('email') # 从JSON数据中提取电话号码 phone_number = data.get('phone_number') # 检查用户名、密码和邮箱是否都存在 if not all([user_name, password, email]): return jsonify({ 'success': False, 'message': '用户名、密码和邮箱是必填项' }) # 对密码进行哈希处理 hashed_password = hash_password(password) # 检查数据库中是否已存在相同用户名或邮箱的用户 existing_user = UserInfo.query.filter( (UserInfo.user_name == user_name) | (UserInfo.email == email)).first() if existing_user: return jsonify({ 'success': False, 'message': '用户名或邮箱已存在' }) # 创建新用户对象 new_user = UserInfo( user_name=user_name, password=hashed_password, email=email, phone_number=phone_number ) try: # 将新用户添加到数据库会话 db.session.add(new_user) # 提交数据库会话,将新用户保存到数据库 db.session.commit() return jsonify({ 'success': True, 'message': '注册成功' }) except Exception as e: # 如果出现异常,回滚数据库会话 db.session.rollback() return jsonify({ 'success': False, 'message': '注册失败:' + str(e) }) else: # 如果是GET请求,渲染注册页面 return render_template('register.html') # 登录路由,支持GET和POST请求 @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': # 获取POST请求中的JSON数据 data = request.get_json() # 从JSON数据中提取用户名 user_name = data.get('user_name') # 从JSON数据中提取密码 password = data.get('password') # 检查用户名和密码是否都存在 if not all([user_name, password]): return jsonify({ 'success': False, 'message': '用户名和密码是必填项' }) # 对密码进行哈希处理 hashed_password = hash_password(password) # 根据用户名和哈希后的密码获取用户 user = get_user(user_name, hashed_password) if user: return jsonify({ 'success': True, 'message': '登录成功' }) else: return jsonify({ 'success': False, 'message': '用户名或密码错误' }) else: # 如果是GET请求,渲染登录页面 return render_template('login.html') if __name__ == '__main__': # 启动Flask应用,开启调试模式 app.run(debug=True)
原创不易,请大家尊重版权,请勿原文抄袭,如有错误地方,欢迎指出,感谢支持。