Compare commits

...

No commits in common. 'master' and 'main' have entirely different histories.
master ... main

3
.idea/.gitignore vendored

@ -1,3 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.11" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.12 (PythonProject)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11" project-jdk-type="Python SDK" />
</project>

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/PythonProject.iml" filepath="$PROJECT_DIR$/.idea/PythonProject.iml" />
</modules>
</component>
</project>

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

@ -1,173 +0,0 @@
from datetime import date
from typing import List, Optional, Dict
from copy import deepcopy
from model.student import Student
from DAL.clear_all_student import IStudentDAL
class StudentBLL:
def __init__(self, dal: IStudentDAL):
self.dal = dal
def add_student(self, student: Student) -> bool:
"""添加学生信息,进行完整校验和唯一性检查"""
# 1. 数据校验
if not student.is_valid:
raise ValueError(f"学生数据校验不通过。错误信息: {student.get_errors()}")
# 2. 唯一性检查
if self.dal.check_student_exists_by_id_card(student.id_card):
raise ValueError(f"身份证号 {student.id_card} 已存在")
if self.dal.check_student_exists_by_stu_id(student.stu_id):
raise ValueError(f"学号 {student.stu_id} 已存在")
# 3. 业务规则检查
today = date.today()
if student.birth_date and student.birth_date > today:
raise ValueError("出生日期不能晚于当前日期")
if student.enrollment_date and student.enrollment_date > today:
raise ValueError("入学日期不能晚于当前日期")
if student.height and (student.height <= 0 or student.height > 250):
raise ValueError("身高必须在0-250厘米之间")
if student.weight and (student.weight <= 0 or student.weight > 300):
raise ValueError("体重必须在0-300公斤之间")
# 4. 添加学生
return self.dal.add_student(student)
def delete_student_by_id_card(self, id_card: str) -> bool:
"""根据身份证号删除学生信息"""
if not self.dal.check_student_exists_by_id_card(id_card):
raise ValueError(f"身份证号 {id_card} 不存在")
return self.dal.delete_student_by_id_card(id_card)
def delete_student_by_stu_id(self, stu_id: str) -> bool:
"""根据学号删除学生信息"""
if not self.dal.check_student_exists_by_stu_id(stu_id):
raise ValueError(f"学号 {stu_id} 不存在")
return self.dal.delete_student_by_stu_id(stu_id)
def update_student(self, id_card: str, student: Student) -> bool:
"""完整更新学生信息"""
# 1. 存在性检查
if not self.dal.check_student_exists_by_id_card(id_card):
raise ValueError(f"身份证号 {id_card} 不存在")
# 2. 数据校验
if not student.is_valid:
raise ValueError(f"学生数据校验不通过。错误信息: {student.get_errors()}")
# 3. 业务规则检查
today = date.today()
if student.birth_date and student.birth_date > today:
raise ValueError("出生日期不能晚于当前日期")
if student.enrollment_date and student.enrollment_date > today:
raise ValueError("入学日期不能晚于当前日期")
if student.height and (student.height <= 0 or student.height > 250):
raise ValueError("身高必须在0-250厘米之间")
if student.weight and (student.weight <= 0 or student.weight > 300):
raise ValueError("体重必须在0-300公斤之间")
# 4. 执行更新
return self.dal.update_student(id_card, student)
def update_student_partial(self, id_card: str, **kwargs) -> bool:
"""部分更新学生信息"""
# 1. 存在性检查
student = self.dal.find_student_by_id_card(id_card)
if not student:
raise ValueError(f"身份证号 {id_card} 不存在")
# 2. 保护关键字段 - 身份证号不可更改
if 'id_card' in kwargs:
raise ValueError("身份证号不可更改")
# 3. 创建更新后的学生对象
updated_student = deepcopy(student)
for key, value in kwargs.items():
if hasattr(updated_student, key):
setattr(updated_student, key, value)
# 4. 数据校验
if not updated_student.is_valid:
raise ValueError(f"学生数据校验不通过。错误信息: {updated_student.get_errors()}")
# 5. 业务规则检查
today = date.today()
if updated_student.birth_date and updated_student.birth_date > today:
raise ValueError("出生日期不能晚于当前日期")
if updated_student.enrollment_date and updated_student.enrollment_date > today:
raise ValueError("入学日期不能晚于当前日期")
if updated_student.height and (updated_student.height <= 0 or updated_student.height > 250):
raise ValueError("身高必须在0-250厘米之间")
if updated_student.weight and (updated_student.weight <= 0 or updated_student.weight > 300):
raise ValueError("体重必须在0-300公斤之间")
# 6. 学号唯一性检查(如果学号有变化)
if 'stu_id' in kwargs and kwargs['stu_id'] != student.stu_id:
if self.dal.check_student_exists_by_stu_id(kwargs['stu_id']):
raise ValueError(f"学号 {kwargs['stu_id']} 已存在")
# 7. 执行更新
return self.dal.update_student(id_card, updated_student)
def get_student_by_id_card(self, id_card: str) -> Optional[Student]:
"""根据身份证号查询学生信息"""
return self.dal.find_student_by_id_card(id_card)
def get_student_by_stu_id(self, stu_id: str) -> Optional[Student]:
"""根据学号查询学生信息"""
return self.dal.find_student_by_stu_id(stu_id)
def get_students_by_name(self, name: str) -> List[Student]:
"""根据姓名查询学生信息(模糊匹配)"""
return self.dal.find_students_by_name(name)
def get_students_by_class_name(self, class_name: str) -> List[Student]:
"""根据班级名称查询学生信息(模糊匹配)"""
return self.dal.find_students_by_class_name(class_name)
def get_students_by_major(self, major: str) -> List[Student]:
"""根据专业查询学生信息(模糊匹配)"""
return self.dal.find_students_by_major(major)
def get_all_students(self) -> List[Student]:
"""获取所有学生信息"""
return self.dal.get_all_students()
def get_student_count(self) -> int:
"""获取学生总数"""
return self.dal.get_student_count()
def get_students_by_height_range(self, min_height: int, max_height: int) -> List[Student]:
"""按身高范围查询学生"""
return self.dal.get_students_by_height_range(min_height, max_height)
def get_students_by_enrollment_year(self, year: int) -> List[Student]:
"""按入学年份查询学生"""
return self.dal.get_students_by_enrollment_year(year)
def get_students_by_age_range(self, min_age: int, max_age: int) -> List[Student]:
"""按年龄范围查询学生"""
return self.dal.get_students_by_age_range(min_age, max_age)
def get_students_by_major_group(self) -> Dict[str, List[Student]]:
"""按专业分组学生"""
return self.dal.get_students_by_major_group()
def get_average_height(self) -> float:
"""计算平均身高"""
return self.dal.get_average_height()
def get_average_weight(self) -> float:
"""计算平均体重"""
return self.dal.get_average_weight()
def get_gender_ratio(self) -> Dict[Optional[bool], float]:
"""计算性别比例"""
return self.dal.get_gender_ratio()
def clear_all_students(self) -> None:
"""清空所有学生信息"""
self.dal.clear_all_students()

