HJW20 2 months ago
parent 164a21006e
commit 2b8b420d84

@ -0,0 +1,11 @@
class StudentDal:
def __init__(self,file_path:str):
self.file_path=file_path
self._ensure_file_exists()
def _ensure_file_exists(self):
if not os.path.exists(self.file_path):
with open(self.file_path,mode='w',encoding='utf-8')as file:
json.dump([],file)

@ -0,0 +1,8 @@
def add_student(self,student:Student)-> bool:
students=self.load_students()
for existing_student in students:
if existing_student.sid==student.sid:
return False
students.append(student)
self.save_students(students)
return True

@ -0,0 +1,2 @@
def check_students_exists(self,sid:str)-> bool:
return self.find_studemts_by_sid(sid) is not None

@ -0,0 +1,6 @@
def clear_all_students(self)-> None:
self.save_students([])
def get_student_count(self)->int:
return len(self.load_students())
def check_student_exists(self,sid:str)-> bool:
return self.find_student_by_sid(sid) is not None

@ -0,0 +1,7 @@
def delete_student(self,sid:str)-> bool:
students= self.load_students()
initial_length=len(students)
students=[student for student in students if student.sid!= sid]
if len(students)<initial_length:
return True
return False

@ -0,0 +1,3 @@
def find_student_by_class_name(self,class_name:str)-> Lise[Student]:
students = self.load_students()
return[s for s in students if class_name.lower()in s.class_name.lower()]

@ -0,0 +1,3 @@
def find_students_by_name(self,name:str)-> List[Student]:
students =self.load_students()
return [student for student in students if name.lower() in student.name.lower()]

@ -0,0 +1,2 @@
def get_all_students(self)-> List[Student]:
return self.load_students()

@ -0,0 +1,2 @@
def get_student_count(self)-> int:
return len(self.load_students())

@ -0,0 +1,67 @@
from abc import ABC, abstractmethod
from typing import List, Optional
from models.student import Student
class IStudentDAL(ABC):
"""学生信息数据访问层接口"""
@abstractmethod
def add_student(self, student: Student) -> bool:
"""添加学生"""
pass
@abstractmethod
def delete_student(self, id_card: str) -> bool:
"""删除学生"""
pass
@abstractmethod
def update_student(self, student: Student) -> bool:
"""更新学生信息"""
pass
@abstractmethod
def get_by_id(self, id_card: str) -> Optional[Student]:
"""根据身份证号获取学生"""
pass
@abstractmethod
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
"""根据学号获取学生"""
pass
@abstractmethod
def get_all(self) -> List[Student]:
"""获取所有学生"""
pass
@abstractmethod
def get_by_name(self, name: str) -> List[Student]:
"""按姓名查询(模糊)"""
pass
@abstractmethod
def get_by_class(self, class_name: str) -> List[Student]:
"""按班级查询(模糊)"""
pass
@abstractmethod
def get_by_major(self, major: str) -> List[Student]:
"""按专业查询(模糊)"""
pass
@abstractmethod
def count_students(self) -> int:
"""统计学生总数"""
pass
@abstractmethod
def count_by_major(self) -> dict:
"""按专业统计人数"""
pass
@abstractmethod
def clear_all(self) -> bool:
"""清空所有学生数据"""
pass

