Flask+SQLite实战:从零构建带用户系统的待办事项管理应用
翻开一本编程教程的最后一章,看到"综合实战项目"这几个字,大部分人心情应该跟我差不多:又兴奋又发怵。兴奋的是前面十章的语法、函数、文件操作、异常处理这些零散知识点,终于要在一条完整的项目链里串起来了;发怵的是万一哪一块学得不够扎实,这一章就会变成大型翻车现场。我最近刷的是一本Python Web方向的教程,第11章的综合实战项目要求用Flask从零搭一个带用户系统的待办事项管理应用。它最大的价值不是逼你再记几个函数,而是逼你回答一个关键问题:面对一个模糊的需求,你该怎么把它拆成可动手的代码、再落到一个能跑起来的Web应用上。这篇文章把我完整的实操过程记录下来,你既可以直接照着敲一遍,也可以重点看我标注的踩坑与设计判断。
1. 实战项目全景:这一章到底在练什么
1.1 项目选题拆解与需求分析
先聊选题。教程把综合实战项目定为"待办事项管理系统",而不是电商平台、社交App这类大而全的东西,我后来才琢磨明白这个选择的巧妙之处。
一个待办事项应用的核心需求特别清晰:用户注册登录、添加任务、查看任务列表、标记完成、删除任务。这五项需求刚好覆盖了Web开发的主干链条——数据模型设计、路由处理、模板渲染、表单交互、会话管理。再往下拆,每项需求又可以展开:
- 用户注册登录:涉及密码的安全存储、Session会话保持、登录态校验。
- 添加任务:涉及POST请求、表单取值、数据库插入。
- 查看任务列表:涉及GET请求、数据查询、模板循环渲染。
- 标记完成与删除:涉及URL参数传递、数据库更新、删除操作。
这个难度系数卡得刚刚好。如果项目太重,比如做一个完整商城,新手很容易被困在支付、库存、订单状态这些业务逻辑里,反而忘了练代码本身;如果太轻,比如只做一个"hello world"页面,那就完全达不到综合练习的目的。待办事项应用是一个"麻雀虽小五脏俱全"的选择,它逼着你走完一遍完整的Web开发流程,但又不至于让你在非核心的地方耗尽精力。
我还留意到,教程在需求描述里特意加了一条:每个用户只能看到自己的待办事项。这个看似不起眼的需求,直接决定了数据库表结构的设计——todo表必须关联user_id,所有查询都必须带当前用户的ID作为条件。这个细节正是"综合"二字的体现,它把一个安全相关的设计判断藏在了需求里,等着你去发现。
1.2 技术栈选型的背后逻辑
教程选的技术栈是Flask加SQLite加Jinja2模板引擎,前端不引框架,纯手写HTML和简单CSS。这个选型我一开始不太理解,毕竟现在主流都是前后端分离、Vue/React一把梭。但真正跟着做了一遍之后,我承认这个组合在教学的场景下确实是合理的。
Flask属于微框架,它不像Django那样自带ORM、Admin后台、表单组件等全套装备。但也正因为"缺东西",你才有机会手写路由、手动连数据库、自己构造SQL语句。如果你直接上Django,ORM把它底层的数据库交互全都封装好了,你反而不理解一条数据到底是怎么从数据库跑到页面上的。
SQLite的选择也很务实。它就是一个单文件数据库,不需要单独安装数据库服务端,Python标准库自带sqlite3模块。对于教学项目,你不需要考虑什么高并发、复杂连接池,把注意力集中在"SQL怎么写、数据怎么读"上就够了。
前端不引框架的理由更简单。Jinja2模板渲染天然支持把后端数据塞进HTML,服务端渲染的交互逻辑足够直观。如果这时候再引入Vue或者React,等于把一个综合实战项目变成了"双线作战"——你既要学后端业务,又要应付前端的响应式状态管理,很多人会直接卡死在前端这一层。
技术栈定下来之后,整个项目的代码组织方式也就有了雏形。我先把目录结构和数据库表设计列出来,这是后面所有代码的基础。
2. 动手前的三项准备
2.1 环境搭建与虚拟环境隔离
动手第一步,永远是搭环境。我知道有些同学会嫌麻烦直接用全局Python环境,pip install flask就直接写了。但虚拟环境这一步,真的是第11章里最值得养成习惯的操作。
虚拟环境的核心作用,就是给每个项目隔离一套独立的Python解释器和第三方依赖包。没有它,你装了A项目要的Flask 2.0,再装B项目要的Django 4.0,依赖之间互相打架是家常便饭。教程里用的是Python自带的venv模块,操作很简单:
激活之后,命令行提示符前面会出现一个(venv)标记。这个时候再装依赖,就只会装到当前项目的环境里:
装完之后可以用pip list确认Flask及其依赖Werkzeug、Jinja2已经就位。
这一步有一个特别容易踩的坑:很多人忘了激活虚拟环境,直接pip install flask,结果包装到了全局环境里,代码运行无报错但项目环境里却找不到Flask。更迷惑的是,如果全局环境和虚拟环境版本不一致,还会出现"本地能跑、换个环境就跑不了"的诡异问题。所以每次新开终端窗口,第一件事就是确认(venv)标记还在。
2.2 目录结构与数据库表设计
我按照教程的建议,把项目组织成下面这个结构。这个结构对Flask项目来说非常经典:
数据库用SQLite,建表逻辑写在models.py里。我先把表结构贴出来,再解释字段为什么这样设计。
这里有两个设计值得单独拎出来说。
第一个是password_hash字段。绝不能把用户密码明文存进数据库,这是所有带用户系统的应用最底线的安全要求。Werkzeug库提供的generate_password_hash函数默认使用PBKDF2算法,它会给同一个密码生成不同的随机盐值再加盐哈希。验证时用check_password_hash比对。这样一来,就算数据库泄露,攻击者拿到的也是一串不可逆的哈希值,而不是密码明文。
第二个是todo表通过user_id外键关联user表。每个待办事项都记录它是属于哪个用户的,后面所有查询都要带上user_id条件,这一步是实现"每个用户只看得到自己的数据"的关键。如果没有这个字段,所有任务就会混成一锅粥,也就谈不上用户隔离了。
2.3 初始化脚本与启动入口
光有建表语句不够,你要在合适的地方调用一次init_db(),数据库文件才会真正生成。我放在app.py里,在创建Flask应用实例的同时执行:
secret_key这个参数也很重要。Flask用它在服务端签名Session Cookie,如果这里不设置,Session功能基本就没法稳定工作。教程环境用固定字符串没问题,但实际发布时必须改成随机值,最好通过环境变量注入,绝不能把这个"开发用密钥"直接带到线上环境。
3. 核心功能逐个落地
3.1 注册与登录:会话管理从零实现
数据库准备好了,接下来写用户体系。注册路由的逻辑是:GET请求返回注册页面,POST请求接收表单数据,写进数据库,然后跳转到登录页面。
注意这里对空值的处理有两个层面。一是HTML表单里加了required属性,前端就能拦截空提交;二是后端依然要校验一遍。我说句实在话,前端校验只是为了用户体验,后端校验才是安全底线。绕过前端直接POST请求是完全可能发生的,所以后端必须把这道关。
登录路由逻辑也是类似的两段式处理:
登录成功之后,把user_id和username写进session。后续每个请求都能从session里读出当前登录用户,这就是会话管理的基本原理。要理解"会话"这个概念,你可以把它想象成一张临时通行证——用户在登录时拿到通行证,后续每次访问都在门口亮一下,服务器看到有效通行证就放行,看到无效的就送回登录页。
3.2 登录校验装饰器:重复代码的优雅封装
待办事项的页面只能让登录用户访问,所以我需要一个统一的校验逻辑。最直接的方式是在每个需要用登录态的路由里写一遍"检查session里有没有user_id",但这样重复劳动太蠢了。Python装饰器在这里派上了用场:
使用的时候,只要在路由函数上方加一行@login_required,就能实现"登录才能访问"的约束。functools.wraps的作用是保留原函数的元信息,保证装饰器不会把函数名、docstring这些信息弄丢。这个模式在Web开发里非常常用,理解了它,后面看其他框架的中间件概念也会容易很多。
3.3 待办事项的增删改查:核心循环
待办事项的四项操作,对应四个路由函数,分别是展示列表、新增、标记完成、删除。
先看展示和新增:
ORDER BY is_done ASC是我后期优化时加的。它的效果是未完成的任务排在前面,已完成的任务沉到底部,配合created_at DESC能让新任务靠前展示,排序逻辑比单纯按时间排要实用得多。
标记完成和删除的代码逻辑非常相似,核心都是先定位记录、再执行更新或删除。但这里面藏着一个特别重要的细节,我必须单独标注出来:
看到了吗?SQL语句里的条件不只是id = ?,而是id = ? AND user_id = ?。这个user_id条件,就是数据隔离的防线。如果没有它,我把URL改成/delete/1就能删掉别人的任务,改成/done/3就能把别人的任务标记成已完成。这是一个典型的越权漏洞,最可怕的是它没有任何报错——攻击者不会看到"没有权限"的提示,因为服务器根本不认识他。
这种问题在Web安全里叫IDOR(不安全的直接对象引用)。教程虽然只是一笔带过,但它正是综合实战项目里最值得深入研究的安全知识点。我建议你测试的时候专门试试不带user_id条件会怎样,感受一下这个权限校验的价值。
3.4 模板层:Jinja2把数据变成页面
后端把数据查出来了,接下来就是把它变成用户在浏览器里看到的页面。Jinja2模板语法不复杂,核心就是{% 控制语句 %}和{{ 变量输出 }}两种。
base.html是所有页面的骨架,用模板继承来复用公共部分:
url_for('static', filename='style.css')是Jinja2里生成静态文件路径的标准方式。它的好处是你改了应用挂载路径,静态文件的地址也会跟着自动更新,不会出现硬编码路径失效的问题。
index.html里,任务列表的渲染是重头戏:
这段模板里有几个值得留意的点。首先是{{ item['title'] }}和{{ item['desc'] }},Jinja2会自动对输出的变量做HTML转义。也就是说,如果用户输入了<script>alert('xss')</script>这类内容,页面会把尖括号原样显示出来,而不是当作浏览器脚本执行。这是模板引擎内置的XSS防护,写模板时不需要额外的处理,但你要知道这个机制存在。
其次是onclick="return confirm(...)"。这是一个纯前端的小优化,在跳转到删除链接之前先弹出一个确认框,防止用户手滑删除。这个交互改进成本极低,但使用体验提升明显。后端当然无法依赖它做安全防护,因为它绕不过去——攻击者可以直接请求删除URL。但它对普通用户来说,是一道很有用的"操作保险"。
登录页和注册页的模板更简单,就是两个表单加基本的样式。这里我不再展开。前端页面全部用一条简单的style.css配上白底卡片式布局,页面就能看得过去了。综合实战项目阶段,把更多精力放在后端逻辑上是对的,CSS能做到整洁清晰足够。
4. 调试、踩坑与上线前检查
4.1 本地联调:从启动到完整流程走通
所有代码写完,接下来就是检验成果的时刻。启动Flask开发服务器:
如果一切正常,控制台会显示Running on http://127.0.0.1:5000。打开浏览器访问这个地址,完整跑一遍流程:注册一个账号、用这个账号登录、添加几条任务、标记完成、删除其中一条、退出登录、再注册一个新账号验证看不到旧账号的任务。
这一步是最容易暴露问题的环节。我实操时遇到的第一坑是:注册成功后跳转到登录页,输对了账号密码却提示"用户名或密码错误"。我检查了大半天才发现,问题出在注册路由里——models.generate_password_hash(password)调用的是models.py中封装好的函数,而登录验证用的是models.check_password_hash(user["password_hash"], password)。听起来没问题对吧?但我在注册时因为粗心写成了generate_password_hash(username),把用户名当作密码去哈希了。这种错误在代码里非常隐蔽,因为注册时数据库写入是成功的,登录时才暴露出"根本对不上"的问题。
排查这类问题我推荐一个土办法:在关键位置加print()输出,把中间值打出来。比如在登录时打印出user["password_hash"]和check_password_hash的返回值,看看是哪一层出了错。等所有问题修完再把这些调试代码删掉。对于第11章这种规模的实战项目,用不上多复杂的调试工具,print大法足够高效。
4.2 高频错误与排查速查表
我把实操过程中最容易出现的坑整理成一张速查表,这些错误在网络问答社区里出现频率也很高:
| 错误现象 | 可能原因 | 解决办法 |
|---|---|---|
sqlite3.OperationalError: no such table: user |
忘了调用init_db(),或者建表失败 |
确认启动时执行了models.init_db(),删除旧的todo.db后重新生成 |
ImportError: No module named 'flask' |
虚拟环境没激活,或Flask没装到这个环境里 | 检查命令行是否存在(venv)前缀,重新pip install flask |
| 注册提示用户名已存在 | 想测试不同用户,但用户名重复 | 换一个用户名,或者手动删除数据库里旧记录 |
| 登录后仍跳回登录页 | Session写入失败,或者secret_key没设置 |
确认app.secret_key已赋值,检查session["user_id"]是否写入 |
| 模板页面显示原始HTML | 模板文件命名或路径不对,渲染没有生效 | 确认模板文件在templates/目录,且目录在app.py同层 |
Bad Request: The CSRF token is missing |
使用了带CSRF防护模块(非本教程场景) | 本教程未引入基础Flask的CSRF,若你加了扩展需配置密钥 |
其中"登录后仍跳回登录页"这个问题最容易误导人。它的根源往往是secret_key没有设置,导致session签名失效,Flask每次请求都无法从Cookie中恢复用户状态,看起来就像"登录成功了但马上又被踢出来"。这种问题你盯着登录逻辑的代码根本看不出毛病,因为问题根本不在这里。
4.3 上线前的安全检查与优化方向
项目能跑通之后,教程进入收尾阶段。这时候有几项检查我建议你认真做一遍,因为"能跑"和"能上线"之间还有不小的距离。
第一是secret_key。开发时用写死的字符串没问题,但真上线前必须改成随机值,并且从环境变量读取。硬编码的密钥等于把你的会话加密密钥公布给了所有人,别人可以伪造Session Cookie。第二是debug=True。开发模式下Flask会展示详细的错误堆栈,方便调试。但这个模式绝不能开在生产环境——它会把源码路径、局部变量全部暴露给访问者,相当于主动给攻击者递了一份你的代码地图。
第三是数据库备份。SQLite就是单文件,备份方式极其简单,直接把todo.db复制一份放到安全位置即可。你可以写一个简单的定时脚本定期复制文件,也可以手动在每次大改动前复制一次。数据无价,这个习惯越早养成越好。
再往后如果你想继续扩展这个项目,自然的演进方向有很多:给任务加截止日期和优先级字段,用datetime做提醒;增加编辑功能,让用户可以修改任务内容;给密码找回加邮箱验证;把单纯的HTML页面升级为前端交互更丰富的界面。这些都是从综合实战项目通往真实业务系统的成长路径。
写在最后
这一章做下来,我的感觉是:第11章的本质不是为了让你学会Flask,而是让你亲身体验一次"从零到一交付一个项目"的完整过程。它逼着你同时去想三个问题——数据怎么建模、代码怎么组织、安全怎么兜底。这些能力在单章知识点里根本练不到。
我实操中最大的一个体会是:一定要亲手把代码敲一遍,而不是复制粘贴。复制粘贴代码的时候,很多细节会在你眼前溜走,比如装饰器里忘了加@wraps(f)、SQL查询漏了user_id条件、模板里写错变量名。这些错误只有亲手敲过之后踩一次坑,你才会真正记住。我第一遍跑通用了整整一个下午,期间至少犯了五个低级错误,但正是这个下午的折腾,让我对Flask应用从启动到响应再到数据库落盘的整个流程有了实实在在的体感。这个体感,才是综合实战项目给你留下的最宝贵的东西。
相关推荐
基于Python+Open CV的手势识别算法设计
伴随着人工智能时代的到来,人机交互的领域也逐渐成为研究的一大方向;其中,手势识别是人机交互领域的一项关键技术,自此手势识别也迎来了一波高潮,近几年无论是在消费领域、电子领域、数码领域、家电领域甚至汽车领域上,都能或多或少的见到手势识别的身影。 手势识别在设计智能高效的人机界面方面具有至关重要的作用, 目前手势识别已应用到手语识别、智能监控、到虚拟现实等各个领域,手势识别的原理都是利用各种传感器(例如红外、摄像头等)对手部的形态进行捕捉并进行建模,形成一个模型信息的序列帧,而后将这些信息序列转换为机器能够识别的相对应指令(例如打开、切换菜单、移动等)来完成控制。 采用Python的集成开发环境Pycharm进行本次课程设计,在Pycharm中进行需要库(模块)的下载,调取电脑摄像头,按帧读取摄像头采集到的头像,形态学处理,图像旋转(由于摄像头采集到的图像是镜像,需要用cv2.flip将摄像头采集到的图像进行处理),选取图片中固定位置作为手势输入,用红线画出手势识别框,基于hsv的肤色检测,进行高斯滤波,找出轮廓,求出图像中手势的凹凸点,手指间角度求取。
ROS-CV-ImageConverter:将ROS图像转换为OpenCV(cv :: Mat)类型的图像
ROS-CV图像转换器 依存关系 cv_bridge(ROS软件包)
计算机视觉图像处理-CV2与图像插值算法
用最近邻插值与双线性插值原理完成图像缩放 最近邻插值,是指将目标图像中的点,对应到源图像中后,找到最相邻的整数点,作为插值后的输。缺点:使用最近邻插值算法处理图像放大时,在图象中可能出现明显的块状效应 双线性插值,在两个方向上做三次线性插值。使用双线性插值算法处理图像放大时,在图象中可能出现马赛克 代码 import cv2 img = cv2.imread(’./xiabang.jpg’, cv2.IMREAD_UNCHANGED) #缩放比例 scale_percent = 0.3 width = int(img.shape[1] * scale_percent) height = int
基于水平集CV模型的图像分割(Matlab代码)
基于水平集CV模型的图像分割(Matlab代码),全套代码
导向滤波(guide_filter)的python实现
导向滤波(guide_filter)的python实现,可以结合暗通道;理论实现比较好的图像去噪效果。

