Compare commits
No commits in common. 'master2' and 'main' have entirely different histories.
@ -1,4 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
/.idea
|
||||
@ -1,36 +0,0 @@
|
||||
# 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)
|
||||
@ -1,32 +0,0 @@
|
||||
|
||||
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)
|
||||
@ -1 +0,0 @@
|
||||
from .studentDAL import JsonStudentDAL
|
||||
@ -1,26 +0,0 @@
|
||||
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)
|
||||
@ -1,323 +0,0 @@
|
||||
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
|
||||
@ -1,12 +0,0 @@
|
||||
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
|
||||
@ -1 +0,0 @@
|
||||
print(123)
|
||||
@ -1,9 +0,0 @@
|
||||
# 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
|
||||
|
||||
|
||||
@ -1,119 +0,0 @@
|
||||
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})")
|
||||
@ -1,17 +0,0 @@
|
||||
|
||||
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, [])
|
||||
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "测试学生",
|
||||
"id_card": "110101200001011234",
|
||||
"stu_id": "2025001",
|
||||
"gender": true,
|
||||
"height": 175,
|
||||
"weight": 70.5,
|
||||
"enrollment_date": "2025-09-01",
|
||||
"class_name": "软件工程1班",
|
||||
"major": "计算机科学与技术"
|
||||
}
|
||||
]
|
||||
@ -1,48 +0,0 @@
|
||||
# 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__])
|
||||
@ -1,13 +0,0 @@
|
||||
[
|
||||
{
|
||||
"name": "测试学生",
|
||||
"id_card": "110101200001011234",
|
||||
"stu_id": "2025001",
|
||||
"gender": true,
|
||||
"height": 175,
|
||||
"weight": 70.5,
|
||||
"enrollment_date": "2025-09-01",
|
||||
"class_name": "软件工程1班",
|
||||
"major": "计算机科学与技术"
|
||||
}
|
||||
]
|
||||
@ -1,78 +0,0 @@
|
||||
# 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_())
|
||||
@ -1 +0,0 @@
|
||||
[]
|
||||
@ -1,42 +0,0 @@
|
||||
# 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
|
||||
@ -1,41 +0,0 @@
|
||||
# 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…
Reference in new issue