Skip to content

前后端开发实战


直播前准备

学习计划

专题课 阶段 教程地址 视频地址
接口测试 L1 常见的接口协议 常见的接口协议 39:09
flask L1 flask 环境安装与配置 flask 环境安装与配置 19:13
flask L1 接口路由技术 接口路由技术 30:12
flask L1 请求与响应 请求方法 请求与响应 请求方法 18:21
flask L1 请求与响应 处理请求数据 请求与响应 处理请求数据 52:08
flask L1 请求与响应 处理响应信息 请求与响应 处理响应信息 30:24
flask L1 测试平台环境配置 测试平台环境配置 11:24
flask L2 模版技术 模版技术 48:48
flask L2 路由跳转 路由跳转 17:50
flask L3 ORM 介绍 ORM 介绍 11:22
flask L3 ORM 中间件配置 ORM 中间件配置 24:00
flask L3 数据库与表管理 数据库与表管理 13:38
flask L3 对象与数据模型 对象与数据模型 18:06
flask L3 数据 CRUD 数据 CRUD 36:31
前端基础 L1 HTML HTML 2:49:14

课程目标

  • 掌握常用的后端框架的基本安装与配置。
  • 掌握路由的定义与使用。
  • 掌握接口请求数据。
  • 掌握接口响应信息。

知识点总览

点击查看:后端开发知识点梳理


需求说明

  • 基于 flask 框架实现 Web 版学生管理系统
  • 系统基于功能需要提供 列表,添加,修改,删除,查询等功能的相关接口
  • 所有数据需通过数据库进行持久化存储
    • 数据表 student 包含以下字段:
      • sid: 学号,主键
      • name: 姓名
      • age: 年龄
      • gender: 性别
  • 列表接口
    • GET 请求方式返回列表页面
  • 添加接口
    • GET 请求方式返回添加页面
    • POST 请求方式完成添加操作,操作完成后返回列表页面,需要包含新添加的数据
  • 修改接口
    • 所有修改相关请求需要携带要修改学生的 ID 信息
    • GET 请求方式返回修改页面,返回修改学生在修改页面回显的数据
    • POST 请求方式完成修改操作,操作完成后返回列表页面,需要包含修改后的数据
  • 删除接口
    • 所有修改相关请求需要携带要删除学生的 ID 信息
    • GET 请求方式删除指定学生信息,操作完成后返回列表页面,不显示删除的数据

实战思路

uml diagram


环境准备

  • 安装 Flask 框架:pip install flask
  • 安装 sqlalchemy:pip install sqlalchemy

创建 Flask 实例

创建 Server.py

from flask import Flask, render_template

# 创建 flask 实例
app = Flask(__name__)

# 首页接口
@app.route("/")
def index():
    return render_template("index.html")

# 添加页面接口
@app.route("/add")
def add():
    return render_template("add.html")

# 修改页面接口
@app.route("/change")
def change():
    return render_template("change.html")


if __name__ == '__main__':
    app.run(debug=True, port=5050)

注意:服务器程序运行起来前,需先建立 templates 文件夹,并建立 index.html、add.html 和 change.html 三个文件。


学生管理系统首页 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>学生管理系统</title>
  </head>
  <body>
    <h1>学生管理系统</h1>
  </body>
</html>


添加学生页面 add.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>学生管理系统</title>
  </head>
  <body>
    <h1>添加学生</h1>
  </body>
</html>


修改学生页面 change.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>学生管理系统</title>
  </head>
  <body>
    <h1>修改学生</h1>
  </body>
</html>


数据库准备

使用 sqlite 数据库,使用 SQLAlchemy 创建数据库连接。

# SQLAlchemy 设置
Base = declarative_base()
# 定义数据库
# 创建引擎,连接到数据库
engine = create_engine("sqlite+pysqlite:///student.db", echo=True)
# 创建session对象
# DBSession = sessionmaker(bind=engine)
# db_session: Session = DBSession()
# 解决session的复用问题   不然会报使用的时候前一个session没有回滚
DBSession = scoped_session(sessionmaker(bind=engine))

数据库表 student

# 创建用例表
class StudentModel(Base):
    # 表名
    __tablename__ = "student_info"
    # 用例id,主键,唯一
    sid = Column(Integer, primary_key=True)
    name = Column(String(80), nullable=False, unique=True)
    gender = Column(String(80), nullable=False)
    age = Column(Integer)
    address = Column(String(160))

