1.5 表单 Forms 处理


1.5 表单 Forms 处理

问题与直觉:用户的数据怎么到服务器

用户填了个注册表单,点"提交"——数据怎么到你的代码里?答案取决于表单的 method:GET 把数据放 URL(?name=xx),POST 把数据放请求体。服务器端分别用 request.args(GET)和 request.form(POST)读取。

表单处理的核心:读取 → 校验 → 处理 → 反馈。校验尤其重要——用户的输入永远不可信。

💡 关键直觉:表单是"门口的信箱",校验是"保安"。信件(数据)能投进来,但保安(校验)要检查内容是否合格才放行。

核心原理:GET 与 POST 表单

HTML 表单

<!-- GET 表单:数据进 URL --> <form method="GET" action="/search"> <input type="text" name="q" placeholder="搜索"> <button>搜索</button> </form> <!-- POST 表单:数据进请求体 --> <form method="POST" action="/register"> <input type="text" name="username" placeholder="用户名"> <input type="password" name="password" placeholder="密码"> <button>注册</button> </form>

Flask 读取

from flask import Flask, request app = Flask(__name__) # GET 表单 → request.args @app.route('/search') def search(): q = request.args.get('q', '') return f"搜索: {q}" # POST 表单 → request.form @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form.get('username') password = request.form.get('password') # 处理注册... return f"注册成功: {username}" return "显示注册表单页面"

GET vs POST 表单

维度 GET POST
数据位置 URL 查询串 请求体
可见性 可见(浏览器历史) 不可见
长度限制 有(URL 长度) 大得多
用途 搜索、筛选 提交、上传
安全 敏感数据勿用 相对安全(仍要 HTTPS)

工程实践要点:校验与文件上传

简单校验

@app.route('/register', methods=['POST']) def register(): username = request.form.get('username', '').strip() password = request.form.get('password', '') email = request.form.get('email', '') errors = [] if len(username) < 3: errors.append("用户名至少 3 个字符") if len(password) < 8: errors.append("密码至少 8 位") if '@' not in email: errors.append("邮箱格式不正确") if errors: return ",".join(errors), 400 # 校验通过,处理注册 return "注册成功"

校验原则:先 strip 去空格、再检查长度/格式、收集全部错误一次性返回(比"报第一个错就停"体验好)。

文件上传

import os from werkzeug.utils import secure_filename ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS @app.route('/upload', methods=['POST']) def upload(): file = request.files.get('file') if not file or not allowed_file(file.filename): return "不支持的文件类型", 400 filename = secure_filename(file.filename) file.save(os.path.join('uploads', filename)) return f"上传成功: {filename}"

文件上传要点request.files 取文件、扩展名白名单校验、secure_filename 清理文件名防路径穿越。

⚠️ 常见坑:文件扩展名校验不够。只查扩展名可被绕过(伪装扩展名)。生产环境还要校验文件内容(magic bytes)与大小上限。

动手实验:带校验的注册表单

from flask import Flask, request, render_template_string app = Flask(__name__) FORM_HTML = ''' <form method="POST" action="/register"> <p><input type="text" name="username" placeholder="用户名"></p> <p><input type="password" name="password" placeholder="密码"></p> <p><input type="email" name="email" placeholder="邮箱"></p> <button>注册</button> </form> ''' @app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form.get('username', '').strip() password = request.form.get('password', '') email = request.form.get('email', '') if len(username) < 3: return "用户名太短", 400 if len(password) < 8: return "密码太短", 400 if '@' not in email: return "邮箱无效", 400 return f"注册成功:{username}" return render_template_string(FORM_HTML) if __name__ == '__main__': app.run(debug=True)

先 GET 看表单,再提交各种数据观察校验反馈——"读取-校验-处理-反馈"四步完整走一遍

进阶:为什么推荐 Flask-WTF

手写校验代码繁琐且易漏(CSRF、类型转换、错误回显)。Flask-WTF(第三章 3.3 详述)把表单变成"类":

from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import DataRequired, Length, Email class RegisterForm(FlaskForm): username = StringField('用户名', validators=[DataRequired(), Length(min=3)]) password = PasswordField('密码', validators=[DataRequired(), Length(min=8)]) email = StringField('邮箱', validators=[DataRequired(), Email()]) submit = SubmitField('注册')

WTF 的价值:校验声明式(写在类里)、错误回显自动、内置 CSRF 防护。复杂表单用 WTF,简单表单手写即可——按复杂度选工具

FAQ:表单高频问题

问:request.form.get 和 request.form['key'] 区别?
get 在键不存在时返回默认值(不报错);索引访问不存在会 KeyError。优先用 get

问:如何防止重复提交?
POST-Redirect-GET 模式:处理成功后重定向到结果页,避免刷新重复提交。

问:CSRF 是什么?
跨站请求伪造——恶意网站诱导用户向你的站点发请求。Flask-WTF 内置 CSRF token 防护,手写表单要自己实现(或用 WTF)。

问:checkbox 和 select 怎么读?
request.form.get('checkbox_name') 返回 'on'(勾选)或 None;request.form.get('select_name') 返回选中的 value;多选框用 request.form.getlist('multi')

