动态网站设计 / 第 7 章

Flask 框架进阶

聚焦最核心的两件事 —— 接住请求,管好用户

🖥️ 云服务器实操 📁 FinalShell 拖传 🎯 可运行代码

📌 本节课你会学到

  • Request 请求对象 是什么,为什么它是后端的入口
  • 如何用 request.args 接收 URL 参数(GET)
  • 如何用 request.form 接收表单数据(POST)
  • 什么是 Cookie 和 Session,服务器怎么"记住"用户
  • 用 Session 实现一个完整的 登录 / 退出 流程
  • 模板继承 让多个页面共享同一个骨架
00 课前准备

今天的操作流程

本节课所有代码都会在 本地编辑器 写好,然后用 FinalShell 拖到服务器 上运行。 保持"本地写、远程跑"的干净工作流。

💻 1. 本地写 在本地编辑器里写好 .py 文件
📤 2. 拖上传 FinalShell 拖拽到服务器目录
▶️ 3. 运行 终端执行 python3 app.py
🌐 4. 看效果 浏览器访问 http://IP:5000
💡 小贴士
本节课只依赖 Flask,在服务器先装好:pip3 install flask
7.1 Flask 请求处理
知识点 1

Request 对象 —— 客户端送来的"快递包裹"

📖 定义
Request 对象封装了客户端发来的请求报文,包括 URL 参数、表单数据、上传的文件、Cookie、请求头…… 所有从前端来的数据都从这个对象取。
💬 人话翻译
request 想象成一个快递包裹——用户从浏览器给后端发了个包裹, 里面可能装了输入框填的内容、上传的图片、URL 里的参数。用什么属性取什么东西:
属性存什么典型场景
request.argsURL 查询字符串参数分页 ?page=2、搜索 ?q=flask
request.formPOST 表单字段登录、注册、发表留言
request.files上传的文件头像、图片、附件
request.method请求方法判断是 GET 还是 POST
request.cookies客户端 Cookie读取登录凭证
💡 核心记忆
GET 参数用 args,POST 表单用 form——这两个今天是重点。
知识点 2

用 request.args 接收 GET 参数

GET 参数就是 URL 里问号后面那一串:

URL
http://192.168.1.100:5000/hello?name=鼎鼎&age=30
                               ↑                    ↑
                               路径                  参数(key=value 用 & 连接)

📄 完整示例代码

📁 demo_get/
  └── app.py
Pythondemo_get/app.py
# ========== 演示:接收 URL 上的 GET 参数 ==========
from flask import Flask, request

app = Flask(__name__)

# 路由:访问 /hello 时执行下面的函数
@app.route('/hello')
def hello():
    # request.args 是一个字典,存放 URL 问号后面的所有参数
    # .get('key', 默认值):参数没传时不会报错,会用默认值
    name = request.args.get('name', '同学')
    age  = request.args.get('age', '未知')

    # 直接返回 HTML 字符串(Flask 会帮我们变成网页)
    return f'''
        <h1>你好,{name}!</h1>
        <p>你今年 {age} 岁了。</p>
    '''

if __name__ == '__main__':
    # host='0.0.0.0' 让外网能访问,必须这样写
    # debug=True 代码改动后服务器会自动重启,调试时很方便
    app.run(host='0.0.0.0', port=5000, debug=True)

▶️ 测试结果

✓ 浏览器测试

http://IP:5000/hello?name=鼎鼎&age=30
→ 页面显示:"你好,鼎鼎!你今年 30 岁了。"

http://IP:5000/hello(什么都不传)
→ 页面显示:"你好,同学!你今年 未知 岁了。"(用了默认值)

❌ 易错点
千万不要写 request.args['name']——如果 URL 里没带 name 参数,程序会直接报错。 永远用 .get() 更安全。
知识点 3

用 request.form 接收表单提交

表单是用户在网页上填写的输入框。用户点"提交"后,数据通过 POST 请求发到服务器, 服务器用 request.form 读取。

1
GET 请求
显示空白表单
2
用户填写
点"提交"按钮
3
POST 请求
浏览器发送数据
4
服务器处理
读 form,给响应