创建数据表

if __name__ == '__main__':
    Base.metadata.create_all(bind=engine)

核心逻辑

class StudentManager:
    def add_student(self, student_info):
        """
        添加学生信息。
        在添加学生信息之前,会通过验证年龄的合法性来确保学生信息的准确性。
        如果年龄信息不合法,则不会添加学生信息。
        参数:
        - student_info (dict): 包含学生信息的字典,其中可能包括学生的年龄('age')和其他信息。
        返回:
        (bool, str): 返回添加是否成功以及可能的错误信息(如果有的话)。
        """
        # 验证学生年龄是否合法
        error = self.validate_age(student_info.get("age"))
        if error:
            return False, error

        # 构造字典并排除 'sid' 字段
        dic = {k: v for k, v in student_info.items() if k != "sid"}

        try:
            # 创建学生实例并添加到数据库
            DBSession.add(StudentModel(**dic))
            DBSession.commit()  # 提交数据库事务
            return True, None  # 成功时返回True和None
        except Exception as e:
            # 捕获异常,返回错误信息
            return False, f"添加学生信息失败,错误信息:{str(e)}"

    def get_student_by_id(self, sid):
        """
        根据学生ID查询学生信息。

        通过提供的学生ID,使用数据库会话查询StudentModel模型中匹配的学生信息。
        如果找到匹配的学生,则返回该学生对象;如果没有找到,则返回None。
        参数:
        - sid (int): 学生的唯一标识符(ID)。
        返回:
        - StudentModel实例或None: 如果找到匹配的学生信息,则返回StudentModel实例,否则返回None。
        """
        return DBSession.query(StudentModel).filter_by(sid=sid).first()

    def get_all_students(self):
        """
        获取所有学生信息
        通过数据库会话查询所有学生记录并返回
        Returns:
            list: 包含所有学生信息的列表,每个学生信息是一个StudentModel实例
        """
        return DBSession.query(StudentModel).all()

    def update_student(self, student_info):
        """
        更新学生信息。
        参数:
        student_info (dict): 包含学生信息的字典,必须包含学生的唯一标识符 'sid' 和其他需要更新的字段。
        返回:
        tuple: (bool, str),表示更新是否成功和错误消息(如果有)。
        """
        # 验证学生年龄是否合法
        error = self.validate_age(student_info.get("age"))
        if error:
            return False, error  # 年龄验证未通过,直接返回

        try:
            # 查询指定的学生
            student_session = DBSession.query(StudentModel).filter_by(sid=student_info.get("sid"))
            if not student_session.first():
                return False, "学生不存在"  # 检查是否找到学生

            # 更新学生信息,排除 'sid' 字段
            dic = {k: v for k, v in student_info.items() if k != "sid"}
            student_session.update(dic)
            DBSession.commit()  # 提交更改
            return True, None  # 成功返回
        except Exception as e:
            return False, f"更新学生信息失败,错误信息:{str(e)}"

    def delete_student(self, sid):
        """
        根据学生ID删除学生记录。
        本函数通过查询学生模型中的记录,匹配传入的学生ID,并将该记录从数据库中删除。
        这是一个数据库操作,需要确保数据库连接正确,并且在函数执行后提交事务。
        参数:
        - sid (int): 学生的唯一标识符,用于定位并删除特定的学生记录。
        返回:
        本函数不返回任何值,但会修改数据库中的数据。
        异常:
        如果数据库操作失败,可能会抛出异常。本函数不处理这些异常,应由调用者处理。
        """
        # 查询并删除数据库中匹配给定学生ID的学生记录
        DBSession.query(StudentModel).filter_by(sid=sid).delete()
        # 提交数据库事务,确保数据更改被保存
        DBSession.commit()

    def search_students(self, search_query):
        """
        根据搜索查询筛选学生。
        此方法从数据库中获取所有学生信息,并根据提供的搜索查询过滤学生列表。
        搜索查询会在学生的姓名、性别、地址中进行匹配,如果是数字,则会与学生的年龄进行比较。
        参数:
        - search_query (str): 用于搜索学生的查询字符串。
        返回:
        - filter_students (list): 匹配搜索查询的学生列表。
        """
        # 初始化一个空列表,用于存储匹配搜索查询的学生对象
        filter_students = []
        # 从数据库中查询所有学生信息
        students = DBSession.query(StudentModel).all()
        # 如果搜索查询存在且不为空,则执行搜索操作
        if search_query:
            for stu in students:
                # 检查搜索查询是否存在于学生的姓名、性别或地址中
                if any(search_query in field for field in [stu.name, stu.gender, stu.address]) or (
                        # 如果搜索查询是数字,检查它是否与学生的年龄匹配
                        search_query.isdigit() and int(search_query) == stu.age):
                    # 如果匹配成功,则将学生添加到过滤列表中
                    filter_students.append(stu)
        # 返回过滤后的学生列表
        return filter_students

    def validate_student_data(self, name, age, gender, address):
        """
        验证学生信息的完整性。
        参数:
        - name: 学生姓名
        - age: 学生年龄
        - gender: 学生性别
        - address: 学生住址
        返回:
        - 如果信息不完整,返回提示信息'所有信息都要填写'
        - 否则调用validate_age函数验证年龄,并返回验证结果
        """
        # 检查所有学生信息是否都已提供
        if not all([name, age, gender, address]):
            return "所有信息都要填写"
        # 调用validate_age函数进行年龄验证
        error = self.validate_age(age)
        return error

    def validate_age(self, age):
        """验证年龄的合法性
        参数:
        age -- 用户输入的年龄,可以是任何类型,但函数会检查其是否能被转换为整型
        返回值:
        如果年龄不合法,返回相应的错误信息字符串;
        如果年龄合法,返回 None。
        """
        try:
            # 将输入的年龄转换为整型
            int_age = int(age)
            # 检查年龄是否在合理范围内(0到200岁之间)
            if int_age < 0 or int_age > 200:
                return "用户输入的年龄超出限制"
            return None
        except ValueError:
            # 如果年龄不能被转换为整型,则返回错误信息
            return "年龄必须是整型"

