You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
math_test/data_manager.py

343 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""
数学学习软件 - 数据管理模块
负责用户数据存储、题目生成等业务逻辑
"""
import json
import os
import random
import re
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from typing import Dict, List, Tuple, Optional
from email_config import get_smtp_config, DEFAULT_SENDER_CONFIG
class DataManager:
"""数据管理类,负责用户信息和题目数据的管理"""
def __init__(self):
self.users_file = "users.json"
self.email_config_file = "email_config.json"
self.current_user = None
# 邮件配置
self.sender_config = DEFAULT_SENDER_CONFIG
self.ensure_data_files()
self.load_email_config()
def ensure_data_files(self):
"""确保数据文件存在"""
if not os.path.exists(self.users_file):
with open(self.users_file, 'w', encoding='utf-8') as f:
json.dump({}, f, ensure_ascii=False, indent=2)
def load_email_config(self):
"""加载邮箱配置"""
try:
if os.path.exists(self.email_config_file):
with open(self.email_config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
self.sender_config.update(config)
except Exception as e:
print(f"加载邮箱配置失败: {e}")
def load_users(self) -> Dict:
"""加载用户数据"""
try:
with open(self.users_file, 'r', encoding='utf-8') as f:
return json.load(f)
except:
return {}
def save_users(self, users_data: Dict):
"""保存用户数据"""
with open(self.users_file, 'w', encoding='utf-8') as f:
json.dump(users_data, f, ensure_ascii=False, indent=2)
def generate_verification_code(self) -> str:
"""生成6位数字验证码"""
return str(random.randint(100000, 999999))
def send_verification_email(self, email: str, verification_code: str) -> bool:
"""发送验证码邮件"""
try:
# 检查发送邮箱配置
if (self.sender_config["email"] == "your_email@qq.com" or
self.sender_config["password"] == "your_app_password"):
print("请先配置发送邮箱信息")
# 在没有配置邮箱的情况下,返回验证码供测试使用
print(f"测试模式 - 验证码: {verification_code}")
return True
# 获取SMTP配置
smtp_config = get_smtp_config(self.sender_config["email"])
# 创建邮件内容
msg = MIMEMultipart()
msg['From'] = self.sender_config['email'] # QQ邮箱要求简单格式
msg['To'] = email
msg['Subject'] = "数学学习软件 - 注册验证码"
# 邮件正文
body = f"""欢迎使用数学学习软件!
您的注册验证码是:{verification_code}
请在软件中输入此验证码完成注册。
验证码有效期为10分钟。
如果您没有申请注册,请忽略此邮件。
祝您学习愉快!
{self.sender_config['name']}"""
msg.attach(MIMEText(body, 'plain', 'utf-8'))
# 连接SMTP服务器并发送邮件
server = smtplib.SMTP(smtp_config["smtp_server"], smtp_config["smtp_port"])
if smtp_config["use_tls"]:
server.starttls() # 启用TLS加密
server.login(self.sender_config["email"], self.sender_config["password"])
text = msg.as_string()
server.sendmail(self.sender_config["email"], email, text)
server.quit()
return True
except Exception as e:
print(f"邮件发送失败: {e}")
# 测试模式:如果邮件发送失败,在控制台显示验证码
print(f"测试模式 - 验证码: {verification_code}")
return True # 返回True以便测试
def register_user(self, email: str, username: str) -> bool:
"""用户注册,发送验证码到邮箱"""
users = self.load_users()
if email in users:
raise ValueError("该邮箱已注册")
# 检查用户名是否已存在
for user_data in users.values():
if user_data.get("username") == username:
raise ValueError("该用户名已存在")
verification_code = self.generate_verification_code()
# 发送验证码邮件
if not self.send_verification_email(email, verification_code):
raise ValueError("验证码邮件发送失败,请检查邮箱地址")
users[email] = {
"username": username,
"verification_code": verification_code,
"verified": False,
"password": None
}
self.save_users(users)
return True
def verify_registration(self, email: str, code: str) -> bool:
"""验证注册码"""
users = self.load_users()
if email not in users:
return False
if users[email]["verification_code"] == code:
users[email]["verified"] = True
self.save_users(users)
return True
return False
def validate_password(self, password: str) -> bool:
"""验证密码格式6-10位必须含大小写字母和数字"""
if len(password) < 6 or len(password) > 10:
return False
has_upper = bool(re.search(r'[A-Z]', password))
has_lower = bool(re.search(r'[a-z]', password))
has_digit = bool(re.search(r'\d', password))
return has_upper and has_lower and has_digit
def set_password(self, email: str, password: str) -> bool:
"""设置用户密码"""
if not self.validate_password(password):
return False
users = self.load_users()
if email not in users or not users[email]["verified"]:
return False
users[email]["password"] = password
self.save_users(users)
return True
def login(self, email: str, password: str) -> bool:
"""用户登录"""
users = self.load_users()
if email not in users:
return False
user = users[email]
if user["verified"] and user["password"] == password:
self.current_user = email
return True
return False
def change_password(self, old_password: str, new_password: str) -> bool:
"""修改密码"""
if not self.current_user:
return False
users = self.load_users()
user = users[self.current_user]
if user["password"] != old_password:
return False
if not self.validate_password(new_password):
return False
user["password"] = new_password
self.save_users(users)
return True
def logout(self):
"""用户登出"""
self.current_user = None
def get_current_username(self) -> str:
"""获取当前登录用户的用户名"""
if not self.current_user:
return ""
users = self.load_users()
if self.current_user in users:
return users[self.current_user].get("username", "")
return ""
class QuestionGenerator:
"""题目生成器"""
def __init__(self):
self.difficulty_levels = {
"小学": {"range": (1, 100), "operations": ["+", "-", "*"]},
"初中": {"range": (1, 1000), "operations": ["+", "-", "*", "/"]},
"高中": {"range": (1, 10000), "operations": ["+", "-", "*", "/", "**"]}
}
def generate_question(self, level: str) -> Dict:
"""生成单个题目"""
config = self.difficulty_levels[level]
min_val, max_val = config["range"]
operations = config["operations"]
# 随机选择运算符
operation = random.choice(operations)
if operation == "**":
# 幂运算特殊处理
base = random.randint(2, 10)
exponent = random.randint(2, 4)
question = f"{base}^{exponent} = ?"
correct_answer = base ** exponent
elif operation == "/":
# 除法确保整除
divisor = random.randint(2, 20)
quotient = random.randint(2, max_val // divisor)
dividend = divisor * quotient
question = f"{dividend} ÷ {divisor} = ?"
correct_answer = quotient
else:
# 加减乘法
num1 = random.randint(min_val, max_val)
num2 = random.randint(min_val, max_val)
if operation == "+":
question = f"{num1} + {num2} = ?"
correct_answer = num1 + num2
elif operation == "-":
# 确保结果为正数
if num1 < num2:
num1, num2 = num2, num1
question = f"{num1} - {num2} = ?"
correct_answer = num1 - num2
else: # multiplication
question = f"{num1} × {num2} = ?"
correct_answer = num1 * num2
# 生成选项
options = self.generate_options(correct_answer)
return {
"question": question,
"options": options,
"correct_answer": correct_answer
}
def generate_options(self, correct_answer: int) -> List[int]:
"""生成四个选项,其中一个是正确答案"""
options = [correct_answer]
# 生成3个错误选项
while len(options) < 4:
# 在正确答案附近生成错误选项
offset = random.randint(-abs(correct_answer)//2, abs(correct_answer)//2)
if offset == 0:
offset = random.choice([-1, 1]) * random.randint(1, 10)
wrong_answer = correct_answer + offset
if wrong_answer > 0 and wrong_answer not in options:
options.append(wrong_answer)
# 打乱选项顺序
random.shuffle(options)
return options
def generate_test_paper(self, level: str, question_count: int) -> List[Dict]:
"""生成试卷"""
questions = []
generated_questions = set() # 用于避免重复题目
attempts = 0
while len(questions) < question_count and attempts < question_count * 10:
question_data = self.generate_question(level)
question_text = question_data["question"]
if question_text not in generated_questions:
generated_questions.add(question_text)
questions.append(question_data)
attempts += 1
return questions
class ScoreCalculator:
"""分数计算器"""
@staticmethod
def calculate_score(correct_count: int, total_count: int) -> int:
"""计算分数(百分制)"""
if total_count == 0:
return 0
return int((correct_count / total_count) * 100)
@staticmethod
def get_grade_comment(score: int) -> str:
"""根据分数获取评价"""
if score >= 90:
return "优秀!继续保持!"
elif score >= 80:
return "良好!再接再厉!"
elif score >= 70:
return "中等,还需努力!"
elif score >= 60:
return "及格,继续加油!"
else:
return "需要更多练习!"