Compare commits

..

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

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

4
stumis/.gitignore vendored

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

@ -0,0 +1,21 @@
# Dockerfile
FROM python:3.9-slim
# 设置工作目录
WORKDIR /app
# 复制依赖文件并安装
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 复制项目代码
COPY . .
# 设置环境变量
ENV PYTHONPATH=/app
# 暴露端口如果是Web应用
EXPOSE 8000
# 启动命令
CMD ["python", "stumis/ui/studentUI.py"]

@ -0,0 +1,36 @@
# stumis/bll/auth_bll.py
import json
import bcrypt
from stumis.model.user import User
class AuthBLL:
def __init__(self, users_file: str):
self.users_file = users_file
self.users = self._load_users()
def _load_users(self) -> dict[str, User]:
try:
with open(self.users_file, 'r') as f:
data = json.load(f)
return {user['username']: User(user['username'], user['password'].encode(), user['role']) for user in data}
except (FileNotFoundError, json.JSONDecodeError):
return {}
def authenticate(self, username: str, password: str) -> User | None:
user = self.users.get(username)
if user and bcrypt.checkpw(password.encode(), user.password):
return user
return None
def add_user(self, user: User) -> bool:
if user.username in self.users:
return False
hashed = bcrypt.hashpw(user.password.encode(), bcrypt.gensalt())
user.password = hashed.decode()
self.users[user.username] = user
self._save_users()
return True
def _save_users(self):
with open(self.users_file, 'w') as f:
json.dump([vars(user) for user in self.users.values()], f)

@ -0,0 +1,32 @@
from typing import List
from stumis.model.score import Score
# 简单模拟 JsonScoreDAL 类
class JsonScoreDAL:
def __init__(self, file_path: str):
self.file_path = file_path
self.data = [] # 模拟数据存储
def add(self, score: Score) -> bool:
self.data.append(score)
return True
def get_all(self) -> List[Score]:
return self.data
class ScoreBLL:
def __init__(self, file_path: str):
self.dal = JsonScoreDAL(file_path)
def add_score(self, score: Score) -> bool:
return self.dal.add(score)
def get_scores_by_student(self, student_id: str) -> List[Score]:
return [s for s in self.dal.get_all() if s.student_id == student_id]
def calculate_average(self, student_id: str) -> float:
scores = self.get_scores_by_student(student_id)
if not scores:
return 0
return sum(s.score for s in scores) / len(scores)

@ -0,0 +1,111 @@
from typing import List, Optional
from stumis.dal.studentDAL import JsonStudentDAL, CsvStudentDAL
from stumis.model.student import Student
class StudentBLL:
def __init__(self, file_path: str):
self.dal = self._get_dal(file_path)
@staticmethod
def _get_dal(file_path: str):
if file_path.endswith('.json'):
return JsonStudentDAL(file_path)
elif file_path.endswith('.csv'):
return CsvStudentDAL(file_path)
else:
raise ValueError("Unsupported file format")
def add(self, student: Student) -> bool:
if not self._validate_student(student):
raise ValueError("Invalid student data")
if self.dal.is_exist(student.id_card):
raise ValueError("Student with this ID card already exists")
if self.dal.is_exist_stu_id(student.stu_id):
raise ValueError("Student with this student ID already exists")
return self.dal.add(student)
def delete(self, id_card: str) -> bool:
if not self.dal.is_exist(id_card):
raise ValueError("Student with this ID card does not exist")
return self.dal.delete(id_card)
def delete_by_stu_id(self, stu_id: str) -> bool:
if not self.dal.is_exist_stu_id(stu_id):
raise ValueError("Student with this student ID does not exist")
return self.dal.delete_by_stu_id(stu_id)
def update(self, student: Student) -> bool:
if not self._validate_student(student):
raise ValueError("Invalid student data")
if not self.dal.is_exist(student.id_card):
raise ValueError("Student with this ID card does not exist")
return self.dal.update(student)
def get_by_id(self, id_card: str) -> Optional[Student]:
return self.dal.get_by_id(id_card)
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
return self.dal.get_by_stu_id(stu_id)
def get_all(self) -> List[Student]:
return self.dal.get_all()
@staticmethod
def _validate_student(student: Student) -> bool:
# 实际验证逻辑示例
required_fields = ['name', 'id_card', 'stu_id']
return all(getattr(student, field) is not None for field in required_fields)
def export_to_json(self, file_path: str) -> bool:
data = [student.to_dict() for student in self.dal.get_all()]
return self.dal.export_to_json(file_path, data)
def import_from_json(self, file_path: str) -> bool:
data = self.dal.import_from_json(file_path)
for item in data:
student = Student.from_dict(item)
self.add(student)
return True
def export_to_csv(self, file_path: str) -> bool:
data = [student.to_dict() for student in self.dal.get_all()]
return self.dal.export_to_csv(file_path, data)
def import_from_csv(self, file_path: str) -> bool:
data = self.dal.import_from_csv(file_path)
for item in data:
student = Student.from_dict(item)
self.add(student)
return True
# stumis/bll/studentBLL.py
def query_students(self, conditions: dict, logic: str = "AND") -> List[Student]:
"""
支持多条件组合查询
:param conditions: 条件字典格式: {字段名: }
:param logic: 逻辑关系"AND" "OR"
:return: 符合条件的学生列表
"""
all_students = self.get_all()
if not conditions:
return all_students
result = []
for student in all_students:
matches = []
for field, value in conditions.items():
# 处理特殊条件(如范围查询)
if isinstance(value, tuple) and len(value) == 2: # 范围查询 (min, max)
field_value = getattr(student, field, None)
matches.append(value[0] <= field_value <= value[1])
else: # 精确匹配
matches.append(getattr(student, field, None) == value)
# 根据逻辑关系决定是否添加到结果
if logic == "AND" and all(matches):
result.append(student)
elif logic == "OR" and any(matches):
result.append(student)
return result