sqlalchemy 的 session 处理 避免长期占用数据库连接

app = Flask(__name__)

# 开启数据库跟踪模式
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True



@app.before_request
def before_request():
    # 在每个请求前执行的代码
    # 在请求开始的时候实例化DBsession
    DBSession()


@app.teardown_request
def teardown_request(exception=None):
    # 在每个请求后执行的代码
    if exception:
        DBSession.rollback()
    # 请求结束之后remove掉DBsession
    DBSession.remove()

查询数据接口

调用核心逻辑,返回学生列表。

@app.route("/")
@app.route("/index")
def index():
  # 获取搜索查询
    search_query = request.args.get('search', '')
    # 如果有搜索查询,则调用搜索接口
    if search_query:
        filtered_students = student_manager.search_students(search_query)
        # 返回搜索结果,使用模板渲染
        return render_template("index.html", students=filtered_students)
    # 否则,返回所有学生列表,使用模板渲染
    return render_template("index.html", students=student_manager.get_all_students())

使用模版继承

由于 HTML 模板中包含公共部分,所以使用模板继承,将公共部分提取出来,其他页面继承公共模板。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>{% block title %}学生管理系统{% endblock %}</title>
  </head>
  <body>
    <div class="header">
      <h1>学生管理系统</h1>
    </div>

    <div class="container">{% block content %}{% endblock %}</div>

    <div class="footer">
      <p>&copy; 2024 My application. All rights reserved.</p>
    </div>
  </body>
</html>

查询页面的模板

{% extends "base.html" %} {% block title %}学生列表{% endblock %} {% block
content %}
<div class="header-actions">
  <div class="item btn-container">
    <a href="/add" class="btn">新增</a>
  </div>
  <div class="search-container">
    <form method="GET" action="/index">
      <input type="text" name="search" placeholder="搜索学生" />
      <button type="submit" class="btn">搜索</button>
      <a href="/index" class="btn clear-btn">清除搜索</a>
    </form>
  </div>
</div>
<table>
  <tr>
    <th>学号</th>
    <th>姓名</th>
    <th>年龄</th>
    <th>性别</th>
    <th>地址</th>
    <th>操作</th>
  </tr>
  {% for s in students %}
  <tr>
    <td>{{ s.sid }}</td>
    <td>{{ s.name }}</td>
    <td>{{ s.age }}</td>
    <td>{{ s.gender }}</td>
    <td>{{ s.address }}</td>
    <td class="action">
      <a href="/change/{{ s.sid }}">修改</a>
      <a href="/del/{{ s.sid }}">删除</a>
    </td>
  </tr>
  {% endfor %}
</table>
{% endblock %}


添加数据接口

