commit
4c1470a028
@ -0,0 +1,8 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="jdk" jdkName="Python 3.11 (PyCharmMiscProject)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/PythonProject1.iml" filepath="$PROJECT_DIR$/.idea/PythonProject1.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
@ -0,0 +1,373 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import json
|
||||
import csv
|
||||
import os
|
||||
from typing import List, Dict, Optional, Any
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
|
||||
class Student:
|
||||
def __init__(self, data: Dict[str, Any]):
|
||||
self.__dict__.update(data)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return self.__dict__.copy()
|
||||
|
||||
def validate(self) -> List[str]:
|
||||
errors = []
|
||||
if not hasattr(self, 'id_number') or not self.id_number:
|
||||
errors.append("身份证号不能为空")
|
||||
if not hasattr(self, 'stu_id') or not self.stu_id:
|
||||
errors.append("学号不能为空")
|
||||
if hasattr(self, 'age') and (not isinstance(self.age, int) or self.age < 0):
|
||||
errors.append("年龄必须为非负整数")
|
||||
return errors
|
||||
|
||||
|
||||
class IStudentDAL(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def get_by_id(self, id_number: str) -> Optional[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_by_stu_id(self, stu_id: str) -> Optional[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_all(self) -> List[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def add(self, student: Dict[str, Any]) -> bool:
|
||||
pass
|
||||
|
||||
def delete(self, id_number: str) -> bool:
|
||||
pass
|
||||
|
||||
def delete_by_stu_id(self, stu_id: str) -> bool:
|
||||
pass
|
||||
|
||||
def update(self, student: Dict[str, Any]) -> bool:
|
||||
pass
|
||||
|
||||
|
||||
class JsonStudentDAL(IStudentDAL):
|
||||
|
||||
def __init__(self, file_path: str):
|
||||
self.file_path = file_path
|
||||
self.data = self._load()
|
||||
|
||||
def _load(self) -> List[Dict[str, Any]]:
|
||||
if not os.path.exists(self.file_path):
|
||||
return []
|
||||
try:
|
||||
with open(self.file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except:
|
||||
return []
|
||||
|
||||
def _save(self) -> None:
|
||||
with open(self.file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.data, f, ensure_ascii=False, indent=4)
|
||||
|
||||
def get_by_id(self, id_number: str) -> Optional[Dict[str, Any]]:
|
||||
for s in self.data:
|
||||
if s.get('id_number') == id_number:
|
||||
return deepcopy(s)
|
||||
return None
|
||||
|
||||
def get_by_stu_id(self, stu_id: str) -> Optional[Dict[str, Any]]:
|
||||
for s in self.data:
|
||||
if s.get('stu_id') == stu_id:
|
||||
return deepcopy(s)
|
||||
return None
|
||||
|
||||
def get_all(self) -> List[Dict[str, Any]]:
|
||||
return [deepcopy(s) for s in self.data]
|
||||
|
||||
def add(self, student: Dict[str, Any]) -> bool:
|
||||
self.data.append(deepcopy(student))
|
||||
self._save()
|
||||
return True
|
||||
|
||||
def delete(self, id_number: str) -> bool:
|
||||
for i, s in enumerate(self.data):
|
||||
if s.get('id_number') == id_number:
|
||||
del self.data[i]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete_by_stu_id(self, stu_id: str) -> bool:
|
||||
for i, s in enumerate(self.data):
|
||||
if s.get('stu_id') == stu_id:
|
||||
del self.data[i]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
|
||||
def update(self, student: Dict[str, Any]) -> bool:
|
||||
for i, s in enumerate(self.data):
|
||||
if s.get('id_number') == student.get('id_number'):
|
||||
self.data[i] = deepcopy(student)
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class CsvStudentDAL(IStudentDAL):
|
||||
FIELD_TYPES = {
|
||||
"id_number": str,
|
||||
"stu_id": str,
|
||||
"name": str,
|
||||
"age": int,
|
||||
"gender": str
|
||||
}
|
||||
|
||||
def __init__(self, file_path: str):
|
||||
self.file_path = file_path
|
||||
self.data = self._load()
|
||||
|
||||
def _load(self) -> List[Dict[str, Any]]:
|
||||
if not os.path.exists(self.file_path):
|
||||
self._ensure_file_exists()
|
||||
return []
|
||||
data = []
|
||||
try:
|
||||
with open(self.file_path, 'r', newline='', encoding='utf-8-sig') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
data.append(self._convert_row(row))
|
||||
except:
|
||||
pass
|
||||
return data
|
||||
|
||||
def _ensure_file_exists(self) -> None:
|
||||
if not os.path.exists(self.file_path):
|
||||
with open(self.file_path, 'w', newline='', encoding='utf-8-sig') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
|
||||
writer.writeheader()
|
||||
|
||||
def _convert_row(self, row: Dict[str, str]) -> Dict[str, Any]:
|
||||
converted = {}
|
||||
for k, v in row.items():
|
||||
if k in self.FIELD_TYPES:
|
||||
try:
|
||||
converted[k] = self.FIELD_TYPES[k](v)
|
||||
except:
|
||||
converted[k] = v
|
||||
else:
|
||||
converted[k] = v
|
||||
return converted
|
||||
|
||||
def _save(self) -> None:
|
||||
with open(self.file_path, 'w', newline='', encoding='utf-8-sig') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
|
||||
writer.writeheader()
|
||||
for s in self.data:
|
||||
writer.writerow(s)
|
||||
|
||||
def get_by_id(self, id_number: str) -> Optional[Dict[str, Any]]:
|
||||
for s in self.data:
|
||||
if s.get('id_number') == id_number:
|
||||
return deepcopy(s)
|
||||
return None
|
||||
|
||||
def get_by_stu_id(self, stu_id: str) -> Optional[Dict[str, Any]]:
|
||||
for s in self.data:
|
||||
if s.get('stu_id') == stu_id:
|
||||
return deepcopy(s)
|
||||
return None
|
||||
|
||||
def get_all(self) -> List[Dict[str, Any]]:
|
||||
return [deepcopy(s) for s in self.data]
|
||||
|
||||
def add(self, student: Dict[str, Any]) -> bool:
|
||||
self.data.append(deepcopy(student))
|
||||
self._save()
|
||||
return True
|
||||
|
||||
def delete(self, id_number: str) -> bool:
|
||||
for i, s in enumerate(self.data):
|
||||
if s.get('id_number') == id_number:
|
||||
del self.data[i]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
|
||||
def delete_by_stu_id(self, stu_id: str) -> bool:
|
||||
for i, s in enumerate(self.data):
|
||||
if s.get('stu_id') == stu_id:
|
||||
del self.data[i]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
|
||||
def update(self, student: Dict[str, Any]) -> bool:
|
||||
for i, s in enumerate(self.data):
|
||||
if s.get('id_number') == student.get('id_number'):
|
||||
self.data[i] = deepcopy(student)
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class StudentBLL:
|
||||
def __init__(self, file_path: str):
|
||||
self.dal = self._get_dal(file_path)
|
||||
|
||||
def _get_dal(self, file_path: str) -> IStudentDAL:
|
||||
ext = os.path.splitext(file_path)[1].lower()
|
||||
if ext == '.json':
|
||||
return JsonStudentDAL(file_path)
|
||||
elif ext == '.csv':
|
||||
return CsvStudentDAL(file_path)
|
||||
else:
|
||||
raise ValueError(f"不支持的文件类型: {ext},请使用.json或.csv")
|
||||
|
||||
def _validate_student(self, student: Student) -> None:
|
||||
errors = student.validate()
|
||||
if errors:
|
||||
raise ValueError(f"学生信息校验失败: {', '.join(errors)}")
|
||||
|
||||
def _check_uniqueness(self, student: Student) -> None:
|
||||
all_students = self.dal.get_all()
|
||||
if any(s.get('id_number') == student.id_number for s in all_students):
|
||||
raise ValueError(f"身份证号 {student.id_number} 已存在")
|
||||
if any(s.get('stu_id') == student.stu_id for s in all_students):
|
||||
raise ValueError(f"学号 {student.stu_id} 已存在")
|
||||
|
||||
def add(self, student: Student) -> bool:
|
||||
self._validate_student(student)
|
||||
self._check_uniqueness(student)
|
||||
return self.dal.add(student.to_dict())
|
||||
|
||||
def delete(self, id_number: str) -> bool:
|
||||
if not self.dal.get_by_id(id_number):
|
||||
raise ValueError(f"学生(身份证号: {id_number})不存在")
|
||||
return self.dal.delete(id_number)
|
||||
|
||||
def delete_by_stu_id(self, stu_id: str) -> bool:
|
||||
if not self.dal.get_by_stu_id(stu_id):
|
||||
raise ValueError(f"学生(学号: {stu_id})不存在")
|
||||
return self.dal.delete_by_stu_id(stu_id)
|
||||
|
||||
def update(self, student: Student) -> bool:
|
||||
self._validate_student(student)
|
||||
if not self.dal.get_by_id(student.id_number):
|
||||
raise ValueError(f"学生(身份证号: {student.id_number})不存在")
|
||||
all_students = self.dal.get_all()
|
||||
if any(s.get('stu_id') == student.stu_id and s.get('id_number') != student.id_number for s in all_students):
|
||||
raise ValueError(f"学号 {student.stu_id} 已被其他学生使用")
|
||||
return self.dal.update(student.to_dict())
|
||||
|
||||
def update_partial(self, id_number: str, fields: Dict[str, Any]) -> bool:
|
||||
student = self.dal.get_by_id(id_number)
|
||||
if not student:
|
||||
raise ValueError(f"学生(身份证号: {id_number})不存在")
|
||||
updated = deepcopy(student)
|
||||
for k, v in fields.items():
|
||||
if k == 'id_number':
|
||||
raise ValueError("身份证号不允许直接修改")
|
||||
updated[k] = v
|
||||
if 'stu_id' in fields:
|
||||
all_students = self.dal.get_all()
|
||||
if any(s.get('stu_id') == fields['stu_id'] and s.get('id_number') != id_number for s in all_students):
|
||||
raise ValueError(f"学号 {fields['stu_id']} 已被其他学生使用")
|
||||
partial_student = Student(updated)
|
||||
self._validate_student(partial_student)
|
||||
return self.dal.update(partial_student.to_dict())
|
||||
|
||||
def get_by_id(self, id_number: str) -> Optional[Student]:
|
||||
data = self.dal.get_by_id(id_number)
|
||||
return Student(data) if data else None
|
||||
|
||||
def get_by_stu_id(self, stu_id: str) -> Optional[Student]:
|
||||
data = self.dal.get_by_stu_id(stu_id)
|
||||
return Student(data) if data else None
|
||||
|
||||
def get_all(self) -> List[Student]:
|
||||
return [Student(deepcopy(s)) for s in self.dal.get_all()]
|
||||
|
||||
def get_by_key(self, field: str, value: str, fuzzy: bool = False) -> List[Student]:
|
||||
all_students = self.dal.get_all()
|
||||
result = []
|
||||
for s in all_students:
|
||||
if field not in s:
|
||||
continue
|
||||
s_value = str(s[field])
|
||||
if fuzzy:
|
||||
if value in s_value:
|
||||
result.append(Student(deepcopy(s)))
|
||||
else:
|
||||
if s_value == value:
|
||||
result.append(Student(deepcopy(s)))
|
||||
return result
|
||||
|
||||
def get_by_range(self, field: str, min_val: Any, max_val: Any) -> List[Student]:
|
||||
all_students = self.dal.get_all()
|
||||
result = []
|
||||
for s in all_students:
|
||||
if field not in s:
|
||||
continue
|
||||
try:
|
||||
s_value = s[field]
|
||||
if min_val <= s_value <= max_val:
|
||||
result.append(Student(deepcopy(s)))
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return result
|
||||
|
||||
def export_to_json(self, file_path: str) -> bool:
|
||||
all_data = [s.to_dict() for s in self.get_all()]
|
||||
try:
|
||||
dal = JsonStudentDAL(file_path)
|
||||
return dal.export_to_json(all_data, file_path)
|
||||
except:
|
||||
return False
|
||||
|
||||
def import_from_json(self, file_path: str) -> List[str]:
|
||||
errors = []
|
||||
try:
|
||||
dal = JsonStudentDAL(file_path)
|
||||
data_list = dal.import_from_json(file_path)
|
||||
for data in data_list:
|
||||
try:
|
||||
student = Student(data)
|
||||
self._validate_student(student)
|
||||
self._check_uniqueness(student)
|
||||
self.dal.add(student.to_dict())
|
||||
except Exception as e:
|
||||
errors.append(f"导入失败:{data.get('id_number', '无ID')} - {str(e)}")
|
||||
return errors
|
||||
except Exception as e:
|
||||
errors.append(f"导入文件错误:{str(e)}")
|
||||
return errors
|
||||
|
||||
def export_to_csv(self, file_path: str) -> bool:
|
||||
all_data = [s.to_dict() for s in self.get_all()]
|
||||
try:
|
||||
dal = CsvStudentDAL(file_path)
|
||||
return dal.export_to_csv(all_data, file_path)
|
||||
except:
|
||||
return False
|
||||
|
||||
def import_from_csv(self, file_path: str) -> List[str]:
|
||||
errors = []
|
||||
try:
|
||||
dal = CsvStudentDAL(file_path)
|
||||
data_list = dal.import_from_csv(file_path)
|
||||
for data in data_list:
|
||||
try:
|
||||
student = Student(data)
|
||||
self._validate_student(student)
|
||||
self._check_uniqueness(student)
|
||||
self.dal.add(student.to_dict())
|
||||
except Exception as e:
|
||||
errors.append(f"导入失败:{data.get('id_number', '无ID')} - {str(e)}")
|
||||
return errors
|
||||
except Exception as e:
|
||||
errors.append(f"导入文件错误:{str(e)}")
|
||||
return errors
|
@ -0,0 +1,241 @@
|
||||
from abc import ABC, abstractmethod
|
||||
import json
|
||||
import csv
|
||||
|
||||
|
||||
class IStudentDAL(ABC):
|
||||
@abstractmethod
|
||||
def get_by_id(self, id_number):
|
||||
pass
|
||||
@abstractmethod
|
||||
def get_by_stu_id(self, stu_id):
|
||||
pass
|
||||
@abstractmethod
|
||||
def get_all(self):
|
||||
pass
|
||||
@abstractmethod
|
||||
def add(self, student):
|
||||
pass
|
||||
@abstractmethod
|
||||
def delete(self, id_number):
|
||||
pass
|
||||
@abstractmethod
|
||||
def delete_by_stu_id(self, stu_id):
|
||||
pass
|
||||
@abstractmethod
|
||||
def update(self, student):
|
||||
pass
|
||||
@abstractmethod
|
||||
def is_exist(self, id_number):
|
||||
pass
|
||||
@abstractmethod
|
||||
def is_exist_stu_id(self, stu_id):
|
||||
pass
|
||||
@abstractmethod
|
||||
def import_from_json(self, file_path):
|
||||
pass
|
||||
@abstractmethod
|
||||
def export_to_json(self, data, file_path):
|
||||
pass
|
||||
@abstractmethod
|
||||
def import_from_csv(self, file_path):
|
||||
pass
|
||||
@abstractmethod
|
||||
def export_to_csv(self, data, file_path):
|
||||
pass
|
||||
|
||||
|
||||
class JsonStudentDAL(IStudentDAL):
|
||||
def __init__(self, file_path):
|
||||
self.file_path = file_path
|
||||
self._ensure_file_exists()
|
||||
self.data = self._load()
|
||||
def _ensure_file_exists(self):
|
||||
try:
|
||||
with open(self.file_path, 'x'):
|
||||
pass
|
||||
except FileExistsError:
|
||||
pass
|
||||
def _load(self):
|
||||
try:
|
||||
with open(self.file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return []
|
||||
def _save(self):
|
||||
with open(self.file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(self.data, f, ensure_ascii=False, indent=4)
|
||||
def get_by_id(self, id_number):
|
||||
for student in self.data:
|
||||
if student['id_number'] == id_number:
|
||||
return student
|
||||
return None
|
||||
def get_by_stu_id(self, stu_id):
|
||||
for student in self.data:
|
||||
if student['stu_id'] == stu_id:
|
||||
return student
|
||||
return None
|
||||
def get_all(self):
|
||||
return self.data
|
||||
def add(self, student):
|
||||
if self.is_exist(student['id_number']):
|
||||
return False
|
||||
self.data.append(student)
|
||||
self._save()
|
||||
return True
|
||||
def delete(self, id_number):
|
||||
for index, student in enumerate(self.data):
|
||||
if student['id_number'] == id_number:
|
||||
del self.data[index]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
def delete_by_stu_id(self, stu_id):
|
||||
for index, student in enumerate(self.data):
|
||||
if student['stu_id'] == stu_id:
|
||||
del self.data[index]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
def update(self, student):
|
||||
for index, existing_student in enumerate(self.data):
|
||||
if existing_student['id_number'] == student['id_number']:
|
||||
self.data[index] = student
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
def is_exist(self, id_number):
|
||||
for student in self.data:
|
||||
if student['id_number'] == id_number:
|
||||
return True
|
||||
return False
|
||||
def is_exist_stu_id(self, stu_id):
|
||||
for student in self.data:
|
||||
if student['stu_id'] == stu_id:
|
||||
return True
|
||||
return False
|
||||
def import_from_json(self, file_path):
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except (json.JSONDecodeError, FileNotFoundError):
|
||||
return []
|
||||
def export_to_json(self, data, file_path):
|
||||
try:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=4)
|
||||
return True
|
||||
except IOError:
|
||||
return False
|
||||
class CsvStudentDAL(IStudentDAL):
|
||||
FIELD_TYPES = {
|
||||
"id_number": str,
|
||||
"stu_id": str,
|
||||
"name": str,
|
||||
"age": int,
|
||||
"gender": str
|
||||
}
|
||||
def __init__(self, file_path):
|
||||
self.file_path = file_path
|
||||
self._ensure_file_exists()
|
||||
self.data = self._load()
|
||||
def _ensure_file_exists(self):
|
||||
try:
|
||||
with open(self.file_path, 'x', newline='', encoding='utf-8-sig') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
|
||||
writer.writeheader()
|
||||
except FileExistsError:
|
||||
pass
|
||||
def _load(self):
|
||||
data = []
|
||||
try:
|
||||
with open(self.file_path, 'r', newline='', encoding='utf-8-sig') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
data.append(self._convert_row(row))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return data
|
||||
def _save(self):
|
||||
with open(self.file_path, 'w', newline='', encoding='utf-8-sig') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
|
||||
writer.writeheader()
|
||||
for student in self.data:
|
||||
writer.writerow(student)
|
||||
def _convert_row(self, row):
|
||||
converted_row = {}
|
||||
for key, value in row.items():
|
||||
try:
|
||||
converted_row[key] = self.FIELD_TYPES[key](value)
|
||||
except ValueError:
|
||||
converted_row[key] = value
|
||||
return converted_row
|
||||
def get_by_id(self, id_number):
|
||||
for student in self.data:
|
||||
if student['id_number'] == id_number:
|
||||
return student
|
||||
return None
|
||||
def get_by_stu_id(self, stu_id):
|
||||
for student in self.data:
|
||||
if student['stu_id'] == stu_id:
|
||||
return student
|
||||
return None
|
||||
def get_all(self):
|
||||
return self.data
|
||||
def add(self, student):
|
||||
if self.is_exist(student['id_number']):
|
||||
return False
|
||||
self.data.append(student)
|
||||
self._save()
|
||||
return True
|
||||
def delete(self, id_number):
|
||||
for index, student in enumerate(self.data):
|
||||
if student['id_number'] == id_number:
|
||||
del self.data[index]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
def delete_by_stu_id(self, stu_id):
|
||||
for index, student in enumerate(self.data):
|
||||
if student['stu_id'] == stu_id:
|
||||
del self.data[index]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
def update(self, student):
|
||||
for index, existing_student in enumerate(self.data):
|
||||
if existing_student['id_number'] == student['id_number']:
|
||||
self.data[index] = student
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
def is_exist(self, id_number):
|
||||
for student in self.data:
|
||||
if student['id_number'] == id_number:
|
||||
return True
|
||||
return False
|
||||
def is_exist_stu_id(self, stu_id):
|
||||
for student in self.data:
|
||||
if student['stu_id'] == stu_id:
|
||||
return True
|
||||
return False
|
||||
def import_from_csv(self, file_path):
|
||||
data = []
|
||||
try:
|
||||
with open(file_path, 'r', newline='', encoding='utf-8-sig') as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
data.append(self._convert_row(row))
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
return data
|
||||
def export_to_csv(self, data, file_path):
|
||||
try:
|
||||
with open(file_path, 'w', newline='', encoding='utf-8-sig') as f:
|
||||
writer = csv.DictWriter(f, fieldnames=self.FIELD_TYPES.keys())
|
||||
writer.writeheader()
|
||||
for student in data:
|
||||
writer.writerow(student)
|
||||
return True
|
||||
except IOError:
|
||||
return False
|
@ -0,0 +1,112 @@
|
||||
from datetime import date
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
|
||||
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[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
|
||||
|
||||
if enrollment_date:
|
||||
if isinstance(enrollment_date, str):
|
||||
self.enrollment_date = date.fromisoformat(enrollment_date)
|
||||
else:
|
||||
self.enrollment_date = enrollment_date
|
||||
else:
|
||||
self.enrollment_date = None
|
||||
|
||||
self.class_name = class_name
|
||||
self.major = major
|
||||
|
||||
@property
|
||||
def birthday(self) -> Optional[date]:
|
||||
if not self.id_card or len(self.id_card) != 18:
|
||||
return None
|
||||
try:
|
||||
birth_str = self.id_card[6:14]
|
||||
return date(int(birth_str[:4]), int(birth_str[4:6]), int(birth_str[6:8]))
|
||||
except:
|
||||
return None
|
||||
|
||||
@property
|
||||
def age(self) -> Optional[int]:
|
||||
if not self.birthday:
|
||||
return None
|
||||
today = date.today()
|
||||
age = today.year - self.birthday.year
|
||||
if (today.month, today.day) < (self.birthday.month, self.birthday.day):
|
||||
age -= 1
|
||||
return age
|
||||
|
||||
@property
|
||||
def errors(self) -> Dict[str, str]:
|
||||
errors = {}
|
||||
id_card_err = self.__validate_id_card(self.id_card)
|
||||
if id_card_err:
|
||||
errors["id_card"] = id_card_err
|
||||
if len(self.name) < 2 or len(self.name) > 20:
|
||||
errors["name"] = "姓名长度需在2-20字符之间"
|
||||
if self.height and not (50 <= self.height <= 250):
|
||||
errors["height"] = "身高需在50-250cm之间"
|
||||
if self.weight and not (5.0 <= self.weight <= 300.0):
|
||||
errors["weight"] = "体重需在5-300kg之间"
|
||||
if self.enrollment_date and self.enrollment_date > date.today():
|
||||
errors["enrollment_date"] = "入学日期不能晚于当前日期"
|
||||
return errors
|
||||
|
||||
@property
|
||||
def is_valid(self) -> bool:
|
||||
return not bool(self.errors)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
data = self.__dict__.copy()
|
||||
if data.get("enrollment_date"):
|
||||
data["enrollment_date"] = data["enrollment_date"].isoformat()
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data: Dict[str, Any]) -> 'Student':
|
||||
if not isinstance(data, dict):
|
||||
raise TypeError("输入必须为字典类型")
|
||||
enrollment_date = data.get("enrollment_date")
|
||||
if enrollment_date and isinstance(enrollment_date, str):
|
||||
data["enrollment_date"] = date.fromisoformat(enrollment_date)
|
||||
return cls(**data)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
attrs = ", ".join([
|
||||
f"{k}='{v}'" if isinstance(v, str) else f"{k}={v}"
|
||||
for k, v in self.__dict__.items()
|
||||
])
|
||||
return f"Student({attrs})"
|
||||
|
||||
@staticmethod
|
||||
def get_properties() -> list:
|
||||
return [k for k, v in vars(Student).items() if isinstance(v, property)]
|
||||
|
||||
@staticmethod
|
||||
def __validate_id_card(id_card: str) -> Optional[str]:
|
||||
if not id_card or len(id_card) != 18:
|
||||
return "身份证号长度必须为18位"
|
||||
if not id_card[:17].isdigit():
|
||||
return "身份证号前17位必须为数字"
|
||||
weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
|
||||
check_codes = "10X98765432"
|
||||
sum_val = sum(int(id_card[i]) * weights[i] for i in range(17))
|
||||
if id_card[17] != check_codes[sum_val % 11]:
|
||||
return "身份证号校验码错误"
|
||||
try:
|
||||
birth_str = id_card[6:14]
|
||||
date(int(birth_str[:4]), int(birth_str[4:6]), int(birth_str[6:8]))
|
||||
except:
|
||||
return "身份证号中出生日期格式错误"
|
||||
return None
|
Loading…
Reference in new issue