@ -0,0 +1,117 @@
import json
import os
from typing import List, Optional
from models.student import Student
from dal.idal import IStudentDAL
class JsonStudentDAL(IStudentDAL):
"""JSON存储实现"""
def __init__(self, file_path: str = 'data/students.json'):
self.file_path = file_path
self._ensure_file_exists()
def _ensure_file_exists(self):
os.makedirs(os.path.dirname(self.file_path), exist_ok=True)
if not os.path.exists(self.file_path):
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump([], f)
def _load_data(self) -> List[dict]:
"""加载JSON数据"""
with open(self.file_path, 'r', encoding='utf-8') as f:
return json.load(f)
def _save_data(self, data: List[dict]):
"""保存数据到JSON文件"""
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
def add_student(self, student: Student) -> bool:
data = self._load_data()
# 检查学号和身份证号是否已存在
for item in data:
if item['id_card'] == student.id_card or item['stu_id'] == student.stu_id:
return False
data.append(student.to_dict())
self._save_data(data)
return True
def delete_student(self, id_card: str) -> bool:
data = self._load_data()
original_length = len(data)
data = [item for item in data if item['id_card'] != id_card]
if len(data) != original_length:
self._save_data(data)
return True
return False
def update_student(self, student: Student) -> bool:
data = self._load_data()
updated = False
for i, item in enumerate(data):
if item['id_card'] == student.id_card:
data[i] = student.to_dict()
updated = True
break
if updated:
self._save_data(data)
return updated
def get_by_id(self, id_card: str) -> Optional[Student]:
data = self._load_data()
for item in data:
if item['id_card'] == id_card:
return Student.from_dict(item)
return None
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
data = self._load_data()
for item in data:
if item['stu_id'] == stu_id:
return Student.from_dict(item)
return None
def get_all(self) -> List[Student]:
data = self._load_data()
return [Student.from_dict(item) for item in data]
def get_by_name(self, name: str) -> List[Student]:
data = self._load_data()
return [Student.from_dict(item) for item in data if name.lower() in item['name'].lower()]
def get_by_class(self, class_name: str) -> List[Student]:
data = self._load_data()
return [Student.from_dict(item) for item in data
if item['class_name'] and class_name.lower() in item['class_name'].lower()]
def get_by_major(self, major: str) -> List[Student]:
data = self._load_data()
return [Student.from_dict(item) for item in data
if item['major'] and major.lower() in item['major'].lower()]
def count_students(self) -> int:
data = self._load_data()
return len(data)
def count_by_major(self) -> dict:
data = self._load_data()
majors = {}
for item in data:
major = item.get('major', '未指定')
majors[major] = majors.get(major, 0) + 1
return majors
def clear_all(self) -> bool:
self._save_data([])
return True

@ -0,0 +1,4 @@
def load_students(self)-> List[Student]:
with open(self.file_path,mode='r',encoding='utf-8')as file:
student_dicts=json.load(file)
return[Student.from_dict(student_dict) for student_dict in student_dicts]

@ -0,0 +1,4 @@
def save_students(self,students:List[Student])-> None:
student_dicts=[student.to_dict() for student in students]
with open(self.file_path,mode="w",encoding="utf-8") as file:
json.dump(student_dicts,file,ensure_ascii=False,indent=4)

@ -0,0 +1,24 @@
import json
from typing_extensions import Optional
from xlwings.pro.reports.filters import height
from 面向对象学生系统.model.test.student import Student
class StudentDAL:
def __init__(self,file_path:str):
self.file_path=file_path
self._ensure_file_exists()
def _ensure_file_exist(self):
if not os.path.exists(self.file_path):
with open(self,file_path,mode='w',encoding='utf-8') as file:
json.dump([],file)

@ -0,0 +1,8 @@
def update_student(self,sid:str,updated_student:Student)-> bool:
students =self.load_students()
for i, student in enumerate(students):
if student.sid== sid:
student[i]=upadted_student
self.save_students(students)
return True
return False

@ -0,0 +1,18 @@
def update_student_partial(self, sid: str, name: Optional[str] = None, height:Optional[float]=None, birth_date: Optional[date] = None, enrollment_date:Optional[date] = None, class_name:Optional[
str] = None)->bool:
students = self.load_students()
for i, student in enumerate(students):
if students.sid == sid:
if name is not None:
students[i].name = name
if height is not None:
students[i].height = height
if birth_date is not None:
students[i].birth_date = birth_date
if enrollemnt_date is not None:
students[i].enrollemnt_date = enrollemnt_date
if class_name is not None:
students[i].class_name = class_name
self.save_students(students)
return True
return False

@ -0,0 +1,3 @@
class StudentTUI:
def __init__(self,bll:StudentBLL):
self.bll=bll

