|
|
@ -6,73 +6,93 @@ from datetime import datetime
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class SQLiteStudentDAL(IStudentDAL):
|
|
|
|
class SQLiteStudentDAL(IStudentDAL):
|
|
|
|
"""学生信息的SQLite数据库存储实现"""
|
|
|
|
"""学生信息的SQLite数据库存储实现,实现了IStudentDAL接口,提供基于SQLite的学生数据持久化功能"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, db_path: str = 'students.db'):
|
|
|
|
def __init__(self, db_path: str = 'students.db'):
|
|
|
|
|
|
|
|
"""初始化SQLite数据访问层
|
|
|
|
|
|
|
|
:param db_path: 数据库文件路径,默认使用当前目录下的students.db"""
|
|
|
|
self.db_path = db_path
|
|
|
|
self.db_path = db_path
|
|
|
|
self._create_table()
|
|
|
|
self._create_table() # 确保学生表存在,不存在则创建
|
|
|
|
|
|
|
|
|
|
|
|
def _create_table(self):
|
|
|
|
def _create_table(self):
|
|
|
|
"""创建学生表(如果不存在)"""
|
|
|
|
"""创建学生信息表(如果不存在)
|
|
|
|
|
|
|
|
表结构设计:
|
|
|
|
|
|
|
|
- id:自增主键
|
|
|
|
|
|
|
|
- id_card/stu_id:设置唯一约束,确保数据唯一性
|
|
|
|
|
|
|
|
- enrollment_date:存储为YYYY-MM-DD格式字符串
|
|
|
|
|
|
|
|
- gender:布尔值存储为整数(0/1)"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute('''
|
|
|
|
cursor.execute('''
|
|
|
|
CREATE TABLE IF NOT EXISTS students (
|
|
|
|
CREATE TABLE IF NOT EXISTS students (
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
name TEXT NOT NULL, -- 学生姓名,非空
|
|
|
|
id_card TEXT UNIQUE NOT NULL,
|
|
|
|
id_card TEXT UNIQUE NOT NULL, -- 身份证号,唯一且非空
|
|
|
|
stu_id TEXT UNIQUE NOT NULL,
|
|
|
|
stu_id TEXT UNIQUE NOT NULL, -- 学号,唯一且非空
|
|
|
|
gender INTEGER,
|
|
|
|
gender INTEGER, -- 性别(0/1)
|
|
|
|
height INTEGER,
|
|
|
|
height INTEGER, -- 身高(厘米)
|
|
|
|
weight REAL,
|
|
|
|
weight REAL, -- 体重(公斤)
|
|
|
|
enrollment_date TEXT,
|
|
|
|
enrollment_date TEXT, -- 入学日期(YYYY-MM-DD)
|
|
|
|
class_name TEXT,
|
|
|
|
class_name TEXT, -- 班级名称
|
|
|
|
major TEXT,
|
|
|
|
major TEXT, -- 专业
|
|
|
|
email TEXT,
|
|
|
|
email TEXT, -- 邮箱
|
|
|
|
phone TEXT
|
|
|
|
phone TEXT -- 电话
|
|
|
|
)
|
|
|
|
)
|
|
|
|
''')
|
|
|
|
''')
|
|
|
|
conn.commit()
|
|
|
|
conn.commit() # 提交DDL语句
|
|
|
|
|
|
|
|
|
|
|
|
def _row_to_student(self, row: tuple) -> Optional[Student]:
|
|
|
|
def _row_to_student(self, row: tuple) -> Optional[Student]:
|
|
|
|
"""将数据库行转换为Student对象"""
|
|
|
|
"""将数据库查询结果行转换为Student对象
|
|
|
|
if not row:
|
|
|
|
:param row: 查询结果元组
|
|
|
|
|
|
|
|
:return: 转换后的Student对象或None
|
|
|
|
|
|
|
|
处理逻辑:
|
|
|
|
|
|
|
|
- 索引映射:将数据库列索引映射到Student属性
|
|
|
|
|
|
|
|
- 类型转换:日期字符串转date对象,整数转布尔值"""
|
|
|
|
|
|
|
|
if not row: # 处理空结果
|
|
|
|
return None
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
# 处理日期类型
|
|
|
|
# 处理日期类型:从字符串转换为datetime.date
|
|
|
|
enrollment_date = row[7]
|
|
|
|
enrollment_date = row[7] # 注意:索引7对应enrollment_date列
|
|
|
|
if enrollment_date:
|
|
|
|
if enrollment_date:
|
|
|
|
enrollment_date = datetime.strptime(enrollment_date, '%Y-%m-%d').date()
|
|
|
|
enrollment_date = datetime.strptime(enrollment_date, '%Y-%m-%d').date()
|
|
|
|
|
|
|
|
|
|
|
|
# 处理布尔类型
|
|
|
|
# 处理布尔类型:数据库中存储为0/1,转换为True/False
|
|
|
|
gender = row[3]
|
|
|
|
gender = row[4] # 注意:索引4对应gender列
|
|
|
|
if gender is not None:
|
|
|
|
if gender is not None:
|
|
|
|
gender = bool(gender)
|
|
|
|
gender = bool(gender)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 构建并返回Student对象
|
|
|
|
return Student(
|
|
|
|
return Student(
|
|
|
|
name=row[1],
|
|
|
|
name=row[1], # 姓名
|
|
|
|
id_card=row[2],
|
|
|
|
id_card=row[2], # 身份证号
|
|
|
|
stu_id=row[3],
|
|
|
|
stu_id=row[3], # 学号
|
|
|
|
gender=gender,
|
|
|
|
gender=gender, # 性别(布尔值)
|
|
|
|
height=row[4],
|
|
|
|
height=row[4], # 身高
|
|
|
|
weight=row[5],
|
|
|
|
weight=row[5], # 体重
|
|
|
|
enrollment_date=enrollment_date,
|
|
|
|
enrollment_date=enrollment_date, # 入学日期
|
|
|
|
class_name=row[8],
|
|
|
|
class_name=row[8], # 班级
|
|
|
|
major=row[9],
|
|
|
|
major=row[9], # 专业
|
|
|
|
email=row[10],
|
|
|
|
email=row[10], # 邮箱
|
|
|
|
phone=row[11]
|
|
|
|
phone=row[11] # 电话
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def get_by_id(self, id_card: str) -> Optional[Student]:
|
|
|
|
def get_by_id(self, id_card: str) -> Optional[Student]:
|
|
|
|
"""根据身份证号获取学生信息"""
|
|
|
|
"""根据身份证号查询学生信息
|
|
|
|
|
|
|
|
:param id_card: 待查询的身份证号
|
|
|
|
|
|
|
|
:return: 匹配的Student对象或None
|
|
|
|
|
|
|
|
实现逻辑:
|
|
|
|
|
|
|
|
- 使用参数化查询防止SQL注入
|
|
|
|
|
|
|
|
- 通过id_card唯一索引快速定位记录"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM students WHERE id_card = ?", (id_card,))
|
|
|
|
cursor.execute("SELECT * FROM students WHERE id_card = ?", (id_card,))
|
|
|
|
row = cursor.fetchone()
|
|
|
|
row = cursor.fetchone() # 获取单条记录
|
|
|
|
return self._row_to_student(row)
|
|
|
|
return self._row_to_student(row) # 转换为Student对象
|
|
|
|
|
|
|
|
|
|
|
|
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
|
|
|
|
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
|
|
|
|
"""根据学号获取学生信息"""
|
|
|
|
"""根据学号查询学生信息,逻辑同get_by_id
|
|
|
|
|
|
|
|
:param stu_id: 待查询的学号
|
|
|
|
|
|
|
|
:return: 匹配的Student对象或None"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM students WHERE stu_id = ?", (stu_id,))
|
|
|
|
cursor.execute("SELECT * FROM students WHERE stu_id = ?", (stu_id,))
|
|
|
@ -80,15 +100,26 @@ class SQLiteStudentDAL(IStudentDAL):
|
|
|
|
return self._row_to_student(row)
|
|
|
|
return self._row_to_student(row)
|
|
|
|
|
|
|
|
|
|
|
|
def get_all(self) -> List[Student]:
|
|
|
|
def get_all(self) -> List[Student]:
|
|
|
|
"""获取所有学生信息"""
|
|
|
|
"""获取所有学生信息
|
|
|
|
|
|
|
|
:return: 包含所有学生的列表
|
|
|
|
|
|
|
|
实现逻辑:
|
|
|
|
|
|
|
|
- 查询全量数据
|
|
|
|
|
|
|
|
- 批量转换为Student对象"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM students")
|
|
|
|
cursor.execute("SELECT * FROM students")
|
|
|
|
rows = cursor.fetchall()
|
|
|
|
rows = cursor.fetchall() # 获取所有记录
|
|
|
|
return [self._row_to_student(row) for row in rows]
|
|
|
|
return [self._row_to_student(row) for row in rows] # 列表推导式批量转换
|
|
|
|
|
|
|
|
|
|
|
|
def add(self, student: Student) -> bool:
|
|
|
|
def add(self, student: Student) -> bool:
|
|
|
|
"""添加学生信息"""
|
|
|
|
"""添加学生信息
|
|
|
|
|
|
|
|
:param student: 待添加的Student对象
|
|
|
|
|
|
|
|
:return: 添加成功返回True,失败返回False
|
|
|
|
|
|
|
|
业务逻辑:
|
|
|
|
|
|
|
|
1. 使用事务确保数据一致性
|
|
|
|
|
|
|
|
2. 自动处理日期类型转换
|
|
|
|
|
|
|
|
3. 利用数据库唯一约束防止重复数据
|
|
|
|
|
|
|
|
4. 异常处理:IntegrityError表示违反唯一性约束"""
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
@ -103,20 +134,26 @@ class SQLiteStudentDAL(IStudentDAL):
|
|
|
|
student.gender,
|
|
|
|
student.gender,
|
|
|
|
student.height,
|
|
|
|
student.height,
|
|
|
|
student.weight,
|
|
|
|
student.weight,
|
|
|
|
str(student.enrollment_date) if student.enrollment_date else None,
|
|
|
|
str(student.enrollment_date) if student.enrollment_date else None, # 日期转字符串
|
|
|
|
student.class_name,
|
|
|
|
student.class_name,
|
|
|
|
student.major,
|
|
|
|
student.major,
|
|
|
|
student.email,
|
|
|
|
student.email,
|
|
|
|
student.phone
|
|
|
|
student.phone
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
conn.commit()
|
|
|
|
conn.commit() # 提交事务
|
|
|
|
return True
|
|
|
|
return True
|
|
|
|
except sqlite3.IntegrityError: # 违反唯一约束
|
|
|
|
except sqlite3.IntegrityError: # 违反唯一约束(身份证号或学号重复)
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
def update(self, student: Student) -> bool:
|
|
|
|
def update(self, student: Student) -> bool:
|
|
|
|
"""更新学生信息"""
|
|
|
|
"""更新学生信息
|
|
|
|
|
|
|
|
:param student: 包含更新信息的Student对象
|
|
|
|
|
|
|
|
:return: 更新成功返回True,失败返回False
|
|
|
|
|
|
|
|
实现逻辑:
|
|
|
|
|
|
|
|
1. 以身份证号作为唯一标识
|
|
|
|
|
|
|
|
2. 返回受影响的行数判断操作是否成功
|
|
|
|
|
|
|
|
3. 自动处理日期类型转换"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute(
|
|
|
|
cursor.execute(
|
|
|
@ -129,27 +166,34 @@ class SQLiteStudentDAL(IStudentDAL):
|
|
|
|
student.gender,
|
|
|
|
student.gender,
|
|
|
|
student.height,
|
|
|
|
student.height,
|
|
|
|
student.weight,
|
|
|
|
student.weight,
|
|
|
|
str(student.enrollment_date) if student.enrollment_date else None,
|
|
|
|
str(student.enrollment_date) if student.enrollment_date else None, # 日期转字符串
|
|
|
|
student.class_name,
|
|
|
|
student.class_name,
|
|
|
|
student.major,
|
|
|
|
student.major,
|
|
|
|
student.email,
|
|
|
|
student.email,
|
|
|
|
student.phone,
|
|
|
|
student.phone,
|
|
|
|
student.id_card
|
|
|
|
student.id_card # 条件:根据身份证号更新
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
conn.commit()
|
|
|
|
conn.commit()
|
|
|
|
return cursor.rowcount > 0
|
|
|
|
return cursor.rowcount > 0 # 判断是否有记录被更新
|
|
|
|
|
|
|
|
|
|
|
|
def delete_by_id(self, id_card: str) -> bool:
|
|
|
|
def delete_by_id(self, id_card: str) -> bool:
|
|
|
|
"""根据身份证号删除学生信息"""
|
|
|
|
"""根据身份证号删除学生信息
|
|
|
|
|
|
|
|
:param id_card: 待删除学生的身份证号
|
|
|
|
|
|
|
|
:return: 删除成功返回True,失败返回False
|
|
|
|
|
|
|
|
实现逻辑:
|
|
|
|
|
|
|
|
- 通过受影响行数判断删除是否成功
|
|
|
|
|
|
|
|
- 使用事务确保操作原子性"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("DELETE FROM students WHERE id_card = ?", (id_card,))
|
|
|
|
cursor.execute("DELETE FROM students WHERE id_card = ?", (id_card,))
|
|
|
|
conn.commit()
|
|
|
|
conn.commit()
|
|
|
|
return cursor.rowcount > 0
|
|
|
|
return cursor.rowcount > 0 # 判断是否有记录被删除
|
|
|
|
|
|
|
|
|
|
|
|
def delete_by_stu_id(self, stu_id: str) -> bool:
|
|
|
|
def delete_by_stu_id(self, stu_id: str) -> bool:
|
|
|
|
"""根据学号删除学生信息"""
|
|
|
|
"""根据学号删除学生信息,逻辑同delete_by_id
|
|
|
|
|
|
|
|
:param stu_id: 待删除学生的学号
|
|
|
|
|
|
|
|
:return: 删除成功返回True,失败返回False"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("DELETE FROM students WHERE stu_id = ?", (stu_id,))
|
|
|
|
cursor.execute("DELETE FROM students WHERE stu_id = ?", (stu_id,))
|
|
|
@ -157,7 +201,12 @@ class SQLiteStudentDAL(IStudentDAL):
|
|
|
|
return cursor.rowcount > 0
|
|
|
|
return cursor.rowcount > 0
|
|
|
|
|
|
|
|
|
|
|
|
def search_by_name(self, name: str) -> List[Student]:
|
|
|
|
def search_by_name(self, name: str) -> List[Student]:
|
|
|
|
"""根据姓名模糊查询学生信息"""
|
|
|
|
"""根据姓名模糊查询学生信息
|
|
|
|
|
|
|
|
:param name: 待查询的姓名片段
|
|
|
|
|
|
|
|
:return: 包含匹配学生的列表
|
|
|
|
|
|
|
|
实现逻辑:
|
|
|
|
|
|
|
|
- 使用LIKE '%name%'实现模糊匹配
|
|
|
|
|
|
|
|
- 支持空字符串查询(返回所有记录)"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM students WHERE name LIKE ?", ('%' + name + '%',))
|
|
|
|
cursor.execute("SELECT * FROM students WHERE name LIKE ?", ('%' + name + '%',))
|
|
|
@ -165,7 +214,9 @@ class SQLiteStudentDAL(IStudentDAL):
|
|
|
|
return [self._row_to_student(row) for row in rows]
|
|
|
|
return [self._row_to_student(row) for row in rows]
|
|
|
|
|
|
|
|
|
|
|
|
def search_by_class(self, class_name: str) -> List[Student]:
|
|
|
|
def search_by_class(self, class_name: str) -> List[Student]:
|
|
|
|
"""根据班级模糊查询学生信息"""
|
|
|
|
"""根据班级模糊查询学生信息,逻辑同search_by_name
|
|
|
|
|
|
|
|
:param class_name: 待查询的班级片段
|
|
|
|
|
|
|
|
:return: 包含匹配学生的列表"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM students WHERE class_name LIKE ?", ('%' + class_name + '%',))
|
|
|
|
cursor.execute("SELECT * FROM students WHERE class_name LIKE ?", ('%' + class_name + '%',))
|
|
|
@ -173,9 +224,11 @@ class SQLiteStudentDAL(IStudentDAL):
|
|
|
|
return [self._row_to_student(row) for row in rows]
|
|
|
|
return [self._row_to_student(row) for row in rows]
|
|
|
|
|
|
|
|
|
|
|
|
def search_by_major(self, major: str) -> List[Student]:
|
|
|
|
def search_by_major(self, major: str) -> List[Student]:
|
|
|
|
"""根据专业模糊查询学生信息"""
|
|
|
|
"""根据专业模糊查询学生信息,逻辑同search_by_name
|
|
|
|
|
|
|
|
:param major: 待查询的专业片段
|
|
|
|
|
|
|
|
:return: 包含匹配学生的列表"""
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
with sqlite3.connect(self.db_path) as conn:
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute("SELECT * FROM students WHERE major LIKE ?", ('%' + major + '%',))
|
|
|
|
cursor.execute("SELECT * FROM students WHERE major LIKE ?", ('%' + major + '%',))
|
|
|
|
rows = cursor.fetchall()
|
|
|
|
rows = cursor.fetchall()
|
|
|
|
return [self._row_to_student(row) for row in rows]
|
|
|
|
return [self._row_to_student(row) for row in rows]
|
|
|
|