|
|
"""
|
|
|
数学学习软件 - 数据管理模块
|
|
|
负责用户数据存储、题目生成等业务逻辑
|
|
|
"""
|
|
|
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 "需要更多练习!" |