📄 完整示例代码(两个文件)

📁 demo_form/
  ├── app.py
  └── 📁 templates/
      └── form.html
Pythondemo_form/app.py
# ========== 演示:接收表单 POST 提交 ==========
from flask import Flask, request, render_template

app = Flask(__name__)

# 核心要点:methods=['GET','POST'] 让同一个路由同时接受两种请求
# GET → 显示表单页面;POST → 处理用户提交的数据
@app.route('/register', methods=['GET', 'POST'])
def register():
    # 判断请求方法:是来填表(GET)还是来提交(POST)
    if request.method == 'POST':
        # request.form 是字典,读取 <input name="xxx"> 中 name 对应的值
        username = request.form.get('username')
        email    = request.form.get('email')
        hobby    = request.form.get('hobby')

        # 简单返回一个确认页面
        return f'''
            <h2>✅ 注册成功!</h2>
            <p>用户名:{username}</p>
            <p>邮 箱:{email}</p>
            <p>爱 好:{hobby}</p>
            <a href="/register">再填一次</a>
        '''

    # GET 请求:渲染 templates/form.html 给用户看
    return render_template('form.html')


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
HTMLdemo_form/templates/form.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>注册</title>
</head>
<body>
  <h2>用户注册</h2>

  <!-- 关键点:
       method="POST"  表示用 POST 方式提交
       action 不写表示提交到当前 URL
       input 的 name 必须和 Python 里 request.form.get('xxx') 的 xxx 一致 -->
  <form method="POST">
    <p>用户名:<input type="text" name="username" required></p>
    <p>邮 箱:<input type="email" name="email" required></p>
    <p>爱 好:<input type="text" name="hobby"></p>
    <button type="submit">注册</button>
  </form>
</body>
</html>

▶️ 运行方式

Bash服务器终端
# 进入项目目录,启动 Flask 应用
cd demo_form
python3 app.py
✓ 测试流程
1. 访问 http://IP:5000/register → 看到注册表单
2. 填入信息点"注册" → 页面跳到确认页,显示你刚才填的内容
3. 点"再填一次"回到表单页
⚠️ 最重要的对应关系
HTML 里 <input name="username"> 的 name 属性,
必须和 Python 里 request.form.get('username') 的字符串完全一致。 写错一个字母数据就取不到。
📋 7.1 小结
GET 用 request.args.get() 取 URL 参数;POST 用 request.form.get() 取表单数据。 同一个路由要同时处理 GET 和 POST,记得加 methods=['GET', 'POST']if request.method == 'POST' 判断。
7.2 响应与 Session 登录
知识点 4

Cookie 和 Session —— 服务器怎么"记住"你

HTTP 协议是无状态的——每次请求之间是独立的, 服务器默认不记得你是谁。你在 /login 登录完, 下一秒访问 /home,服务器根本不知道是同一个人。

所以需要 Cookie 和 Session 这两个机制来"记住"用户。

💬 人话翻译(景区类比)
Cookie 就像景区门口发的手环——戴在你手上(存在浏览器里),每次进园区给工作人员看一下。

Session 是景区后台的游客登记本——工作人员看到手环号,翻开登记本查到你是谁、买了什么票、哪天进的园。

Flask 的 session 对象把这两个机制都打包好了——我们只需要像操作字典一样操作它,细节 Flask 自动处理。

Session 登录的完整流程

1
用户提交
账号密码
2
服务器验证
对就记录到 session
3
浏览器保存
加密 Cookie
4
后续请求
服务器认出你
知识点 5

Session 的三个核心操作

Flask 的 session 用起来就像一个字典:

Python
# ① 登录时:把用户信息写入 session
session['user'] = 'admin'

# ② 任何时候:读取 session 判断是否登录
user = session.get('user')   # 返回用户名 或 None
if user:
    print('已登录:', user)