@ -1,476 +0,0 @@
import os
import json
import csv
from abc import ABC, abstractmethod
from datetime import date
from typing import List, Optional, Dict
from model.student import Student
class IStudentDAL(ABC):
"""学生信息数据访问层接口"""
@abstractmethod
def add_student(self, student: Student) -> bool:
"""添加学生"""
pass
@abstractmethod
def delete_student_by_id_card(self, id_card: str) -> bool:
"""根据身份证号删除学生"""
pass
@abstractmethod
def delete_student_by_stu_id(self, stu_id: str) -> bool:
"""根据学号删除学生"""
pass
@abstractmethod
def update_student(self, id_card: str, updated_student: Student) -> bool:
"""更新学生信息"""
pass
@abstractmethod
def find_student_by_id_card(self, id_card: str) -> Optional[Student]:
"""根据身份证号查找学生"""
pass
@abstractmethod
def find_student_by_stu_id(self, stu_id: str) -> Optional[Student]:
"""根据学号查找学生"""
pass
@abstractmethod
def find_students_by_name(self, name: str) -> List[Student]:
"""根据姓名查找学生(模糊匹配)"""
pass
@abstractmethod
def find_students_by_class_name(self, class_name: str) -> List[Student]:
"""根据班级名称查找学生(模糊匹配)"""
pass
@abstractmethod
def find_students_by_major(self, major: str) -> List[Student]:
"""根据专业查找学生(模糊匹配)"""
pass
@abstractmethod
def get_all_students(self) -> List[Student]:
"""获取所有学生信息"""
pass
@abstractmethod
def get_student_count(self) -> int:
"""获取学生总数"""
pass
@abstractmethod
def get_students_by_height_range(self, min_height: int, max_height: int) -> List[Student]:
"""按身高范围查找学生"""
pass
@abstractmethod
def get_students_by_enrollment_year(self, year: int) -> List[Student]:
"""按入学年份查找学生"""
pass
@abstractmethod
def get_students_by_age_range(self, min_age: int, max_age: int) -> List[Student]:
"""按年龄范围查找学生"""
pass
@abstractmethod
def get_students_by_major_group(self) -> Dict[str, List[Student]]:
"""按专业分组学生"""
pass
@abstractmethod
def get_average_height(self) -> float:
"""计算平均身高"""
pass
@abstractmethod
def get_average_weight(self) -> float:
"""计算平均体重"""
pass
@abstractmethod
def get_gender_ratio(self) -> Dict[Optional[bool], float]:
"""计算性别比例"""
pass
@abstractmethod
def clear_all_students(self) -> None:
"""清空所有学生信息"""
pass
@abstractmethod
def check_student_exists_by_id_card(self, id_card: str) -> bool:
"""检查身份证号是否存在"""
pass
@abstractmethod
def check_student_exists_by_stu_id(self, stu_id: str) -> bool:
"""检查学号是否存在"""
pass
class JsonStudentDAL(IStudentDAL):
"""JSON文件存储实现"""
def __init__(self, file_path: str):
self.file_path = file_path
self.__ensure_file_exists()
def __ensure_file_exists(self):
"""确保JSON文件存在"""
if not os.path.exists(self.file_path):
with open(self.file_path, 'w', encoding='utf-8') as file:
# noinspection PyTypeChecker
json.dump([], file)
def __load_students(self) -> List[Student]:
"""从JSON文件加载学生列表"""
try:
with open(self.file_path, 'r', encoding='utf-8') as file:
student_dicts = json.load(file)
return [Student.from_dict(sd) for sd in student_dicts]
except json.JSONDecodeError:
return []
def __save_students(self, students: List[Student]) -> None:
"""将学生列表保存到JSON文件"""
student_dicts = [s.to_dict() for s in students]
with open(self.file_path, 'w', encoding='utf-8') as file:
json.dump(student_dicts, file, ensure_ascii=False, indent=4)
# 实现接口方法
def add_student(self, student: Student) -> bool:
students = self.__load_students()
if any(s.id_card == student.id_card for s in students):
return False
if any(s.stu_id == student.stu_id for s in students):
return False
students.append(student)
self.__save_students(students)
return True
def delete_student_by_id_card(self, id_card: str) -> bool:
students = self.__load_students()
original_count = len(students)
students = [s for s in students if s.id_card != id_card]
if len(students) < original_count:
self.__save_students(students)
return True
return False
def delete_student_by_stu_id(self, stu_id: str) -> bool:
students = self.__load_students()
original_count = len(students)
students = [s for s in students if s.stu_id != stu_id]
if len(students) < original_count:
self.__save_students(students)
return True
return False
def update_student(self, id_card: str, updated_student: Student) -> bool:
students = self.__load_students()
for i, s in enumerate(students):
if s.id_card == id_card:
students[i] = updated_student
self.__save_students(students)
return True
return False
def find_student_by_id_card(self, id_card: str) -> Optional[Student]:
students = self.__load_students()
for s in students:
if s.id_card == id_card:
return s
return None
def find_student_by_stu_id(self, stu_id: str) -> Optional[Student]:
students = self.__load_students()
for s in students:
if s.stu_id == stu_id:
return s
return None
def find_students_by_name(self, name: str) -> List[Student]:
students = self.__load_students()
return [s for s in students if name.lower() in s.name.lower()]
def find_students_by_class_name(self, class_name: str) -> List[Student]:
students = self.__load_students()
return [s for s in students if s.class_name and class_name.lower() in s.class_name.lower()]
def find_students_by_major(self, major: str) -> List[Student]:
students = self.__load_students()
return [s for s in students if s.major and major.lower() in s.major.lower()]
def get_all_students(self) -> List[Student]:
return self.__load_students()
def get_student_count(self) -> int:
return len(self.__load_students())
def get_students_by_height_range(self, min_height: int, max_height: int) -> List[Student]:
students = self.__load_students()
return [s for s in students if s.height and min_height <= s.height <= max_height]
def get_students_by_enrollment_year(self, year: int) -> List[Student]:
students = self.__load_students()
return [s for s in students if s.enrollment_date and s.enrollment_date.year == year]
def get_students_by_age_range(self, min_age: int, max_age: int) -> List[Student]:
students = self.__load_students()
today = date.today()
result = []
for s in students:
if s.birth_date:
age = today.year - s.birth_date.year
if (today.month, today.day) < (s.birth_date.month, s.birth_date.day):
age -= 1
if min_age <= age <= max_age:
result.append(s)
return result
def get_students_by_major_group(self) -> Dict[str, List[Student]]:
students = self.__load_students()
groups = {}
for s in students:
if s.major:
if s.major not in groups:
groups[s.major] = []
groups[s.major].append(s)
return groups
def get_average_height(self) -> float:
students = self.__load_students()
heights = [s.height for s in students if s.height is not None]
if not heights:
return 0.0
return sum(heights) / len(heights)
def get_average_weight(self) -> float:
students = self.__load_students()
weights = [s.weight for s in students if s.weight is not None]
if not weights:
return 0.0
return sum(weights) / len(weights)
def get_gender_ratio(self) -> Dict[Optional[bool], float]:
students = self.__load_students()
genders = [s.gender for s in students if s.gender is not None]
total = len(genders)
if total == 0:
return {True: 0.0, False: 0.0}
male_count = sum(1 for g in genders if g)
female_count = total - male_count
return {
True: male_count / total * 100,
False: female_count / total * 100
}
def clear_all_students(self) -> None:
self.__save_students([])
def check_student_exists_by_id_card(self, id_card: str) -> bool:
return self.find_student_by_id_card(id_card) is not None
def check_student_exists_by_stu_id(self, stu_id: str) -> bool:
return self.find_student_by_stu_id(stu_id) is not None
class CsvStudentDAL(IStudentDAL):
"""CSV文件存储实现"""
def __init__(self, file_path: str):
self.file_path = file_path
self.__ensure_file_exists()
def __ensure_file_exists(self):
"""确保CSV文件存在"""
if not os.path.exists(self.file_path):
with open(self.file_path, 'w', encoding='utf-8', newline='') as file:
writer = csv.writer(file)
writer.writerow([
'name', 'id_card', 'stu_id', 'gender', 'height', 'weight',
'enrollment_date', 'class_name', 'major', 'email', 'phone'
])
def __load_students(self) -> List[Student]:
"""从CSV文件加载学生列表"""
students = []
try:
with open(self.file_path, 'r', encoding='utf-8', newline='') as file:
reader = csv.DictReader(file)
for row in reader:
# 转换数据类型
if row['gender']:
row['gender'] = True if row['gender'] == 'True' else False
if row['height']:
row['height'] = int(row['height'])
if row['weight']:
row['weight'] = float(row['weight'])
if row['enrollment_date']:
row['enrollment_date'] = date.fromisoformat(row['enrollment_date'])
students.append(Student.from_dict(row))
except (FileNotFoundError, csv.Error, ValueError):
pass
return students
def __save_students(self, students: List[Student]) -> None:
"""将学生列表保存到CSV文件"""
with open(self.file_path, 'w', encoding='utf-8', newline='') as file:
fieldnames = [
'name', 'id_card', 'stu_id', 'gender', 'height', 'weight',
'enrollment_date', 'class_name', 'major', 'email', 'phone'
]
writer = csv.DictWriter(file, fieldnames=fieldnames)
writer.writeheader()
for student in students:
writer.writerow(student.to_dict())
# 实现接口方法
def add_student(self, student: Student) -> bool:
students = self.__load_students()
if any(s.id_card == student.id_card for s in students):
return False
if any(s.stu_id == student.stu_id for s in students):
return False
students.append(student)
self.__save_students(students)
return True
def delete_student_by_id_card(self, id_card: str) -> bool:
students = self.__load_students()
original_count = len(students)
students = [s for s in students if s.id_card != id_card]
if len(students) < original_count:
self.__save_students(students)
return True
return False
def delete_student_by_stu_id(self, stu_id: str) -> bool:
students = self.__load_students()
original_count = len(students)
students = [s for s in students if s.stu_id != stu_id]
if len(students) < original_count:
self.__save_students(students)
return True
return False
def update_student(self, id_card: str, updated_student: Student) -> bool:
students = self.__load_students()
for i, s in enumerate(students):
if s.id_card == id_card:
students[i] = updated_student
self.__save_students(students)
return True
return False
def find_student_by_id_card(self, id_card: str) -> Optional[Student]:
students = self.__load_students()
for s in students:
if s.id_card == id_card:
return s
return None
def find_student_by_stu_id(self, stu_id: str) -> Optional[Student]:
students = self.__load_students()
for s in students:
if s.stu_id == stu_id:
return s
return None
def find_students_by_name(self, name: str) -> List[Student]:
students = self.__load_students()
return [s for s in students if name.lower() in s.name.lower()]
def find_students_by_class_name(self, class_name: str) -> List[Student]:
students = self.__load_students()
return [s for s in students if s.class_name and class_name.lower() in s.class_name.lower()]
def find_students_by_major(self, major: str) -> List[Student]:
students = self.__load_students()
return [s for s in students if s.major and major.lower() in s.major.lower()]
def get_all_students(self) -> List[Student]:
return self.__load_students()
def get_student_count(self) -> int:
return len(self.__load_students())
def get_students_by_height_range(self, min_height: int, max_height: int) -> List[Student]:
students = self.__load_students()
return [s for s in students if s.height and min_height <= s.height <= max_height]
def get_students_by_enrollment_year(self, year: int) -> List[Student]:
students = self.__load_students()
return [s for s in students if s.enrollment_date and s.enrollment_date.year == year]
def get_students_by_age_range(self, min_age: int, max_age: int) -> List[Student]:
students = self.__load_students()
today = date.today()
result = []
for s in students:
if s.birth_date:
age = today.year - s.birth_date.year
if (today.month, today.day) < (s.birth_date.month, s.birth_date.day):
age -= 1
if min_age <= age <= max_age:
result.append(s)
return result
def get_students_by_major_group(self) -> Dict[str, List[Student]]:
students = self.__load_students()
groups = {}
for s in students:
if s.major:
if s.major not in groups:
groups[s.major] = []
groups[s.major].append(s)
return groups
def get_average_height(self) -> float:
students = self.__load_students()
heights = [s.height for s in students if s.height is not None]
if not heights:
return 0.0
return sum(heights) / len(heights)
def get_average_weight(self) -> float:
students = self.__load_students()
weights = [s.weight for s in students if s.weight is not None]
if not weights:
return 0.0
return sum(weights) / len(weights)
def get_gender_ratio(self) -> Dict[Optional[bool], float]:
students = self.__load_students()
genders = [s.gender for s in students if s.gender is not None]
total = len(genders)
if total == 0:
return {True: 0.0, False: 0.0}
male_count = sum(1 for g in genders if g)
female_count = total - male_count
return {
True: male_count / total * 100,
False: female_count / total * 100
}
def clear_all_students(self) -> None:
self.__save_students([])
def check_student_exists_by_id_card(self, id_card: str) -> bool:
return self.find_student_by_id_card(id_card) is not None
def check_student_exists_by_stu_id(self, stu_id: str) -> bool:
return self.find_student_by_stu_id(stu_id) is not None

