Python Web应用开发之Flask框架——高级应用(一)

八、上下文管理

在 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)中,这个栈确保每个线程都有自己独立的上下文栈,保证多线程环境下请求处理的隔离性。

  • 使用:在请求处理过程中,例如在视图函数、中间件等地方,都可以通过requestsession对象访问请求上下文的信息。

  • 销毁:一旦请求处理完成,无论是正常结束还是发生异常,请求上下文都会从栈中弹出并销毁。这意味着requestsession对象中的数据在请求处理完成后将不再可用,除非将相关数据保存到其他地方(如数据库)。

    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的一些安全属性,如 httponlysecurehttponly 确保 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_sqlalchemymysql-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. 其它关于数据库的配置

  1. 数据库连接URI配置

    • MySQLapp.config['SQLALCHEMY_DATABASE_URI'] ='mysql+mysqlconnector://username:password@localhost:3306/test_db'
    • PostgreSQLapp.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://username:password@localhost:5432/test_db'
    • SQLiteapp.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///test.db'(文件型数据库,test.db是数据库文件名,若不存在则会自动创建;如果使用内存数据库,可写成sqlite://
  2. 跟踪修改配置

    • app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False:默认值为True,Flask - SQLAlchemy 会跟踪对象的修改并发送信号。这在调试和某些特定场景下可能有用,但会增加内存开销。一般在生产环境中建议设置为False
  3. 连接池相关配置

    • 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 秒。
  4. 连接池回收时间配置

    • app.config['SQLALCHEMY_POOL_RECYCLE'] = 3600 :设置连接在连接池中的最长存活时间(秒)。超过这个时间,连接会被回收并重新创建,以防止数据库服务端关闭空闲连接导致的连接失效问题。此处表示连接在连接池中最长存活 1 小时。
  5. 引擎选项配置

    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
      }
      
  6. 数据库会话相关

    • app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN'] = True:如果设置为 True,Flask - SQLAlchemy 会在每次请求结束时自动提交数据库会话中的更改。这意味着在请求处理过程中对数据库所做的所有修改,在请求结束时会自动提交到数据库持久化保存。如果设置为 False(默认值),则需要手动调用 db.session.commit() 来提交更改。
    • **注意事项:**虽然自动提交在某些场景下很方便,但在复杂的事务处理或需要精细控制数据库操作的情况下,手动提交更有助于保证数据的一致性和事务的完整性。
  7. 日志相关

    • app.config['SQLALCHEMY_ECHO'] = True:当设置为 True 时,SQLAlchemy 会将所有生成的 SQL 语句输出到标准输出(通常是控制台)。这在开发和调试过程中非常有用,能够帮助开发者查看实际执行的 SQL 语句,方便排查数据库相关的问题,比如查询语句是否正确、参数是否正确传递等。
    • **注意事项:**在生产环境中,由于性能和安全原因(可能会暴露敏感信息,如数据库用户名和密码在某些复杂查询日志中),一般不建议开启此选项。
  8. 元数据缓存相关

    • 用于配置多个数据库连接(绑定)。如果你的应用需要连接多个不同的数据库(例如,一个主数据库用于主要业务数据,另一个数据库用于日志记录等),可以使用此配置。它是一个字典,键是绑定名称,值是对应的数据库连接 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)
          # 其他字段定义
      
  9. 连接池预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,其中 dbSQLAlchemy 的实例。这种继承关系赋予了模型类与数据库交互的能力,使得我们可以通过操作模型类的实例来操作数据库表中的记录。

class UserInfo(db.Model):
    pass

1. 表名定义

通过 __tablename__ 类属性指定模型类对应的数据库表名。

class UserInfo(db.Model):
    __tablename__ = 'user_info'

2. 字段定义

模型类的属性对应数据库表的列,通过 db.Column 来定义。db.Column 接受多个参数来指定列的特性:

  1. 数据库类型:指定列的数据类型,如 db.Integer(整数)、db.String(50)(长度为 50 的字符串)、db.TIMESTAMP(时间戳)等。不同的数据类型适用于不同的数据存储需求,例如 db.Integer 适合存储数字 ID,db.String 用于存储文本数据。

  2. 主键:通过设置 primary_key = True 来指定某列为主键。主键是表中唯一标识每一条记录的字段,例如:

    user_id = db.Column(db.Integer, primary_key=True)
    
  3. 自动递增:对于整数类型的主键,通常设置 autoincrement = True 使其值自动递增。每次向表中插入新记录时,该列的值会自动增加,无需手动指定:

    user_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    
  4. 唯一性:使用 unique = True 确保列中的值在表中是唯一的。例如,电子邮件地址通常需要唯一,以避免重复注册:

    email = db.Column(db.String(100), unique=True)
    
  5. 非空约束:通过 nullable = False 表示该列不允许为空值。在插入或更新记录时,如果没有为该列提供值,将会引发错误:

    user_name = db.Column(db.String(50), nullable=False)
    
  6. 默认值:可以使用 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. 添加数据

  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)}')
    
  2. 批量添加数据

    批量添加使用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. 查询数据

  1. 获取所有数据

    查询表中所有信息,使用db.session.query(<要查询的表的模型类>).all()

    def test_query_user(self):
    	# 获取所有用户信息
    	users = db.session.query(UserInfo).all()
    	for user in users:
    		print(user.user_name)
    
  2. 获取单个用户信息

    查询单个用户信息使用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)
    

    原创不易,请大家尊重版权,请勿原文抄袭,如有错误地方,欢迎指出,感谢支持。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Smile丶Life丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值