# ③ 退出时:从 session 中删除
session.pop('user', None)   # 第二个参数 None:key 不存在也不报错
⚠️ 使用 Session 的前提
必须设置 app.secret_key——它是用来加密 Session 数据的"钥匙"。没设这个会直接报错。
app.secret_key = 'your-secret-key-xxx'
知识点 6

🎯 示例:完整的登录 / 退出系统

把前面学的全部串起来:一个最小的登录系统,有首页、登录页、退出三个路由。

📁 demo_login/
  └── app.py # 一个文件搞定
Pythondemo_login/app.py
# ========== 极简登录系统:Session 实战 ==========
from flask import Flask, request, session, redirect, url_for

app = Flask(__name__)

# 关键:必须设置 secret_key,Session 才能工作
# 实际项目要换成复杂的随机字符串,别直接照搬
app.secret_key = 'baoshan-university-flask-2026'


# ========== 路由 1:首页 ==========
@app.route('/')
def index():
    # 从 session 取当前登录用户,没登录返回 None
    user = session.get('user')

    if user:
        # 已登录:显示欢迎 + 退出按钮
        return f'''
            <h1>欢迎回来,{user}!👋</h1>
            <p>这里是你的个人首页</p>
            <a href="/logout">退出登录</a>
        '''

    # 未登录:提示去登录
    return '''
        <h1>你还没有登录</h1>
        <a href="/login">点此登录</a>
    '''


# ========== 路由 2:登录 ==========
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        # 从表单读取用户输入
        username = request.form.get('username')
        password = request.form.get('password')

        # 简单硬编码验证:admin/123 才能登录
        # 真实项目要查数据库 + 密码加密
        if username == 'admin' and password == '123':
            # ★ 核心一步:把用户信息写入 session
            session['user'] = username
            # redirect 跳转到首页,url_for 自动生成首页的 URL
            return redirect(url_for('index'))

        # 验证失败:提示错误
        return '''
            <h2>❌ 账号或密码错误</h2>
            <a href="/login">重新登录</a>
        '''

    # GET 请求:返回登录表单
    return '''
        <h2>用户登录</h2>
        <form method="POST">
            <p>用户名:<input name="username" required></p>
            <p>密 码:<input name="password" type="password" required></p>
            <button>登录</button>
        </form>
        <p style="color:#888">提示:账号 admin,密码 123</p>
    '''


# ========== 路由 3:退出 ==========
@app.route('/logout')
def logout():
    # ★ 核心一步:从 session 中移除用户
    session.pop('user', None)
    # 跳回首页,此时首页会变回"未登录"状态
    return redirect(url_for('index'))


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)
✓ 完整测试流程

1. 访问 http://IP:5000/ → 显示"你还没有登录"

2. 点"点此登录" → 看到登录表单

3. 输入 admin / 123 → 点登录 → 自动跳回首页,变成"欢迎回来,admin!"

4. 点"退出登录" → 回到"你还没有登录"状态

5. 就算关掉浏览器再打开,只要 Cookie 没过期,首页还能认出你

📋 7.2 小结
Session 操作就三件事:写入 session['user']=xxx读取 session.get('user')删除 session.pop('user')。 配合 redirect(url_for(...)) 实现路由跳转,登录退出流程就完整了。
7.3 模板继承(简介)

为什么要有模板继承?

一个网站往往有多个页面——首页、关于、联系我们……它们的导航栏和页脚都一样。 如果每个 HTML 都复制一遍,改导航就要改 N 次。

💬 人话翻译
模板继承就像 PPT 的母版:母版定好页眉页脚样式,每张幻灯片只管自己的内容。 改母版 = 全站统一改动。

🎯 一个最小示例

两个文件演示模板继承:base.html 是父模板(骨架), index.html 是子模板(填内容)。

Jinja2templates/base.html(父模板)
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>我的网站</title>
</head>
<body>
  <!-- 所有页面共用的导航栏 -->
  <nav><a href="/">首页</a> | <a href="/about">关于</a></nav>
  <hr>

  <!-- ★ 核心:挖一个"坑",名字叫 content,让子模板去填 -->
  {% block content %}{% endblock %}

  <hr>
  <!-- 所有页面共用的页脚 -->
  <footer>© 2026 保山学院</footer>