@ -0,0 +1,2 @@
# wlhdl

@ -1,15 +0,0 @@
import os, sys
from ui.add_student import StudentTUI
cur_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(cur_dir)
sys.path.append(project_dir)
def main():
file_path ='./data/students.json'
ui = StudentTUI(file_path)
ui.run()
if __name__=='__main__':
main()

@ -1,324 +0,0 @@
from datetime import date
from typing import List, Optional, Dict, Union
import re
class Student:
def __init__(self, name: str, id_card: str, stu_id: str,
gender: Optional[bool] = None, height: Optional[int] = None,
weight: Optional[float] = None, enrollment_date: Optional[Union[date, str]] = None,
class_name: Optional[str] = None, major: Optional[str] = None,
email: Optional[str] = None, phone: Optional[str] = None):
# 初始化错误字典
self._errors: Dict[str, List[str]] = {}
# 基本属性(必填)
self.name = name.strip() # 数据清洗:去除首尾空格
self.id_card = id_card.strip().upper() # 统一转为大写
self.stu_id = stu_id.strip()
# 可选属性
self.gender = gender
self.height = height
self.weight = round(weight, 1) if weight is not None else None
self.email = email.strip() if email else None
self.phone = phone.strip() if phone else None
# 解析入学日期
self.enrollment_date = None
if enrollment_date is not None:
if isinstance(enrollment_date, date):
self.enrollment_date = enrollment_date
elif isinstance(enrollment_date, str):
try:
self.enrollment_date = date.fromisoformat(enrollment_date)
except ValueError:
self._add_error('enrollment_date', '入学日期格式无效应为YYYY-MM-DD')
else:
self._add_error('enrollment_date', '入学日期类型错误应为date对象或字符串')
self.class_name = class_name.strip() if class_name else None
self.major = major.strip() if major else None
# 执行校验
self._validate()
def _validate(self) -> None:
"""执行所有校验"""
self._validate_name()
self._validate_id_card()
self._validate_stu_id()
self._validate_height()
self._validate_weight()
self._validate_enrollment_date()
self._validate_email()
self._validate_phone()
def _validate_name(self) -> None:
"""验证姓名格式"""
if not self.name:
self._add_error('name', '姓名不能为空')
return
if not (2 <= len(self.name) <= 20):
self._add_error('name', '姓名长度需在2-20个字符之间')
# 允许空格但必须是可打印字符
if not all(c.isprintable() or c.isspace() for c in self.name):
self._add_error('name', '姓名包含非法字符')
def _validate_id_card(self) -> None:
"""验证身份证号格式"""
# 长度校验
if len(self.id_card) != 18:
self._add_error('id_card', f'身份证号应为18位当前为{len(self.id_card)}')
return
# 前17位必须为数字
if not self.id_card[:17].isdigit():
self._add_error('id_card', '身份证号前17位必须为数字')
return # 提前返回,避免后续操作出错
# 最后一位校验
last_char = self.id_card[17]
if last_char not in '0123456789X':
self._add_error('id_card', f'身份证号最后一位无效: {last_char}')
return
# 校验码验证
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
check_codes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
total = sum(int(d) * w for d, w in zip(self.id_card[:17], weights))
calculated_code = check_codes[total % 11]
if calculated_code != last_char:
self._add_error('id_card', f'校验码错误,应为{calculated_code},实际为{last_char}')
return # 校验码错误时不进行后续验证
# 出生日期校验
birth_str = self.id_card[6:14]
try:
year = int(birth_str[0:4])
month = int(birth_str[4:6])
day = int(birth_str[6:8])
# 尝试创建日期对象验证有效性
birth_date = date(year, month, day)
# 检查出生日期是否合理
today = date.today()
if birth_date > today:
self._add_error('id_card', f'出生日期不能在未来: {birth_str}')
elif birth_date.year < 1900:
self._add_error('id_card', f'出生年份不合理: {birth_str}')
except ValueError:
self._add_error('id_card', f'无效的出生日期: {birth_str}')
def _validate_stu_id(self) -> None:
"""验证学号"""
if not self.stu_id:
self._add_error('stu_id', '学号不能为空')
def _validate_height(self) -> None:
"""验证身高是否在合理范围内"""
if self.height is None:
return
if not (50 <= self.height <= 250):
self._add_error('height', f'身高{self.height}cm超出合理范围(50-250cm)')
def _validate_weight(self) -> None:
"""验证体重是否在合理范围内"""
if self.weight is None:
return
if not (5.0 <= self.weight <= 300.0):
self._add_error('weight', f'体重{self.weight}kg超出合理范围(5-300kg)')
def _validate_enrollment_date(self) -> None:
"""验证入学日期"""
if self.enrollment_date is None:
return
today = date.today()
if self.enrollment_date > today:
self._add_error('enrollment_date', '入学日期不能晚于当前日期')
# 如果出生日期有效,检查入学日期是否晚于出生日期
birthday = self.birthday
if birthday:
if self.enrollment_date < birthday:
self._add_error('enrollment_date', '入学日期不能早于出生日期')
def _validate_email(self) -> None:
"""验证邮箱格式(选做)"""
if not self.email:
return
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(pattern, self.email):
self._add_error('email', '邮箱格式无效')
def _validate_phone(self) -> None:
"""验证手机号格式(选做)"""
if not self.phone:
return
pattern = r'^1[3-9]\d{9}$'
if not re.match(pattern, self.phone):
self._add_error('phone', '手机号格式无效应为11位数字')
def _add_error(self, field: str, message: str) -> None:
"""添加错误信息
:rtype: object
"""
if field not in self._errors:
self._errors[field] = []
self._errors[field].append(message)
@property
def birthday(self) -> Optional[date]:
"""从身份证号解析出生日期"""
if len(self.id_card) < 14:
return None
birth_str = self.id_card[6:14]
try:
return date(
int(birth_str[0:4]),
int(birth_str[4:6]),
int(birth_str[6:8]))
except (ValueError, TypeError):
return None
@property
def birth_date(self) -> Optional[date]:
"""birthday的别名"""
return self.birthday
@property
def age(self) -> Optional[int]:
"""计算年龄"""
birthday = self.birthday
if not birthday:
return None
today = date.today()
age = today.year - birthday.year
# 如果生日还没到,减一岁
if (today.month, today.day) < (birthday.month, birthday.day):
age -= 1
return age
@property
def sid(self) -> str:
"""stu_id的别名"""
return self.stu_id
@property
def errors(self) -> dict:
"""获取所有错误信息"""
return self._errors.copy()
def get_errors(self) -> dict:
"""获取错误信息(方法形式)"""
return self.errors
@property
def is_valid(self) -> bool:
"""数据有效性标记"""
return not bool(self._errors)
# 对象比较方法
def __eq__(self, other) -> bool:
if not isinstance(other, Student):
return NotImplemented
return (
self.name == other.name and
self.id_card == other.id_card and
self.stu_id == other.stu_id and
self.gender == other.gender and
self.height == other.height and
self.weight == other.weight and
self.enrollment_date == other.enrollment_date and
self.class_name == other.class_name and
self.major == other.major
)
# 序列化方法
def to_dict(self) -> dict:
"""将对象序列化为字典"""
return {
'name': self.name,
'id_card': self.id_card,
'stu_id': self.stu_id,
'gender': self.gender,
'height': self.height,
'weight': self.weight,
'enrollment_date': self.enrollment_date.isoformat() if self.enrollment_date else None,
'class_name': self.class_name,
'major': self.major,
'email': self.email,
'phone': self.phone
}
# 反序列化方法
@classmethod
def from_dict(cls, data: dict) -> 'Student':
"""从字典创建对象"""
if not isinstance(data, dict):
raise TypeError("输入数据必须是字典类型")
return cls(
name=data.get('name', ''),
id_card=data.get('id_card', ''),
stu_id=data.get('stu_id', ''),
gender=data.get('gender'),
height=data.get('height'),
weight=data.get('weight'),
enrollment_date=data.get('enrollment_date'),
class_name=data.get('class_name'),
major=data.get('major'),
email=data.get('email'),
phone=data.get('phone')
)
@classmethod
def get_field_names(cls) -> List[str]:
"""获取所有字段名称(包括别名)"""
return [
'name', 'id_card', 'stu_id', 'sid',
'gender', 'height', 'weight',
'enrollment_date', 'class_name', 'major',
'birthday', 'birth_date', 'age',
'email', 'phone'
]
def __repr__(self) -> str:
"""修复被截断的__repr__方法"""
attrs = [
f"name='{self.name}'",
f"id_card='{self.id_card}'",
f"stu_id='{self.stu_id}'",
f"sid='{self.sid}'",
f"gender={self.gender}",
f"height={self.height}",
f"weight={self.weight}",
f"enrollment_date={self.enrollment_date.isoformat() if self.enrollment_date else None}",
f"class_name='{self.class_name}'" if self.class_name else "class_name=None",
f"major='{self.major}'" if self.major else "major=None",
f"birthday={self.birthday.isoformat() if self.birthday else None}",
f"birth_date={self.birth_date.isoformat() if self.birth_date else None}",
f"age={self.age}",
f"email='{self.email}'" if self.email else "email=None",
f"phone='{self.phone}'" if self.phone else "phone=None"
]
return f"Student({', '.join(attrs)})"