@ -0,0 +1,88 @@
import json
from typing_extensions import Optional
from xlwings.pro.reports.filters import height
from 面向对象学生系统.model.test.student import Student
class StudentDAL:
def __init__(self,file_path:str):
self.file_path=file_path
self._ensure_file_exists()
def _ensure_file_exist(self):
if not os.path.exists(self.file_path):
with open(self,file_path,mode='w',encoding='utf-8') as file:
json.dump([],file)
def save_students(self,students:List[Student])-> None:
student_dicts=[student.to_dict() for student in students]
with open(self.file_path,mode="w",encoding="utf-8") as file:
json.dump(student_dicts,file,ensure_ascii=False,indent=4)
def load_students(self)-> List[Student]:
with open(self.file_path,mode='r',encoding='utf-8')as file:
student_dicts=json.load(file)
return[Student.from_dict(student_dict) for student_dict in student_dicts]
def add_student(self,student:Student)-> bool:
students=self.load_students()
for existing_student in students:
if existing_student.sid==student.sid:
return False
students.append(student)
self.save_students(students)
return True
def find_student_by_sid(self,sid:str)-> Optional[Student]:
students =self.load_students()
for student in students:
if student.sid==sid:
return student
return None
def find_students_by_name(self,name:str)-> List[Student]:
students =self.load_students()
return [student for student in students if name.lower() in student.name.lower()]
def find_student_by_class_name(slef,class_nmae:str)-> Lise[Student]:
students = self.load_students()
return[s for s in students if class_name.lower()in s.class_name.lower()]
def get_all_students(self)-> List[Student]:
return self.load_students()
def update_student(self,sid:str,upadted_student:Student)-> bool:
students =self.load_students()
for i, student in enumerate(students):
if student.sid== sid:
student[i]=upadted_student
self.save_students(students)
return True
return False
def update_student_partial(self,sid:str,name:Optional[str]=None,height=Optional[float]=None,birth_date:Optional[date]=None,enrollment_date:Optional[date]=None,class_name:Optional[str]=None)->bool:
students= self.load_students()
for i,student in enumerate(students):
if students.sid==sid:
if name is not None:
students[i].name=name
if height is not None:
students[i].height=height
if birth_date is not None:
students[i].birth_date=birth_date
if enrollemnt_date is not None:
students[i].enrollemnt_date=enrollemnt_date
if class_name is not None:
students[i].class_name=class_name
self.save_students(students)
return True
return False
def delete_student(self,sid:str)-> bool:
students= self.load_students()
initial_length=len(students)
students=[student for student in students if student.sid!= sid]
if len(students)<initial_length:
return True
return False
def clear_all_students(self)-> None:
self.save_students([])
def get_student_count(self)->int:
return len(self.load_students())
def check_student_exists(self,sid:str)-> bool:
return self.find_student_by_sid(sid) is not None

@ -0,0 +1,7 @@
class StudentBll:
def __init__(self,dal:StudentDal):
self.dal=dal
def delete_student(self,sid:str)-> bool:
if not self.dal.check_student_exists(sid):
raise ValueError(f"学生 ID {sid}不存在。")
return self.dal.delete_students(sid)