</body>
</html>
Jinja2templates/index.html(子模板)
<!-- 第一行:声明继承哪个父模板 -->
{% extends 'base.html' %}

<!-- 填 content 这个"坑",里面写本页独有的内容即可 -->
{% block content %}
  <h1>欢迎来到首页</h1>
  <p>这里只需要写首页独有的内容,导航和页脚都从父模板继承。</p>
{% endblock %}
💡 记住三个关键词就够了
父模板用 {% block 名字 %}{% endblock %} 挖坑
子模板用 {% extends '父模板' %} 继承
子模板用同名的 {% block %} 填坑

💪 动手练习

做两个任务,把今天的知识点用起来。代码都已经写好并给出, 直接复制到本地保存成 .py 文件,用 FinalShell 拖到服务器运行。 重点是理解每一行代码在做什么,运行成功后记得截图保存。

任务 1:个人信息卡

基础 · 练习 7.1 GET 参数

目标:写一个 Flask 程序,通过 URL 参数接收姓名、年龄、城市、专业, 在网页上显示成一张好看的个人信息卡。

访问示例:
http://IP:5000/card?name=鼎鼎&age=30&city=保山&major=人工智能

📄 完整代码

📁 task1_card/
  └── app.py
Pythontask1_card/app.py
# ========== 任务 1:个人信息卡 ==========
# 知识点:用 request.args.get() 接收 URL 参数
from flask import Flask, request

app = Flask(__name__)


@app.route('/card')
def card():
    # 从 URL 中读取 4 个参数,每个都有默认值
    # 语法:request.args.get('参数名', '默认值')
    name  = request.args.get('name',  '匿名')
    age   = request.args.get('age',   '保密')
    city  = request.args.get('city',  '未知')
    major = request.args.get('major', '未填')

    # 返回带简单样式的 HTML(f-string 可以在字符串里嵌入变量)
    # 注意:f-string 中的 CSS 花括号要写成 {{ }} 来转义
    return f'''
        <!DOCTYPE html>
        <html>
        <head>
            <meta charset="UTF-8">
            <title>个人信息卡</title>
            <style>
                body {{
                    font-family: sans-serif;
                    background: #F4F7FB;
                    display: flex;
                    justify-content: center;
                    padding-top: 50px;
                }}
                .card {{
                    background: white;
                    padding: 30px 40px;
                    border-radius: 12px;
                    box-shadow: 0 4px 16px rgba(0,0,0,0.08);
                    width: 340px;
                    border-top: 4px solid #3B6B9A;
                }}
                h1 {{ color: #1E3A5C; margin: 0 0 20px; }}
                p  {{ color: #334155; line-height: 1.8; margin: 6px 0; }}
                .label {{ color: #64748B; display: inline-block; width: 60px; }}
            </style>
        </head>
        <body>
            <div class="card">
                <h1>👤 {name}</h1>
                <p><span class="label">年龄</span>{age}</p>
                <p><span class="label">城市</span>{city}</p>
                <p><span class="label">专业</span>{major}</p>
            </div>
        </body>
        </html>
    '''


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

▶️ 运行步骤

Bash
# 1. 本地创建 task1_card 文件夹,把上面的 app.py 放进去
# 2. 用 FinalShell 拖到服务器
# 3. 在服务器终端运行:
cd task1_card
python3 app.py
✓ 测试 URL

浏览器访问:
http://服务器IP:5000/card?name=鼎鼎&age=30&city=保山&major=人工智能

自己尝试:改 URL 里的参数值,看页面怎么变;
再尝试:不传任何参数直接访问 /card,看默认值效果。

任务 2:带登录保护的个人主页

综合 · 练习 7.1 + 7.2

目标:实现一个需要登录才能访问的个人主页。未登录用户访问 /profile 会被自动踢回登录页;登录成功后才能看到主页内容。

提供两个账号:admin / 123user / 456

涉及的路由:

📄 完整代码

📁 task2_login/
  └── app.py
