前后端开发实战
直播前准备
学习计划
专题课 | 阶段 | 教程地址 | 视频地址 |
---|---|---|---|
接口测试 | 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: 性别
- 数据表 student 包含以下字段:
- 列表接口
- GET 请求方式返回列表页面
- 添加接口
- GET 请求方式返回添加页面
- POST 请求方式完成添加操作,操作完成后返回列表页面,需要包含新添加的数据
- 修改接口
- 所有修改相关请求需要携带要修改学生的 ID 信息
- GET 请求方式返回修改页面,返回修改学生在修改页面回显的数据
- POST 请求方式完成修改操作,操作完成后返回列表页面,需要包含修改后的数据
- 删除接口
- 所有修改相关请求需要携带要删除学生的 ID 信息
- GET 请求方式删除指定学生信息,操作完成后返回列表页面,不显示删除的数据
实战思路
环境准备
- 安装 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>© 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>© 2024 My application. All rights reserved.</p> </div> </body> </html>
课后练习
总结
- 掌握常用的后端框架的基本安装与配置。
- 掌握路由的定义与使用。
- 掌握接口请求数据。
- 掌握接口响应信息。