@ -0,0 +1,45 @@
class Student:
def __init__(self,sid:str,name:str,height:int,birth_date:date|str,enrollment_date:date| str,class_name:str):
self.sid=sid
self.name=name.strip()
self.height=height
self.birth_date =birth_date if isinstance(birth_date,date) else date.fromisoformat(birth_date)
self.enrollment_date=enrollment_date if isinstance(enrollment_date,date)else date.fromisoformat(enrollment_date)
self.class_name=class_name
self._validation_errors=[]
self._validation_height()
self._validation_date()
self._validation_name()
def _validate_height(self)-> None:
if not (50<=self.height<=250):
self._validation_errors.append(f"身高{self.height}cm超出合理范围50-250厘米")
def _validate_name(self)-> None:
if not(2<=len(self.name)<=20):
self._validation_errors.append(f"姓名包含不可打印字符")
def _validate_date(self)-> None:
today =date.today()
if self.birth_date>today:
self._validation_errors.append("出生日期不能在未来 ")
if self.enrollment_date >today:
self._validation_errors.append(" 入学日期不能在未来 ")
if self.birth_date>self.enrollment_date:
self._validation_errors.append("入学日期不能早于出生日期")
@property
def is_valid(self)->bool:
return len(self._validtion_errors)==0
def get_errors(self)-> list[str]:
return self.validtion_errors.copy()
def __eq__(self,other)->bool:
if not isinstance(other,Student):
return NotImplemented
return(self.sid==other.sid and self.name==other.name and self.height== other.height and self.birth_date==other.birth_date and self.enrollment_date==other.enrollment_date and self.class_name==other.class_name)
def to_dict(self)-> dict:
return{'sid':self.sid,'name':self.name,'height':self.height,'birth_date':self.birth_date.isoformat(),'enrollment_date':self.enrollment_date_isoformat().'class_name':self.class_name}
@classmethod
def from_dict(cls,date:dict)->'Student':
birth_date =bir_d if isinstance(bir_d:=data['birth_date'],date)else date.fromisoformat(bir_d)
enrollment_date=enr_d if isinstance(enr_d:=data['enrollment_date'],data)else date.fromisoformat(enr_d)
return cls(sid=data['sid'],name=data['name'].strip(),height=data['height'],birth_date=birth_date,enrollment_date=enrollment_date,class_name=data['class_name'])
def __repr__(self)->str:
return(f"{self.__class__.__name__}("f"sid='{self.sid}',"f"name='{self.name}',"f"height={self.height},"f"birth_date=date({self.birth_date.year},{self.birth_date.month},{self.birth_date.day}),"
f")

@ -0,0 +1,354 @@
import re
from datetime import date, datetime
from typing import Dict, Optional, Union, List
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: Union[date, str, None] = None,
class_name: Optional[str] = None,
major: Optional[str] = None
):
"""
初始化学生对象
:param name: 学生姓名2-20个字符
:param id_card: 身份证号18位含正确校验码
:param stu_id: 学号非空
:param gender: 性别True()/False()/None(未知)
:param height: 身高(cm)50-250之间
:param weight: 体重(kg)5-300之间保留1位小数
:param enrollment_date: 入学日期date对象或YYYY-MM-DD格式字符串
:param class_name: 班级名称
:param major: 专业名称
"""
self.name = name
self.id_card = id_card
self.stu_id = stu_id
self.gender = gender
self.height = height
self.weight = weight
self.class_name = class_name
self.major = major
# 处理入学日期
if isinstance(enrollment_date, str):
try:
self.enrollment_date = date.fromisoformat(enrollment_date)
except ValueError:
self.enrollment_date = None
else:
self.enrollment_date = enrollment_date
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, value: str):
self._name = value
@property
def id_card(self) -> str:
return self._id_card
@id_card.setter
def id_card(self, value: str):
self._id_card = value
@property
def stu_id(self) -> str:
return self._stu_id
@stu_id.setter
def stu_id(self, value: str):
self._stu_id = value
@property
def gender(self) -> Optional[bool]:
return self._gender
@gender.setter
def gender(self, value: Optional[bool]):
self._gender = value
@property
def height(self) -> Optional[int]:
return self._height
@height.setter
def height(self, value: Optional[int]):
self._height = value
@property
def weight(self) -> Optional[float]:
return self._weight
@weight.setter
def weight(self, value: Optional[float]):
self._weight = value
@property
def enrollment_date(self) -> Optional[date]:
return self._enrollment_date
@enrollment_date.setter
def enrollment_date(self, value: Optional[Union[date, str]]):
if isinstance(value, str):
try:
self._enrollment_date = date.fromisoformat(value)
except ValueError:
self._enrollment_date = None
else:
self._enrollment_date = value
@property
def class_name(self) -> Optional[str]:
return self._class_name
@class_name.setter
def class_name(self, value: Optional[str]):
self._class_name = value
@property
def major(self) -> Optional[str]:
return self._major
@major.setter
def major(self, value: Optional[str]):
self._major = value
# 只读属性
@property
def birthday(self) -> Optional[date]:
"""从身份证号中提取出生日期"""
if not self.id_card or len(self.id_card) != 18:
return None
birth_date_str = self.id_card[6:14]
try:
return date(
year=int(birth_date_str[0:4]),
month=int(birth_date_str[4:6]),
day=int(birth_date_str[6:8])
)
except ValueError:
return None
@property
def age(self) -> Optional[int]:
"""计算年龄"""
if not self.birthday:
return None
today = date.today()
age = today.year - self.birthday.year
# 如果生日还没到年龄减1
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
age -= 1
return age
@property
def errors(self) -> Dict[str, str]:
"""返回所有校验错误信息"""
errors = {}
# 校验姓名
if not isinstance(self.name, str) or len(self.name) < 2 or len(self.name) > 20:
errors['name'] = "姓名必须为2-20个字符的字符串"
# 校验学号
if not isinstance(self.stu_id, str) or not self.stu_id:
errors['stu_id'] = "学号不能为空"
# 校验身份证号
id_card_errors = self.__validate_id_card()
if id_card_errors:
errors['id_card'] = id_card_errors
# 校验身高
if self.height is not None:
if not isinstance(self.height, int) or self.height < 50 or self.height > 250:
errors['height'] = "身高必须在50-250厘米之间"
# 校验体重
if self.weight is not None:
if not isinstance(self.weight, (int, float)) or self.weight < 5 or self.weight > 300:
errors['weight'] = "体重必须在5-300千克之间"
elif isinstance(self.weight, float) and len(str(self.weight).split('.')[1]) > 1:
errors['weight'] = "体重最多保留一位小数"
# 校验入学日期
if self.enrollment_date is not None:
if not isinstance(self.enrollment_date, date):
errors['enrollment_date'] = "入学日期格式无效"
elif self.enrollment_date > date.today():
errors['enrollment_date'] = "入学日期不能晚于当前日期"
return errors
@property
def is_valid(self) -> bool:
"""检查学生信息是否全部有效"""
return not bool(self.errors)
def __validate_id_card(self) -> Optional[str]:
"""验证身份证号有效性"""
if not isinstance(self.id_card, str):
return "身份证号必须为字符串"
id_card = self.id_card.upper()
# 长度校验
if len(id_card) != 18:
return "身份证号长度必须为18位"
# 前17位必须为数字
if not id_card[:17].isdigit():
return "身份证号前17位必须为数字"
# 最后一位可以是数字或X
last_char = id_card[17]
if not (last_char.isdigit() or last_char == 'X'):
return "身份证号最后一位必须是数字或X"
# 校验码验证
check_code = self.__get_check_code(id_card[:17])
if check_code != last_char:
return f"身份证号校验位错误,应为{check_code}"
# 出生日期验证
birth_date_str = id_card[6:14]
if not self.__validate_date(birth_date_str):
return "身份证号中的出生日期无效"
return None
@staticmethod
def __get_check_code(first_17: str) -> str:
"""计算身份证校验码"""
if len(first_17) != 17 or not first_17.isdigit():
raise ValueError("输入必须是17位数字")
# 加权因子
weights = [2 ** (17 - i) % 11 for i in range(17)]
# 计算加权和
total = sum(int(digit) * weight for digit, weight in zip(first_17, weights))
# 计算校验码
check_map = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8',
5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'}
return check_map[total % 11]
@staticmethod
def __validate_date(date_str: str) -> bool:
"""验证日期字符串(YYYYMMDD)是否有效"""
if len(date_str) != 8 or not date_str.isdigit():
return False
year = int(date_str[0:4])
month = int(date_str[4:6])
day = int(date_str[6:8])
# 简单验证年份范围
if year < 1900 or year > date.today().year:
return False
# 验证月份
if month < 1 or month > 12:
return False
# 验证日期
try:
date(year=year, month=month, day=day)
return True
except ValueError:
return False
def to_dict(self) -> Dict:
"""将学生对象转换为字典"""
data = {
'name': self.name,
'id_card': self.id_card,
'stu_id': self.stu_id,
'gender': self.gender,
'height': self.height,
'weight': self.weight,
'class_name': self.class_name,
'major': self.major
}
# 处理入学日期
if self.enrollment_date:
data['enrollment_date'] = self.enrollment_date.isoformat()
else:
data['enrollment_date'] = None
return data
@classmethod
def from_dict(cls, data: Dict) -> 'Student':
"""从字典创建学生对象"""
if not isinstance(data, dict):
raise TypeError("输入数据必须是字典类型")
# 处理入学日期
enrollment_date = data.get('enrollment_date')
if isinstance(enrollment_date, str):
try:
enrollment_date = date.fromisoformat(enrollment_date)
except ValueError:
enrollment_date = None
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=enrollment_date,
class_name=data.get('class_name'),
major=data.get('major')
)
def __repr__(self) -> str:
"""返回对象的字符串表示"""
attrs = []
for key, value in vars(self).items():
# 去掉下划线前缀
clean_key = key.lstrip('_')
if value is None:
attrs.append(f"{clean_key}=None")
elif isinstance(value, str):
attrs.append(f"{clean_key}='{value}'")
elif isinstance(value, date):
attrs.append(f"{clean_key}=date.fromisoformat('{value.isoformat()}')")
else:
attrs.append(f"{clean_key}={value}")
return f"Student({', '.join(attrs)})"
@classmethod
def get_properties(cls) -> List[str]:
"""获取使用@property装饰的属性列表"""
properties = []
for name, value in vars(cls).items():
if isinstance(value, property):
properties.append(name)
return properties