Pythontask2_login/app.py
# ========== 任务 2:带登录保护的个人主页 ==========
# 知识点:request.form(表单)+ session(登录状态)+ redirect(跳转)
from flask import Flask, request, session, redirect, url_for

app = Flask(__name__)
# Session 必须配置 secret_key
app.secret_key = 'task2-secret-key-change-me'

# 允许的用户字典:key 是用户名,value 是密码
# 用字典比写好几个 if-else 优雅得多
USERS = {
    'admin': '123',
    'user':  '456',
}


# ========== 路由 1:首页 ==========
@app.route('/')
def index():
    user = session.get('user')
    if user:
        return f'''
            <h1>👋 欢迎 {user}</h1>
            <p><a href="/profile">进入个人主页</a></p>
            <p><a href="/logout">退出登录</a></p>
        '''
    return '''
        <h1>欢迎来到本站</h1>
        <p>你还没登录,<a href="/login">点此登录</a></p>
    '''


# ========== 路由 2:登录 ==========
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')

        # 用字典校验:如果用户名存在 且 密码匹配
        if USERS.get(username) == password:
            session['user'] = username
            # 登录成功:跳到个人主页
            return redirect(url_for('profile'))

        # 验证失败
        return '''
            <h2>❌ 账号或密码错误</h2>
            <a href="/login">重新登录</a>
        '''

    # GET:显示登录表单
    return '''
        <!DOCTYPE html>
        <html>
        <head><meta charset="UTF-8"><title>登录</title></head>
        <body style="font-family:sans-serif; max-width:360px; margin:60px auto;">
            <h2>🔐 用户登录</h2>
            <form method="POST">
                <p>用户名:<input name="username" required></p>
                <p>密 码:<input name="password" type="password" required></p>
                <button>登录</button>
            </form>
            <p style="color:#888; font-size:13px;">
                测试账号:admin/123 或 user/456
            </p>
        </body>
        </html>
    '''


# ========== 路由 3:个人主页(需要登录才能看)==========
@app.route('/profile')
def profile():
    # ★ 这里是重点:先检查有没有登录
    user = session.get('user')

    if not user:
        # 没登录:踢回登录页
        return redirect(url_for('login'))

    # 已登录:展示个人主页
    return f'''
        <!DOCTYPE html>
        <html>
        <head><meta charset="UTF-8"><title>个人主页</title></head>
        <body style="font-family:sans-serif; max-width:500px; margin:40px auto;">
            <h1>🏠 这是 {user} 的个人主页</h1>
            <p>只有登录用户才能看到这个页面。</p>
            <p>当前登录账号:<strong>{user}</strong></p>
            <hr>
            <p>
                <a href="/">返回首页</a> |
                <a href="/logout">退出登录</a>
            </p>
        </body>
        </html>
    '''


# ========== 路由 4:退出登录 ==========
@app.route('/logout')
def logout():
    # 从 session 中删除用户信息
    session.pop('user', None)
    return redirect(url_for('index'))


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000, debug=True)

▶️ 运行步骤

Bash
cd task2_login
python3 app.py
✓ 完整测试清单

① 没登录直接闯关:访问 /profile → 应该被自动踢到 /login

② 错误密码:输入 admin/999 → 显示"账号或密码错误"

③ 正确登录:输入 admin/123 → 跳到个人主页显示"这是 admin 的个人主页"

④ 换账号:退出后用 user/456 登录 → 主页显示"这是 user 的个人主页"

⑤ 退出:点"退出登录" → 回到首页,再访问 /profile 又被踢走

💡 思考一下
为什么 /profile 里第一件事就是检查 session.get('user')
因为任何人都能在地址栏输入 URL——只检查"来的路径"靠不住,必须检查"这个用户是不是登录了"。 这就是后端安全的第一课:永远不要相信前端传来的任何东西

📤 练习交付要求

⚠️ 常见坑
端口占用:任务 1 跑完再跑任务 2,要先 Ctrl+C 停掉上一个程序, 不然会报错"Address already in use"。也可以把第二个任务的 port=5000 改成 port=5001