@ -0,0 +1 @@
from .studentDAL import JsonStudentDAL

@ -0,0 +1,26 @@
import json
from cryptography.fernet import Fernet
from stumis.model.student import Student
from stumis.dal.studentDAL import JsonStudentDAL
class EncryptedStudentDAL(JsonStudentDAL):
def __init__(self, file_path: str, key: bytes):
super().__init__(file_path)
self.cipher = Fernet(key)
def _load(self) -> list[Student]:
try:
with open(self.file_path, 'rb') as f:
encrypted_data = f.read()
decrypted_data = self.cipher.decrypt(encrypted_data)
data = json.loads(decrypted_data.decode('utf-8'))
return [Student.from_dict(item) for item in data]
except (FileNotFoundError, json.JSONDecodeError):
return []
def _save(self):
data_to_save = json.dumps([student.to_dict() for student in self.data]).encode('utf-8')
encrypted_data = self.cipher.encrypt(data_to_save)
with open(self.file_path, 'wb') as f:
f.write(encrypted_data)

@ -0,0 +1,323 @@
import json
import csv
from abc import ABC, abstractmethod
from typing import List, Dict, Optional, Union
from stumis.model.student import Student
class DataConverter:
@staticmethod
def csv_row_to_dict(row: dict) -> dict:
converted = {}
for key, value in row.items():
if value == '':
converted[key] = None
elif key == 'gender':
converted[key] = value.lower() == 'true' if value else None
elif key == 'height':
converted[key] = int(value) if value else None
elif key == 'weight':
converted[key] = float(value) if value else None
else:
converted[key] = value
return converted
@staticmethod
def ensure_student_dict(data: Union[Student, Dict]) -> Dict:
if isinstance(data, Student):
return data.to_dict()
return data
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) -> bool:
pass
@abstractmethod
def delete_by_stu_id(self, stu_id: str) -> bool:
pass
@abstractmethod
def update(self, student: Student) -> bool:
pass
@abstractmethod
def is_exist(self, id_card: str) -> bool:
pass
@abstractmethod
def is_exist_stu_id(self, stu_id: str) -> bool:
pass
@abstractmethod
def import_from_json(self, file_path: str) -> List[Dict]:
pass
@abstractmethod
def export_to_json(self, file_path: str, data: List[Union[Student, Dict]]) -> bool:
pass
@abstractmethod
def import_from_csv(self, file_path: str) -> List[Dict]:
pass
@abstractmethod
def export_to_csv(self, file_path: str, data: List[Union[Student, Dict]]) -> bool:
pass
class JsonStudentDAL(IStudentDAL):
def __init__(self, file_path: str):
self.file_path = file_path
self.data = self._load()
def _load(self) -> List[Student]:
try:
with open(self.file_path, 'r', encoding='utf-8') as f:
return [Student.from_dict(item) for item in json.load(f)]
except (FileNotFoundError, json.JSONDecodeError):
return []
def _save(self):
with open(self.file_path, 'w', encoding='utf-8') as f:
json.dump([student.to_dict() for student in self.data], f, ensure_ascii=False, indent=4)
def get_by_id(self, id_card: str) -> Optional[Student]:
return next((s for s in self.data if s.id_card == id_card), None)
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
return next((s for s in self.data if s.stu_id == stu_id), None)
def get_all(self) -> List[Student]:
return self.data
def add(self, student: Student) -> bool:
if self.is_exist(student.id_card):
return False
self.data.append(student)
self._save()
return True
def delete(self, id_card: str) -> bool:
initial_length = len(self.data)
self.data = [s for s in self.data if s.id_card != id_card]
if len(self.data) < initial_length:
self._save()
return True
return False
def delete_by_stu_id(self, stu_id: str) -> bool:
initial_length = len(self.data)
self.data = [s for s in self.data if s.stu_id != stu_id]
if len(self.data) < initial_length:
self._save()
return True
return False
def update(self, student: Student) -> bool:
for i, s in enumerate(self.data):
if s.id_card == student.id_card:
self.data[i] = student
self._save()
return True
return False
def is_exist(self, id_card: str) -> bool:
return any(s.id_card == id_card for s in self.data)
def is_exist_stu_id(self, stu_id: str) -> bool:
return any(s.stu_id == stu_id for s in self.data)
def import_from_json(self, file_path: str) -> List[Dict]:
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error importing JSON: {e}")
return []
def export_to_json(self, file_path: str, data: List[Union[Student, Dict]]) -> bool:
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(
[DataConverter.ensure_student_dict(item) for item in data],
f,
ensure_ascii=False,
indent=4
)
return True
except Exception as e:
print(f"Error exporting JSON: {e}")
return False
def import_from_csv(self, file_path: str) -> List[Dict]:
try:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
return [DataConverter.csv_row_to_dict(row) for row in reader]
except Exception as e:
print(f"Error importing CSV: {e}")
return []
def export_to_csv(self, file_path: str, data: List[Union[Student, Dict]]) -> bool:
try:
if not data:
return False
first_item = DataConverter.ensure_student_dict(data[0])
with open(file_path, 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=first_item.keys())
writer.writeheader()
writer.writerows([DataConverter.ensure_student_dict(item) for item in data])
return True
except Exception as e:
print(f"Error exporting CSV: {e}")
return False
class CsvStudentDAL(IStudentDAL):
FIELD_TYPES = {
"name": str,
"id_card": str,
"stu_id": str,
"gender": bool,
"height": int,
"weight": float,
"enrollment_date": str,
"class_name": str,
"major": str
}
def __init__(self, file_path: str):
self.file_path = file_path
self._ensure_file_exists()
self.data = self._load()
def _ensure_file_exists(self):
try:
with open(self.file_path, 'r', encoding='utf-8-sig') as f:
csv.DictReader(f)
except FileNotFoundError:
with open(self.file_path, 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
writer.writeheader()
def _load(self) -> List[Dict]:
with open(self.file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
return [DataConverter.csv_row_to_dict(row) for row in reader]
def _save(self):
with open(self.file_path, 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
writer.writeheader()
writer.writerows(self.data)
def get_by_id(self, id_card: str) -> Optional[Student]:
for item in self.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]:
for item in self.data:
if item["stu_id"] == stu_id:
return Student.from_dict(item)
return None
def get_all(self) -> List[Student]:
return [Student.from_dict(item) for item in self.data]
def add(self, student: Student) -> bool:
if self.is_exist(student.id_card):
return False
self.data.append(student.to_dict())
self._save()
return True
def delete(self, id_card: str) -> bool:
for i, item in enumerate(self.data):
if item["id_card"] == id_card:
del self.data[i]
self._save()
return True
return False
def delete_by_stu_id(self, stu_id: str) -> bool:
for i, item in enumerate(self.data):
if item["stu_id"] == stu_id:
del self.data[i]
self._save()
return True
return False
def update(self, student: Student) -> bool:
for i, item in enumerate(self.data):
if item["id_card"] == student.id_card:
self.data[i] = student.to_dict()
self._save()
return True
return False
def is_exist(self, id_card: str) -> bool:
return any(item["id_card"] == id_card for item in self.data)
def is_exist_stu_id(self, stu_id: str) -> bool:
return any(item["stu_id"] == stu_id for item in self.data)
def import_from_json(self, file_path: str) -> List[Dict]:
try:
with open(file_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
print(f"Error importing JSON: {e}")
return []
def export_to_json(self, file_path: str, data: List[Union[Student, Dict]]) -> bool:
try:
with open(file_path, 'w', encoding='utf-8') as f:
json.dump(
[DataConverter.ensure_student_dict(item) for item in data],
f,
ensure_ascii=False,
indent=4
)
return True
except Exception as e:
print(f"Error exporting JSON: {e}")
return False
def import_from_csv(self, file_path: str) -> List[Dict]:
try:
with open(file_path, 'r', encoding='utf-8-sig') as f:
reader = csv.DictReader(f)
return [DataConverter.csv_row_to_dict(row) for row in reader]
except Exception as e:
print(f"Error importing CSV: {e}")
return []
def export_to_csv(self, file_path: str, data: List[Union[Student, Dict]]) -> bool:
try:
if not data:
return False
first_item = DataConverter.ensure_student_dict(data[0])
with open(file_path, 'w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=first_item.keys())
writer.writeheader()
writer.writerows([DataConverter.ensure_student_dict(item) for item in data])
return True
except Exception as e:
print(f"Error exporting CSV: {e}")
return False

@ -0,0 +1,12 @@
version: '3'
services:
student-management:
build: .
volumes:
- ./data:/app/data # 挂载数据目录,持久化存储
- ./logs:/app/logs # 挂载日志目录
environment:
- FILE_PATH=/app/data/students.json
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
restart: always

@ -0,0 +1 @@
print(123)

@ -0,0 +1,9 @@
# stumis/model/score.py
class Score:
def __init__(self, student_id: str, course: str, score: float):
self.student_id = student_id # 关联学生ID
self.course = course
self.score = score

@ -0,0 +1,119 @@
from datetime import datetime, date
from typing import 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: Optional[str] = None):
self.name = name
self.id_card = id_card
self.stu_id = stu_id
self.gender = gender
self.height = height
self.weight = weight
self.enrollment_date = self._parse_date(enrollment_date)
self.class_name = class_name
self.major = major
self._birthday = self._extract_birthday(id_card)
self._age = self._calculate_age(self._birthday)
@staticmethod
def _validate_date(date_str: Optional[str]) -> bool:
if not date_str:
return True
try:
date.fromisoformat(date_str)
return True
except ValueError:
return False
@property
def birthday(self) -> date:
return self._birthday
@property
def age(self) -> int:
return self._age
@staticmethod
def _parse_date(date_input: Optional[Union[date, str]]) -> Optional[date]:
if date_input is None:
return None
if isinstance(date_input, date):
return date_input
try:
return datetime.strptime(date_input, "%Y-%m-%d").date()
except ValueError:
raise ValueError("Invalid date format. Expected YYYY-MM-DD.")
@staticmethod
def _extract_birthday(id_card: str) -> date:
if len(id_card) != 18:
raise ValueError("身份证号长度必须为18位")
if not id_card[:17].isdigit():
raise ValueError("身份证号前17位必须为数字")
try:
year = int(id_card[6:10])
month = int(id_card[10:12])
day = int(id_card[12:14])
return date(year, month, day)
except ValueError as e:
# 记录日志或进行其他处理
raise ValueError(f"身份证号中的出生日期无效: {e}")
@staticmethod
def _calculate_age(birthday: date) -> int:
today = date.today()
age = today.year - birthday.year
if (today.month, today.day) < (birthday.month, birthday.day):
age -= 1
return age
@staticmethod
def _validate_id_card(id_card: str) -> bool:
if len(id_card) != 18:
return False
if not id_card[:17].isdigit():
return False
weights = [2 ** (17 - i) % 11 for i in range(18)]
check_code_map = {0: '1', 1: '0', 2: 'X', 3: '9', 4: '8', 5: '7', 6: '6', 7: '5', 8: '4', 9: '3', 10: '2'}
weighted_sum = sum(int(id_card[i]) * weights[i] for i in range(17))
check_code = check_code_map[weighted_sum % 11]
return id_card[-1] == check_code
def to_dict(self) -> dict:
return {
"name": self.name,
"id_card": self.id_card,
"stu_id": self.stu_id,
"gender": self.gender,
"height": self.height,
"weight": round(self.weight, 1) if self.weight is not None else None,
"enrollment_date": self.enrollment_date.isoformat() if self.enrollment_date else None,
"class_name": self.class_name,
"major": self.major
}
@classmethod
def from_dict(cls, data: dict) -> 'Student':
enrollment_date = cls._parse_date(data.get("enrollment_date"))
return cls(
name=data["name"],
id_card=data["id_card"],
stu_id=data["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):
return (f"Student(name={self.name}, id_card={self.id_card}, stu_id={self.stu_id}, gender={self.gender}, "
f"height={self.height}, weight={self.weight}, enrollment_date={self.enrollment_date}, "
f"class_name={self.class_name}, major={self.major})")

@ -0,0 +1,17 @@
class User:
ROLES = {
"admin": ["add", "delete", "update", "query", "manage_users"],
"teacher": ["add", "update", "query"],
"student": ["query"]
}
def __init__(self, username: str, password: str, role: str = "student"):
self.username = username
self.password = password # 实际应存储哈希值
self.role = role
def has_permission(self, action: str) -> bool:
return action in self.ROLES.get(self.role, [])

@ -0,0 +1,13 @@
[
{
"name": "测试学生",
"id_card": "110101200001011234",
"stu_id": "2025001",
"gender": true,
"height": 175,
"weight": 70.5,
"enrollment_date": "2025-09-01",
"class_name": "软件工程1班",
"major": "计算机科学与技术"
}
]

@ -0,0 +1,48 @@
# test_student_bll.py
from stumis.bll.studentBLL import StudentBLL
from stumis.model.student import Student
import os
import pytest
from datetime import date
import uuid
@pytest.fixture
def student_bll(tmp_path):
file_path = tmp_path / "test_students.json"
bll = StudentBLL(str(file_path))
yield bll
# 清理资源(如果需要)
@pytest.fixture
def test_student():
unique_suffix = uuid.uuid4().hex[:6]
return Student(
name=f"测试学生_{unique_suffix}",
id_card=f"110101{date.today().year}{date.today().month:02d}{date.today().day:02d}{unique_suffix}",
stu_id=f"{date.today().year}{int(date.today().strftime('%m%d'))}{unique_suffix}",
gender=True,
height=175,
weight=70.5,
enrollment_date=f"{date.today().year}-09-01",
class_name=f"软件工程1班_{unique_suffix}",
major="计算机科学与技术"
)
def test_add_student(student_bll, test_student):
# 测试添加学生
print("测试添加学生...")
result = student_bll.add(test_student)
assert result is True
print("添加结果:", result)
# 测试查询学生
print("测试查询学生...")
retrieved_student = student_bll.get_by_id(test_student.id_card)
assert retrieved_student is not None
assert retrieved_student.name == test_student.name
print("查询成功:", retrieved_student.name)
print("所有测试完成!")
if __name__ == "__main__":
pytest.main([__file__])

@ -0,0 +1,13 @@
[
{
"name": "测试学生",
"id_card": "110101200001011234",
"stu_id": "2025001",
"gender": true,
"height": 175,
"weight": 70.5,
"enrollment_date": "2025-09-01",
"class_name": "软件工程1班",
"major": "计算机科学与技术"
}
]

@ -0,0 +1,267 @@
from typing import Optional, List
from stumis.model.student import Student
from stumis.bll.studentBLL import StudentBLL
from stumis.dal import JsonStudentDAL
class StudentUI:
def __init__(self, file_path):
self.bll = StudentBLL(file_path)
print("UI层中StudentBLL的方法列表:", dir(self.bll))
assert hasattr(self.bll, 'add'), "add方法缺失"
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.query_student()
elif choice == '5':
self.show_stats()
elif choice == '6':
self.import_export_data()
elif choice == '7':
self.clear_students()
elif choice == '0':
print("退出系统。")
break
else:
print("无效的选择,请重新输入。")
def display_menu(self):
print("=====学生信息管理系统=====")
print("1.添加学生信息")
print("2.删除学生信息")
print("3.更新学生信息")
print("4.查询学生信息")
print("5.统计分析")
print("6.数据导入导出")
print("7.清空所有学生信息")
print("0.退出系统")
print("==========================")
def add_student(self):
name = input("姓名: ")
id_card = input("身份证号: ")
stu_id = input("学号: ")
gender = input("性别 (男/女): ").lower() == ''
height = input("身高 (cm): ")
weight = input("体重 (kg): ")
enrollment_date = input("入学日期 (YYYY-MM-DD): ")
class_name = input("班级: ")
major = input("专业: ")
if not Student._validate_id_card(id_card):
print("无效的身份证号,请重新输入。")
return
if not Student._validate_date(enrollment_date):
print("无效的入学日期格式请使用YYYY-MM-DD格式。")
return
try:
height = int(height) if height else None
weight = float(weight) if weight else None
enrollment_date = enrollment_date if enrollment_date else None
student = Student(name, id_card, stu_id, gender, height, weight, enrollment_date, class_name, major)
if self.bll.add(student):
print("学生信息添加成功。")
else:
print("学生信息添加失败。")
except ValueError as e:
print(f"错误: {e}")
def delete_student(self):
id_card = input("请输入要删除的学生身份证号: ")
if self.bll.delete(id_card):
print("学生信息删除成功。")
else:
print("学生信息删除失败。")
def update_student(self):
id_card = input("请输入要更新的学生身份证号: ")
student = self.bll.get_by_id(id_card)
if not student:
print("未找到该学生信息。")
return
print("当前学生信息:")
self.display_student(student)
name = input(f"姓名 ({student.name}): ") or student.name
gender = input(f"性别 (男/女) ({'' if student.gender else ''}): ").lower() == '' if input else student.gender
height = input(f"身高 (cm) ({student.height}): ") or student.height
weight = input(f"体重 (kg) ({student.weight}): ") or student.weight
enrollment_date = input(f"入学日期 (YYYY-MM-DD) ({student.enrollment_date}): ") or student.enrollment_date
class_name = input(f"班级 ({student.class_name}): ") or student.class_name
major = input(f"专业 ({student.major}): ") or student.major
if not Student._validate_date(enrollment_date):
print("无效的入学日期格式请使用YYYY-MM-DD格式。")
return
try:
height = int(height) if height else None
weight = float(weight) if weight else None
enrollment_date = enrollment_date if enrollment_date else None
updated_student = Student(name, id_card, student.stu_id, gender, height, weight, enrollment_date, class_name, major)
if self.bll.update(updated_student):
print("学生信息更新成功。")
else:
print("学生信息更新失败。")
except ValueError as e:
print(f"错误: {e}")
def query_student(self):
print("=====查询学生信息=====")
print("1.查询所有学生")
print("2.按身份证号查询")
print("3.按学号查询")
print("4.按姓名查询")
print("5.按班级查询")
print("6.返回上一级")
choice = input("请选择查询方式: ")
if choice == '1':
self.display_students(self.bll.get_all())
elif choice == '2':
id_card = input("请输入身份证号: ")
student = self.bll.get_by_id(id_card)
if student:
self.display_student(student)
else:
print("未找到该学生信息。")
elif choice == '3':
stu_id = input("请输入学号: ")
student = self.bll.get_by_stu_id(stu_id)
if student:
self.display_student(student)
else:
print("未找到该学生信息。")
elif choice == '4':
name = input("请输入姓名: ")
students = [student for student in self.bll.get_all() if name in student.name]
self.display_students(students)
elif choice == '5':
class_name = input("请输入班级: ")
students = [student for student in self.bll.get_all() if class_name in student.class_name]
self.display_students(students)
elif choice == '6':
return
else:
print("无效的选择,请重新输入。")
def show_stats(self):
print("=====统计分析=====")
print("1.学生总数")
print("2.平均身高")
print("3.按身高范围统计")
print("4.按入学年份统计")
print("5.按年龄范围统计")
print("6.返回上一级")
choice = input("请选择统计方式: ")
if choice == '1':
students = self.bll.get_all()
print(f"学生总数: {len(students)}")
elif choice == '2':
students = self.bll.get_all()
heights = [student.height for student in students if student.height is not None]
if heights:
avg_height = sum(heights) / len(heights)
print(f"平均身高: {avg_height:.2f} cm")
else:
print("没有身高数据。")
# 按身高范围统计示例
elif choice == '3':
min_height = int(input("请输入最小身高 (cm): "))
max_height = int(input("请输入最大身高 (cm): "))
students = self.bll.get_all()
count = len([student for student in students if
student.height is not None and min_height <= student.height <= max_height])
print(f"身高在 {min_height} - {max_height} cm 之间的学生数量: {count}")
elif choice == '4':
print("按入学年份统计功能尚未实现。")
elif choice == '5':
print("按年龄范围统计功能尚未实现。")
elif choice == '6':
return
else:
print("无效的选择,请重新输入。")
def import_export_data(self):
print("=====数据导入导出=====")
print("1.导出数据到JSON")
print("2.从JSON导入数据")
print("3.导出数据到CSV")
print("4.从CSV导入数据")
print("5.返回上一级")
choice = input("请选择数据导入导出方式: ")
if choice == '1':
file_path = input("请输入导出JSON文件路径: ")
if self.bll.export_to_json(file_path):
print("数据导出成功。")
else:
print("数据导出失败。")
elif choice == '2':
file_path = input("请输入导入JSON文件路径: ")
if self.bll.import_from_json(file_path):
print("数据导入成功。")
else:
print("数据导入失败。")
elif choice == '3':
file_path = input("请输入导出CSV文件路径: ")
if self.bll.export_to_csv(file_path):
print("数据导出成功。")
else:
print("数据导出失败。")
elif choice == '4':
file_path = input("请输入导入CSV文件路径: ")
if self.bll.import_from_csv(file_path):
print("数据导入成功。")
else:
print("数据导入失败。")
elif choice == '5':
return
else:
print("无效的选择,请重新输入。")
def clear_students(self):
confirm = input("确定要清空所有学生信息吗?(y/n): ")
if confirm.lower() == 'y':
self.bll.dal.data = []
self.bll.dal._save()
print("所有学生信息已清空。")
else:
print("取消清空操作。")
def display_student(self, student: Student):
print(f"姓名: {student.name}")
print(f"身份证号: {student.id_card}")
print(f"学号: {student.stu_id}")
print(f"性别: {'' if student.gender else ''}")
print(f"身高: {student.height} cm")
print(f"体重: {student.weight} kg")
print(f"入学日期: {student.enrollment_date}")
print(f"班级: {student.class_name}")
print(f"专业: {student.major}")
def display_students(self, students: List[Student]):
for student in students:
self.display_student(student)
print("-" * 20)
if __name__ == "__main__":
file_path = "students.json" # 你可以根据需要更改文件路径
ui = StudentUI(file_path)
ui.run()

@ -0,0 +1,78 @@
# stumis/ui/student_gui.py
import sys
from PyQt5.QtWidgets import (QApplication, QMainWindow, QTableWidget, QTableWidgetItem,
QPushButton, QVBoxLayout, QHBoxLayout, QWidget, QLabel,
QLineEdit, QComboBox, QMessageBox)
from stumis.bll.studentBLL import StudentBLL
class StudentGUI(QMainWindow):
def __init__(self, file_path):
super().__init__()
self.bll = StudentBLL(file_path)
self.init_ui()
def init_ui(self):
# 创建主布局
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
# 创建表格显示学生数据
self.table = QTableWidget()
self.table.setColumnCount(9)
self.table.setHorizontalHeaderLabels(
["姓名", "身份证号", "学号", "性别", "身高", "体重", "入学日期", "班级", "专业"])
main_layout.addWidget(self.table)
# 创建操作按钮
button_layout = QHBoxLayout()
self.add_btn = QPushButton("添加")
self.update_btn = QPushButton("修改")
self.delete_btn = QPushButton("删除")
self.query_btn = QPushButton("查询")
button_layout.addWidget(self.add_btn)
button_layout.addWidget(self.update_btn)
button_layout.addWidget(self.delete_btn)
button_layout.addWidget(self.query_btn)
main_layout.addLayout(button_layout)
# 连接按钮事件
self.add_btn.clicked.connect(self.add_student)
self.update_btn.clicked.connect(self.update_student)
self.delete_btn.clicked.connect(self.delete_student)
self.query_btn.clicked.connect(self.query_student)
# 初始化显示
self.refresh_table()
# 设置窗口属性
self.setWindowTitle("学生信息管理系统")
self.setGeometry(100, 100, 800, 600)
self.show()
def refresh_table(self):
"""刷新表格数据"""
students = self.bll.get_all()
self.table.setRowCount(len(students))
for row, student in enumerate(students):
self.table.setItem(row, 0, QTableWidgetItem(student.name))
self.table.setItem(row, 1, QTableWidgetItem(student.id_card))
self.table.setItem(row, 2, QTableWidgetItem(student.stu_id))
self.table.setItem(row, 3, QTableWidgetItem("" if student.gender else ""))
self.table.setItem(row, 4, QTableWidgetItem(str(student.height) if student.height else ""))
self.table.setItem(row, 5, QTableWidgetItem(str(student.weight) if student.weight else ""))
self.table.setItem(row, 6, QTableWidgetItem(student.enrollment_date or ""))
self.table.setItem(row, 7, QTableWidgetItem(student.class_name or ""))
self.table.setItem(row, 8, QTableWidgetItem(student.major or ""))
# 其他方法(添加、修改、删除、查询)...
if __name__ == "__main__":
app = QApplication(sys.argv)
file_path = "students.json"
window = StudentGUI(file_path)
sys.exit(app.exec_())

@ -0,0 +1,42 @@
# stumis/utils/backup_manager.py
import os
import shutil
from datetime import datetime
class BackupManager:
def __init__(self, source_dir: str, backup_dir: str):
self.source_dir = source_dir
self.backup_dir = backup_dir
os.makedirs(backup_dir, exist_ok=True)
def create_backup(self) -> str:
"""创建数据备份"""
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = os.path.join(self.backup_dir, f"backup_{timestamp}")
shutil.copytree(self.source_dir, backup_path)
return backup_path
except Exception as e:
print(f"创建备份失败: {e}")
return None
def restore_backup(self, backup_path: str) -> bool:
"""恢复数据备份"""
if not os.path.exists(backup_path):
print("备份路径不存在。")
return False
try:
# 先删除当前数据
for root, dirs, files in os.walk(self.source_dir):
for file in files:
os.remove(os.path.join(root, file))
for dir in dirs:
os.rmdir(os.path.join(root, dir))
# 复制备份数据
shutil.copytree(backup_path, self.source_dir)
return True
except Exception as e:
print(f"恢复备份失败: {e}")
return False

@ -0,0 +1,41 @@
# stumis/utils/logger.py
import logging
def setup_logger(name: str, log_file: str, level: logging.Level = logging.INFO):
"""设置日志记录器"""
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
file_handler = logging.FileHandler(log_file)
file_handler.setFormatter(formatter)
stream_handler = logging.StreamHandler()
stream_handler.setFormatter(formatter)
logger = logging.getLogger(name)
logger.setLevel(level)
logger.addHandler(file_handler)
logger.addHandler(stream_handler)
return logger
# 在需要记录日志的类中使用
class StudentBLL:
def __init__(self, file_path):
self.dal = self._get_dal(file_path)
self.logger = setup_logger("student_bll", "student_management.log")
def add(self, student: Student) -> bool:
try:
if not self._validate_student(student):
raise ValueError("Invalid student data")
if self.dal.is_exist(student.id_card):
raise ValueError("Student with this ID card already exists")
result = self.dal.add(student)
self.logger.info(f"Added student: {student.name}")
return result
except Exception as e:
self.logger.error(f"Failed to add student: {e}")
raise
Loading…
Cancel
Save