@ -0,0 +1,49 @@
from datetime import date
class Student:
def __init__(self,sid:str,name:str,height:int,birth_date:date|str,enrollment_date:date| str,class_name:str):
self.sid=sid
self.name=name.strip()
self.height=height
self.birth_date =birth_date if isinstance(birth_date,date) else date.fromisoformat(birth_date)
self.enrollment_date=enrollment_date if isinstance(enrollment_date,date)else date.fromisoformat(enrollment_date)
self.class_name=class_name
self._validation_errors=[]
self._validation_height()
self._validation_date()
self._validation_name()
def _validate_height(self)-> None:
if not(50<=self.height<=250):
self._validation_errors.append(f"身高{self.height}cm超出合理范围50-250厘米")
def _validate_name(self)-> None:
if not(2<=len(self.name)<=20):
self._validation_errors.append("姓名长度需在2-20个字符之间")
if not self.name.isprintable():
self._validation_errors.append("姓名包含不可打印字符")
def _validate_date(self)-> None:
today=date.today()
if self.birth_date>today:
self._validation_errors.append("出生日期不能在未来")
if self.enrollment_date>today:
self._validation_errors.append("入学日期不能在未来")
if self.birth_date>self.enrollment_date:
self._validation_errors.append("入学日期不能早于出生日期")
@property
def is_valid(self)-> bool:
return len(self._validation_errors)==0
def get_errors(self)-> list[str]:
return self.validation_error.copy()
def __eq__(self,other)->bool:
if not isinstance(other,Student):
return NotImplemented
return(self.sid==other.sid and self.name==other.name and self.height== other.height and self.birth_date==other.birth_date and self.enrollment_date==other.enrollment_date and self.class_name==other.class_na)
def to_dict(self)-> dict:
return{'sid':self.sid,'name':self.name,'height':self.height,'birth_date':self.birth_date.isoformat(),'enrollment_date':self.enrollment_date_isoformat().'class_name':self.class_name}
@classmethod
def from_dict(cls,date:dict)->'Student':
birth_date =bir_d if isinstance(bir_d:=data['birth_date'],date)else date.fromisoformat(bir_d)
enrollment_date=enr_d if isinstance(enr_d:=data['enrollment_date'],data)else date.fromisoformat(enr_d)
return cla(sid=data['sid'],name=data['name'].strip(),height=data['height'],birth_date=birth_date,enrollment_date=enrollment_date,class_name=data['class_name'])
def __repr__(self)->str:
return(f"{self.__class__.__name__}("f"sid='{self.sid}',"f"name='{self.name}',"f"height={self.height},"f"birth_date=date({self.birth_date.year},{self.birth_date.month},{self.birth_date.day}),"
f")
Loading…
Cancel
Save