|
|
|
@ -0,0 +1,836 @@
|
|
|
|
|
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()
|