import csv import json import os import sqlite3 from abc import ABC, abstractmethod from datetime import date from typing import List, Optional, Union # 学生类定义 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: str = None): self.name = name self.id_card = id_card self.stu_id = stu_id self.gender = gender self.height = height self.weight = weight if isinstance(enrollment_date, str): self.enrollment_date = date.fromisoformat(enrollment_date) else: self.enrollment_date = enrollment_date self.class_name = class_name self.major = major self.age, self.birthday = self._calculate_age_and_birthday() self._validation_errors = [] self._validate_all() def _calculate_age_and_birthday(self): birth_year = int(self.id_card[6:10]) birth_month = int(self.id_card[10:12]) birth_day = int(self.id_card[12:14]) birthday = date(birth_year, birth_month, birth_day) today = date.today() age = today.year - birth_year if (today.month, today.day) < (birth_month, birth_day): age -= 1 return age, birthday def _validate_all(self): self._validate_id_card() self._validate_stu_id() self._validate_name() self._validate_enrollment_date() self._validate_height() self._validate_weight() def _validate_id_card(self): if len(self.id_card) != 18: self._validation_errors.append("身份证号必须为18位") # 简单的校验位验证,可根据国家标准完善 try: int(self.id_card[:17]) except ValueError: self._validation_errors.append("身份证号前17位必须为数字") def _validate_stu_id(self): if not self.stu_id: self._validation_errors.append("学号不能为空") def _validate_name(self): if not (2 <= len(self.name) <= 20): self._validation_errors.append("姓名长度需在2-20个字符之间") if any(char.isdigit() or not char.isalpha() for char in self.name): self._validation_errors.append("姓名不能包含数字和特殊符号") def _validate_enrollment_date(self): if self.enrollment_date and self.enrollment_date < self.birthday: self._validation_errors.append("入学日期不能早于出生日期") def _validate_height(self): if self.height is not None and not (50 <= self.height <= 250): self._validation_errors.append("身高需在50-250cm之间") def _validate_weight(self): if self.weight is not None and not (5 <= self.weight <= 300): self._validation_errors.append("体重需在5-300kg之间") @property def is_valid(self): return len(self._validation_errors) == 0 def get_errors(self): return self._validation_errors.copy() def to_dict(self): 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, 'age': self.age, 'birthday': self.birthday.isoformat() } @classmethod def from_dict(cls, data): return cls( name=data['name'], id_card=data['id_card'], stu_id=data['stu_id'], gender=data['gender'], height=data['height'], weight=data['weight'], enrollment_date=data['enrollment_date'], class_name=data['class_name'], major=data['major'] ) # 数据访问层接口 class IStudentDAL(ABC): @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 add(self, student: Student) -> bool: pass @abstractmethod def delete(self, id_card: str = None, stu_id: str = None) -> bool: pass @abstractmethod def update(self, student: Student) -> bool: pass # CSV数据访问层实现 class CSVStudentDAL(IStudentDAL): def __init__(self, file_path): self.file_path = file_path if not os.path.exists(file_path): with open(file_path, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=Student.from_dict({}).to_dict().keys()) writer.writeheader() def get_by_id(self, id_card: str) -> Optional[Student]: with open(self.file_path, 'r', newline='') as f: reader = csv.DictReader(f) for row in reader: if row['id_card'] == id_card: return Student.from_dict(row) return None def get_by_stu_id(self, stu_id: str) -> Optional[Student]: with open(self.file_path, 'r', newline='') as f: reader = csv.DictReader(f) for row in reader: if row['stu_id'] == stu_id: return Student.from_dict(row) return None def get_all(self) -> List[Student]: students = [] with open(self.file_path, 'r', newline='') as f: reader = csv.DictReader(f) for row in reader: students.append(Student.from_dict(row)) return students def add(self, student: Student) -> bool: if self.get_by_id(student.id_card) or self.get_by_stu_id(student.stu_id): return False with open(self.file_path, 'a', newline='') as f: writer = csv.DictWriter(f, fieldnames=student.to_dict().keys()) writer.writerow(student.to_dict()) return True def delete(self, id_card: str = None, stu_id: str = None) -> bool: students = self.get_all() initial_length = len(students) if id_card: students = [s for s in students if s.id_card != id_card] elif stu_id: students = [s for s in students if s.stu_id != stu_id] if len(students) < initial_length: with open(self.file_path, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=students[0].to_dict().keys() if students else []) writer.writeheader() for s in students: writer.writerow(s.to_dict()) return True return False def update(self, student: Student) -> bool: students = self.get_all() for i, s in enumerate(students): if s.id_card == student.id_card or s.stu_id == student.stu_id: students[i] = student with open(self.file_path, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=students[0].to_dict().keys() if students else []) writer.writeheader() for s in students: writer.writerow(s.to_dict()) return True return False # JSON数据访问层实现 class JSONStudentDAL(IStudentDAL): def __init__(self, file_path): self.file_path = file_path if not os.path.exists(file_path): with open(file_path, 'w') as f: json.dump([], f) def get_by_id(self, id_card: str) -> Optional[Student]: with open(self.file_path, 'r') as f: data = json.load(f) for row in data: if row['id_card'] == id_card: return Student.from_dict(row) return None def get_by_stu_id(self, stu_id: str) -> Optional[Student]: with open(self.file_path, 'r') as f: data = json.load(f) for row in data: if row['stu_id'] == stu_id: return Student.from_dict(row) return None def get_all(self) -> List[Student]: with open(self.file_path, 'r') as f: data = json.load(f) return [Student.from_dict(row) for row in data] def add(self, student: Student) -> bool: if self.get_by_id(student.id_card) or self.get_by_stu_id(student.stu_id): return False with open(self.file_path, 'r') as f: data = json.load(f) data.append(student.to_dict()) with open(self.file_path, 'w') as f: json.dump(data, f, indent=4) return True def delete(self, id_card: str = None, stu_id: str = None) -> bool: with open(self.file_path, 'r') as f: data = json.load(f) initial_length = len(data) if id_card: data = [s for s in data if s['id_card'] != id_card] elif stu_id: data = [s for s in data if s['stu_id'] != stu_id] if len(data) < initial_length: with open(self.file_path, 'w') as f: json.dump(data, f, indent=4) return True return False def update(self, student: Student) -> bool: with open(self.file_path, 'r') as f: data = json.load(f) for i, s in enumerate(data): if s['id_card'] == student.id_card or s['stu_id'] == student.stu_id: data[i] = student.to_dict() with open(self.file_path, 'w') as f: json.dump(data, f, indent=4) return True return False # SQLite数据访问层实现 class SQLiteStudentDAL(IStudentDAL): def __init__(self, db_path): self.db_path = db_path self._create_table() def _create_table(self): conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(''' CREATE TABLE IF NOT EXISTS students ( id_card TEXT PRIMARY KEY, stu_id TEXT UNIQUE, name TEXT, gender BOOLEAN, height INTEGER, weight REAL, enrollment_date TEXT, class_name TEXT, major TEXT, age INTEGER, birthday TEXT ) ''') conn.commit() conn.close() def get_by_id(self, id_card: str) -> Optional[Student]: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT * FROM students WHERE id_card =?", (id_card,)) row = cursor.fetchone() conn.close() if row: data = dict(zip([description[0] for description in cursor.description], row)) return Student.from_dict(data) return None def get_by_stu_id(self, stu_id: str) -> Optional[Student]: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT * FROM students WHERE stu_id =?", (stu_id,)) row = cursor.fetchone() conn.close() if row: data = dict(zip([description[0] for description in cursor.description], row)) return Student.from_dict(data) return None def get_all(self) -> List[Student]: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute("SELECT * FROM students") rows = cursor.fetchall() conn.close() return [Student.from_dict(dict(zip([description[0] for description in cursor.description], row))) for row in rows] def add(self, student: Student) -> bool: if self.get_by_id(student.id_card) or self.get_by_stu_id(student.stu_id): return False conn = sqlite3.connect(self.db_path) cursor = conn.cursor() try: cursor.execute(''' INSERT INTO students (id_card, stu_id, name, gender, height, weight, enrollment_date, class_name, major, age, birthday) VALUES (?,?,?,?,?,?,?,?,?,?,?) ''', ( student.id_card, student.stu_id, student.name, student.gender, student.height, student.weight, student.enrollment_date.isoformat() if student.enrollment_date else None, student.class_name, student.major, student.age, student.birthday.isoformat())) conn.commit() return True except sqlite3.IntegrityError: return False finally: conn.close() def delete(self, id_card: str = None, stu_id: str = None) -> bool: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() if id_card: cursor.execute("DELETE FROM students WHERE id_card =?", (id_card,)) elif stu_id: cursor.execute("DELETE FROM students WHERE stu_id =?", (stu_id,)) rows_affected = cursor.rowcount conn.commit() conn.close() return rows_affected > 0 def update(self, student: Student) -> bool: conn = sqlite3.connect(self.db_path) cursor = conn.cursor() try: cursor.execute(''' UPDATE students SET stu_id =?, name =?, gender =?, height =?, weight =?, enrollment_date =?, class_name =?, major =?, age =?, birthday =? WHERE id_card =? ''', ( student.stu_id, student.name, student.gender, student.height, student.weight, student.enrollment_date.isoformat() if student.enrollment_date else None, student.class_name, student.major, student.age, student.birthday.isoformat(), student.id_card)) rows_affected = cursor.rowcount conn.commit() return rows_affected > 0 except sqlite3.IntegrityError: return False finally: conn.close() # 业务逻辑层 class StudentBLL: def __init__(self, dal: IStudentDAL): self.dal = dal def add_student(self, student: Student) -> bool: if not student.is_valid: raise ValueError(f"学生数据无效: {', '.join(student.get_errors())}") return self.dal.add(student) def delete_student(self, id_card: str = None, stu_id: str = None) -> bool: return self.dal.delete(id_card, stu_id) def update_student(self, student: Student) -> bool: if not student.is_valid: raise ValueError(f"学生数据无效: {', '.join(student.get_errors())}") return self.dal.update(student) def get_student_by_id(self, id_card: str) -> Optional[Student]: return self.dal.get_by_id(id_card) def get_student_by_stu_id(self, stu_id: str) -> Optional[Student]: return self.dal.get_by_stu_id(stu_id) def get_all_students(self) -> List[Student]: return self.dal.get_all() def query_by_name(self, name: str) -> List[Student]: return [s for s in self.get_all_students() if name.lower() in s.name.lower()] def query_by_class(self, class_name: str) -> List[Student]: return [s for s in self.get_all_students() if class_name.lower() in s.class_name.lower()] def query_by_major(self, major: str) -> List[Student]: return [s for s in self.get_all_students() if major.lower() in s.major.lower()] def get_student_count(self) -> int: return len(self.get_all_students()) def get_major_count(self) -> dict: major_count = {} for student in self.get_all_students(): if student.major in major_count: major_count[student.major] += 1 else: major_count[student.major] = 1 return major_count def get_avg_height_by_class(self, class_name: str) -> float: students = [s for s in self.get_all_students() if s.class_name == class_name and s.height is not None] if not students: return 0 return sum(s.height for s in students) / len(students) def get_avg_weight_by_class(self, class_name: str) -> float: students = [s for s in self.get_all_students() if s.class_name == class_name and s.weight is not None] if not students: return 0 return sum(s.weight for s in students) / len(students) def get_avg_height_by_major(self, major: str) -> float: students = [s for s in self.get_all_students() if s.major == major and s.height is not None] if not students: return 0 return sum(s.height for s in students) / len(students) def get_avg_weight_by_major(self, major: str) -> float: students = [s for s in self.get_all_students() if s.major == major and s.weight is not None] if not students: return 0 return sum(s.weight for s in students) / len(students) def import_from_csv(self, file_path): with open(file_path, 'r', newline='') as f: reader = csv.DictReader(f) for row in reader: student = Student.from_dict(row) if student.is_valid: self.add_student(student) def import_from_json(self, file_path): with open(file_path, 'r') as f: data = json.load(f) for row in data: student = Student.from_dict(row) if student.is_valid: self.add_student(student) def export_to_csv(self, file_path): students = self.get_all_students() with open(file_path, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=students[0].to_dict().keys() if students else []) writer.writeheader() for s in students: writer.writerow(s.to_dict()) def export_to_json(self, file_path): students = self.get_all_students() with open(file_path, 'w') as f: json.dump([s.to_dict() for s in students], f, indent=4) # 用户界面层 class StudentUI: def __init__(self, bll: StudentBLL): self.bll = bll def display_menu(self): print("\n===== 学生信息管理系统 =====") print("1. 添加学生信息") print("2. 删除学生信息") print("3. 修改学生信息") print("4. 查看学生详细信息") print("5. 查询学生信息") print("6. 统计信息") print("7. 数据导入") print("8. 数据导出") print("0. 退出系统") print("==========================") def display_query_menu(self): print("\n===== 查询学生信息 =====") print("1. 按学号查询") print("2. 按姓名查询") print("3. 按班级查询") print("4. 按专业查询") print("5. 返回上一级") print("======================") def display_stats_menu(self): print("\n===== 统计信息 =====") print("1. 学生总数") print("2. 各专业学生人数") print("3. 按班级计算平均身高") print("4. 按班级计算平均体重") print("5. 按专业计算平均身高") print("6. 按专业计算平均体重") print("7. 返回上一级") print("====================") def display_import_menu(self): print("\n===== 数据导入 =====") print("1. 从CSV导入") print("2. 从JSON导入") print("3. 返回上一级") print("====================") def display_export_menu(self): print("\n===== 数据导出 =====") print("1. 导出到CSV") print("2. 导出到JSON") print("3. 返回上一级") print("====================") def get_student_input(self, existing_student=None): """获取学生输入,支持使用现有学生数据作为默认值""" if existing_student: print(f"当前值: {existing_student.name}") name = input("请输入姓名 (直接回车保持不变): ") or (existing_student.name if existing_student else "") if existing_student: print(f"当前值: {existing_student.id_card}") id_card = input("请输入身份证号 (直接回车保持不变): ") or (existing_student.id_card if existing_student else "") if existing_student: print(f"当前值: {existing_student.stu_id}") stu_id = input("请输入学号 (直接回车保持不变): ") or (existing_student.stu_id if existing_student else "") if existing_student and existing_student.gender is not None: print(f"当前值: {existing_student.gender}") gender_str = input("请输入性别 (True/False,直接回车保持不变): ") gender = None if gender_str: gender = gender_str.lower() == 'true' elif existing_student: gender = existing_student.gender if existing_student and existing_student.height is not None: print(f"当前值: {existing_student.height}") height_str = input("请输入身高 (cm,直接回车保持不变): ") height = None if height_str: height = int(height_str) elif existing_student: height = existing_student.height if existing_student and existing_student.weight is not None: print(f"当前值: {existing_student.weight}") weight_str = input("请输入体重 (kg,直接回车保持不变): ") weight = None if weight_str: weight = float(weight_str) elif existing_student: weight = existing_student.weight if existing_student and existing_student.enrollment_date: print(f"当前值: {existing_student.enrollment_date.isoformat()}") enrollment_date = input("请输入入学日期 (YYYY-MM-DD,直接回车保持不变): ") or ( existing_student.enrollment_date.isoformat() if existing_student and existing_student.enrollment_date else None ) if existing_student: print(f"当前值: {existing_student.class_name}") class_name = input("请输入班级名称 (直接回车保持不变): ") or ( existing_student.class_name if existing_student else "") if existing_student: print(f"当前值: {existing_student.major}") major = input("请输入专业 (直接回车保持不变): ") or (existing_student.major if existing_student else "") return Student(name, id_card, stu_id, gender, height, weight, enrollment_date, class_name, major) def run(self): while True: self.display_menu() choice = input("请选择操作: ") if choice == "1": self.add_student() elif choice == "2": self.delete_student() elif choice == "3": self.update_student() elif choice == "4": self.view_student_details() elif choice == "5": self.query_student() elif choice == "6": self.statistics() elif choice == "7": self.import_data() elif choice == "8": self.export_data() elif choice == "0": print("感谢使用,再见!") break else: print("无效选择,请重试!") def add_student(self): print("\n===== 添加学生信息 =====") try: student = self.get_student_input() if self.bll.add_student(student): print("添加成功!") else: print("添加失败,学号或身份证号可能已存在。") except ValueError as e: print(f"添加失败: {e}") def delete_student(self): print("\n===== 删除学生信息 =====") choice = input("按学号删除请输入1,按身份证号删除请输入2: ") if choice == "1": stu_id = input("请输入要删除的学号: ") if self.bll.delete_student(stu_id=stu_id): print("删除成功!") else: print("删除失败,学号不存在。") elif choice == "2": id_card = input("请输入要删除的身份证号: ") if self.bll.delete_student(id_card=id_card): print("删除成功!") else: print("删除失败,身份证号不存在。") else: print("无效选择,请重试!") def update_student(self): print("\n===== 修改学生信息 =====") stu_id = input("请输入要修改的学生学号: ") student = self.bll.get_student_by_stu_id(stu_id) if not student: print("学号不存在。") return print(f"当前信息: {student.to_dict()}") print("(直接回车表示不修改)") # 使用现有的学生数据作为默认值 new_student = self.get_student_input(student) # 确保不修改ID和学号 new_student.id_card = student.id_card new_student.stu_id = student.stu_id try: if self.bll.update_student(new_student): print("修改成功!") else: print("修改失败,可能是数据验证不通过。") except ValueError as e: print(f"修改失败: {e}") def view_student_details(self): print("\n===== 查看学生详细信息 =====") stu_id = input("请输入要查看的学生学号: ") student = self.bll.get_student_by_stu_id(stu_id) if student: print(student.to_dict()) else: print("学号不存在。") def query_student(self): while True: self.display_query_menu() choice = input("请选择查询方式: ") if choice == "1": stu_id = input("请输入学号: ") student = self.bll.get_student_by_stu_id(stu_id) if student: print(student.to_dict()) else: print("未找到该学生。") elif choice == "2": name = input("请输入姓名: ") students = self.bll.query_by_name(name) if students: for s in students: print(s.to_dict()) else: print("未找到匹配的学生。") elif choice == "3": class_name = input("请输入班级名称: ") students = self.bll.query_by_class(class_name) if students: for s in students: print(s.to_dict()) else: print("未找到匹配的学生。") elif choice == "4": major = input("请输入专业名称: ") students = self.bll.query_by_major(major) if students: for s in students: print(s.to_dict()) else: print("未找到匹配的学生。") elif choice == "5": break else: print("无效选择,请重试!") def statistics(self): while True: self.display_stats_menu() choice = input("请选择统计方式: ") if choice == "1": print(f"学生总数: {self.bll.get_student_count()}") elif choice == "2": major_count = self.bll.get_major_count() for major, count in major_count.items(): print(f"{major}: {count}人") elif choice == "3": class_name = input("请输入班级名称: ") avg_height = self.bll.get_avg_height_by_class(class_name) print(f"{class_name} 班级平均身高: {avg_height:.2f} cm") elif choice == "4": class_name = input("请输入班级名称: ") avg_weight = self.bll.get_avg_weight_by_class(class_name) print(f"{class_name} 班级平均体重: {avg_weight:.2f} kg") elif choice == "5": major = input("请输入专业名称: ") avg_height = self.bll.get_avg_height_by_major(major) print(f"{major} 专业平均身高: {avg_height:.2f} cm") elif choice == "6": major = input("请输入专业名称: ") avg_weight = self.bll.get_avg_weight_by_major(major) print(f"{major} 专业平均体重: {avg_weight:.2f} kg") elif choice == "7": break else: print("无效选择,请重试!") def import_data(self): while True: self.display_import_menu() choice = input("请选择导入方式: ") if choice == "1": file_path = input("请输入CSV文件路径: ") try: self.bll.import_from_csv(file_path) print("导入成功!") except Exception as e: print(f"导入失败: {e}") elif choice == "2": file_path = input("请输入JSON文件路径: ") try: self.bll.import_from_json(file_path) print("导入成功!") except Exception as e: print(f"导入失败: {e}") elif choice == "3": break else: print("无效选择,请重试!") def export_data(self): while True: self.display_export_menu() choice = input("请选择导出方式: ") if choice == "1": file_path = input("请输入导出的CSV文件路径: ") try: self.bll.export_to_csv(file_path) print("导出成功!") except Exception as e: print(f"导出失败: {e}") elif choice == "2": file_path = input("请输入导出的JSON文件路径: ") try: self.bll.export_to_json(file_path) print("导出成功!") except Exception as e: print(f"导出失败: {e}") elif choice == "3": break else: print("无效选择,请重试!") if __name__ == "__main__": # 选择数据存储方式 storage_choice = input("请选择数据存储方式 (1: CSV, 2: JSON, 3: SQLite): ") if storage_choice == "1": dal = CSVStudentDAL("students.csv") elif storage_choice == "2": dal = JSONStudentDAL("students.json") elif storage_choice == "3": dal = SQLiteStudentDAL("students.db") else: print("无效选择,默认使用SQLite。") dal = SQLiteStudentDAL("students.db") bll = StudentBLL(dal) ui = StudentUI(bll) print("欢迎使用学生信息管理系统!") ui.run() print(123)