本教程将带你从零搭建一个带用户系统的个人生活管理应用,包含待办提醒、目标倒计时、记账三大核心功能。后端使用 Flask + SQLAlchemy + Flask-Login,前端使用原生 HTML/CSS/JS 与 jQuery。通过本项目,你将掌握完整的 Web 开发流程,并理解用户认证、数据隔离、REST API 设计等关键概念。


一、项目预览

  • 用户系统:注册、登录、登出,每个用户的数据完全隔离。
  • 待办模块:创建任务、设置提醒时间、标记完成/未完成,到期自动弹窗提醒。
  • 目标模块:设定目标与截止日期,实时显示倒计时(天/时/分)。
  • 记账模块:记录收入/支出,按分类统计,自动计算余额。

技术栈:

  • 后端:Flask (Python Web 框架)
  • 数据库:SQLite + SQLAlchemy (ORM)
  • 用户认证:Flask-Login
  • 前端:jQuery (AJAX 请求)、原生 JavaScript、响应式 CSS

二、环境准备与项目初始化

2.1 安装 Python

确保你的电脑已安装 Python 3.8 或更高版本。在终端输入:

python --version

2.2 创建项目文件夹

mkdir life_manager
cd life_manager

2.3 创建虚拟环境(推荐)

python -m venv venv
# 激活虚拟环境
# Windows:
venv\Scripts\activate
# Mac/Linux:
source venv/bin/activate

2.4 安装依赖

创建 requirements.txt 文件,内容如下:

Flask==2.3.3
Flask-SQLAlchemy==3.1.1
Flask-Login==0.6.2

然后执行:

pip install -r requirements.txt

2.5 项目结构

life_manager/
├── app.py                 # 主程序
├── templates/             # 模板文件夹
│   ├── login.html
│   ├── register.html
│   └── index.html
└── life_manager.db        # 运行后自动生成(SQLite数据库)

将本教程后面提供的三个 HTML 文件放入 templates 文件夹,将 app.py 放在根目录。


三、后端代码详解(app.py)

3.1 导入模块与配置

from flask import Flask, request, jsonify, render_template, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required, current_user
from datetime import datetime
from werkzeug.security import generate_password_hash, check_password_hash
  • Flask:核心框架。
  • render_template:渲染 HTML 模板。
  • redirect/url_for:页面跳转。
  • flash:闪现消息(用于登录错误提示)。
  • SQLAlchemy:ORM,操作数据库。
  • LoginManagerUserMixin 等:处理用户会话。
  • werkzeug.security:密码哈希与校验。

配置部分:

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'  # 请改为随机字符串
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///life_manager.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'   # 未登录时跳转到 login 路由

3.2 数据库模型

用户模型(User)
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password_hash = db.Column(db.String(200), nullable=False)
    
    tasks = db.relationship('Task', backref='user', lazy=True, cascade='all, delete-orphan')
    goals = db.relationship('Goal', backref='user', lazy=True, cascade='all, delete-orphan')
    transactions = db.relationship('Transaction', backref='user', lazy=True, cascade='all, delete-orphan')
    
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
    
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)
  • UserMixin 提供了 is_authenticated 等属性,简化 Flask-Login 集成。
  • relationship 定义了与子表的一对多关系,cascade='all, delete-orphan' 表示删除用户时自动删除其所有关联数据。
  • set_password / check_password 负责密码加密与验证。
