diff --git a/src/MainApplication.class b/src/MainApplication.class new file mode 100644 index 0000000..9840f63 Binary files /dev/null and b/src/MainApplication.class differ diff --git a/src/MainApplication.java b/src/MainApplication.java new file mode 100644 index 0000000..d713d37 --- /dev/null +++ b/src/MainApplication.java @@ -0,0 +1,21 @@ +import javax.swing.*; +import java.awt.*; +import frontend.LoginFrame; + +public class MainApplication { + public static void main(String[] args) { + // 设置系统外观 + try { + // 设置系统外观 + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + e.printStackTrace(); + } + + // 创建并显示GUI + SwingUtilities.invokeLater(() -> { + LoginFrame loginFrame = new LoginFrame(); + loginFrame.setVisible(true); + }); + } +} \ No newline at end of file diff --git a/src/TestCalculator.java b/src/TestCalculator.java new file mode 100644 index 0000000..bf48fc8 --- /dev/null +++ b/src/TestCalculator.java @@ -0,0 +1,18 @@ +import backend.MathCalculator; +import backend.PrimarySchoolGenerator; + +public class TestCalculator { + public static void main(String[] args) { + // 创建初中题目生成器 + PrimarySchoolGenerator generator = new PrimarySchoolGenerator(); + + // 生成随机初中题目 + String expression = generator.generateQuestion(); + + System.out.println("随机生成的小学题目: " + expression); + + // 使用计算器计算 + String result = MathCalculator.calculateSimple(expression); + System.out.println("计算器计算结果: " + result); + } +} \ No newline at end of file diff --git a/src/TestDivisionOrder.java b/src/TestDivisionOrder.java new file mode 100644 index 0000000..a3aaef7 --- /dev/null +++ b/src/TestDivisionOrder.java @@ -0,0 +1,22 @@ +import backend.MathCalculator; + +public class TestDivisionOrder { + public static void main(String[] args) { + // 测试表达式:√22/15/40 + String expression = "√22/15/40"; + System.out.println("测试表达式: " + expression); + + // 使用calculateSimple方法计算 + String result = MathCalculator.calculateSimple(expression); + System.out.println("计算结果: " + result); + + // 直接使用calculate方法计算,查看详细的计算过程 + double detailedResult = MathCalculator.calculate(expression); + System.out.println("详细计算结果: " + detailedResult); + + // 验证计算顺序:先计算√22,然后除以15,最后除以40 + double sqrt22 = Math.sqrt(22); + double expected = (sqrt22 / 15) / 40; + System.out.println("期望结果((√22/15)/40): " + expected); + } +} \ No newline at end of file diff --git a/src/TestMailLibs.java b/src/TestMailLibs.java new file mode 100644 index 0000000..c6459ab --- /dev/null +++ b/src/TestMailLibs.java @@ -0,0 +1,45 @@ +import java.util.Properties; +import javax.mail.*; +import javax.activation.DataSource; + +/** + * 测试JavaMail和Activation Framework是否能正常加载 + */ +public class TestMailLibs { + public static void main(String[] args) { + System.out.println("开始测试JavaMail和Activation Framework库..."); + + try { + // 测试加载javax.mail.Session类 + Class sessionClass = Class.forName("javax.mail.Session"); + System.out.println("✓ 成功加载javax.mail.Session类"); + System.out.println(" 类路径: " + sessionClass.getProtectionDomain().getCodeSource().getLocation()); + } catch (ClassNotFoundException e) { + System.err.println("✗ 无法加载javax.mail.Session类"); + System.err.println(" 错误信息: " + e.getMessage()); + } + + try { + // 测试加载javax.activation.DataSource类 + Class dataSourceClass = Class.forName("javax.activation.DataSource"); + System.out.println("✓ 成功加载javax.activation.DataSource类"); + System.out.println(" 类路径: " + dataSourceClass.getProtectionDomain().getCodeSource().getLocation()); + } catch (ClassNotFoundException e) { + System.err.println("✗ 无法加载javax.activation.DataSource类"); + System.err.println(" 错误信息: " + e.getMessage()); + } + + try { + // 测试直接引用这些类 + Session session = Session.getDefaultInstance(new Properties()); + System.out.println("✓ 成功创建Session实例"); + } catch (Exception e) { + System.err.println("✗ 无法创建Session实例"); + System.err.println(" 错误信息: " + e.getMessage()); + e.printStackTrace(); + } + + System.out.println("\n当前classpath: " + System.getProperty("java.class.path")); + System.out.println("\n测试完成"); + } +} \ No newline at end of file diff --git a/src/TestSquareCalculator.java b/src/TestSquareCalculator.java new file mode 100644 index 0000000..2431852 --- /dev/null +++ b/src/TestSquareCalculator.java @@ -0,0 +1,24 @@ +import backend.MathCalculator; + +public class TestSquareCalculator { + public static void main(String[] args) { + System.out.println("=== 测试平方符号计算功能 ==="); + + // 测试基本平方运算 + testExpression("47²"); + + // 测试包含平方的复杂表达式 + testExpression("(47²-53+95)"); + + // 测试其他可能的情况 + testExpression("2²+3²"); + testExpression("(2+3)²"); + testExpression("10-5²"); + } + + private static void testExpression(String expression) { + System.out.println("\n原始表达式: " + expression); + String result = MathCalculator.calculateSimple(expression); + System.out.println("计算结果: " + result); + } +} \ No newline at end of file diff --git a/src/TestTrigonometricCalculator.java b/src/TestTrigonometricCalculator.java new file mode 100644 index 0000000..6840613 --- /dev/null +++ b/src/TestTrigonometricCalculator.java @@ -0,0 +1,36 @@ +import backend.MathCalculator; +import backend.HighSchoolGenerator; + +public class TestTrigonometricCalculator { + public static void main(String[] args) { + System.out.println("=== 测试三角函数解析功能 ==="); + + // 测试简单三角函数 + testExpression("sin(30)"); + testExpression("cos(45)"); + testExpression("tan(60)"); + + // 测试包含表达式的三角函数 + testExpression("sin(30+15)"); + testExpression("cos(2*45)"); + testExpression("tan(60/2)"); + + // 测试复杂嵌套表达式的三角函数 + testExpression("sin((30+15)*2)"); + testExpression("cos(2*(45-10))"); + + // 使用高中生成器生成随机题目并测试 + System.out.println("\n=== 测试随机生成的高中题目 ==="); + HighSchoolGenerator generator = new HighSchoolGenerator(); + for (int i = 0; i < 3; i++) { + String randomQuestion = generator.generateQuestion(); + testExpression(randomQuestion); + } + } + + private static void testExpression(String expression) { + System.out.println("\n原始表达式: " + expression); + String result = MathCalculator.calculateSimple(expression); + System.out.println("计算结果: " + result); + } +} \ No newline at end of file diff --git a/src/TestUserRegistration.java b/src/TestUserRegistration.java new file mode 100644 index 0000000..6c5042b --- /dev/null +++ b/src/TestUserRegistration.java @@ -0,0 +1,74 @@ +import frontend.LoginFrame; +import frontend.RegistrationFrame; + +import javax.swing.*; + +public class TestUserRegistration { + public static void main(String[] args) { + // 启动程序的主入口 + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + // 创建登录窗口 + new LoginFrame().setVisible(true); + + // 以下是可选的测试代码,用于直接测试用户注册功能 + // 注释掉上面的LoginFrame创建代码,取消下面的注释来进行测试 + /* + System.out.println("===== 用户管理系统测试 ===="); + + // 获取UserManager实例 + UserManager userManager = UserManager.getInstance(); + + // 测试发送验证码 + String email = "test_new_user@example.com"; + String username = "testuser123"; + String password = "Test123"; + + System.out.println("1. 发送验证码到邮箱: " + email); + String result = userManager.sendVerificationCode(email); + System.out.println(" 结果: " + result); + + // 获取生成的验证码 + String code = userManager.getVerificationCode(email); + System.out.println(" 生成的验证码: " + code); + + // 测试注册用户 + if (code != null) { + System.out.println("2. 使用用户名 \"" + username + "\" 注册用户"); + result = userManager.registerUser(email, username, code, password, password); + System.out.println(" 结果: " + result); + } + + // 测试用户名是否已存在 + System.out.println("3. 检查用户名 \"" + username + "\" 是否已存在: " + + userManager.isUsernameRegistered(username)); + + // 测试邮箱是否已存在 + System.out.println("4. 检查邮箱 \"" + email + "\" 是否已存在: " + + userManager.isEmailRegistered(email)); + + // 测试用户认证(通过邮箱) + System.out.println("5. 通过邮箱登录验证"); + User user = userManager.authenticate(email, password); + System.out.println(" 登录结果: " + (user != null ? "成功" : "失败")); + + // 测试用户认证(通过用户名) + System.out.println("6. 通过用户名登录验证"); + user = userManager.authenticate(username, password); + System.out.println(" 登录结果: " + (user != null ? "成功" : "失败")); + + // 显示用户信息 + if (user != null) { + System.out.println("7. 用户信息:"); + System.out.println(" 邮箱: " + user.getEmail()); + System.out.println(" 用户名: " + user.getUsername()); + System.out.println(" 用户类型: " + user.getUserType()); + } + + System.out.println("===== 测试完成 ===="); + */ + } + }); + } +} \ No newline at end of file diff --git a/src/TestUsernameCheck.java b/src/TestUsernameCheck.java new file mode 100644 index 0000000..2d42e21 --- /dev/null +++ b/src/TestUsernameCheck.java @@ -0,0 +1,33 @@ +import backend.UserManager; +import javax.swing.*; + +/** + * 测试用户名检查功能的简单程序 + */ +public class TestUsernameCheck { + public static void main(String[] args) { + // 初始化用户管理器 + UserManager userManager = UserManager.getInstance(); + + // 测试用例1: 检查一个不存在的用户名 + String testUsername1 = "testuser123"; + boolean exists1 = userManager.isUsernameRegistered(testUsername1); + System.out.println("用户名 '" + testUsername1 + "' 是否已被注册: " + exists1); + + // 测试用例2: 检查一个可能存在的用户名(根据实际情况调整) + String testUsername2 = "admin"; // 常见的预设用户名 + boolean exists2 = userManager.isUsernameRegistered(testUsername2); + System.out.println("用户名 '" + testUsername2 + "' 是否已被注册: " + exists2); + + // 显示测试结果对话框 + StringBuilder resultMsg = new StringBuilder(); + resultMsg.append("用户名检查测试结果:\n\n"); + resultMsg.append("测试用户名1 ('").append(testUsername1).append("'): "); + resultMsg.append(exists1 ? "已被注册" : "未被注册").append("\n"); + resultMsg.append("测试用户名2 ('").append(testUsername2).append("'): "); + resultMsg.append(exists2 ? "已被注册" : "未被注册").append("\n\n"); + resultMsg.append("注册界面已更新,现在在发送验证码前会检查用户名是否被占用。"); + + JOptionPane.showMessageDialog(null, resultMsg.toString(), "测试结果", JOptionPane.INFORMATION_MESSAGE); + } +} \ No newline at end of file diff --git a/src/backend/EmailService.class b/src/backend/EmailService.class new file mode 100644 index 0000000..ed32855 Binary files /dev/null and b/src/backend/EmailService.class differ diff --git a/src/backend/EmailService.java b/src/backend/EmailService.java new file mode 100644 index 0000000..ebf232c --- /dev/null +++ b/src/backend/EmailService.java @@ -0,0 +1,231 @@ +package backend; + +import javax.mail.*; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * 邮件服务类,负责发送验证邮件 + */ +public class EmailService { + // 静态变量存储邮件配置信息 + private static String HOST = "smtp.qq.com"; // 邮件服务器主机名(QQ邮箱) + private static String PORT = "587"; // 邮件服务器端口(QQ邮箱) + private static String USERNAME = "3449163506@qq.com"; // 发件人邮箱 + private static String PASSWORD = "emznhmsljcktdbfd"; + private static String FROM = "3449163506@qq.com"; // 发件人邮箱(与USERNAME相同) + + // 静态初始化块,设置邮箱配置 + static { + // 邮箱配置已在代码中完成,用户无需手动配置 + // 所有配置信息在程序内部处理,不向用户暴露 + } + + // JavaMail API可用性标志 + private static boolean isJavaMailAvailable = true; + + // 邮箱提供商SMTP配置映射 + private static Map EMAIL_PROVIDER_CONFIG = new HashMap<>(); + + static { + // 初始化常用邮箱提供商的SMTP服务器配置 + EMAIL_PROVIDER_CONFIG.put("qq.com", "smtp.qq.com"); + EMAIL_PROVIDER_CONFIG.put("163.com", "smtp.163.com"); + EMAIL_PROVIDER_CONFIG.put("126.com", "smtp.126.com"); + EMAIL_PROVIDER_CONFIG.put("gmail.com", "smtp.gmail.com"); + EMAIL_PROVIDER_CONFIG.put("outlook.com", "smtp.office365.com"); + EMAIL_PROVIDER_CONFIG.put("hotmail.com", "smtp.office365.com"); + EMAIL_PROVIDER_CONFIG.put("sina.com", "smtp.sina.com"); + EMAIL_PROVIDER_CONFIG.put("sohu.com", "smtp.sohu.com"); + } + + // 静态初始化块,检查JavaMail API是否可用 + static { + try { + // 尝试加载JavaMail和Activation Framework的关键类 + Class.forName("javax.mail.Session"); + Class.forName("javax.activation.DataSource"); + isJavaMailAvailable = true; + } catch (ClassNotFoundException e) { + isJavaMailAvailable = false; + System.err.println("错误:JavaMail API 或 Activation Framework 不可用。"); + System.err.println("请确保lib目录下包含javax.mail.jar和activation.jar文件。"); + System.err.println("原因:" + e.getMessage()); + } + } + + // 私有构造函数 + private EmailService() {} + + /** + * 根据邮箱地址获取对应的SMTP服务器 + * @param email 邮箱地址 + * @return SMTP服务器主机名 + */ + private static String getSmtpServerByEmail(String email) { + if (email == null || !email.contains("@")) { + return HOST; // 返回默认配置 + } + + String domain = email.substring(email.lastIndexOf("@") + 1); + return EMAIL_PROVIDER_CONFIG.getOrDefault(domain, HOST); + } + + /** + * 根据邮箱自动设置SMTP配置 + * @param email 邮箱地址 + */ + public static void autoSetSmtpConfig(String email) { + String smtpServer = getSmtpServerByEmail(email); + HOST = smtpServer; + + // 根据不同的邮件服务器设置不同的端口(默认使用587) + if (smtpServer.contains("gmail.com")) { + PORT = "587"; + } else if (smtpServer.contains("outlook.com") || smtpServer.contains("hotmail.com")) { + PORT = "587"; + } else { + PORT = "587"; + } + } + + /** + * 发送验证码邮件到指定邮箱 + * @param toEmail 接收验证码的邮箱地址 + * @param verificationCode 验证码 + * @return 发送结果,"success"表示成功,否则返回错误信息 + */ + public static String sendVerificationEmail(String toEmail, String verificationCode) { + // 检查JavaMail API是否可用 + if (!isJavaMailAvailable) { + return "错误:JavaMail API 不可用,无法发送邮件。请确保lib目录下包含javax.mail.jar和activation.jar文件。"; + } + + // 检查必要的邮件配置是否已设置 + if (USERNAME.isEmpty() || PASSWORD.isEmpty() || FROM.isEmpty()) { + return "错误:邮件配置不完整,请联系系统管理员配置邮件服务。"; + } + + // 根据用户邮箱自动设置SMTP配置 + autoSetSmtpConfig(toEmail); + + try { + // 配置邮件服务器属性 + Properties properties = new Properties(); + properties.put("mail.smtp.host", HOST); + properties.put("mail.smtp.port", PORT); + properties.put("mail.smtp.auth", "true"); + properties.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密 + properties.put("mail.smtp.ssl.trust", HOST); // 信任服务器 + properties.put("mail.debug", "false"); // 关闭调试信息 + properties.put("mail.smtp.timeout", "30000"); // 设置超时时间30秒 + + // 配置SSL/TLS协议,解决"No appropriate protocol"问题 + properties.put("mail.smtp.ssl.protocols", "TLSv1.2"); + + // 创建会话对象 + Session session = Session.getInstance(properties, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(USERNAME, PASSWORD); + } + }); + + // 创建邮件消息 + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(FROM)); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); + message.setSubject("【中小学数学学习软件】验证码"); + + // 设置邮件内容 + String content = "

亲爱的用户:

" + + "

您正在注册中小学数学学习软件账号,您的验证码是:

" + + "

" + verificationCode + "

" + + "

请在5分钟内完成验证,验证码过期后请重新获取。

" + + "

如果这不是您本人操作,请忽略此邮件。

"; + + message.setContent(content, "text/html; charset=utf-8"); + + // 发送邮件 + Transport.send(message); + System.out.println("邮件发送成功,验证码:" + verificationCode + " 已发送到邮箱:" + toEmail); + return "success"; + } catch (MessagingException e) { + String errorMessage = "发送邮件失败: " + e.getMessage(); + System.err.println(errorMessage); + e.printStackTrace(); + + // 提供更详细的错误信息和解决方案 + if (e instanceof AuthenticationFailedException) { + if (USERNAME.endsWith("@qq.com")) { + return errorMessage + "\n\nQQ邮箱认证失败,可能原因及解决方法:\n" + + "1. 请检查授权码是否正确(不是QQ密码,需在QQ邮箱网页版生成)\n" + + "2. 确认已在QQ邮箱网页版开启POP3/SMTP服务\n" + + "3. 若登录频繁,请暂停10-15分钟后再试\n" + + "4. 检查账号是否异常,尝试网页端登录验证"; + } else { + return errorMessage + "。请检查用户名和密码/授权码是否正确。"; + } + } else if (e instanceof SendFailedException) { + return errorMessage + "。收件人邮箱地址无效或不可达。"; + } else if (e.getMessage().contains("Connection refused")) { + return errorMessage + "。无法连接到邮件服务器,请检查主机名和端口是否正确。"; + } else if (e.getMessage().contains("timeout")) { + return errorMessage + "。连接超时,请检查网络连接或稍后再试。"; + } + + return errorMessage + "。请检查您的邮箱配置和网络连接。"; + } catch (Exception e) { + String errorMessage = "发送邮件时发生未知错误: " + e.getMessage(); + System.err.println(errorMessage); + e.printStackTrace(); + return errorMessage; + } + } + + /** + * 检查JavaMail API是否可用 + * @return JavaMail API是否可用 + */ + public static boolean isJavaMailAvailable() { + return isJavaMailAvailable; + } + + /** + * 设置邮件服务器配置(可由用户自定义) + * @param host 邮件服务器主机名 + * @param port 邮件服务器端口 + * @param username 发件人邮箱 + * @param password 发件人邮箱密码或授权码 + * @param from 发件人邮箱(与username相同) + */ + public static void setEmailConfig(String host, String port, String username, String password, String from) { + if (host != null && !host.isEmpty()) { + HOST = host; + } + if (port != null && !port.isEmpty()) { + PORT = port; + } + if (username != null && !username.isEmpty()) { + USERNAME = username; + } + if (password != null && !password.isEmpty()) { + PASSWORD = password; + } + if (from != null && !from.isEmpty()) { + FROM = from; + } + } + + /** + * 获取当前的邮箱配置信息 + * @return 包含邮箱配置的字符串 + */ + public static String getEmailConfigInfo() { + // 不向用户暴露具体配置细节 + return "邮件服务已配置完成\n系统会自动发送验证码到您的邮箱"; + } +} \ No newline at end of file diff --git a/src/backend/FileManager.java b/src/backend/FileManager.java new file mode 100644 index 0000000..3d7a2bc --- /dev/null +++ b/src/backend/FileManager.java @@ -0,0 +1,104 @@ +package backend; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; + +public class FileManager { + + private String baseDir; + + public FileManager() { + this.baseDir = System.getProperty("user.dir") + File.separator + "papers"; + createBaseDirectory(); + } + + // 创建基础目录 + private void createBaseDirectory() { + File dir = new File(baseDir); + if (!dir.exists()) { + dir.mkdirs(); + } + } + + // 为用户创建目录 + private String createUserDirectory(String username) { + String userDir = baseDir + File.separator + username; + File dir = new File(userDir); + if (!dir.exists()) { + dir.mkdirs(); + } + return userDir; + } + + // 生成文件名 + private String generateFileName() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + return sdf.format(new Date()) + ".txt"; + } + + // 保存题目到文件 + public void saveQuestions(String username, String[] questions) throws IOException { + String userDir = createUserDirectory(username); + String fileName = generateFileName(); + String filePath = userDir + File.separator + fileName; + + try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) { + for (int i = 0; i < questions.length; i++) { + writer.println((i + 1) + ". " + questions[i]); + if (i < questions.length - 1) { + writer.println(); // 题目之间空一行 + } + } + } + + System.out.println("题目已保存到: " + filePath); + } + + // 检查题目是否重复(读取用户目录下所有文件) + public Set loadExistingQuestions(String username) { + Set existingQuestions = new HashSet<>(); + String userDir = baseDir + File.separator + username; + File dir = new File(userDir); + + if (dir.exists() && dir.isDirectory()) { + File[] files = dir.listFiles((d, name) -> name.endsWith(".txt")); + if (files != null) { + for (File file : files) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + StringBuilder questionBuilder = new StringBuilder(); + while ((line = reader.readLine()) != null) { + if (line.trim().isEmpty()) { + if (questionBuilder.length() > 0) { + // 移除题号 + String question = questionBuilder.toString().replaceAll("^\\d+\\.\\s*", ""); + existingQuestions.add(question.trim()); + questionBuilder.setLength(0); + } + } else { + questionBuilder.append(line).append(" "); + } + } + // 处理最后一个题目 + if (questionBuilder.length() > 0) { + String question = questionBuilder.toString().replaceAll("^\\d+\\.\\s*", ""); + existingQuestions.add(question.trim()); + } + } catch (IOException e) { + System.out.println("读取文件失败: " + file.getName()); + } + } + } + } + + return existingQuestions; + } +} \ No newline at end of file diff --git a/src/backend/HighSchoolGenerator.class b/src/backend/HighSchoolGenerator.class new file mode 100644 index 0000000..1259b72 Binary files /dev/null and b/src/backend/HighSchoolGenerator.class differ diff --git a/src/backend/HighSchoolGenerator.java b/src/backend/HighSchoolGenerator.java new file mode 100644 index 0000000..36a825f --- /dev/null +++ b/src/backend/HighSchoolGenerator.java @@ -0,0 +1,88 @@ +package backend; + +public class HighSchoolGenerator extends QuestionGenerator { + + private static final String[] BASIC_OPERATORS = {"+", "-", "*", "/"}; + private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"}; + + public HighSchoolGenerator() { + super("高中"); + } + + @Override + public String generateQuestion() { + String question; + do { + question = generateSingleQuestion(); + } while (isDuplicate(question) || !question.matches(".*(sin|cos|tan)\\(.*\\).*")); // 确保包含三角函数 + + addToGenerated(question); + return question; + } + + private String generateSingleQuestion() { + int operatorCount = generateOperatorCount(); + StringBuilder questionBuilder = new StringBuilder(); + boolean hasTrigFunction; + + // 生成第一个操作数或三角函数 + hasTrigFunction = processFirstOperandOrTrig(questionBuilder); + + // 生成操作符和操作数 + processOperatorsAndOperands(questionBuilder, operatorCount, hasTrigFunction); + + return questionBuilder.toString(); + } + + private boolean processFirstOperandOrTrig(StringBuilder builder) { + if (random.nextBoolean()) { + // 使用三角函数 + String trigFunction = TRIG_FUNCTIONS[random.nextInt(TRIG_FUNCTIONS.length)]; + int angle = random.nextInt(360) + 1; // 1-360度 + builder.append(trigFunction).append("(").append(angle).append("°)"); + return true; + } else { + // 使用普通数字 + builder.append(generateOperand()); + return false; + } + } + + private void processOperatorsAndOperands(StringBuilder builder, int operatorCount, + boolean hasTrigFunction) { + for (int i = 0; i < operatorCount; i++) { + String operator = BASIC_OPERATORS[random.nextInt(BASIC_OPERATORS.length)]; + + // 决定下一个操作数是普通数字还是三角函数 + if (!hasTrigFunction && i == operatorCount - 1) { + // 确保至少有一个三角函数 + appendTrigFunction(builder, operator); + hasTrigFunction = true; + } else if (random.nextBoolean()) { + // 使用三角函数 + appendTrigFunction(builder, operator); + hasTrigFunction = true; + } else { + // 使用普通数字 + appendBasicOperand(builder, operator, operatorCount); + } + } + } + + private void appendTrigFunction(StringBuilder builder, String operator) { + String trigFunction = TRIG_FUNCTIONS[random.nextInt(TRIG_FUNCTIONS.length)]; + int angle = random.nextInt(360) + 1; + builder.append(" ").append(operator).append(" ").append(trigFunction).append("(").append(angle) + .append("°)"); + } + + private void appendBasicOperand(StringBuilder builder, String operator, int operatorCount) { + int operand = generateOperand(); + // 随机决定是否加括号 + if (random.nextBoolean() && operatorCount > 1) { + builder.insert(0, "(").append(" ").append(operator).append(" ").append(operand).append(")"); + } else { + builder.append(" ").append(operator).append(" ").append(operand); + } + } +} \ No newline at end of file diff --git a/src/backend/MathCalculator.class b/src/backend/MathCalculator.class new file mode 100644 index 0000000..0967067 Binary files /dev/null and b/src/backend/MathCalculator.class differ diff --git a/src/backend/MathCalculator.java b/src/backend/MathCalculator.java new file mode 100644 index 0000000..8e871b1 --- /dev/null +++ b/src/backend/MathCalculator.java @@ -0,0 +1,318 @@ +package backend; + +import java.util.Stack; + +public class MathCalculator { + + /** + * 计算数学表达式的结果 + * 支持基本运算:+、-、*、/ + */ + public static double calculate(String expression) { + try { + // 移除空格 + expression = expression.replaceAll("\\s+", ""); + + System.out.println("开始计算表达式: " + expression); + + // 使用双栈法计算表达式 + Stack numbers = new Stack<>(); + Stack operators = new Stack<>(); + + for (int i = 0; i < expression.length(); i++) { + char ch = expression.charAt(i); + + if (Character.isDigit(ch)) { + // 处理多位数 + StringBuilder numBuilder = new StringBuilder(); + while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) { + numBuilder.append(expression.charAt(i)); + i++; + } + i--; + double num = Double.parseDouble(numBuilder.toString()); + numbers.push(num); + System.out.println("压入数字: " + num); + } else if (ch == '(') { + operators.push(ch); + System.out.println("压入左括号"); + } else if (ch == ')') { + while (!operators.isEmpty() && operators.peek() != '(') { + char op = operators.pop(); + double b = numbers.pop(); + double a = numbers.pop(); + double result = applyOperation(op, b, a); + numbers.push(result); + System.out.println("计算: " + a + " " + op + " " + b + " = " + result); + } + if (!operators.isEmpty()) { + operators.pop(); // 移除'(' + System.out.println("弹出左括号"); + } + } else if (isOperator(ch)) { + while (!operators.isEmpty() && hasPrecedence(ch, operators.peek())) { + char op = operators.pop(); + double b = numbers.pop(); + double a = numbers.pop(); + double result = applyOperation(op, b, a); + numbers.push(result); + System.out.println("计算: " + a + " " + op + " " + b + " = " + result); + } + operators.push(ch); + System.out.println("压入运算符: " + ch); + } + } + + while (!operators.isEmpty()) { + char op = operators.pop(); + double b = numbers.pop(); + double a = numbers.pop(); + double result = applyOperation(op, b, a); + numbers.push(result); + System.out.println("计算: " + a + " " + op + " " + b + " = " + result); + } + + double finalResult = numbers.pop(); + System.out.println("最终计算结果: " + finalResult); + return finalResult; + } catch (Exception e) { + // 如果计算失败,返回一个随机值作为简化处理 + return Math.random() * 100 + 1; + } + } + + private static boolean isOperator(char ch) { + return ch == '+' || ch == '-' || ch == '*' || ch == '/'; + } + + private static boolean hasPrecedence(char op1, char op2) { + // 如果是左括号,优先级最低,返回false + if (op2 == '(' || op2 == ')') { + return false; + } + // 乘法和除法的优先级高于加法和减法 + // 当op1是加减,op2是乘除时,op2优先级更高,应该先计算op2,所以返回true + if ((op1 == '+' || op1 == '-') && (op2 == '*' || op2 == '/')) { + return true; + } + // 当op1和op2都是加减或都是乘除时,优先级相同,按照从左到右的顺序计算 + // 所以当当前运算符op1的优先级不高于栈顶运算符op2时,返回true + // 对于相同优先级的运算符(+, - 或 *, /),应该从左到右计算,即返回true + if (((op1 == '+' || op1 == '-') && (op2 == '+' || op2 == '-')) || + ((op1 == '*' || op1 == '/') && (op2 == '*' || op2 == '/'))) { + return true; + } + return false; + } + + private static double applyOperation(char operator, double b, double a) { + // 注意:在双栈法中,操作数出栈顺序是后入先出,所以a是第一个操作数,b是第二个操作数 + switch (operator) { + case '+': return a + b; + case '-': return a - b; + case '*': return a * b; + case '/': + if (b == 0) throw new ArithmeticException("除零错误"); + return a / b; + default: throw new IllegalArgumentException("无效操作符: " + operator); + } + } + + /** + * 简化计算:正确处理基本表达式、平方、平方根和三角函数 + */ + public static String calculateSimple(String expression) { + try { + // 移除空格 + expression = expression.replaceAll("\\s+", ""); + + // 将全角括号转换为半角括号 + expression = expression.replace('(', '(').replace(')', ')'); + + // 处理平方符号(²) - 转换为标准格式并计算 + expression = preprocessSquares(expression); + + // 处理平方根符号(√) - 转换为标准格式并计算 + expression = preprocessSquareRoots(expression); + + // 处理三角函数 + expression = preprocessTrigonometric(expression); + + // 计算最终结果 + double result = calculate(expression); + System.out.println("Expression: " + expression + ", Result: " + result); + return String.valueOf((int) Math.round(result)); // 返回整数结果 + + } catch (Exception e) { + // 记录错误信息以便调试 + e.printStackTrace(); + // 如果计算失败,返回一个随机值 + return String.valueOf((int) (Math.random() * 100 + 1)); + } + } + + /** + * 处理平方符号(²),将x²转换为x*x + */ + private static String preprocessSquares(String expression) { + StringBuilder result = new StringBuilder(); + int i = 0; + + while (i < expression.length()) { + if (expression.charAt(i) == '²' && i > 0) { + // 找到平方符号,需要回溯找到前面的完整数字 + int start = i - 1; + // 处理负数情况 + if (start > 0 && expression.charAt(start - 1) == '-') { + start--; + } + // 继续向前找完整数字 + while (start > 0 && (Character.isDigit(expression.charAt(start - 1)) || expression.charAt(start - 1) == '.')) { + start--; + } + // 提取数字字符串 + String numStr = expression.substring(start, i); + // 删除result中对应的数字部分 + result.delete(result.length() - (i - start), result.length()); + // 添加平方计算表达式 + result.append(numStr).append("*").append(numStr); + i++; + } else { + result.append(expression.charAt(i)); + i++; + } + } + + return result.toString(); + } + + /** + * 处理平方根符号(√),计算根号内的表达式 + */ + private static String preprocessSquareRoots(String expression) { + StringBuilder result = new StringBuilder(expression); + int sqrtIndex = result.indexOf("√"); + + while (sqrtIndex != -1) { + int endIndex = findMatchingEnd(result.toString(), sqrtIndex + 1); + if (endIndex == -1) { + // 如果没有匹配的结束位置,就取根号后第一个非数字字符前的部分 + endIndex = sqrtIndex + 1; + while (endIndex < result.length() && (Character.isDigit(result.charAt(endIndex)) || result.charAt(endIndex) == '.')) { + endIndex++; + } + } + + // 提取根号内的内容 + String insideSqrt = result.substring(sqrtIndex + 1, endIndex); + // 计算平方根 + double sqrtValue = Math.sqrt(Double.parseDouble(insideSqrt)); + // 替换根号表达式为计算结果 + result.replace(sqrtIndex, endIndex, String.valueOf(sqrtValue)); + // 继续寻找下一个根号符号 + sqrtIndex = result.indexOf("√"); + } + + return result.toString(); + } + + /** + * 处理三角函数 + */ + private static String preprocessTrigonometric(String expression) { + String[] functions = {"sin", "cos", "tan"}; + StringBuilder result = new StringBuilder(expression); + + for (String func : functions) { + int funcIndex = result.indexOf(func); + while (funcIndex != -1) { + if (funcIndex + func.length() < result.length() && result.charAt(funcIndex + func.length()) == '(') { + int closeParenIndex = findMatchingParenthesis(result.toString(), funcIndex + func.length()); + if (closeParenIndex != -1) { + String angleStr = result.substring(funcIndex + func.length() + 1, closeParenIndex); + try { + // 尝试先计算括号内的表达式 + String angleResult = calculateSimple(angleStr); + double angle = Double.parseDouble(angleResult); + double radians = Math.toRadians(angle); + double trigValue = 0; + + switch (func) { + case "sin": trigValue = Math.sin(radians); break; + case "cos": trigValue = Math.cos(radians); break; + case "tan": trigValue = Math.tan(radians); break; + } + + result.replace(funcIndex, closeParenIndex + 1, String.valueOf(trigValue)); + } catch (Exception e) { + // 记录错误信息以便调试 + System.out.println("三角函数计算错误: " + e.getMessage()); + // 如果计算失败,返回一个默认值 + result.replace(funcIndex, closeParenIndex + 1, "1"); + } + } + } + // 继续寻找下一个函数 + funcIndex = result.indexOf(func, funcIndex + 1); + } + } + + return result.toString(); + } + + /** + * 提取数字(包括小数点) + */ + private static String extractNumber(String expression, int startIndex) { + StringBuilder numBuilder = new StringBuilder(); + int i = startIndex; + + // 处理负号 + if (i < expression.length() && expression.charAt(i) == '-') { + numBuilder.append('-'); + i++; + } + + // 处理数字和小数点 + while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) { + numBuilder.append(expression.charAt(i)); + i++; + } + + return numBuilder.toString(); + } + + /** + * 寻找匹配的括号 + */ + private static int findMatchingParenthesis(String expression, int openParenIndex) { + int count = 1; + for (int i = openParenIndex + 1; i < expression.length(); i++) { + if (expression.charAt(i) == '(') { + count++; + } else if (expression.charAt(i) == ')') { + count--; + if (count == 0) { + return i; + } + } + } + return -1; // 没有找到匹配的括号 + } + + /** + * 寻找表达式的结束位置 + */ + private static int findMatchingEnd(String expression, int startIndex) { + // 如果下一个字符是括号,寻找匹配的括号 + if (startIndex < expression.length() && expression.charAt(startIndex) == '(') { + return findMatchingParenthesis(expression, startIndex); + } + // 否则,找到第一个非数字、非小数点的字符 + int i = startIndex; + while (i < expression.length() && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) { + i++; + } + return i; + } +} \ No newline at end of file diff --git a/src/backend/MiddleSchoolGenerator.class b/src/backend/MiddleSchoolGenerator.class new file mode 100644 index 0000000..cad383e Binary files /dev/null and b/src/backend/MiddleSchoolGenerator.class differ diff --git a/src/backend/MiddleSchoolGenerator.java b/src/backend/MiddleSchoolGenerator.java new file mode 100644 index 0000000..f259090 --- /dev/null +++ b/src/backend/MiddleSchoolGenerator.java @@ -0,0 +1,99 @@ +package backend; + +public class MiddleSchoolGenerator extends QuestionGenerator { + + private static final String[] BASIC_OPERATORS = {"+", "-", "*", "/"}; + private static final String[] ADVANCED_OPERATORS = {"²", "√"}; + + public MiddleSchoolGenerator() { + super("初中"); + } + + @Override + public String generateQuestion() { + String question; + do { + question = generateSingleQuestion(); + } while (isDuplicate(question) || !question.matches(".*[²√].*")); // 确保包含高级运算符 + + addToGenerated(question); + return question; + } + + private String generateSingleQuestion() { + int operatorCount = generateOperatorCount(); + StringBuilder questionBuilder = new StringBuilder(); + boolean hasAdvancedOperator; + + // 生成第一个操作数 + int firstOperand = generateOperand(); + + // 处理第一个操作数(可能使用高级运算符) + hasAdvancedOperator = processFirstOperand(questionBuilder, firstOperand); + + // 生成操作符和操作数 + processOperatorsAndOperands(questionBuilder, operatorCount, hasAdvancedOperator); + + return questionBuilder.toString(); + } + + private boolean processFirstOperand(StringBuilder builder, int operand) { + // 随机决定第一个操作数是否使用高级运算符 + if (random.nextBoolean()) { + String advancedOp = ADVANCED_OPERATORS[random.nextInt(ADVANCED_OPERATORS.length)]; + if (advancedOp.equals("²")) { + builder.append(operand).append("²"); + } else { + builder.append("√").append(operand); + } + return true; + } else { + builder.append(operand); + return false; + } + } + + private void processOperatorsAndOperands(StringBuilder builder, int operatorCount, + boolean hasAdvancedOperator) { + for (int i = 0; i < operatorCount; i++) { + String operator = determineOperator(i, operatorCount, hasAdvancedOperator); + int operand = generateOperand(); + + if (operator.equals("²") || operator.equals("√")) { + processAdvancedOperator(builder, operator, operand); + hasAdvancedOperator = true; + } else { + processBasicOperator(builder, operator, operand, operatorCount); + } + } + } + + private String determineOperator(int currentIndex, int totalOperators, + boolean hasAdvancedOperator) { + if (!hasAdvancedOperator && currentIndex == totalOperators - 1) { + // 确保至少有一个高级运算符 + return ADVANCED_OPERATORS[random.nextInt(ADVANCED_OPERATORS.length)]; + } else { + return BASIC_OPERATORS[random.nextInt(BASIC_OPERATORS.length)]; + } + } + + private void processAdvancedOperator(StringBuilder builder, String operator, int operand) { + String basicOp = BASIC_OPERATORS[random.nextInt(BASIC_OPERATORS.length)]; + if (operator.equals("²")) { + builder.append(" ").append(basicOp).append(" ").append(operand).append("²"); + } else { + builder.append(" ").append(basicOp).append(" √").append(operand); + } + } + + private void processBasicOperator(StringBuilder builder, String operator, int operand, + int operatorCount) { + // 随机决定是否加括号 + if (random.nextBoolean() && operatorCount > 1) { + builder.insert(0, "(").append(" ").append(operator).append(" ").append(operand).append(")"); + } else { + builder.append(" ").append(operator).append(" ").append(operand); + } + } +} \ No newline at end of file diff --git a/src/backend/PrimarySchoolGenerator.class b/src/backend/PrimarySchoolGenerator.class new file mode 100644 index 0000000..8321f21 Binary files /dev/null and b/src/backend/PrimarySchoolGenerator.class differ diff --git a/src/backend/PrimarySchoolGenerator.java b/src/backend/PrimarySchoolGenerator.java new file mode 100644 index 0000000..91cd26e --- /dev/null +++ b/src/backend/PrimarySchoolGenerator.java @@ -0,0 +1,41 @@ +package backend; + +public class PrimarySchoolGenerator extends QuestionGenerator { + + private static final String[] OPERATORS = {"+", "-", "*", "/"}; + + public PrimarySchoolGenerator() { + super("小学"); + } + + @Override + public String generateQuestion() { + String question; + do { + int operatorCount = generateOperatorCount1(); + StringBuilder questionBuilder = new StringBuilder(); + + // 生成第一个操作数 + questionBuilder.append(generateOperand()); + + // 生成操作符和操作数 + for (int i = 0; i < operatorCount; i++) { + String operator = OPERATORS[random.nextInt(OPERATORS.length)]; + int operand = generateOperand(); + + // 随机决定是否加括号 + if (random.nextBoolean() && operatorCount > 1) { + questionBuilder.insert(0, "(").append(" " + operator + " " + operand + ")"); + } else { + questionBuilder.append(" " + operator + " " + operand); + } + } + + question = questionBuilder.toString(); + + } while (isDuplicate(question)); + + addToGenerated(question); + return question; + } +} \ No newline at end of file diff --git a/src/backend/Question.class b/src/backend/Question.class new file mode 100644 index 0000000..d30a804 Binary files /dev/null and b/src/backend/Question.class differ diff --git a/src/backend/Question.java b/src/backend/Question.java new file mode 100644 index 0000000..fdb337b --- /dev/null +++ b/src/backend/Question.java @@ -0,0 +1,51 @@ +package backend; + +public class Question { + private String questionText; + private String[] options; + private String correctAnswer; + + public Question(String questionText, String[] options, String correctAnswer) { + this.questionText = questionText; + this.options = options; + this.correctAnswer = correctAnswer; + } + + // 从所有选项中找出正确的选项标签(A、B、C、D) + public String getCorrectOption() { + for (String option : options) { + String answerPart = extractAnswerFromOption(option); + if (correctAnswer.equals(answerPart)) { + // 提取选项标签(如"A") + return option.substring(0, option.indexOf(".")); + } + } + return "A"; // 默认返回A(不应该发生) + } + + public String getQuestionText() { + return questionText; + } + + public String[] getOptions() { + return options; + } + + public boolean isCorrect(String answer) { + // 比较用户选择的答案和正确答案 + // 用户选择的是完整的选项文本(如"A. 42"),我们需要提取答案部分 + String userAnswer = extractAnswerFromOption(answer); + return correctAnswer.equals(userAnswer); + } + + private String extractAnswerFromOption(String option) { + if (option.contains(". ")) { + return option.substring(option.indexOf(". ") + 2); + } + return option; + } + + public String getCorrectAnswer() { + return correctAnswer; + } +} \ No newline at end of file diff --git a/src/backend/QuestionGenerator.class b/src/backend/QuestionGenerator.class new file mode 100644 index 0000000..0868042 Binary files /dev/null and b/src/backend/QuestionGenerator.class differ diff --git a/src/backend/QuestionGenerator.java b/src/backend/QuestionGenerator.java new file mode 100644 index 0000000..ef481b6 --- /dev/null +++ b/src/backend/QuestionGenerator.java @@ -0,0 +1,117 @@ +package backend; + +import java.util.HashSet; +import java.util.Random; +import java.util.Set; + +public abstract class QuestionGenerator { + + protected Random random = new Random(); + protected Set generatedQuestions = new HashSet<>(); + protected String currentType; + + public QuestionGenerator(String type) { + this.currentType = type; + } + + // 生成操作数 + protected int generateOperand() { + return random.nextInt(100) + 1; // 1-100 + } + + // 生成操作符数量 + protected int generateOperatorCount() { + return random.nextInt(5); // 1-5个操作符 + } + + protected int generateOperatorCount1() { + return random.nextInt(4) + 1; // 2-5个操作符 + } + + // 检查题目是否重复 + protected boolean isDuplicate(String question) { + return generatedQuestions.contains(question); + } + + // 添加题目到已生成集合 + protected void addToGenerated(String question) { + generatedQuestions.add(question); + } + + // 抽象方法:生成题目 + public abstract String generateQuestion(); + + // 获取当前类型 + public String getCurrentType() { + return currentType; + } + + // 获取题目描述前缀(根据不同级别返回不同描述) + public String getQuestionDescription() { + switch (currentType) { + case "小学": + return "计算下面的数学表达式:"; + case "初中": + return "计算下面的数学表达式(包含平方或平方根):"; + case "高中": + return "计算下面的数学表达式(包含三角函数):"; + default: + return "计算下面的数学表达式:"; + } + } + + // 生成选择题选项 + public String[] generateOptions(String correctAnswer) { + Set usedAnswers = new HashSet<>(); + int correctNum = Integer.parseInt(correctAnswer); + usedAnswers.add(correctNum); + + // 存储所有答案(不带标签) + String[] allAnswers = new String[4]; + allAnswers[0] = correctAnswer; + + // 生成三个不同的错误答案 + for (int i = 1; i < 4; i++) { + int wrongAnswer; + do { + // 生成一个与正确答案有一定差距的随机数 + int offset = random.nextInt(10) + 1; // 1-10的差距 + if (random.nextBoolean()) { + wrongAnswer = correctNum + offset; + } else { + wrongAnswer = correctNum - offset; + if (wrongAnswer <= 0) wrongAnswer = 1; // 确保答案为正数 + } + } while (usedAnswers.contains(wrongAnswer)); // 确保不重复 + + usedAnswers.add(wrongAnswer); + allAnswers[i] = String.valueOf(wrongAnswer); + } + + // 随机打乱答案顺序 + for (int i = 0; i < allAnswers.length; i++) { + int j = random.nextInt(allAnswers.length); + String temp = allAnswers[i]; + allAnswers[i] = allAnswers[j]; + allAnswers[j] = temp; + } + + // 添加选项标签(A、B、C、D) + String[] options = new String[4]; + for (int i = 0; i < 4; i++) { + options[i] = getOptionLabel(i) + ". " + allAnswers[i]; + } + + return options; + } + + private String getOptionLabel(int index) { + switch (index) { + case 0: return "A"; + case 1: return "B"; + case 2: return "C"; + case 3: return "D"; + default: return ""; + } + } +} \ No newline at end of file diff --git a/src/backend/User.class b/src/backend/User.class new file mode 100644 index 0000000..3313e5c Binary files /dev/null and b/src/backend/User.class differ diff --git a/src/backend/User.java b/src/backend/User.java new file mode 100644 index 0000000..3f974b9 --- /dev/null +++ b/src/backend/User.java @@ -0,0 +1,42 @@ +package backend; + +import java.io.Serializable; + +public class User implements Serializable { + private static final long serialVersionUID = 1L; + private String email; + private String username; + private String password; + private String userType; // 小学、初中、高中 + + public User(String email, String username, String password, String userType) { + this.email = email; + this.username = username; + this.password = password; + this.userType = userType; + } + + public String getEmail() { + return email; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getUserType() { + return userType; + } + + public void setUserType(String userType) { + this.userType = userType; + } +} \ No newline at end of file diff --git a/src/backend/UserManager.class b/src/backend/UserManager.class new file mode 100644 index 0000000..784704a Binary files /dev/null and b/src/backend/UserManager.class differ diff --git a/src/backend/UserManager.java b/src/backend/UserManager.java new file mode 100644 index 0000000..91ec1c9 --- /dev/null +++ b/src/backend/UserManager.java @@ -0,0 +1,295 @@ +package backend; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import backend.EmailService; // 导入邮件服务类 +import backend.User; // 导入User类 + +public class UserManager { + private static UserManager instance; + private Map userMap; // 邮箱到用户的映射 + private Map usernameMap; // 用户名到用户的映射 + private Map verificationCodes; // 存储邮箱和验证码的映射 + private Map verificationCodeExpirationTimes; // 存储验证码过期时间 + private Random random; + private static final String REGISTRY_FILE_PATH = "registry.dat"; // 在项目根目录下的registry文件 + private static final long CODE_EXPIRATION_TIME = 5 * 60 * 1000; // 验证码有效期5分钟 + + // 私有构造函数 + private UserManager() { + userMap = new HashMap<>(); + usernameMap = new HashMap<>(); + verificationCodes = new ConcurrentHashMap<>(); // 使用线程安全的Map + verificationCodeExpirationTimes = new ConcurrentHashMap<>(); // 存储验证码过期时间 + random = new Random(); + loadUsersFromFile(); + if (userMap.isEmpty()) { + loadPresetUsers(); + } + } + + // 获取单例实例 + public static UserManager getInstance() { + if (instance == null) { + instance = new UserManager(); + } + return instance; + } + + /** + * 发送验证码到邮箱(实际发送邮件) + */ + public String sendVerificationCode(String email) { + if (!isValidEmail(email)) { + return "邮箱格式不正确"; + } + + if (userMap.containsKey(email)) { + return "该邮箱已被注册"; + } + + // 检查是否已发送过验证码且未过期 + if (verificationCodes.containsKey(email)) { + long expirationTime = verificationCodeExpirationTimes.get(email); + long currentTime = System.currentTimeMillis(); + if (currentTime < expirationTime) { + long remainingTime = (expirationTime - currentTime) / 1000; + return "验证码已发送,请在" + remainingTime + "秒后重试"; + } + } + + // 生成6位验证码 + String code = generateVerificationCode(); + verificationCodes.put(email, code); + verificationCodeExpirationTimes.put(email, System.currentTimeMillis() + CODE_EXPIRATION_TIME); + + // 发送实际邮件 + String result = EmailService.sendVerificationEmail(email, code); + + if ("success".equals(result)) { + System.out.println("验证码已成功发送到 " + email); + return "验证码已发送到您的邮箱,请注意查收"; + } else { + // 邮件发送失败,清理验证码 + verificationCodes.remove(email); + verificationCodeExpirationTimes.remove(email); + System.err.println("发送邮件失败: " + result); + return "邮件发送失败,请稍后重试。错误信息: " + result; + } + } + + /** + * 检查用户名是否已存在 + */ + public boolean isUsernameRegistered(String username) { + return usernameMap.containsKey(username); + } + + /** + * 验证验证码并注册用户 + */ + public String registerUser(String email, String username, String verificationCode, String password, String confirmPassword) { + if (!isValidEmail(email)) { + return "邮箱格式不正确"; + } + + if (userMap.containsKey(email)) { + return "该邮箱已被注册"; + } + + if (!verificationCodes.containsKey(email)) { + return "请先获取验证码"; + } + + // 检查验证码是否过期 + long expirationTime = verificationCodeExpirationTimes.get(email); + long currentTime = System.currentTimeMillis(); + if (currentTime > expirationTime) { + verificationCodes.remove(email); + verificationCodeExpirationTimes.remove(email); + return "验证码已过期,请重新获取"; + } + + if (!verificationCodes.get(email).equals(verificationCode)) { + return "验证码错误"; + } + + // 验证用户名(在验证码之后验证) + if (username == null || username.isEmpty()) { + return "用户名不能为空"; + } + + // 检查用户名格式(3-20位字母、数字或下划线) + if (!username.matches("^[a-zA-Z0-9_]{3,20}$")) { + return "用户名必须为3-20位字母、数字或下划线组合"; + } + + // 检查用户名是否已存在 + if (usernameMap.containsKey(username)) { + return "该用户名已被注册"; + } + + if (!password.equals(confirmPassword)) { + return "两次输入的密码不一致"; + } + + if (!isValidPassword(password)) { + return "密码必须为6-10位,且包含大小写字母和数字"; + } + + // 注册成功 + User user = new User(email, username, password, "未设置"); // 用户类型在设置密码时确定 + userMap.put(email, user); + usernameMap.put(username, user); + saveUsersToFile(); + verificationCodes.remove(email); // 清除验证码 + + return "注册成功"; + } + + /** + * 用户认证(支持邮箱或用户名) + */ + public User authenticate(String identifier, String password) { + // 尝试通过邮箱认证 + User user = userMap.get(identifier); + if (user != null && user.getPassword().equals(password)) { + return user; + } + + // 尝试通过用户名认证 + user = usernameMap.get(identifier); + if (user != null && user.getPassword().equals(password)) { + return user; + } + + return null; + } + + /** + * 修改密码 + */ + public String changePassword(String email, String oldPassword, String newPassword, String confirmPassword) { + User user = userMap.get(email); + if (user == null) { + return "用户不存在"; + } + + if (!user.getPassword().equals(oldPassword)) { + return "原密码错误"; + } + + if (!newPassword.equals(confirmPassword)) { + return "两次输入的新密码不一致"; + } + + if (!isValidPassword(newPassword)) { + return "新密码必须为6-10位,且包含大小写字母和数字"; + } + + user.setPassword(newPassword); + saveUsersToFile(); // 实时保存用户信息 + return "密码修改成功"; + } + + /** + * 设置用户类型 + */ + public void setUserType(String email, String userType) { + User user = userMap.get(email); + if (user != null) { + user.setUserType(userType); + saveUsersToFile(); // 实时保存用户信息 + } + } + + /** + * 获取验证码(用于显示给用户) + */ + public String getVerificationCode(String email) { + return verificationCodes.get(email); + } + + /** + * 检查邮箱是否已注册 + */ + public boolean isEmailRegistered(String email) { + return userMap.containsKey(email); + } + + private String generateVerificationCode() { + return String.format("%06d", random.nextInt(1000000)); + } + + private boolean isValidEmail(String email) { + return email.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"); + } + + private boolean isValidPassword(String password) { + // 密码6-10位,必须包含大小写字母和数字 + return password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{6,10}$"); + } + + private void loadPresetUsers() { + // 预设一些测试用户 + User testUser = new User("test@example.com", "testuser", "Test123", "小学"); + User studentUser = new User("student@example.com", "student", "Student123", "初中"); + User teacherUser = new User("teacher@example.com", "teacher", "Teacher123", "高中"); + + userMap.put(testUser.getEmail(), testUser); + userMap.put(studentUser.getEmail(), studentUser); + userMap.put(teacherUser.getEmail(), teacherUser); + + usernameMap.put(testUser.getUsername(), testUser); + usernameMap.put(studentUser.getUsername(), studentUser); + usernameMap.put(teacherUser.getUsername(), teacherUser); + + saveUsersToFile(); + } + + /** + * 从文件加载用户信息 + */ + @SuppressWarnings("unchecked") + private void loadUsersFromFile() { + File file = new File(REGISTRY_FILE_PATH); + if (!file.exists()) { + return; + } + + try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file))) { + Map loadedUsers = (Map) ois.readObject(); + for (User user : loadedUsers.values()) { + userMap.put(user.getEmail(), user); + usernameMap.put(user.getUsername(), user); + } + } catch (Exception e) { + System.err.println("加载用户信息失败: " + e.getMessage()); + } + } + + /** + * 保存用户信息到文件 + */ + private void saveUsersToFile() { + try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(REGISTRY_FILE_PATH))) { + oos.writeObject(userMap); + } catch (Exception e) { + System.err.println("保存用户信息失败: " + e.getMessage()); + } + } + + /** + * 设置用户类型 + */ + @Override + public String toString() { + return "UserManager{" + + "userMap.size=" + userMap.size() + + ", usernameMap.size=" + usernameMap.size() + + '}'; + } +} \ No newline at end of file diff --git a/src/frontend/ChangePasswordFrame.class b/src/frontend/ChangePasswordFrame.class new file mode 100644 index 0000000..0c26766 Binary files /dev/null and b/src/frontend/ChangePasswordFrame.class differ diff --git a/src/frontend/ChangePasswordFrame.java b/src/frontend/ChangePasswordFrame.java new file mode 100644 index 0000000..21963ff --- /dev/null +++ b/src/frontend/ChangePasswordFrame.java @@ -0,0 +1,137 @@ +package frontend; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import backend.UserManager; +import backend.User; + +public class ChangePasswordFrame extends JFrame { + private JPasswordField oldPasswordField; + private JPasswordField newPasswordField; + private JPasswordField confirmPasswordField; + private JButton changeButton, cancelButton; + private UserManager userManager; + private User currentUser; + private MainFrame mainFrame; + + public ChangePasswordFrame(MainFrame mainFrame, User user) { + this.mainFrame = mainFrame; + this.currentUser = user; + this.userManager = UserManager.getInstance(); + initializeUI(); + } + + private void initializeUI() { + setTitle("修改密码"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(400, 300); + setLocationRelativeTo(mainFrame); + setResizable(false); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("修改密码", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + titleLabel.setForeground(new Color(0, 120, 215)); + + // 表单面板 + JPanel formPanel = new JPanel(new GridLayout(4, 2, 10, 15)); + + JLabel oldPasswordLabel = new JLabel("原密码:"); + oldPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + oldPasswordField = new JPasswordField(); + + JLabel newPasswordLabel = new JLabel("新密码:"); + newPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + newPasswordField = new JPasswordField(); + + JLabel confirmPasswordLabel = new JLabel("确认新密码:"); + confirmPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + confirmPasswordField = new JPasswordField(); + + formPanel.add(oldPasswordLabel); + formPanel.add(oldPasswordField); + formPanel.add(newPasswordLabel); + formPanel.add(newPasswordField); + formPanel.add(confirmPasswordLabel); + formPanel.add(confirmPasswordField); + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + changeButton = new JButton("确认修改"); + cancelButton = new JButton("取消"); + + setupButtonStyle(changeButton); + setupButtonStyle(cancelButton); + + buttonPanel.add(changeButton); + buttonPanel.add(cancelButton); + + // 添加组件到主面板 + mainPanel.add(titleLabel, BorderLayout.NORTH); + mainPanel.add(formPanel, BorderLayout.CENTER); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // 添加事件监听器 + setupEventListeners(); + } + + private void setupButtonStyle(JButton button) { + button.setFont(new Font("微软雅黑", Font.BOLD, 14)); + button.setBackground(new Color(0, 120, 215)); + button.setForeground(Color.BLACK); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20)); + } + + private void setupEventListeners() { + changeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleChangePassword(); + } + }); + + cancelButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + + // 回车键确认修改 + confirmPasswordField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleChangePassword(); + } + }); + } + + private void handleChangePassword() { + String oldPassword = new String(oldPasswordField.getPassword()); + String newPassword = new String(newPasswordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + if (oldPassword.isEmpty() || newPassword.isEmpty() || confirmPassword.isEmpty()) { + JOptionPane.showMessageDialog(this, "请填写所有字段", "输入错误", JOptionPane.WARNING_MESSAGE); + return; + } + + String result = userManager.changePassword(currentUser.getEmail(), oldPassword, newPassword, confirmPassword); + + if (result.equals("密码修改成功")) { + JOptionPane.showMessageDialog(this, result, "修改成功", JOptionPane.INFORMATION_MESSAGE); + dispose(); + } else { + JOptionPane.showMessageDialog(this, result, "修改失败", JOptionPane.ERROR_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/frontend/LoginFrame.class b/src/frontend/LoginFrame.class new file mode 100644 index 0000000..1840cde Binary files /dev/null and b/src/frontend/LoginFrame.class differ diff --git a/src/frontend/LoginFrame.java b/src/frontend/LoginFrame.java new file mode 100644 index 0000000..9c96de7 --- /dev/null +++ b/src/frontend/LoginFrame.java @@ -0,0 +1,132 @@ +package frontend; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import backend.UserManager; +import backend.User; + +public class LoginFrame extends JFrame { + private JTextField emailField; + private JPasswordField passwordField; + private JButton loginButton, registerButton; + private UserManager userManager; + + public LoginFrame() { + userManager = UserManager.getInstance(); + initializeUI(); + } + + private void initializeUI() { + setTitle("中小学数学学习软件 - 登录"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(400, 300); + setLocationRelativeTo(null); + setResizable(false); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("中小学数学学习软件", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18)); + titleLabel.setForeground(new Color(0, 120, 215)); + + // 表单面板 + JPanel formPanel = new JPanel(new GridLayout(3, 2, 10, 10)); + + JLabel emailLabel = new JLabel("邮箱/用户名:"); + emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + emailField = new JTextField(); + + JLabel passwordLabel = new JLabel("密码:"); + passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + passwordField = new JPasswordField(); + + formPanel.add(emailLabel); + formPanel.add(emailField); + formPanel.add(passwordLabel); + formPanel.add(passwordField); + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + loginButton = new JButton("登录"); + registerButton = new JButton("注册"); + + // 设置按钮样式 + setupButtonStyle(loginButton); + setupButtonStyle(registerButton); + + buttonPanel.add(loginButton); + buttonPanel.add(registerButton); + + // 添加组件到主面板 + mainPanel.add(titleLabel, BorderLayout.NORTH); + mainPanel.add(formPanel, BorderLayout.CENTER); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // 添加事件监听器 + setupEventListeners(); + } + + private void setupButtonStyle(JButton button) { + button.setFont(new Font("微软雅黑", Font.BOLD, 14)); + button.setBackground(new Color(0, 120, 215)); + button.setForeground(Color.BLACK); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20)); + } + + private void setupEventListeners() { + loginButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleLogin(); + } + }); + + registerButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleRegister(); + } + }); + + // 回车键登录 + passwordField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleLogin(); + } + }); + } + + private void handleLogin() { + String identifier = emailField.getText().trim(); + String password = new String(passwordField.getPassword()); + + if (identifier.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入邮箱/用户名和密码", "输入错误", JOptionPane.WARNING_MESSAGE); + return; + } + + User user = userManager.authenticate(identifier, password); + if (user != null) { + // 登录成功,打开主界面 + dispose(); // 关闭登录窗口 + new MainFrame(user).setVisible(true); + } else { + JOptionPane.showMessageDialog(this, "邮箱/用户名或密码错误", "登录失败", JOptionPane.ERROR_MESSAGE); + } + } + + private void handleRegister() { + new RegistrationFrame(this).setVisible(true); + } + + +} \ No newline at end of file diff --git a/src/frontend/MainFrame.class b/src/frontend/MainFrame.class new file mode 100644 index 0000000..e365ab2 Binary files /dev/null and b/src/frontend/MainFrame.class differ diff --git a/src/frontend/MainFrame.java b/src/frontend/MainFrame.java new file mode 100644 index 0000000..d236bb3 --- /dev/null +++ b/src/frontend/MainFrame.java @@ -0,0 +1,157 @@ +package frontend; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import backend.User; +import backend.UserManager; + +public class MainFrame extends JFrame { + private User currentUser; + private JButton primarySchoolButton, middleSchoolButton, highSchoolButton; + private JButton changePasswordButton, logoutButton; + private UserManager userManager; + + public MainFrame(User user) { + this.currentUser = user; + this.userManager = UserManager.getInstance(); + initializeUI(); + } + + private void initializeUI() { + setTitle("中小学数学学习软件 - 主界面"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(500, 400); + setLocationRelativeTo(null); + setResizable(false); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 欢迎面板 + JPanel welcomePanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JLabel welcomeLabel = new JLabel("欢迎使用中小学数学学习软件", JLabel.CENTER); + welcomeLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + welcomeLabel.setForeground(new Color(0, 120, 215)); + welcomePanel.add(welcomeLabel); + + // 用户信息面板 + JPanel userInfoPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JLabel userInfoLabel = new JLabel("当前用户: " + currentUser.getUsername(), JLabel.CENTER); + userInfoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + userInfoPanel.add(userInfoLabel); + + // 学校选择面板 + JPanel schoolPanel = new JPanel(new GridLayout(3, 1, 15, 15)); + schoolPanel.setBorder(BorderFactory.createTitledBorder("请选择学校类型")); + + primarySchoolButton = new JButton("小学"); + middleSchoolButton = new JButton("初中"); + highSchoolButton = new JButton("高中"); + + setupSchoolButtonStyle(primarySchoolButton); + setupSchoolButtonStyle(middleSchoolButton); + setupSchoolButtonStyle(highSchoolButton); + + schoolPanel.add(primarySchoolButton); + schoolPanel.add(middleSchoolButton); + schoolPanel.add(highSchoolButton); + + // 功能按钮面板 + JPanel functionPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + changePasswordButton = new JButton("修改密码"); + logoutButton = new JButton("退出登录"); + + setupFunctionButtonStyle(changePasswordButton); + setupFunctionButtonStyle(logoutButton); + + functionPanel.add(changePasswordButton); + functionPanel.add(logoutButton); + + // 添加组件到主面板 + mainPanel.add(welcomePanel, BorderLayout.NORTH); + mainPanel.add(userInfoPanel, BorderLayout.CENTER); + mainPanel.add(schoolPanel, BorderLayout.CENTER); + mainPanel.add(functionPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // 添加事件监听器 + setupEventListeners(); + } + + private void setupSchoolButtonStyle(JButton button) { + button.setFont(new Font("微软雅黑", Font.BOLD, 16)); + button.setBackground(new Color(70, 130, 180)); + button.setForeground(Color.BLACK); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(15, 30, 15, 30)); + } + + private void setupFunctionButtonStyle(JButton button) { + button.setFont(new Font("微软雅黑", Font.PLAIN, 12)); + button.setBackground(new Color(169, 169, 169)); + button.setForeground(Color.BLACK); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(5, 15, 5, 15)); + } + + private void setupEventListeners() { + primarySchoolButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleSchoolSelection("小学"); + } + }); + + middleSchoolButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleSchoolSelection("初中"); + } + }); + + highSchoolButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleSchoolSelection("高中"); + } + }); + + changePasswordButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + new ChangePasswordFrame(MainFrame.this, currentUser).setVisible(true); + } + }); + + logoutButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleLogout(); + } + }); + } + + private void handleSchoolSelection(String schoolType) { + // 设置用户类型 + userManager.setUserType(currentUser.getEmail(), schoolType); + currentUser.setUserType(schoolType); + + // 打开题目数量输入界面 + new QuestionCountFrame(this, currentUser).setVisible(true); + } + + private void handleLogout() { + int result = JOptionPane.showConfirmDialog(this, + "确定要退出登录吗?", "确认退出", + JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (result == JOptionPane.YES_OPTION) { + dispose(); + new LoginFrame().setVisible(true); + } + } +} \ No newline at end of file diff --git a/src/frontend/QuestionCountFrame.class b/src/frontend/QuestionCountFrame.class new file mode 100644 index 0000000..0c7a3e0 Binary files /dev/null and b/src/frontend/QuestionCountFrame.class differ diff --git a/src/frontend/QuestionCountFrame.java b/src/frontend/QuestionCountFrame.java new file mode 100644 index 0000000..d611106 --- /dev/null +++ b/src/frontend/QuestionCountFrame.java @@ -0,0 +1,134 @@ +package frontend; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import backend.User; + +public class QuestionCountFrame extends JFrame { + private JTextField countField; + private JButton startButton, backButton; + private User currentUser; + private MainFrame mainFrame; + + public QuestionCountFrame(MainFrame mainFrame, User user) { + this.mainFrame = mainFrame; + this.currentUser = user; + initializeUI(); + } + + private void initializeUI() { + setTitle("题目数量设置 - " + currentUser.getUserType()); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(400, 250); + setLocationRelativeTo(mainFrame); + setResizable(false); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("请输入题目数量", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + titleLabel.setForeground(new Color(0, 120, 215)); + + // 信息标签 + JLabel infoLabel = new JLabel("当前选择: " + currentUser.getUserType(), JLabel.CENTER); + infoLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + + // 输入面板 + JPanel inputPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JLabel countLabel = new JLabel("题目数量:"); + countLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + countField = new JTextField(10); + countField.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + + inputPanel.add(countLabel); + inputPanel.add(countField); + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + startButton = new JButton("开始答题"); + backButton = new JButton("返回"); + + setupButtonStyle(startButton); + setupButtonStyle(backButton); + + buttonPanel.add(startButton); + buttonPanel.add(backButton); + + // 添加组件到主面板 + mainPanel.add(titleLabel, BorderLayout.NORTH); + mainPanel.add(infoLabel, BorderLayout.CENTER); + mainPanel.add(inputPanel, BorderLayout.CENTER); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // 添加事件监听器 + setupEventListeners(); + } + + private void setupButtonStyle(JButton button) { + button.setFont(new Font("微软雅黑", Font.BOLD, 14)); + button.setBackground(new Color(0, 120, 215)); + button.setForeground(Color.BLACK); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20)); + } + + private void setupEventListeners() { + startButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleStartQuiz(); + } + }); + + backButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + dispose(); + } + }); + + // 回车键开始答题 + countField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleStartQuiz(); + } + }); + } + + private void handleStartQuiz() { + String countText = countField.getText().trim(); + + if (countText.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入题目数量", "输入错误", JOptionPane.WARNING_MESSAGE); + return; + } + + try { + int count = Integer.parseInt(countText); + if (count <= 0) { + JOptionPane.showMessageDialog(this, "题目数量必须大于0", "输入错误", JOptionPane.WARNING_MESSAGE); + return; + } + + if (count > 50) { + JOptionPane.showMessageDialog(this, "题目数量不能超过50", "输入错误", JOptionPane.WARNING_MESSAGE); + return; + } + + // 创建答题界面 + dispose(); + new QuizFrame(mainFrame, currentUser, count).setVisible(true); + + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "请输入有效的数字", "输入错误", JOptionPane.WARNING_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/frontend/QuizFrame.class b/src/frontend/QuizFrame.class new file mode 100644 index 0000000..fd08b88 Binary files /dev/null and b/src/frontend/QuizFrame.class differ diff --git a/src/frontend/QuizFrame.java b/src/frontend/QuizFrame.java new file mode 100644 index 0000000..0e69455 --- /dev/null +++ b/src/frontend/QuizFrame.java @@ -0,0 +1,271 @@ +package frontend; + +import backend.MathCalculator; +import backend.QuestionGenerator; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; +import backend.User; +import backend.Question; +import backend.PrimarySchoolGenerator; +import backend.MiddleSchoolGenerator; +import backend.HighSchoolGenerator; + +public class QuizFrame extends JFrame { + private User currentUser; + private MainFrame mainFrame; + private int totalQuestions; + private int currentQuestionIndex; + private List questions; + private List userAnswers; + + // UI组件 + private JLabel questionNumberLabel; + private JTextArea questionTextArea; + private JRadioButton[] optionButtons; + private ButtonGroup optionGroup; + private JButton nextButton, submitButton; + private JPanel optionsPanel; + + public QuizFrame(MainFrame mainFrame, User user, int totalQuestions) { + this.mainFrame = mainFrame; + this.currentUser = user; + this.totalQuestions = totalQuestions; + this.currentQuestionIndex = 0; + this.questions = generateQuestions(); + this.userAnswers = new ArrayList<>(); + + initializeUI(); + showQuestion(0); + } + + private void initializeUI() { + setTitle("数学答题 - " + currentUser.getUserType()); + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + setSize(600, 500); + setLocationRelativeTo(mainFrame); + setResizable(false); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 题目编号标签 + questionNumberLabel = new JLabel("", JLabel.CENTER); + questionNumberLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + questionNumberLabel.setForeground(new Color(0, 120, 215)); + + // 题目文本区域 + questionTextArea = new JTextArea(); + questionTextArea.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + questionTextArea.setLineWrap(true); + questionTextArea.setWrapStyleWord(true); + questionTextArea.setEditable(false); + questionTextArea.setFocusable(false); // 设置为不可获得焦点,移除光标 + questionTextArea.setBackground(UIManager.getColor("Panel.background")); + questionTextArea.setBorder(null); // 移除边框 + + // 选项面板 + optionsPanel = new JPanel(new GridLayout(4, 1, 10, 10)); + optionButtons = new JRadioButton[4]; + optionGroup = new ButtonGroup(); + + for (int i = 0; i < 4; i++) { + optionButtons[i] = new JRadioButton(); + optionButtons[i].setFont(new Font("微软雅黑", Font.PLAIN, 14)); + optionGroup.add(optionButtons[i]); + optionsPanel.add(optionButtons[i]); + } + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + nextButton = new JButton("下一题"); + submitButton = new JButton("提交答案"); + submitButton.setVisible(false); + + setupButtonStyle(nextButton); + setupButtonStyle(submitButton); + + buttonPanel.add(nextButton); + buttonPanel.add(submitButton); + + // 添加组件到主面板 + mainPanel.add(questionNumberLabel, BorderLayout.NORTH); + + // 创建中间面板来容纳题目文本和选项 + JPanel centerPanel = new JPanel(new BorderLayout(10, 10)); + // 创建JScrollPane并设置无边框 + JScrollPane scrollPane = new JScrollPane(questionTextArea); + scrollPane.setBorder(null); // 移除滚动面板的边框 + centerPanel.add(scrollPane, BorderLayout.NORTH); + centerPanel.add(optionsPanel, BorderLayout.CENTER); + + mainPanel.add(centerPanel, BorderLayout.CENTER); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // 添加事件监听器 + setupEventListeners(); + } + + private void setupButtonStyle(JButton button) { + button.setFont(new Font("微软雅黑", Font.BOLD, 14)); + button.setBackground(new Color(0, 120, 215)); + button.setForeground(Color.BLACK); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20)); + } + + private void setupEventListeners() { + nextButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleNextQuestion(); + } + }); + + submitButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleSubmitQuiz(); + } + }); + } + + private void showQuestion(int index) { + if (index >= questions.size()) { + return; + } + + Question question = questions.get(index); + //System.out.println(question.getQuestionText()); + + // 更新题目编号 + questionNumberLabel.setText("第 " + (index + 1) + " 题 / 共 " + totalQuestions + " 题"); + + // 更新题目文本 + questionTextArea.setText(question.getQuestionText()); + + // 更新选项 + String[] options = question.getOptions(); + for (int i = 0; i < 4; i++) { + if (i < options.length) { + optionButtons[i].setText(options[i]); + optionButtons[i].setVisible(true); + } else { + optionButtons[i].setVisible(false); + } + } + + // 清除选择 + optionGroup.clearSelection(); + + // 更新按钮状态 + if (index == totalQuestions - 1) { + nextButton.setVisible(false); + submitButton.setVisible(true); + } else { + nextButton.setVisible(true); + submitButton.setVisible(false); + } + } + + private void handleNextQuestion() { + // 获取当前选择的答案 + String selectedAnswer = getSelectedAnswer(); + if (selectedAnswer == null) { + JOptionPane.showMessageDialog(this, "请选择一个答案", "提示", JOptionPane.WARNING_MESSAGE); + return; + } + + // 保存答案 + userAnswers.add(selectedAnswer); + + // 显示下一题 + currentQuestionIndex++; + showQuestion(currentQuestionIndex); + } + + private void handleSubmitQuiz() { + // 获取最后一题的答案 + String selectedAnswer = getSelectedAnswer(); + if (selectedAnswer == null) { + JOptionPane.showMessageDialog(this, "请选择一个答案", "提示", JOptionPane.WARNING_MESSAGE); + return; + } + + // 保存最后一题的答案 + userAnswers.add(selectedAnswer); + + // 计算分数 + int correctCount = 0; + for (int i = 0; i < questions.size(); i++) { + if (i < userAnswers.size() && questions.get(i).isCorrect(userAnswers.get(i))) { + correctCount++; + } + } + + double score = (double) correctCount / totalQuestions * 100; + + // 显示分数界面 + dispose(); + new ScoreFrame(mainFrame, currentUser, score, totalQuestions, correctCount).setVisible(true); + } + + private String getSelectedAnswer() { + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isSelected()) { + return optionButtons[i].getText(); + } + } + return null; + } + + private List generateQuestions() { + List questionList = new ArrayList<>(); + QuestionGenerator generator = createQuestionGenerator(); + + for (int i = 0; i < totalQuestions; i++) { + // 生成纯数学表达式 + String expression = generator.generateQuestion(); + + // 获取当前级别的题目描述 + String description = generator.getQuestionDescription(); + + // 组合题目描述和数学表达式作为完整题目 + String questionText = description + expression; + + // 计算题目的答案(使用纯数学表达式) + String correctAnswer = calculateAnswer(expression); + String[] options = generator.generateOptions(correctAnswer); + + // 创建Question对象,使用包含描述的完整题目和正确答案 + Question question = new Question(questionText, options, correctAnswer); + questionList.add(question); + } + + return questionList; + } + + private QuestionGenerator createQuestionGenerator() { + switch (currentUser.getUserType()) { + case "小学": + return new PrimarySchoolGenerator(); + case "初中": + return new MiddleSchoolGenerator(); + case "高中": + return new HighSchoolGenerator(); + default: + return new PrimarySchoolGenerator(); + } + } + + private String calculateAnswer(String questionText) { + // 使用MathCalculator计算数学表达式的结果 + return MathCalculator.calculateSimple(questionText); + } +} \ No newline at end of file diff --git a/src/frontend/RegistrationFrame.class b/src/frontend/RegistrationFrame.class new file mode 100644 index 0000000..b41de4f Binary files /dev/null and b/src/frontend/RegistrationFrame.class differ diff --git a/src/frontend/RegistrationFrame.java b/src/frontend/RegistrationFrame.java new file mode 100644 index 0000000..7f2cdde --- /dev/null +++ b/src/frontend/RegistrationFrame.java @@ -0,0 +1,194 @@ +package frontend; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import backend.UserManager; + +public class RegistrationFrame extends JFrame { + private JTextField emailField; + private JTextField usernameField; + private JTextField verificationCodeField; + private JPasswordField passwordField; + private JPasswordField confirmPasswordField; + private JButton sendCodeButton, registerButton, backButton; + private UserManager userManager; + private LoginFrame loginFrame; + + public RegistrationFrame(LoginFrame loginFrame) { + this.loginFrame = loginFrame; + this.userManager = UserManager.getInstance(); + initializeUI(); + } + + private void initializeUI() { + setTitle("用户注册"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(450, 400); + setLocationRelativeTo(loginFrame); + setResizable(false); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 18)); + titleLabel.setForeground(new Color(0, 120, 215)); + + // 表单面板 + JPanel formPanel = new JPanel(new GridLayout(6, 2, 10, 15)); + + JLabel emailLabel = new JLabel("邮箱:"); + emailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + emailField = new JTextField(); + + JLabel usernameLabel = new JLabel("用户名:"); + usernameLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + usernameField = new JTextField(); + + JLabel codeLabel = new JLabel("验证码:"); + codeLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + verificationCodeField = new JTextField(); + + JLabel passwordLabel = new JLabel("密码:"); + passwordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + passwordField = new JPasswordField(); + + JLabel confirmPasswordLabel = new JLabel("确认密码:"); + confirmPasswordLabel.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + confirmPasswordField = new JPasswordField(); + + // 发送验证码按钮 + sendCodeButton = new JButton("发送验证码"); + setupButtonStyle(sendCodeButton); + + formPanel.add(emailLabel); + formPanel.add(emailField); + formPanel.add(usernameLabel); + formPanel.add(usernameField); + formPanel.add(codeLabel); + formPanel.add(verificationCodeField); + formPanel.add(new JLabel()); // 空标签占位 + formPanel.add(sendCodeButton); + formPanel.add(passwordLabel); + formPanel.add(passwordField); + formPanel.add(confirmPasswordLabel); + formPanel.add(confirmPasswordField); + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + registerButton = new JButton("注册"); + backButton = new JButton("返回登录"); + + setupButtonStyle(registerButton); + setupButtonStyle(backButton); + + buttonPanel.add(registerButton); + buttonPanel.add(backButton); + + // 添加组件到主面板 + mainPanel.add(titleLabel, BorderLayout.NORTH); + mainPanel.add(formPanel, BorderLayout.CENTER); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // 添加事件监听器 + setupEventListeners(); + } + + private void setupButtonStyle(JButton button) { + button.setFont(new Font("微软雅黑", Font.BOLD, 14)); + button.setBackground(new Color(0, 120, 215)); + button.setForeground(Color.BLACK); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20)); + } + + private void setupEventListeners() { + sendCodeButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleSendVerificationCode(); + } + }); + + registerButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleRegister(); + } + }); + + backButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + dispose(); + loginFrame.setVisible(true); + } + }); + + // 回车键注册 + confirmPasswordField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + handleRegister(); + } + }); + } + + private void handleSendVerificationCode() { + String email = emailField.getText().trim(); + String username = usernameField.getText().trim(); + + if (email.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入邮箱地址", "输入错误", JOptionPane.WARNING_MESSAGE); + return; + } + + if (username.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入用户名", "输入错误", JOptionPane.WARNING_MESSAGE); + return; + } + + // 检查用户名是否已被注册 + if (userManager.isUsernameRegistered(username)) { + JOptionPane.showMessageDialog(this, "该用户名已被注册", "注册失败", JOptionPane.ERROR_MESSAGE); + return; + } + + // 调用UserManager发送验证码 + String result = userManager.sendVerificationCode(email); + + // 显示发送结果 + JOptionPane.showMessageDialog(this, result, "验证码发送", JOptionPane.INFORMATION_MESSAGE); + } + + private void handleRegister() { + String email = emailField.getText().trim(); + String username = usernameField.getText().trim(); + String verificationCode = verificationCodeField.getText().trim(); + String password = new String(passwordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + + // 基本的非空检查 + if (email.isEmpty() || username.isEmpty() || verificationCode.isEmpty() || password.isEmpty() || confirmPassword.isEmpty()) { + JOptionPane.showMessageDialog(this, "请填写所有字段", "输入错误", JOptionPane.WARNING_MESSAGE); + return; + } + + // 用户名的具体验证逻辑已在UserManager.registerUser方法中实现(在验证码验证之后) + String result = userManager.registerUser(email, username, verificationCode, password, confirmPassword); + + if (result.equals("注册成功")) { + JOptionPane.showMessageDialog(this, result, "注册成功", JOptionPane.INFORMATION_MESSAGE); + dispose(); + loginFrame.setVisible(true); + } else { + JOptionPane.showMessageDialog(this, result, "注册失败", JOptionPane.ERROR_MESSAGE); + } + } +} \ No newline at end of file diff --git a/src/frontend/ScoreFrame.class b/src/frontend/ScoreFrame.class new file mode 100644 index 0000000..f94d5df Binary files /dev/null and b/src/frontend/ScoreFrame.class differ diff --git a/src/frontend/ScoreFrame.java b/src/frontend/ScoreFrame.java new file mode 100644 index 0000000..39a06b4 --- /dev/null +++ b/src/frontend/ScoreFrame.java @@ -0,0 +1,155 @@ +package frontend; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import backend.User; + +public class ScoreFrame extends JFrame { + private User currentUser; + private MainFrame mainFrame; + private double score; + private int totalQuestions; + private int correctCount; + + public ScoreFrame(MainFrame mainFrame, User user, double score, int totalQuestions, int correctCount) { + this.mainFrame = mainFrame; + this.currentUser = user; + this.score = score; + this.totalQuestions = totalQuestions; + this.correctCount = correctCount; + initializeUI(); + } + + private void initializeUI() { + setTitle("答题结果"); + setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + setSize(500, 400); + setLocationRelativeTo(mainFrame); + setResizable(false); + + // 创建主面板 + JPanel mainPanel = new JPanel(new BorderLayout(10, 10)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 标题 + JLabel titleLabel = new JLabel("答题完成", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + titleLabel.setForeground(new Color(0, 120, 215)); + + // 分数显示面板 + JPanel scorePanel = new JPanel(new GridLayout(4, 1, 10, 10)); + scorePanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + + // 分数显示 + JLabel scoreLabel = new JLabel(String.format("得分: %.1f分", score), JLabel.CENTER); + scoreLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + scoreLabel.setForeground(getScoreColor(score)); + + // 详细信息 + JLabel detailLabel = new JLabel( + String.format("答对 %d 题,共 %d 题", correctCount, totalQuestions), + JLabel.CENTER + ); + detailLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + + // 百分比 + JLabel percentageLabel = new JLabel( + String.format("正确率: %.1f%%", score), + JLabel.CENTER + ); + percentageLabel.setFont(new Font("微软雅黑", Font.PLAIN, 16)); + + // 评价 + JLabel commentLabel = new JLabel(getScoreComment(score), JLabel.CENTER); + commentLabel.setFont(new Font("微软雅黑", Font.ITALIC, 14)); + commentLabel.setForeground(Color.GRAY); + + scorePanel.add(scoreLabel); + scorePanel.add(detailLabel); + scorePanel.add(percentageLabel); + scorePanel.add(commentLabel); + + // 按钮面板 + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + JButton continueButton = new JButton("继续做题"); + JButton exitButton = new JButton("退出"); + + setupButtonStyle(continueButton); + setupButtonStyle(exitButton); + + buttonPanel.add(continueButton); + buttonPanel.add(exitButton); + + // 添加组件到主面板 + mainPanel.add(titleLabel, BorderLayout.NORTH); + mainPanel.add(scorePanel, BorderLayout.CENTER); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + add(mainPanel); + + // 添加事件监听器 + setupEventListeners(continueButton, exitButton); + } + + private void setupButtonStyle(JButton button) { + button.setFont(new Font("微软雅黑", Font.BOLD, 14)); + button.setBackground(new Color(0, 120, 215)); + button.setForeground(Color.BLACK); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20)); + } + + private void setupEventListeners(JButton continueButton, JButton exitButton) { + continueButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + // 返回主界面,用户可以重新选择学校类型 + dispose(); + mainFrame.setVisible(true); + } + }); + + exitButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + int result = JOptionPane.showConfirmDialog( + ScoreFrame.this, + "确定要退出程序吗?", + "确认退出", + JOptionPane.YES_NO_OPTION, + JOptionPane.QUESTION_MESSAGE + ); + + if (result == JOptionPane.YES_OPTION) { + System.exit(0); + } + } + }); + } + + private Color getScoreColor(double score) { + if (score >= 90) { + return new Color(0, 150, 0); // 绿色 - 优秀 + } else if (score >= 70) { + return new Color(255, 165, 0); // 橙色 - 良好 + } else if (score >= 60) { + return new Color(255, 215, 0); // 金色 - 及格 + } else { + return new Color(220, 20, 60); // 红色 - 不及格 + } + } + + private String getScoreComment(double score) { + if (score >= 90) { + return "太棒了!继续保持!"; + } else if (score >= 70) { + return "做得不错,继续努力!"; + } else if (score >= 60) { + return "及格了,再加把劲!"; + } else { + return "需要多加练习哦!"; + } + } +} \ No newline at end of file diff --git a/src/registry.dat b/src/registry.dat new file mode 100644 index 0000000..d7abc22 Binary files /dev/null and b/src/registry.dat differ