|
|
from datetime import date, datetime
|
|
|
from typing import Dict, Any, List, Optional, Tuple
|
|
|
from data_access import StudentDataAccess
|
|
|
|
|
|
|
|
|
class IDCardValidator:
|
|
|
"""身份证验证工具类"""
|
|
|
|
|
|
@staticmethod
|
|
|
def validate_id_card(id_card: str) -> Tuple[bool, str]:
|
|
|
"""验证身份证号有效性"""
|
|
|
# 基本格式验证
|
|
|
if len(id_card) != 18:
|
|
|
return False, "身份证号必须为18位"
|
|
|
|
|
|
# 前17位必须是数字,最后一位可以是数字或X
|
|
|
if not (id_card[:-1].isdigit() and (id_card[-1].isdigit() or id_card[-1].upper() == 'X')):
|
|
|
return False, "身份证号格式不正确,前17位必须是数字,最后一位可以是数字或X"
|
|
|
|
|
|
# 验证出生日期
|
|
|
birth_date = id_card[6:14]
|
|
|
if not IDCardValidator.validate_birth_date(birth_date):
|
|
|
return False, "身份证号中的出生日期无效"
|
|
|
|
|
|
# 验证校验码
|
|
|
if not IDCardValidator.validate_check_code(id_card):
|
|
|
return False, "身份证号校验码不正确"
|
|
|
|
|
|
return True, "身份证号有效"
|
|
|
|
|
|
@staticmethod
|
|
|
def validate_birth_date(birth_date: str) -> bool:
|
|
|
"""验证出生日期是否有效"""
|
|
|
try:
|
|
|
# 尝试解析日期
|
|
|
year = int(birth_date[0:4])
|
|
|
month = int(birth_date[4:6])
|
|
|
day = int(birth_date[6:8])
|
|
|
|
|
|
# 检查日期范围
|
|
|
if not (1900 <= year <= date.today().year):
|
|
|
return False
|
|
|
if not (1 <= month <= 12):
|
|
|
return False
|
|
|
|
|
|
# 检查每月天数
|
|
|
max_day = IDCardValidator.get_days_in_month(year, month)
|
|
|
if not (1 <= day <= max_day):
|
|
|
return False
|
|
|
|
|
|
return True
|
|
|
except:
|
|
|
return False
|
|
|
|
|
|
@staticmethod
|
|
|
def get_days_in_month(year: int, month: int) -> int:
|
|
|
"""获取某年某月的天数"""
|
|
|
if month == 2:
|
|
|
return 29 if IDCardValidator.is_leap_year(year) else 28
|
|
|
elif month in [4, 6, 9, 11]:
|
|
|
return 30
|
|
|
else:
|
|
|
return 31
|
|
|
|
|
|
@staticmethod
|
|
|
def is_leap_year(year: int) -> bool:
|
|
|
"""判断是否为闰年"""
|
|
|
return (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0)
|
|
|
|
|
|
@staticmethod
|
|
|
def validate_check_code(id_card: str) -> bool:
|
|
|
"""验证身份证校验码"""
|
|
|
# 权重因子
|
|
|
weight = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
|
|
|
# 校验码对应表
|
|
|
check_code_map = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2']
|
|
|
|
|
|
# 计算加权和
|
|
|
total = sum(int(id_card[i]) * weight[i] for i in range(17))
|
|
|
# 计算校验码
|
|
|
calculated_code = check_code_map[total % 11]
|
|
|
|
|
|
# 比较校验码
|
|
|
return id_card[-1].upper() == calculated_code
|
|
|
|
|
|
@staticmethod
|
|
|
def get_gender(id_card: str) -> str:
|
|
|
"""根据身份证号获取性别"""
|
|
|
if len(id_card) < 17:
|
|
|
return "未知"
|
|
|
gender_num = int(id_card[16])
|
|
|
return "男" if gender_num % 2 == 1 else "女"
|
|
|
|
|
|
@staticmethod
|
|
|
def get_birth_date(id_card: str) -> Optional[date]:
|
|
|
"""从身份证号获取出生日期"""
|
|
|
if len(id_card) < 14:
|
|
|
return None
|
|
|
try:
|
|
|
birth_str = id_card[6:14]
|
|
|
return datetime.strptime(birth_str, "%Y%m%d").date()
|
|
|
except:
|
|
|
return None
|
|
|
|
|
|
@staticmethod
|
|
|
def calculate_age(id_card: str) -> Optional[int]:
|
|
|
"""根据身份证号计算年龄"""
|
|
|
birth_date = IDCardValidator.get_birth_date(id_card)
|
|
|
if not birth_date:
|
|
|
return None
|
|
|
|
|
|
today = date.today()
|
|
|
age = today.year - birth_date.year
|
|
|
# 如果生日还没到,年龄减1
|
|
|
if (today.month, today.day) < (birth_date.month, birth_date.day):
|
|
|
age -= 1
|
|
|
return age
|
|
|
|
|
|
|
|
|
class StudentService:
|
|
|
def __init__(self, data_access: StudentDataAccess):
|
|
|
self.data_access = data_access
|
|
|
self.subject_name = {
|
|
|
'math': '数学',
|
|
|
'python': 'Python程序设计',
|
|
|
'english': '英语',
|
|
|
'physics': '物理',
|
|
|
'sum': '总分'
|
|
|
}
|
|
|
|
|
|
def validate_id_card(self, id_card: str) -> Tuple[bool, str]:
|
|
|
"""验证身份证号格式"""
|
|
|
return IDCardValidator.validate_id_card(id_card)
|
|
|
|
|
|
def is_id_card_exists(self, id_card: str, exclude_id: str = None) -> bool:
|
|
|
"""检查身份证号是否已存在"""
|
|
|
students = self.data_access.get_all_students()
|
|
|
for sid, student in students.items():
|
|
|
if exclude_id and sid == exclude_id:
|
|
|
continue
|
|
|
if student['basic_info'].get('id_card', '').upper() == id_card.upper():
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
def add_student(self, student_info: Dict[str, Any]) -> bool:
|
|
|
"""添加新学生"""
|
|
|
student_id = student_info['basic_info']['id']
|
|
|
|
|
|
# 验证学号
|
|
|
if not student_id.isdigit() or len(student_id) != 8:
|
|
|
raise ValueError("学号必须为8位数字!")
|
|
|
|
|
|
# 验证身份证号
|
|
|
id_card = student_info['basic_info'].get('id_card', '')
|
|
|
if not id_card:
|
|
|
raise ValueError("身份证号不能为空!")
|
|
|
|
|
|
is_valid, msg = self.validate_id_card(id_card)
|
|
|
if not is_valid:
|
|
|
raise ValueError(f"身份证号无效: {msg}")
|
|
|
|
|
|
if self.is_id_card_exists(id_card):
|
|
|
raise ValueError("该身份证号已存在!")
|
|
|
|
|
|
# 自动填充性别和出生日期
|
|
|
student_info['basic_info']['gender'] = IDCardValidator.get_gender(id_card)
|
|
|
birth_date = IDCardValidator.get_birth_date(id_card)
|
|
|
if birth_date:
|
|
|
student_info['basic_info']['birth_date'] = birth_date.strftime("%Y-%m-%d")
|
|
|
student_info['basic_info']['age'] = IDCardValidator.calculate_age(id_card)
|
|
|
|
|
|
# 计算总分
|
|
|
scores = student_info['score']
|
|
|
scores['sum'] = sum([scores['math'], scores['python'], scores['english'], scores['physics']])
|
|
|
|
|
|
return self.data_access.add_student(student_id, student_info)
|
|
|
|
|
|
def update_student(self, student_id: str, student_info: Dict[str, Any]) -> bool:
|
|
|
"""更新学生信息"""
|
|
|
# 验证身份证号
|
|
|
id_card = student_info['basic_info'].get('id_card', '')
|
|
|
if id_card: # 如果提供了身份证号才验证
|
|
|
is_valid, msg = self.validate_id_card(id_card)
|
|
|
if not is_valid:
|
|
|
raise ValueError(f"身份证号无效: {msg}")
|
|
|
if self.is_id_card_exists(id_card, exclude_id=student_id):
|
|
|
raise ValueError("该身份证号已被其他学生使用!")
|
|
|
|
|
|
# 自动更新性别和出生日期
|
|
|
student_info['basic_info']['gender'] = IDCardValidator.get_gender(id_card)
|
|
|
birth_date = IDCardValidator.get_birth_date(id_card)
|
|
|
if birth_date:
|
|
|
student_info['basic_info']['birth_date'] = birth_date.strftime("%Y-%m-%d")
|
|
|
student_info['basic_info']['age'] = IDCardValidator.calculate_age(id_card)
|
|
|
|
|
|
# 计算总分
|
|
|
scores = student_info['score']
|
|
|
scores['sum'] = sum([scores['math'], scores['python'], scores['english'], scores['physics']])
|
|
|
|
|
|
return self.data_access.update_student(student_id, student_info)
|
|
|
|
|
|
def delete_student(self, student_id: str) -> bool:
|
|
|
"""删除学生"""
|
|
|
return self.data_access.delete_student(student_id)
|
|
|
|
|
|
def get_student(self, student_id: str) -> Dict[str, Any]:
|
|
|
"""获取单个学生信息"""
|
|
|
return self.data_access.get_student(student_id)
|
|
|
|
|
|
def get_all_students(self) -> Dict[str, Any]:
|
|
|
"""获取所有学生信息"""
|
|
|
return self.data_access.get_all_students()
|
|
|
|
|
|
def search_student_by_id(self, student_id: str) -> Dict[str, Any]:
|
|
|
"""按学号查询学生信息"""
|
|
|
return self.data_access.get_student(student_id)
|
|
|
|
|
|
def get_statistics(self) -> Dict[str, float]:
|
|
|
"""获取统计信息"""
|
|
|
students = self.data_access.get_all_students().values()
|
|
|
if not students:
|
|
|
return {}
|
|
|
|
|
|
total = len(students)
|
|
|
return {
|
|
|
'total': total,
|
|
|
'math_avg': sum(s['score']['math'] for s in students) / total,
|
|
|
'python_avg': sum(s['score']['python'] for s in students) / total,
|
|
|
'english_avg': sum(s['score']['english'] for s in students) / total,
|
|
|
'physics_avg': sum(s['score']['physics'] for s in students) / total,
|
|
|
'total_avg': sum(s['score']['sum'] for s in students) / total
|
|
|
}
|
|
|
|
|
|
def get_sorted_students(self) -> List[Dict[str, Any]]:
|
|
|
"""按总分排序获取学生"""
|
|
|
students = list(self.data_access.get_all_students().values())
|
|
|
return sorted(students, key=lambda x: x['score']['sum'], reverse=True)
|
|
|
|
|
|
def load_data(self):
|
|
|
pass |