You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xsxx/学生信息管理系统.py

837 lines
31 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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)