@ -1,542 +0,0 @@
import json
import csv
from datetime import date
from typing import List, Tuple
from BLL.add_student import StudentBLL
from model.student import Student
from DAL.clear_all_student import CsvStudentDAL
from DAL.clear_all_student import JsonStudentDAL
def display_menu():
print("\n=== 学生信息管理系统 ====")
print("1. 添加学生信息")
print("2. 删除学生信息")
print("3. 更新学生信息")
print("4. 查询学生信息")
print("5. 统计分析")
print("6. 数据导入导出")
print("7. 清空所有学生信息")
print("8. 退出系统")
print("---")
def display_query_menu():
print("\n==== 查询学生信息 ====")
print("1. 查询所有学生")
print("2. 按身份证号查询")
print("3. 按学号查询")
print("4. 按姓名查询")
print("5. 按班级查询")
print("6. 按专业查询")
print("7. 返回上一级")
print("=======================")
def display_stats_menu():
print("\n==== 统计分析 ====")
print("1. 学生总数")
print("2. 平均身高")
print("3. 按身高范围统计")
print("4. 按入学年份统计")
print("5. 按年龄范围统计")
print("6. 按专业分组统计")
print("7. 性别比例")
print("8. 返回上一级")
print("=======================")
def display_import_export_menu():
print("\n==== 数据导入导出 ====")
print("1. 导出数据到JSON")
print("2. 从JSON导入数据")
print("3. 导出数据到CSV")
print("4. 从CSV导入数据")
print("5. 返回上一级")
print("=======================")
def display_students(students: List[Student]):
if not students:
print("没有找到学生信息")
return
print("\n学生信息列表:")
print("-" * 120)
print(
f"{'姓名':<10}{'学号':<15}{'身份证号':<20}{'性别':<6}{'年龄':<6}{'身高':<6}{'体重':<6}{'班级':<10}{'专业':<15}{'入学日期':<12}")
print("-" * 120)
for student in students:
gender = '' if student.gender else '' if student.gender is not None else '未知'
print(
f"{student.name:<10}{student.stu_id:<15}{student.id_card:<20}"
f"{gender:<6}{student.age or '-':<6}{student.height or '-':<6}"
f"{student.weight or '-':<6}{student.class_name or '-':<10}"
f"{student.major or '-':<15}{student.enrollment_date or '-'}"
)
print("-" * 120)
print(f"共找到 {len(students)} 名学生")
class StudentTUI:
def __init__(self, bll: StudentBLL):
self.bll = bll
# 省份代码映射表
self.province_codes = {
'11': '北京市', '12': '天津市', '13': '河北省', '14': '山西省', '15': '内蒙古自治区',
'21': '辽宁省', '22': '吉林省', '23': '黑龙江省', '31': '上海市', '32': '江苏省',
'33': '浙江省', '34': '安徽省', '35': '福建省', '36': '江西省', '37': '山东省',
'41': '河南省', '42': '湖北省', '43': '湖南省', '44': '广东省', '45': '广西壮族自治区',
'46': '海南省', '50': '重庆市', '51': '四川省', '52': '贵州省', '53': '云南省',
'54': '西藏自治区', '61': '陕西省', '62': '甘肃省', '63': '青海省', '64': '宁夏回族自治区',
'65': '新疆维吾尔自治区'
}
def validate_id_card(self, id_card: str) -> bool:
"""验证身份证号是否合法"""
if len(id_card) != 18:
return False
# 校验前17位是否为数字
if not id_card[:-1].isdigit():
return False
# 校验最后一位是否为数字或X
if not (id_card[-1].isdigit() or id_card[-1].upper() == 'X'):
return False
return True
def get_gender_from_id_card(self, id_card: str) -> bool:
"""从身份证号获取性别(男True/女False)"""
if len(id_card) != 18:
raise ValueError("身份证号长度不正确")
gender_code = int(id_card[16])
return gender_code % 2 == 1 # 奇数表示男性,偶数表示女性
def get_hometown_from_id_card(self, id_card: str) -> str:
"""从身份证号获取籍贯"""
if len(id_card) != 18:
raise ValueError("身份证号长度不正确")
province_code = id_card[:2]
return self.province_codes.get(province_code, "未知地区")
def run(self):
while True:
display_menu()
choice = input("请选择操作: ").strip()
if choice == '1':
self.add_student()
elif choice == '2':
self.delete_student()
elif choice == '3':
self.update_student()
elif choice == '4':
self.query_students()
elif choice == '5':
self.stats_students()
elif choice == '6':
self.import_export_data()
elif choice == '7':
self.clear_all_students()
elif choice == '8':
print("感谢使用学生信息管理系统,再见!")
break
else:
print("无效的选择,请重新输入!")
def add_student(self):
print("\n==== 添加学生信息 ====")
try:
name = input("姓名: ").strip()
id_card = input("身份证号: ").strip()
# 验证身份证号
if not self.validate_id_card(id_card):
print("身份证号不合法!")
return
# 自动获取性别
gender = self.get_gender_from_id_card(id_card)
print(f"自动识别性别: {'' if gender else ''}")
# 自动获取籍贯
hometown = self.get_hometown_from_id_card(id_card)
print(f"籍贯: {hometown}")
stu_id = input("学号: ").strip()
height = int(input("身高(cm): ").strip())
weight = float(input("体重(kg): ").strip())
enrollment_date = input("入学日期(YYYY-MM-DD): ").strip()
class_name = input("班级名称: ").strip()
major = input("专业: ").strip()
email = input("邮箱(可选): ").strip() or None
phone = input("手机号(可选): ").strip() or None
student = Student(
name=name,
id_card=id_card,
stu_id=stu_id,
gender=gender,
height=height,
weight=weight,
enrollment_date=enrollment_date,
class_name=class_name,
major=major,
email=email,
phone=phone
)
if self.bll.add_student(student):
print("学生信息添加成功!")
else:
print("添加失败,可能学号或身份证号已存在")
except ValueError as e:
print(f"输入错误: {e}")
except Exception as e:
print(f"添加学生信息时出错: {e}")
def delete_student(self):
print("\n==== 删除学生信息 ====")
print("1. 按身份证号删除")
print("2. 按学号删除")
choice = input("请选择删除方式: ").strip()
if choice == '1':
id_card = input("请输入身份证号: ").strip()
try:
if self.bll.delete_student_by_id_card(id_card):
print("学生信息删除成功!")
else:
print("删除失败,未找到匹配的学生")
except ValueError as e:
print(f"错误: {e}")
elif choice == '2':
stu_id = input("请输入学号: ").strip()
try:
if self.bll.delete_student_by_stu_id(stu_id):
print("学生信息删除成功!")
else:
print("删除失败,未找到匹配的学生")
except ValueError as e:
print(f"错误: {e}")
else:
print("无效的选择")
def update_student(self):
print("\n==== 更新学生信息 ====")
id_card = input("请输入要更新的学生身份证号: ").strip()
try:
student = self.bll.get_student_by_id_card(id_card)
if not student:
print("未找到该学生")
return
print("当前学生信息:")
print(student)
print("\n请输入要更新的字段 (留空表示不更新):")
name = input(f"姓名({student.name}): ").strip() or student.name
stu_id = input(f"学号({student.stu_id}): ").strip() or student.stu_id
height = input(f"身高({student.height}): ").strip()
height = int(height) if height else student.height
weight = input(f"体重({student.weight}): ").strip()
weight = float(weight) if weight else student.weight
enrollment_date = input(f"入学日期({student.enrollment_date}): ").strip()
enrollment_date = enrollment_date or student.enrollment_date
class_name = input(f"班级名称({student.class_name}): ").strip() or student.class_name
major = input(f"专业({student.major}): ").strip() or student.major
email = input(f"邮箱({student.email}): ").strip() or student.email
phone = input(f"手机号({student.phone}): ").strip() or student.phone
updated_student = Student(
name=name,
id_card=id_card, # 身份证号不可更改
stu_id=stu_id,
gender=student.gender, # 性别从身份证号自动获取,不可更改
height=height,
weight=weight,
enrollment_date=enrollment_date,
class_name=class_name,
major=major,
email=email,
phone=phone
)
if self.bll.update_student(id_card, updated_student):
print("学生信息更新成功!")
else:
print("更新失败")
except ValueError as e:
print(f"输入错误: {e}")
except Exception as e:
print(f"更新学生信息时出错: {e}")
def query_students(self):
while True:
display_query_menu()
choice = input("请选择查询方式: ").strip()
if choice == '1': # 查询所有学生
students = self.bll.get_all_students()
display_students(students)
elif choice == '2': # 按身份证号查询
id_card = input("请输入身份证号: ").strip()
student = self.bll.get_student_by_id_card(id_card)
if student:
# 显示籍贯信息
hometown = self.get_hometown_from_id_card(student.id_card)
print(f"\n籍贯: {hometown}")
display_students([student])
else:
print("未找到匹配的学生")
elif choice == '3': # 按学号查询
stu_id = input("请输入学号: ").strip()
student = self.bll.get_student_by_stu_id(stu_id)
if student:
display_students([student])
else:
print("未找到匹配的学生")
elif choice == '4': # 按姓名查询
name = input("请输入姓名: ").strip()
students = self.bll.get_students_by_name(name)
display_students(students)
elif choice == '5': # 按班级查询
class_name = input("请输入班级名称: ").strip()
students = self.bll.get_students_by_class_name(class_name)
display_students(students)
elif choice == '6': # 按专业查询
major = input("请输入专业名称: ").strip()
students = self.bll.get_students_by_major(major)
display_students(students)
elif choice == '7': # 返回上一级
break
else:
print("无效的选择,请重新输入!")
def stats_students(self):
while True:
display_stats_menu()
choice = input("请选择统计方式: ").strip()
if choice == '1': # 学生总数
count = self.bll.get_student_count()
print(f"\n学生总数: {count}")
elif choice == '2': # 平均身高
avg_height = self.bll.get_average_height()
print(f"\n平均身高: {avg_height:.2f} cm")
elif choice == '3': # 按身高范围统计
min_height = int(input("最小身高(cm): ").strip())
max_height = int(input("最大身高(cm): ").strip())
students = self.bll.get_students_by_height_range(min_height, max_height)
print(f"\n身高在 {min_height}-{max_height} cm 的学生 ({len(students)} 人):")
display_students(students)
elif choice == '4': # 按入学年份统计
year = int(input("请输入入学年份: ").strip())
students = self.bll.get_students_by_enrollment_year(year)
print(f"\n{year}年入学的学生 ({len(students)} 人):")
display_students(students)
elif choice == '5': # 按年龄范围统计
min_age = int(input("最小年龄: ").strip())
max_age = int(input("最大年龄: ").strip())
students = self.bll.get_students_by_age_range(min_age, max_age)
print(f"\n年龄在 {min_age}-{max_age} 岁的学生 ({len(students)} 人):")
display_students(students)
elif choice == '6': # 按专业分组统计
groups = self.bll.get_students_by_major_group()
for major, students in groups.items():
print(f"\n专业: {major} ({len(students)} 人)")
display_students(students)
elif choice == '7': # 性别比例
ratio = self.bll.get_gender_ratio()
print(f"\n性别比例:")
print(f"男生: {ratio.get(True, 0.0):.2f}%")
print(f"女生: {ratio.get(False, 0.0):.2f}%")
elif choice == '8': # 返回上一级
break
else:
print("无效的选择,请重新输入!")
def import_export_data(self):
while True:
display_import_export_menu()
choice = input("请选择操作: ").strip()
if choice == '1': # 导出到JSON
file_path = input("请输入导出文件路径: ").strip()
if self.export_to_json(file_path):
print("导出成功!")
else:
print("导出失败")
elif choice == '2': # 从JSON导入
file_path = input("请输入导入文件路径: ").strip()
success, error = self.import_from_json(file_path)
print(f"导入完成: 成功 {success} 条, 失败 {error}")
elif choice == '3': # 导出到CSV
file_path = input("请输入导出文件路径: ").strip()
if self.export_to_csv(file_path):
print("导出成功!")
else:
print("导出失败")
elif choice == '4': # 从CSV导入
file_path = input("请输入导入文件路径: ").strip()
success, error = self.import_from_csv(file_path)
print(f"导入完成: 成功 {success} 条, 失败 {error}")
elif choice == '5': # 返回上一级
break
else:
print("无效的选择,请重新输入!")
def clear_all_students(self):
confirm = input("确定要清空所有学生信息吗? (y/n): ").strip().lower()
if confirm == 'y':
self.bll.clear_all_students()
print("所有学生信息已清空!")
else:
print("操作已取消")
def export_to_json(self, file_path: str) -> bool:
"""导出学生信息到JSON文件"""
students = self.bll.get_all_students()
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump([s.to_dict() for s in students], f, ensure_ascii=False, indent=4)
return True
except Exception as e:
print(f"导出学生数据到JSON时出错: {e}")
return False
def import_from_json(self, file_path: str) -> Tuple[int, int]:
"""从JSON文件导入学生信息"""
success_count = 0
error_count = 0
try:
with open(file_path, 'r', encoding='utf-8') as f:
student_dicts = json.load(f)
for s_dict in student_dicts:
try:
student = Student.from_dict(s_dict)
if self.bll.add_student(student):
success_count += 1
except Exception as e:
error_count += 1
print(f"导入学生数据时出错: {e}")
return success_count, error_count
except Exception as e:
print(f"从JSON导入学生数据时出错: {e}")
return 0, 0
def export_to_csv(self, file_path: str) -> bool:
"""导出学生信息到CSV文件"""
students = self.bll.get_all_students()
if not students:
return False
try:
with open(file_path, 'w', encoding='utf-8', newline='') as f:
# 获取第一个学生的字段作为CSV表头
fieldnames = list(students[0].to_dict().keys())
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for student in students:
writer.writerow(student.to_dict())
return True
except Exception as e:
print(f"导出学生数据到CSV时出错: {e}")
return False
def import_from_csv(self, file_path: str) -> Tuple[int, int]:
"""从CSV文件导入学生信息"""
success_count = 0
error_count = 0
try:
with open(file_path, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
for row in reader:
try:
# 转换数据类型
converted_row = {}
for key, value in row.items():
if key in ['birth_date', 'enrollment_date'] and value:
converted_row[key] = date.fromisoformat(value)
elif key in ['height', 'weight'] and value:
converted_row[key] = float(value) if value else 0.0
else:
converted_row[key] = value
student = Student.from_dict(converted_row)
if self.bll.add_student(student):
success_count += 1
except Exception as e:
error_count += 1
print(f"导入学生数据时出错: {e}")
return success_count, error_count
except Exception as e:
print(f"从CSV导入学生数据时出错: {e}")
return 0, 0
def main():
# 选择存储方式
print("请选择数据存储方式:")
print("1. JSON")
print("2. CSV")
storage_choice = input("请输入选择 (1/2): ").strip()
file_path = "students.json" if storage_choice == '1' else "students.csv"
# 创建数据访问层
if storage_choice == '1':
dal = JsonStudentDAL(file_path)
elif storage_choice == '2':
dal = CsvStudentDAL(file_path)
else:
print("无效的选择默认使用JSON存储")
dal = JsonStudentDAL("students.json")
# 创建业务逻辑层
bll = StudentBLL(dal)
# 创建用户界面
tui = StudentTUI(bll)
tui.run()
def fun(self):
"""运行UI"""
while True:
self.display_menu()
choice = input("请选择操作: ").strip()
if choice == '1':
self.add_student()
elif choice == '2':
self.delete_student()
elif choice == '3':
self.update_student()
elif choice == '5':
self.show_stats()
elif choice == '6':
self.import_export_data()
elif choice == '7':
self.clear_students()
elif choice == '8':
self.query_student()
elif choice == '4':
print("感谢使用学生信息管理系统!")
break
else:
print("无效的选择,请重新输入!")
if __name__ == "__main__":
main()

@ -1 +0,0 @@
name,id_card,stu_id,gender,height,weight,enrollment_date,class_name,major,email,phone
1 name id_card stu_id gender height weight enrollment_date class_name major email phone
Loading…
Cancel
Save