@app.route("/add", methods=['GET', 'POST'])
def add():
    error = None
    # 判断请求方式,如果是GET请求,则返回添加页面,否则执行添加操作
    if request.method == "POST":
        # 调用validate_student_data函数对表单数据进行验证
        error = student_manager.validate_student_data(**request.form)
        # 如果验证通过,则调用add_student函数添加数据
        if not error:
            flag, error = student_manager.add_student(request.form)
            if not flag:
              # 如果添加失败,则返回添加页面,并显示错误信息
                return render_template("add.html", error=error)
            # 添加成功,则重定向到首页
            return redirect(url_for('index'))
    # 如果不是POST则返回添加页面,用于填写数据
    return render_template("add.html", error=error)

添加页面模板

{% extends "base.html" %} {% block title %}添加学生{% endblock %} {% block
content %}
<form action="/add" method="post">
  <div class="item">
    <label>姓名:</label>
    <input type="text" placeholder="请输入姓名" name="name" />
  </div>
  <div class="item">
    <label>年龄:</label>
    <input type="text" placeholder="请输入年龄" name="age" />
  </div>
  <div class="item">
    <label>性别:</label>
    <input type="text" placeholder="请输入性别" name="gender" />
  </div>
  <div class="item">
    <label>地址:</label>
    <input type="text" placeholder="请输入地址" name="address" />
  </div>
  <div class="item btn-container">
    <button type="submit" class="btn">添加</button>
    <a href="/index" class="btn">返回列表</a>
  </div>
</form>
{% if error %}
<div class="error" style="color: red;">{{ error }}</div>
{% endif %} {% endblock %}


修改数据接口

@app.route("/change/<int:sid>", methods=['GET', 'POST'])
def change(sid):
    # 获取学生信息
    student = student_manager.get_student_by_id(sid)
    error = None
    # 判断请求方式,如果是GET请求,则返回修改页面,否则执行修改操作
    if request.method == "POST":
        # 调用validate 校验数据
        error = student_manager.validate_student_data(**request.form)
        if not error:
           # 把flask的表单数据转化为字典
            dict_data = dict(request.form)
           # 添加sid
            dict_data['sid'] = sid
           # 调用update_student函数修改数据
            flag, error = student_manager.update_student(dict_data)
            if not flag:
              # 如果修改失败,则返回修改页面,并显示错误信息
                return render_template("change.html", student=student, error=error)
           # 修改成功,则重定向到首页
           return redirect(url_for('index'))
    # 如果不是POST则返回修改页面,并且把查到的学员数据返回到页面,用于填写数据
    return render_template("change.html", student=student, error=error)

修改页面模板

{% extends "base.html" %} {% block title %}修改学生信息{% endblock %} {% block
content %}
<form action="/change/{{ student.sid }}" method="post">
  <div class="item">
    <label>姓名:</label>
    <input type="text" name="name" value="{{ student.name }}" />
  </div>
  <div class="item">
    <label>年龄:</label>
    <input type="text" name="age" value="{{ student.age }}" />
  </div>
  <div class="item">
    <label>性别:</label>
    <input type="text" name="gender" value="{{ student.gender }}" />
  </div>
  <div class="item">
    <label>地址:</label>
    <input type="text" name="address" value="{{ student.address }}" />
  </div>
  <div class="item btn-container">
    <button type="submit" class="btn">修改</button>
    <a href="/index" class="btn">返回列表</a>
  </div>
  {% if error %}
  <div class="error" style="color: red;">{{ error }}</div>
  {% endif %}
</form>
{% endblock %}

删除数据接口

@app.route("/del/<int:sid>")
def delete(sid):
    # 调用delete_student函数删除数据
    student_manager.delete_student(sid)
    # 重定向到首页
    return redirect(url_for('index'))

页面美化

  • 美化 css 文件,点击下载:美化 css
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>学生管理系统</title>
        <link rel="stylesheet" type="text/css" href="static/style.css" />
    </head>
    <body>
        <div class="header">
        <h1>学生管理系统</h1>
        </div>
    
        <div class="container"></div>
    
        <div class="footer">
        <p>&copy; 2024 My application. All rights reserved.</p>
        </div>
    </body>
    </html>
    


课后练习

点击查看课后练习


总结

  • 掌握常用的后端框架的基本安装与配置。
  • 掌握路由的定义与使用。
  • 掌握接口请求数据。
  • 掌握接口响应信息。