待办模型(Task)
class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    remind_time = db.Column(db.DateTime, nullable=True)
    completed = db.Column(db.Boolean, default=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
目标模型(Goal)
class Goal(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(200), nullable=False)
    target_date = db.Column(db.DateTime, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
记账模型(Transaction)
class Transaction(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    type = db.Column(db.String(10), nullable=False)   # 'income' 或 'expense'
    category = db.Column(db.String(50), nullable=False)
    amount = db.Column(db.Float, nullable=False)
    date = db.Column(db.DateTime, default=datetime.now)
    note = db.Column(db.String(200))
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

每个模型都通过 user_id 外键关联到用户,实现数据隔离。

3.3 创建数据库表与测试用户

with app.app_context():
    db.drop_all()   # 仅在开发时使用,会清空已有数据
    db.create_all()
    # 创建一个演示用户 demo / demo123
    if not User.query.filter_by(username='demo').first():
        demo = User(username='demo')
        demo.set_password('demo123')
        db.session.add(demo)
        db.session.commit()
  • db.drop_all() 会删除所有表,适合开发阶段快速重置结构。生产环境切勿使用,应用 Alembic 等迁移工具。

3.4 用户认证路由

登录路由
@app.route('/login', methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        user = User.query.filter_by(username=username).first()
        if user and user.check_password(password):
            login_user(user)
            next_page = request.args.get('next')
            return redirect(next_page or url_for('index'))
        flash('用户名或密码错误', 'error')
    return render_template('login.html')
  • current_user.is_authenticated:检查是否已登录。
  • login_user(user):Flask-Login 提供的函数,建立会话。
  • next 参数:实现登录后跳转到原来想访问的页面。
注册路由
@app.route('/register', methods=['GET', 'POST'])
def register():
    # 类似,校验两次密码是否一致,用户名是否已存在
    # 创建新用户后调用 login_user 或跳转到登录页
登出
@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))
  • @login_required 装饰器确保只有登录用户才能访问此路由。

3.5 API 设计(用户数据隔离)

以任务模块为例,其他模块结构完全相同。

获取当前用户的所有任务
@app.route('/api/tasks', methods=['GET'])
@login_required
def get_tasks():
    tasks = Task.query.filter_by(user_id=current_user.id).all()
    return jsonify([...])

关键点:filter_by(user_id=current_user.id) 确保只返回属于当前登录用户的数据。

创建任务
@app.route('/api/tasks', methods=['POST'])
@login_required
def create_task():
    data = request.get_json()
    task = Task(title=data['title'], ..., user_id=current_user.id)
    db.session.add(task)
    db.session.commit()
    return jsonify({'id': task.id}), 201
更新/删除任务
@app.route('/api/tasks/<int:task_id>', methods=['PUT'])
@login_required
def update_task(task_id):
    task = Task.query.filter_by(id=task_id, user_id=current_user.id).first_or_404()
    # 修改并提交
  • first_or_404() 会自动返回 404 如果不存在或不属于当前用户,避免了越权访问。

记账与目标的 API 模式完全一致,只需替换模型类名即可。


四、前端页面详解

4.1 登录与注册页面(login.html / register.html)

  • 使用简单的居中卡片样式,表单提交到同路由的 POST 方法。
  • 通过 flash 消息显示错误或成功提示。
  • 注册页面额外增加“确认密码”字段,前端验证与后端双重保证。

4.2 主界面(index.html)

用户信息与登出按钮
<div class="user-area">
    <span>👤 {{ username }}</span>
    <a href="{{ url_for('logout') }}" class="logout-btn">退出</a>
</div>
  • {{ username }} 是 Jinja2 模板语法,从后端 render_template('index.html', username=current_user.username) 传递。
选项卡切换
$('.tab').on('click', function() {
    $('.tab').removeClass('active');
    $(this).addClass('active');
    const panelId = $(this).data('panel') + 'Panel';
    $('.panel').removeClass('active-panel');
    $('#' + panelId).addClass('active-panel');
});
  • 使用 data-panel 属性存储面板标识,通过 jQuery 动态显示对应面板。
AJAX 数据交互

所有增删改查都通过 AJAX 调用后端 API,并实时刷新界面。

获取待办列表

function loadTodos() {
    $.get('/api/tasks', function(tasks) {
        // 渲染列表,并调用 checkReminders 检测提醒
    }).fail(handleUnauth);
}
  • handleUnauth 函数:若返回 401 状态码,则跳转到登录页。

添加待办

$.ajax({
    url: '/api/tasks',
    method: 'POST',
    contentType: 'application/json',
    data: JSON.stringify({ title: title, remind_time: remindTime || null }),
    success: () => loadTodos()
});
提醒机制
  • 每次加载待办列表时,遍历所有未完成且设置了提醒时间的任务。
  • 若当前时间 >= 提醒时间,且该任务 ID 未被记录到 remindedTaskIds 数组,则显示横幅提醒。
  • 使用 setInterval 每分钟重新加载待办列表,实现准实时提醒。
倒计时计算
const diffMs = targetDate - now;
const days = Math.floor(diffMs / (1000*60*60*24));
const hours = Math.floor((diffMs % (86400000)) / (3600000));
const minutes = Math.floor((diffMs % 3600000) / 60000);
余额计算
let total = 0;
trans.forEach(t => {
    total += (t.type === 'income' ? t.amount : -t.amount);
});
XSS 防护

在渲染用户输入的内容时(如任务标题、备注),使用 escapeHtml 函数转义 &<> 字符,防止跨站脚本攻击。


五、项目运行与测试

  1. 确保虚拟环境已激活,依赖已安装。
  2. 在项目根目录下执行:
    python app.py
    
  3. 浏览器访问 http://127.0.0.1:5000
  4. 可使用演示账号 demo / demo123 登录,或注册新账号。
  5. 尝试添加待办、目标、记账记录,体验数据隔离效果(不同账号数据互不可见)。

六、知识点深度解析

6.1 Flask 核心概念

  • 路由(Route):通过 @app.route 将 URL 绑定到 Python 函数,支持 GET、POST 等方法。
  • 请求对象(request):获取表单数据(request.form)、JSON 数据(request.get_json())、查询参数(request.args)。
  • 响应(Response)render_template 返回 HTML,jsonify 返回 JSON,redirect 重定向。
  • 会话(Session):Flask 默认基于客户端 cookie 的会话,Flask-Login 在此基础上管理用户登录状态。

6.2 SQLAlchemy ORM

  • 模型定义:类继承自 db.Model,属性映射为数据库列。
  • 关系(relationship):定义表之间的关联,backref 允许反向访问,lazy=True 表示在访问时自动查询。
  • 增删改查
    • 查询:Model.query.filter_by(条件).all().first().get_or_404()
    • 添加:db.session.add(实例)db.session.commit()
    • 删除:db.session.delete(实例)commit()
  • 外键约束db.ForeignKey('user.id') 确保数据完整性。

6.3 Flask-Login 认证流程

  • 用户加载回调@login_manager.user_loader 根据用户 ID 返回 User 对象。
  • 登录login_user(user) 将用户 ID 存入 session。
  • 登出logout_user() 清除 session。
  • 保护路由@login_required 装饰器,未登录时自动跳转到 login_view 指定的路由。
  • 模板中获取用户current_user 全局变量,可直接在视图和模板中使用。

6.4 密码安全

  • 哈希加盐generate_password_hash 使用随机盐和 PBKDF2 算法,不可逆。
  • 验证check_password_hash 对比明文与哈希值。
  • 绝不存储明文密码

6.5 REST API 设计原则

  • 使用 HTTP 方法表示操作:GET(获取)、POST(创建)、PUT(更新)、DELETE(删除)。
  • URL 使用资源复数形式:/api/tasks,对特定资源:/api/tasks/<id>
  • 返回 JSON 格式数据,并携带合适的 HTTP 状态码(200、201、404 等)。
  • 无状态:每个请求独立,服务器不保存客户端上下文(除 session 外)。

6.6 前后端交互(AJAX)

  • jQuery 的 $.get$.post$.ajax 简化异步请求。
  • 跨站请求伪造(CSRF)防护:Flask 默认不启用,生产环境需添加 CSRF token(如 Flask-WTF)。
  • 错误处理:统一处理 401 状态码,跳转登录。

6.7 前端数据渲染与用户体验

  • 动态生成 HTML 并插入 DOM,避免页面刷新。
  • 使用 setInterval 定时轮询,模拟实时提醒(也可使用 WebSocket 实现更高效推送)。
  • 响应式布局:使用 flex、grid、百分比宽度适配移动端。

七、常见问题与排查

问题 可能原因 解决方法
运行后报 No module named flask 未激活虚拟环境或未安装依赖 激活 venv,执行 pip install -r requirements.txt
登录后仍提示未登录 SECRET_KEY 未设置或 session 失效 检查 app.config['SECRET_KEY'],确保固定且随机
API 返回 401 未登录或 session 过期 清除浏览器 cookie 后重新登录
提醒横幅不出现 待办提醒时间格式不对 检查 remind_time 是否为 ISO 格式字符串,前端 new Date() 能正确解析
倒计时显示 NaN 目标截止日期字段为 NULL 确保添加目标时填写了有效日期
数据混乱(看到他人数据) 查询时未过滤 user_id 检查每个 API 是否添加了 filter_by(user_id=current_user.id)

八、总结

通过本项目,你亲手搭建了一个完整的多用户 Web 应用,涵盖了认证、数据库操作、前后端分离交互等核心技能。你可以将本项目作为个人效率工具使用,也可以继续扩展,加入更多功能(如共享清单、月度报表等)。

希望这篇教程能帮助你巩固 Flask 全栈开发的知识,并激发更多创造灵感。祝你编码愉快!

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