深入理解:表单数据获取与校验

获取数据的三种来源(复习+加深)

来源 对象 适用场景
URL 查询参数 request.args GET 表单、搜索、分页
表单体 request.form POST 表单
JSON 体 request.json AJAX/API 提交

小技巧:表单和 JSON 混用时代码容易乱,第三章的 Flask-WTF 会把"取值+校验"统一起来。本章先掌握手写方式,理解底层。

手写校验的完整模式

@app.route('/register', methods=['GET', 'POST']) def register(): if request.method == 'POST': username = request.form.get('username', '').strip() password = request.form.get('password', '') confirm = request.form.get('confirm', '') errors = [] if len(username) < 3: errors.append('用户名至少 3 个字符') if len(password) < 8: errors.append('密码至少 8 位') if password != confirm: errors.append('两次密码不一致') if errors: return render_template('register.html', errors=errors, username=username) # 校验通过:处理业务(存库等) return '注册成功!' return render_template('register.html', errors=[])

模板中展示错误

{% if errors %} <ul class="errors"> {% for e in errors %} <li>{{ e }}</li> {% endfor %} </ul> {% endif %} <form method="post"> <input name="username" value="{{ username }}"> <input type="password" name="password"> <input type="password" name="confirm"> <button>注册</button> </form>

文件上传的安全要点

import os from werkzeug.utils import secure_filename from flask import request ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} UPLOAD_DIR = 'uploads' @app.route('/upload', methods=['POST']) def upload(): f = request.files.get('file') if f is None or not allowed_file(f.filename): return '文件类型不允许', 400 # secure_filename 清洗文件名,防止路径穿越 safe_name = secure_filename(f.filename) f.save(os.path.join(UPLOAD_DIR, safe_name)) return f'上传成功:{safe_name}'

安全要点

  1. 扩展名白名单:只允许已知图片类型;
  2. secure_filename:清洗掉 ../、非法字符,防路径穿越;
  3. 限制大小app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024(16MB);
  4. 不要信任用户文件名:重命名为随机名更安全。

常见表单问题排查

现象 原因 解法
POST 路由 405 路由没写 methods=['POST'] 加上 methods
取到的值都是 None 表单 name 与取值 key 不一致 核对 HTML name 属性
中文乱码 页面编码问题 模板加 <meta charset="utf-8">
校验失败后输入丢失 没有回填 value value="{{ username }}" 回填
上传文件名异常 未清洗 secure_filename + 白名单
上传大文件超时 未限制大小 MAX_CONTENT_LENGTH

重点提炼

  • 数据来源:GET 表单用 args,POST 表单用 form,AJAX 用 json。
  • 手写校验模式:取值 → 逐项校验收集 errors → 失败重新渲染并回填。
  • 错误展示:模板中循环 errors 列表逐条显示。
  • 文件上传:扩展名白名单 + secure_filename 防路径穿越 + 限制大小。
  • 回填技巧:校验失败后 value 属性保留用户输入,避免重新输入。
  • 升级预告:第三章 Flask-WTF 把"取值+校验+CSRF"统一封装。

表单设计的用户视角

表单是用户与你的应用交互最频繁的界面,设计得好不好直接决定体验。从用户视角看,好表单有三个标准:

第一,错误要"提前"且"具体"。 密码太短、邮箱格式不对——最好在用户提交时就指出具体问题,而不是笼统一句"输入有误"。本章的逐项校验、逐条错误展示就是为此设计。更进一步的体验是"边输入边校验"(前端配合),但服务端校验永远是最后防线——前端校验可以绕过。

第二,失败要"保数据"。 用户填了十个字段,因为一个密码太短被拒绝——如果十个字段全清空重填,体验极差。这就是本章回填技巧的价值:value="{{ username }}" 把已填内容保留,用户只需改错的那一项。

第三,操作要"可预期"。 提交按钮要明确("注册"而不是"确定"),危险操作(删除)要二次确认,提交中要有反馈(防止重复点击)。这些细节在第四章的项目实战中会体现。

表单与安全的两个提醒

  • 不要信任前端校验:攻击者可以绕过任何前端限制直接构造请求。所有规则必须在服务端实现(本章的手写校验,第三章的 WTF 校验器);
  • 敏感字段不过 URL:密码等敏感信息必须用 POST 提交。GET 参数会出现在浏览器历史、服务器日志、代理日志里——泄露面太大。

一个完整的表单流程回顾:GET 访问显示空白表单 → 用户填写提交(POST)→ 服务端校验 → 失败则重新渲染并回填+显示错误 → 成功则处理业务并重定向(避免刷新重复提交)。这个"PRG(Post-Redirect-Get)"模式是 Web 表单的标准流程——成功提交后重定向,能有效防止用户刷新页面导致重复提交。

关于表单的下一步:本章的手写方式让你理解了"取值→校验→回填"的完整机制;第三章的 Flask-WTF 会把"字段声明、校验规则、CSRF 防护"统一封装成表单类——到那时你会更深刻地体会框架带来的效率提升。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U