diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..6983ea1 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,59 @@ +# 带UI的数学学习软件 + +## 项目结构 +Math_learning +|——lib  依赖(jar包) +  |——javax.mail-1.6.2.jar  发送邮件相关 +  |——activation-1.1.1.jar  javax.mail所需依赖 +|——src  源代码目录 +  |——Base  基础类包 +    |——Email_settings.java  邮件发送服务配置 +    |——Exam_result.java  考试结果类 +    |——Question.java  题目类 +    |——User.java  用户类 +  |——Generator  题目生成器包 +    |——G_ques.java  生成器接口 +    |——Pri_g_ques.java  小学题目生成器 +    |——Jun_g_ques.java  初中题目生成器 +    |——Sen_g_ques.java  高中题目生成器 +    |——Generate_paper.java  生成试卷 +  |——Send_Email  邮件发送包 +    |——Deal_i_code.java  管理验证码以及验证码校验 +    |——Generate_i_code.java  产生验证码 +    |——Send_email.java  发送邮件类 +  |——Service  服务类包,供前端调用 +    |——User_service.java  用户服务类,包括注册、登录、修改密码等 +    |——Exam_service.java  考试服务类 +    |——Deal_file.java  可选功能:保存试卷为文件 +  |——View  前端类包 +    |——MainFrame.java  前端界面 +  |——Math_learning_app.java  主类 +|——doc  说明文档 +  |——README.md + +## 邮件发送说明 +发件人信息通过配置文件存储,首次运行时会创建config/email.properties配置文件,并使用默认配置(乔毅凡的qq邮箱) +修改配置文件可更改发件人配置,如果要使用自己的邮箱,需要去设置中开启特定配置。 +以QQ邮箱为例子: +1. 需要去自己的邮箱设置中找到POP3/IMAP/SMTP/Exchange/CardDAV 服务,选择开启,生成授权码。 +2. 查看QQ邮箱配置方法中的说明,看到“发送邮件服务器: smtp.qq.com,使用SSL,端口号465或587”,这就是发件服务器和使用的端口,以及使用SSL。 +3. 将上述信息修改到配置文件中。(发件服务器、端口、SSL在默认配置中已经为QQ邮箱的配置,修改邮箱和授权码即可) +4. 其他邮箱配置方法具体见官方说明。 + +## 用户信息 +用户信息保存在本地,用户/用户信息.txt(运行时会产生),密码已加密。 + +## 运行环境 +可执行文件:Math_Learning.jar(依赖已经打包) +本软件使用java 23编译,请使用java 17及以上版本运行  UTF-8编码   + +--- +软件2302 +刘星宇 202326010226 +毛承上 202326010227 +乔毅凡 202326010228 + + + + + diff --git a/lib/activation-1.1.1.jar b/lib/activation-1.1.1.jar new file mode 100644 index 0000000..1b703ab Binary files /dev/null and b/lib/activation-1.1.1.jar differ diff --git a/lib/javax.mail-1.6.2.jar b/lib/javax.mail-1.6.2.jar new file mode 100644 index 0000000..0cd0528 Binary files /dev/null and b/lib/javax.mail-1.6.2.jar differ diff --git a/src/Base/Email_settings.java b/src/Base/Email_settings.java new file mode 100644 index 0000000..ea57b1f --- /dev/null +++ b/src/Base/Email_settings.java @@ -0,0 +1,86 @@ +package Base; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Properties; + +public class Email_settings { + private static final String CONFIG_FILE = "config/email.properties"; + private static Properties properties; + private static long lastModified = 0; + + static { + loadConfig(); + } + + private static void loadConfig() { + properties = new Properties(); + setDefaultProperties(); + File configFile = new File(CONFIG_FILE); + if (configFile.exists()) { + try (FileInputStream input = new FileInputStream(configFile)) { + properties.load(input); + lastModified = configFile.lastModified(); + } catch (IOException e) { + System.err.println("加载邮箱配置文件失败,使用默认配置: " + e.getMessage()); + } + } else { + createDefaultConfigFile(); + } + } + + private static void setDefaultProperties() { + properties.setProperty("smtp.host", "smtp.qq.com"); + properties.setProperty("smtp.port", "587"); + properties.setProperty("from.email", "835981889@qq.com"); + properties.setProperty("email.password", "fpqfprqznbvdbcdf"); + properties.setProperty("ssl.enable", "true"); + } + + private static void createDefaultConfigFile() { + File configDir = new File("config"); + if (!configDir.exists()) { + configDir.mkdirs(); + } + + try (FileOutputStream output = new FileOutputStream(CONFIG_FILE)) { + properties.store(output, "Settings"); + } catch (IOException e) { + System.err.println("创建默认配置文件失败: " + e.getMessage()); + } + } + + private static void checkForUpdates() { + File configFile = new File(CONFIG_FILE); + if (configFile.exists() && configFile.lastModified() > lastModified) { + loadConfig(); + } + } + + public static String getSmtpHost() { + checkForUpdates(); + return properties.getProperty("smtp.host"); + } + + public static String getSmtpPort() { + checkForUpdates(); + return properties.getProperty("smtp.port"); + } + + public static String getFromEmail() { + checkForUpdates(); + return properties.getProperty("from.email"); + } + + public static String getEmailPassword() { + checkForUpdates(); + return properties.getProperty("email.password"); + } + + public static boolean isSslEnable() { + checkForUpdates(); + return Boolean.parseBoolean(properties.getProperty("ssl.enable")); + } +} diff --git a/src/Base/Exam_result.java b/src/Base/Exam_result.java new file mode 100644 index 0000000..1b117aa --- /dev/null +++ b/src/Base/Exam_result.java @@ -0,0 +1,41 @@ +package Base; + +import java.util.Date; +import java.util.List; + +public class Exam_result { + private String exam_type; + private int total_questions; + private int correct_answers; + private double score; + private long duration; // 考试时长(秒) + private List wrong_questions; // 错题索引 + + public Exam_result( String examType, int total, + int correct, double score, long duration, + List wrong) { + this.exam_type = examType; + this.total_questions = total; + this.correct_answers = correct; + this.score = Math.round(score * 100.0) / 100.0; + this.duration = duration; + this.wrong_questions = wrong; + } + + public String getExamType() { return exam_type; } + public int getTotalQuestions() { return total_questions; } + public int getCorrectAnswers() { return correct_answers; } + public double getScore() { return score; } + public long getDuration() { return duration; } + public List getWrongQuestions() { return wrong_questions; } + + public String get_time() { + long minutes = duration / 60; + long seconds = duration % 60; + return String.format("%d分%d秒", minutes, seconds); + } + + public String getCorrectRate() { + return String.format("%.1f%%", (double) correct_answers / total_questions * 100); + } +} diff --git a/src/Base/Question.java b/src/Base/Question.java new file mode 100644 index 0000000..71a0fe9 --- /dev/null +++ b/src/Base/Question.java @@ -0,0 +1,267 @@ +package Base; + +import java.util.HashMap; +import java.util.Random; +import java.util.Stack; + +public class Question { + private int number; + private String content; + private String type; + private String answer; + private String[] options; + private final Random ra= new Random(); + public static final HashMap trigValues = new HashMap<>(); + + static { + // 15° + trigValues.put("sin(15°)", "√2*(√3-1)/4"); + trigValues.put("cos(15°)", "√2*(√3+1)/4"); + trigValues.put("tan(15°)", "(2-√3)"); + trigValues.put("sin²(15°)", "(2-√3)/4"); + trigValues.put("cos²(15°)", "(2+√3)/4"); + trigValues.put("tan²(15°)", "(7-4*√3)"); + + // 22.5° + trigValues.put("sin(22.5°)", "√0.59/2"); //√(2-√2)/2 + trigValues.put("cos(22.5°)", "√3.41/2"); //√(2+√2)/2 + trigValues.put("tan(22.5°)", "(√2-1)"); + trigValues.put("sin²(22.5°)", "(2-√2)/4"); + trigValues.put("cos²(22.5°)", "(2+√2)/4"); + trigValues.put("tan²(22.5°)", "(3-2*√2)"); + + // 75° + trigValues.put("sin(75°)", "√2*(√3+1)/4"); + trigValues.put("cos(75°)", "√2*(√3-1)/4"); + trigValues.put("tan(75°)", "(2+√3)"); + trigValues.put("sin²(75°)", "(2+√3)/4"); + trigValues.put("cos²(75°)", "(2-√3)/4"); + trigValues.put("tan²(75°)", "(7+4*√3)"); + + // 105° + trigValues.put("sin(105°)", "√2*(√3+1)/4"); + trigValues.put("cos(105°)", "√2*(1-√3)/4"); + trigValues.put("tan(105°)", "(0-(2+√3))"); + trigValues.put("sin²(105°)", "(2+√3)/4"); + trigValues.put("cos²(105°)", "(2-√3)/4"); + trigValues.put("tan²(105°)", "(7+4*√3)"); + + // 120° + trigValues.put("sin(120°)", "√3/2"); + trigValues.put("cos(120°)", "(0-1/2)"); + trigValues.put("tan(120°)", "(0-√3)"); + trigValues.put("sin²(120°)", "3/4"); + trigValues.put("cos²(120°)", "1/4"); + trigValues.put("tan²(120°)", "3"); + + // 135° + trigValues.put("sin(135°)", "√2/2"); + trigValues.put("cos(135°)", "(0-√2/2)"); + trigValues.put("tan(135°)", "(0-1)"); + trigValues.put("sin²(135°)", "1/2"); + trigValues.put("cos²(135°)", "1/2"); + trigValues.put("tan²(135°)", "1"); + + // 150° + trigValues.put("sin(150°)", "1/2"); + trigValues.put("cos(150°)", "(0-√3/2)"); + trigValues.put("tan(150°)", "(0-√3/3)"); + trigValues.put("sin²(150°)", "1/4"); + trigValues.put("cos²(150°)", "3/4"); + trigValues.put("tan²(150°)", "1/3"); + + // 210° + trigValues.put("sin(210°)", "(0-1/2)"); + trigValues.put("cos(210°)", "(0-√3/2)"); + trigValues.put("tan(210°)", "√3/3"); + trigValues.put("sin²(210°)", "1/4"); + trigValues.put("cos²(210°)", "3/4"); + trigValues.put("tan²(210°)", "1/3"); + + // 300° + trigValues.put("sin(300°)", "(0-√3/2)"); + trigValues.put("cos(300°)", "1/2"); + trigValues.put("tan(300°)", "(0-√3)"); + trigValues.put("sin²(300°)", "3/4"); + trigValues.put("cos²(300°)", "1/4"); + trigValues.put("tan²(300°)", "3"); + } + + public Question(int number, String content, String type) { + this.number = number; + this.content = content; + this.type = type; + options=new String[4]; + } + + public int getNumber() { return number; } + public String getContent() { return content; } + public String getType() { return type; } + public String getAnswer() {return answer;} + public String getOptions(int i) {return options[i];} + + @Override + public String toString(){ + return this.number+". "+this.content+" = ?"; + } + + public void set_options(){ + answer=calculate(this.content); + int which=ra.nextInt(4); + options[which]=answer; + for (int i=0;i<4;i++) { + if (i != which) { + if (this.type.equals("高中")) { + options[i] = answer.equals("不可解") ? String.format("%.2f", Math.sqrt(3) * ra.nextInt(10) + ra.nextDouble(1)) : + String.format("%.2f", Double.parseDouble(answer) + Math.sqrt(2) * ra.nextInt(5)); + } else { + if (Double.parseDouble(answer) ==Math.floor(Double.parseDouble(answer)) ) { + options[i] = answer.equals("不可解") ? String.valueOf((int) ra.nextInt(1000) + ra.nextInt(10)) : + String.valueOf((int) Double.parseDouble(answer) + ra.nextInt(10)); + } else { + options[i] = answer.equals("不可解") ? String.format("%.2f", ra.nextInt(1000) + ra.nextDouble(10)) : + String.format("%.2f", Double.parseDouble(answer) + ra.nextDouble(10)); + } + } + for (int j = 0; j < i; j++) { + if (options[j].equals(options[i])) { + i--; + break; + } + } + if (options[i].equals(answer)) { + i--; + } + } + } + } + + public String calculate(String question){ + try { + String expr = question.replaceAll(" ", ""); + double result; + if (!type.equals("高中")) { + result = deal_calculate(expr); + if (Double.isNaN(result) || Double.isInfinite(result)) { + return "不可解"; + } + if (result == Math.floor(result)) { + return String.valueOf((int) result); + } else { + return String.format("%.2f", result); + } + } + else{ + return deal_sen_calculate(expr); + } + } catch (Exception e) { + return "不可解"; + } + } + + private double deal_calculate(String expr){ + Stack numbers = new Stack<>(); + Stack operators = new Stack<>(); + expr=expr.replace("²", "^2"); + for (int i = 0; i < expr.length(); i++) { + char c = expr.charAt(i); + if (Character.isDigit(c)) { + StringBuilder temp = new StringBuilder(); + while (i < expr.length() && (Character.isDigit(expr.charAt(i)) || expr.charAt(i) == '.')) { + temp.append(expr.charAt(i++)); + } + i--; + numbers.push(Double.parseDouble(temp.toString())); + } else if (c == '(') { + operators.push(c); + } else if (c == ')') { + while (operators.peek() != '(') { + numbers.push(Deal_Operator(operators.pop(), numbers.pop(), numbers.pop())); + } + operators.pop(); + } else if (isOperator(c)) { + while (!operators.isEmpty() && hasPrecedence(c, operators.peek())) { + numbers.push(Deal_Operator(operators.pop(), numbers.pop(), numbers.pop())); + } + operators.push(c); + } + else if (c == '√'){ + i++; + StringBuilder temp = new StringBuilder(); + while (i < expr.length() && (Character.isDigit(expr.charAt(i)) || expr.charAt(i) == '.')) { + temp.append(expr.charAt(i++)); + } + i--; + numbers.push(Math.sqrt(Double.parseDouble(temp.toString()))); + } + } + while (!operators.isEmpty()) { + numbers.push(Deal_Operator(operators.pop(), numbers.pop(), numbers.pop())); + } + return numbers.pop(); + } + + private String deal_sen_calculate(String m_expr){ + try { + String expr = m_expr.replaceAll(" ", ""); + StringBuilder result = new StringBuilder(); + + int i = 0; + while (i < expr.length()) { + char c = expr.charAt(i); + if (c == 's' || c == 'c' || c == 't') { + StringBuilder trigFunc = new StringBuilder(); + while (i < expr.length() && expr.charAt(i) != ')') { + trigFunc.append(expr.charAt(i++)); + } + trigFunc.append(')'); // 添加右括号 + String trigKey = trigFunc.toString(); + String trigValue = trigValues.get(trigKey); + if (trigValue != null) { + result.append(trigValue); + } + } else if (isOperator(c) || c == '(' || c == ')') { + result.append(c); + } + i++; + } + return String.format("%.2f",deal_calculate(Deal_Expression(result.toString()))); + } catch (Exception e) { + return "不可解"; + } + } + + private double Deal_Operator(char operator, double b, double a) { + switch (operator) { + case '+': return a + b; + case '-': return a - b; + case '*': return a * b; + case '/': { + if (b == 0) throw new ArithmeticException("除零错误"); + return a / b; + } + case '^': return Math.pow(a, b); + default: return 0; + } + } + + private boolean isOperator(char c) { + return c == '+' || c == '-' || c == '*' || c == '/' || c == '^'; + } + + //检验运算优先级,来决定先计算再压栈还是直接压栈 + private boolean hasPrecedence(char op1, char op2) { + if ( (op2 == '(' || op2 == ')') + || ((op1 == '*' || op1 == '/') && (op2 == '+' || op2 == '-')) + || (op1 == '^' && (op2 == '*' || op2 == '/' || op2 == '+' || op2 == '-'))) { + return false; + } + return true; + } + + private String Deal_Expression(String expr) { + String simplified = expr.replace("+-","-"); + simplified = simplified.replace("--","+"); + return simplified; + } +} diff --git a/src/Base/User.java b/src/Base/User.java new file mode 100644 index 0000000..08fe956 --- /dev/null +++ b/src/Base/User.java @@ -0,0 +1,55 @@ +package Base; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class User { + private String email; + private String id; + private String password; + + public User(String email) { + this.email = email; + } + + public String get_email() { return email; } + public String get_password() { return password; } + public String get_id() {return id;} + public void set_id(String id) {this.id=id;} + public void set_password(String password) { this.password = password; } + + public static boolean check_password(String password) { + if (password == null || password.length() < 6 || password.length() > 20) { + return false; + } + + boolean hasUpper = false; + boolean hasLower = false; + boolean hasDigit = false; + + for (char c : password.toCharArray()) { + if (Character.isUpperCase(c)) hasUpper = true; + if (Character.isLowerCase(c)) hasLower = true; + if (Character.isDigit(c)) hasDigit = true; + } + return hasUpper && hasLower && hasDigit; + } + + public static String hash_pwd(String password){ + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] hashBytes = digest.digest(password.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(); + for (byte b : hashBytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("无法创建SHA-256实例", e); + } + } + + public static boolean check_hash_pwd(String pwd,String hash_pwd){ + return hash_pwd(pwd).equals(hash_pwd); + } +} diff --git a/src/Generator/G_ques.java b/src/Generator/G_ques.java new file mode 100644 index 0000000..5a18448 --- /dev/null +++ b/src/Generator/G_ques.java @@ -0,0 +1,8 @@ +package Generator; + +public interface G_ques { + String g_question(); + String g_type(); + char g_operator(); + String add_brackets(StringBuilder s,int count); +} diff --git a/src/Generator/Generate_paper.java b/src/Generator/Generate_paper.java new file mode 100644 index 0000000..ebf5c6d --- /dev/null +++ b/src/Generator/Generate_paper.java @@ -0,0 +1,53 @@ +package Generator; + +import Base.Question; +import Service.Deal_file; + +import java.util.ArrayList; + +public class Generate_paper { + public static ArrayList g_paper(int num,String type,String id) { + ArrayList result = new ArrayList<>(); + G_ques generator; + switch (type){ + case "小学":{ + generator=new Pri_g_ques(); + break; + } + case "初中":{ + generator=new Jun_g_ques(); + break; + } + case "高中":{ + generator=new Sen_g_ques(); + break; + } + default:{ + generator=new Pri_g_ques(); + } + } + for (int i=0;i all,String ques){ + for (Question q:all){ + if (q.getContent().equals(ques)){ + return true; + } + } + return false; + } +} diff --git a/src/Generator/Jun_g_ques.java b/src/Generator/Jun_g_ques.java new file mode 100644 index 0000000..4eb84df --- /dev/null +++ b/src/Generator/Jun_g_ques.java @@ -0,0 +1,82 @@ +package Generator; + +import java.util.Random; + +public class Jun_g_ques implements G_ques{ + private Random ra= new Random(); + + @Override + public String g_question() { + int count=ra.nextInt(5)+1; + StringBuilder question = new StringBuilder(); + boolean flag=false; + for (int i=0;i0){ + question.append(" ").append(g_operator()).append(" "); + } + if (ra.nextDouble()<0.25) { + if (ra.nextBoolean()) { + question.append(ra.nextInt(30) + 1).append("²"); + } + else { + question.append("√").append(ra.nextInt(100) + 1); + } + flag=true; + } + else { + question.append(ra.nextInt(100) + 1); + } + } + if (!flag){ + if(count==1){ + if (ra.nextBoolean()){ + question.append("²"); + } + else { + question.insert(0,"√"); + } + } + else { + String[] parts = question.toString().split(" "); + int pos = ra.nextInt(count); + if (ra.nextBoolean()) { + parts[pos * 2] = parts[pos * 2] + "²"; + } else { + parts[pos * 2] = "√" + parts[pos * 2]; + } + question = new StringBuilder(String.join(" ", parts)); + } + } + return add_brackets(question,count); + } + + @Override + public String g_type(){ + return "初中"; + } + + @Override + public char g_operator(){ + char[] op={'+','-','*','/'}; + return op[ra.nextInt(op.length)]; + } + + @Override + public String add_brackets(StringBuilder s,int count){ + String res=s.toString(); + String[] parts=s.toString().split(" "); + if (ra.nextBoolean()&&parts.length!=1) { + int num=ra.nextInt(3)+1; + for (int i=0;i 0) { + question.append(" ").append(g_operator()).append(" "); + } + if (i!=count-1&&ra.nextDouble()<0.4){ + question.append("("); + count_bracket++; + } + question.append(ra.nextInt(100) + 1); + } + if (count_bracket!=0){ + question.append(")".repeat(Math.max(0, count_bracket))); + } + return question.toString(); + } + + @Override + public String g_type(){ + return "小学"; + } + + @Override + public char g_operator(){ + char[] op={'+','-','*','/'}; + return op[ra.nextInt(op.length)]; + } + + @Override + public String add_brackets(StringBuilder s,int count){ + return s.toString(); + } +} diff --git a/src/Generator/Sen_g_ques.java b/src/Generator/Sen_g_ques.java new file mode 100644 index 0000000..f4ca55f --- /dev/null +++ b/src/Generator/Sen_g_ques.java @@ -0,0 +1,64 @@ +package Generator; + +import java.util.Random; + +public class Sen_g_ques implements G_ques{ + private Random ra= new Random(); + + @Override + public String g_question() { + int count=ra.nextInt(5)+1; + StringBuilder question = new StringBuilder(); + for (int i=0;i 0) { + question.append(" ").append(g_operator()).append(" "); + } + if (ra.nextDouble()<0.3){ + question.append(g_trig()).append("²").append("(").append(g_angle()).append("°)"); + } + else { + question.append(g_trig()).append("(").append(g_angle()).append("°)"); + } + } + return add_brackets(question,count); + } + + @Override + public String g_type(){ + return "高中"; + } + + @Override + public char g_operator(){ + char[] op={'+','-','*','/'}; + return op[ra.nextInt(op.length)]; + } + + public String g_trig(){ + String[] trig={"sin","cos","tan"}; + return trig[ra.nextInt(trig.length)]; + } + + public String g_angle(){ + String[] angle={"15","22.5","75","105","120","135","150","210","300"}; + return angle[ra.nextInt(angle.length)]; + } + + public String add_brackets(StringBuilder s,int count){ + String res=s.toString(); + String[] parts=s.toString().split(" "); + if (ra.nextBoolean()&&parts.length!=1) { + int num=ra.nextInt(3)+1; + for (int i=0;i codeMap = new ConcurrentHashMap<>(); + + private static class I_Code { + String code; + long createTime; + private static final long EXPIRATION_TIME = 5 * 60 * 1000; + + I_Code(String code) { + this.code = code; + this.createTime = System.currentTimeMillis(); + } + + boolean isExpired() { + return System.currentTimeMillis() - createTime > EXPIRATION_TIME; + } + } + + public static String generate_code(String email) { + String code = Generate_i_code.generateCode(6); + codeMap.put(email, new I_Code(code)); + return code; + } + + public static boolean judge_code(String email, String inputCode) { + I_Code codeInfo = codeMap.get(email); + if (codeInfo == null) { + return false; + } + if (codeInfo.isExpired()) { + codeMap.remove(email); + return false; + } + boolean isValid = codeInfo.code.equals(inputCode); + if (isValid) { + codeMap.remove(email); + } + return isValid; + } + + public static void clean_codes() { + codeMap.entrySet().removeIf(entry -> entry.getValue().isExpired()); + } + + public static void clean_all_codes(){ + if (!codeMap.isEmpty()) { + codeMap.clear(); + } + } +} diff --git a/src/Send_Email/Generate_i_code.java b/src/Send_Email/Generate_i_code.java new file mode 100644 index 0000000..d9404ac --- /dev/null +++ b/src/Send_Email/Generate_i_code.java @@ -0,0 +1,18 @@ +package Send_Email; + +import java.util.Random; + +public class Generate_i_code { + public static String generateCode(int length) { + Random ra = new Random(); + StringBuilder code = new StringBuilder(); + for (int i = 0; i < length; i++) { + if (ra.nextBoolean()){ + code.append((char)('A'+ra.nextInt(26))); + } else { + code.append((char)('0'+ra.nextInt(10))); + } + } + return code.toString(); + } +} diff --git a/src/Send_Email/Send_email.java b/src/Send_Email/Send_email.java new file mode 100644 index 0000000..809024f --- /dev/null +++ b/src/Send_Email/Send_email.java @@ -0,0 +1,97 @@ +package Send_Email; + +import Base.Email_settings; + +import javax.mail.*; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; +import java.util.Properties; +import java.util.regex.Pattern; + +public class Send_email { + public static String judge_email_address(String email) { + if (email == null || email.trim().isEmpty()) { + return "邮箱不能为空"; + } + // 去除前后空格 + email = email.trim(); + // 检查长度 + if (email.length() > 254) { // RFC标准规定邮箱最大长度 + return "邮箱地址过长"; + } + // 检查是否包含@ + if (!email.contains("@")) { + return "邮箱格式错误:缺少@符号"; + } + // 分割本地部分和域名部分 + String[] parts = email.split("@"); + if (parts.length != 2) { + return "邮箱格式错误:只能有一个@符号"; + } + String localPart = parts[0]; + String domainPart = parts[1]; + // 验证本地部分 + if (localPart.isEmpty()) { + return "邮箱格式错误:@前必须有内容"; + } + if (localPart.length() > 64) { + return "邮箱格式错误:@前内容过长"; + } + // 验证域名部分 + if (domainPart.isEmpty()) { + return "邮箱格式错误:@后必须有内容"; + } + if (!domainPart.contains(".")) { + return "邮箱格式错误:域名不完整"; + } + // 验证顶级域名 + String[] domainParts = domainPart.split("\\."); + String topLevelDomain = domainParts[domainParts.length - 1]; + if (topLevelDomain.length() < 2) { + return "邮箱格式错误:顶级域名太短"; + } + String emailRegex = "^(?=.{1,64}@)[A-Za-z0-9_-]+(\\.[A-Za-z0-9_-]+)*@" + + "[^-][A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*(\\.[A-Za-z]{2,})$"; + boolean matches = Pattern.matches(emailRegex, email); + return matches ? "邮箱格式正确" : "邮箱格式错误"; + } + + public static boolean send_email(String toEmail, String Code) { + try { + Properties props = new Properties(); + props.put("mail.smtp.host", Email_settings.getSmtpHost()); + props.put("mail.smtp.port", Email_settings.getSmtpPort()); + props.put("mail.smtp.auth", "true"); + if (Email_settings.isSslEnable()) { + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.ssl.trust", Email_settings.getSmtpHost()); + } + // 创建会话 + Session session = Session.getInstance(props, new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(Email_settings.getFromEmail(), Email_settings.getEmailPassword()); + } + }); + // 创建邮件消息 + Message message = new MimeMessage(session); + message.setFrom(new InternetAddress(Email_settings.getFromEmail())); + message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail)); + message.setSubject("注册验证码"); + // 邮件内容 + String content = String.format( + "尊敬的用户:%s

" + + "您的注册验证码是:%s

" + + "验证码有效期为5分钟,请尽快完成注册。

" + + "如果不是您本人操作,请忽略此邮件。", + toEmail,Code + ); + message.setContent(content, "text/html;charset=UTF-8"); + // 发送邮件 + Transport.send(message); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/src/Service/Deal_file.java b/src/Service/Deal_file.java new file mode 100644 index 0000000..ff012b1 --- /dev/null +++ b/src/Service/Deal_file.java @@ -0,0 +1,43 @@ +package Service; + +import Base.Question; + +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.*; + +public class Deal_file { + private static final String BASE_DIR="试卷/"; + + public Deal_file() { + File baseDir = new File(BASE_DIR); + if (!baseDir.exists()) { + if (!baseDir.mkdirs()) + System.out.println("目录创建失败!"); + } + } + + public void savePaper(ArrayList paper, String username) { + String userDirPath = BASE_DIR + username + "/"; + File userDir = new File(userDirPath); + if (!userDir.exists()) { + if (!userDir.mkdirs()) { + System.out.println("目录创建失败!"); + return; + } + } + String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss").format(new Date()); + String filePath = userDirPath + timestamp + ".txt"; + + try (PrintWriter writer = new PrintWriter(new FileWriter(filePath))) { + for (int i = 0; i < paper.size(); i++) { + writer.println(paper.get(i).toString()); + if (i < paper.size() - 1) { + writer.println(); + } + } + } catch (IOException e) { + System.out.println("保存文件出错: " + e.getMessage()); + } + } +} diff --git a/src/Service/Exam_service.java b/src/Service/Exam_service.java new file mode 100644 index 0000000..dc84cf2 --- /dev/null +++ b/src/Service/Exam_service.java @@ -0,0 +1,115 @@ +package Service; + +import Base.Exam_result; +import Base.Question; +import Generator.Generate_paper; + +import java.util.*; + +public class Exam_service { + private String id; + private String type; + private ArrayList paper; + private int now_index; + private Date start_time; + private Date end_time; + private Map user_answers; + + public Exam_service(int num,String type,String id){ + this.id=id; + this.type=type; + paper= Generate_paper.g_paper(num,type,id); + now_index=0; + this.start_time = new Date(); + this.user_answers = new HashMap<>(); + } + + public ArrayList get_paper() { return paper; } + public int get_now_index() { return now_index; } + public Date get_start_time() { return start_time; } + public Date get_end_time() { return end_time; } + public Map get_user_answers() {return user_answers;} + + public void set_now_index(int i){ + if (i==1 && now_index0){ + now_index+=i; + } + else if (i==0){ + now_index=0; + } + } + + public Question get_now_question() { + if (now_index < paper.size()) { + return paper.get(now_index); + } + return null; + } + + public boolean next_one(int answer_index) { + if (now_index < paper.size()) { + user_answers.put(now_index, answer_index); + + if (now_index < paper.size() - 1) { + now_index++; + return true; + } else { + end_time = new Date(); + return false; + } + } + return false; + } + + public boolean pre_one() { + if (now_index > 0) { + now_index--; + return true; + } + return false; + } + + public boolean change_answer(int index,int choice){ + if (user_answers.containsKey(index)){ + user_answers.put(index,choice); + return true; + } + return false; + } + + public Integer get_user_answer(int question_index) { + return user_answers.get(question_index); + } + + public boolean check_finished(){ + for (int i=0;i< paper.size();i++){ + if (user_answers.get(i)==-1){ + return false; + } + } + return true; + } + + public Exam_result calculate_result(){ + int correct = 0; + List wrong = new ArrayList<>(); + + for (int i = 0; i < paper.size(); i++) { + Integer userAnswer = user_answers.get(i); + if (userAnswer != null && paper.get(i).getOptions(userAnswer).equals(paper.get(i).getAnswer())) { + correct++; + } else { + wrong.add(i); + } + } + + double score = (double) correct / paper.size() * 100; + long duration = (end_time.getTime() - start_time.getTime()) / 1000; // 秒 + + return new Exam_result(type, paper.size(), correct, + score, duration, wrong); + } +} diff --git a/src/Service/User_service.java b/src/Service/User_service.java new file mode 100644 index 0000000..8cac970 --- /dev/null +++ b/src/Service/User_service.java @@ -0,0 +1,173 @@ +package Service; + +import Base.User; +import Send_Email.Deal_i_code; +import Send_Email.Send_email; + +import java.io.*; +import java.util.ArrayList; + +public class User_service { + private ArrayList users; + private final String base_dir="用户/"; + + public User_service() { + if (!load_users()){ + users=new ArrayList<>(); + } + } + + public String register(String email){ + Deal_i_code.clean_all_codes(); + if (find_user(email)!=null){ + return "邮箱已被注册"; + } + String judge_result=Send_email.judge_email_address(email); + if (judge_result.equals("邮箱格式正确")){ + String code=Deal_i_code.generate_code(email); + if (!Send_email.send_email(email,code)){ + return "邮件发送失败"; + } + return "验证码已发送"; + } + else{ + return judge_result; + } + } + + public String check_register(String email,String input_code,String pwd,String id){ + if (Deal_i_code.judge_code(email,input_code)){ + if (!User.check_password(pwd)){ + return "密码长6-20位,至少包含大小写字母和数字"; + } + User new_one=new User(email); + new_one.set_password(User.hash_pwd(pwd)); + new_one.set_id(id); + if (find_user_i(id)!=null){ + return "用户名重复"; + } + users.add(new_one); + Deal_i_code.clean_all_codes(); + if (!save_users()){ + return "注册失败,请重试"; + } + return "注册成功"; + } + else { + return "验证码不存在或不正确"; + } + } + + public String login(String id,String pwd){ + User u=find_user_i(id); + if (u==null){ + return "请先注册"; + } + if (User.check_hash_pwd(pwd,u.get_password())){ + return "登陆成功"; + } + return "密码有误"; + } + + public String change_pwd(String email, String oldPassword, String newPassword){ + User user=find_user(email); + if (user == null) { + return "用户不存在"; + } + if (!User.check_hash_pwd(oldPassword,user.get_password())) { + return "原密码不正确"; + } + if (oldPassword.equals(newPassword)){ + return "新密码与原密码一致"; + } + if (!User.check_password(newPassword)) { + return "密码长6-20位,至少包含大小写字母和数字"; + } + user.set_password(newPassword); + if (save_users()) { + return "修改成功"; + } + return "修改失败"; + } + + public String Unregister(String email){ + User user=find_user(email); + if (user!=null) { + if (users.remove(user)) { + if (save_users()) { + return "删除成功"; + } + } + } + return "删除失败"; + } + + public User find_user(String email){ + for (User user : users) { + if (user.get_email().equals(email)) { + return user; + } + } + return null; + } + + public User find_user_i(String id){ + for (User user : users) { + if (user.get_id().equals(id)) { + return user; + } + } + return null; + } + + private boolean save_users(){ + File baseDir = new File(base_dir); + if (!baseDir.exists()) { + if (!baseDir.mkdirs()){ + return false; + } + } + String file_path=base_dir+"用户信息.txt"; + try (PrintWriter writer = new PrintWriter(new FileWriter(file_path))) { + if (users.isEmpty()){ + return true; + } + for (int i = 0; i < users.size(); i++) { + writer.println(users.get(i).get_email()+" "+users.get(i).get_password()+" "+users.get(i).get_id()); + if (i < users.size() - 1) { + writer.println(); + } + } + return true; + } catch (IOException e) { + return false; + } + } + + private boolean load_users(){ + users=new ArrayList<>(); + String file_path=base_dir+"用户信息.txt"; + File file=new File(file_path); + if (file.exists()){ + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.trim().isEmpty()) { + String l=line.trim(); + String[] parts=l.split(" "); + if (parts.length==3){ + User temp=new User(parts[0]); + temp.set_password(parts[1]); + temp.set_id(parts[2]); + users.add(temp); + } + } + } + return true; + } catch (IOException e) { + return false; + } + } + return false; + } +} diff --git a/src/View/MainFrame.java b/src/View/MainFrame.java new file mode 100644 index 0000000..51ed72a --- /dev/null +++ b/src/View/MainFrame.java @@ -0,0 +1,731 @@ +package View; + +import Service.User_service; +import Service.Exam_service; +import Base.Exam_result; +import Base.Question; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class MainFrame extends JFrame { + private final User_service userService; + private Exam_service examService; + private CardLayout cardLayout; + private JPanel mainPanel; + private String currentUserId; + + // UI组件字段 + private JTextField loginIdField; + private JPasswordField loginPasswordField; + private JTextField regEmailField; + private JTextField regCodeField; + private JPasswordField regPasswordField; + private JPasswordField regConfirmPasswordField; + private JTextField regUserIdField; + private JLabel questionLabel; + private JRadioButton[] optionButtons; + private ButtonGroup optionGroup; + private JLabel questionNumberLabel; + private JButton prevButton; + private JButton nextButton; + private JButton submitButton; + private JLabel resultLabel; + + public MainFrame() { + userService = new User_service(); + initializeUI(); + setTitle("数学学习系统"); + setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + setSize(800, 600); + setLocationRelativeTo(null); + } + + private void initializeUI() { + cardLayout = new CardLayout(); + mainPanel = new JPanel(cardLayout); + + createLoginPanel(); + createRegisterPanel(); + createGradeSelectionPanel(); + createExamPanel(); + createResultPanel(); + createWrongQuestionsPanel(); // 新增错题面板 + createPasswordChangePanel(); + + add(mainPanel); + showLoginPanel(); + } + + private void createLoginPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(50, 100, 50, 100)); + + JLabel titleLabel = new JLabel("数学学习系统", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + panel.add(titleLabel, BorderLayout.NORTH); + + JPanel formPanel = new JPanel(new GridLayout(3, 2, 15, 15)); + formPanel.setBorder(BorderFactory.createEmptyBorder(30, 50, 30, 50)); + + formPanel.add(new JLabel("用户名:", JLabel.CENTER)); + loginIdField = new JTextField(); + formPanel.add(loginIdField); + + formPanel.add(new JLabel("密码:", JLabel.CENTER)); + loginPasswordField = new JPasswordField(); + formPanel.add(loginPasswordField); + + panel.add(formPanel, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + + JButton loginButton = createStyledButton("登录"); + loginButton.addActionListener(e -> login()); + buttonPanel.add(loginButton); + + JButton registerButton = createStyledButton("注册账号"); + registerButton.addActionListener(e -> showRegisterPanel()); + buttonPanel.add(registerButton); + + JButton exitButton = createStyledButton("退出"); + exitButton.addActionListener(e -> exitchoice()); + buttonPanel.add(exitButton); + + panel.add(buttonPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "Login"); + } + + private void createRegisterPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(30, 80, 30, 80)); + + JLabel titleLabel = new JLabel("用户注册", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + panel.add(titleLabel, BorderLayout.NORTH); + + JPanel formPanel = new JPanel(new GridLayout(5, 2, 15, 15)); + formPanel.setBorder(BorderFactory.createEmptyBorder(20, 50, 20, 50)); + + formPanel.add(new JLabel("邮箱:", JLabel.CENTER)); + regEmailField = new JTextField(); + formPanel.add(regEmailField); + + formPanel.add(new JLabel("验证码:", JLabel.CENTER)); + JPanel codePanel = new JPanel(new BorderLayout(5, 0)); + regCodeField = new JTextField(); + codePanel.add(regCodeField, BorderLayout.CENTER); + + JButton sendCodeButton = new JButton("发送验证码"); + sendCodeButton.setFont(new Font("微软雅黑", Font.PLAIN, 10)); + sendCodeButton.addActionListener(e -> sendVerificationCode()); + codePanel.add(sendCodeButton, BorderLayout.EAST); + formPanel.add(codePanel); + + formPanel.add(new JLabel("密码:", JLabel.CENTER)); + regPasswordField = new JPasswordField(); + formPanel.add(regPasswordField); + + formPanel.add(new JLabel("确认密码:", JLabel.CENTER)); + regConfirmPasswordField = new JPasswordField(); + formPanel.add(regConfirmPasswordField); + + formPanel.add(new JLabel("用户名:", JLabel.CENTER)); + regUserIdField = new JTextField(); + formPanel.add(regUserIdField); + + panel.add(formPanel, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + + JButton registerButton = createStyledButton("注册"); + registerButton.addActionListener(e -> register()); + buttonPanel.add(registerButton); + + JButton backButton = createStyledButton("返回登录"); + backButton.addActionListener(e -> showLoginPanel()); + buttonPanel.add(backButton); + + panel.add(buttonPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "Register"); + } + + private void createGradeSelectionPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(50, 100, 50, 100)); + + JLabel titleLabel = new JLabel("选择学习阶段", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + panel.add(titleLabel, BorderLayout.NORTH); + + JPanel gradePanel = new JPanel(new GridLayout(4, 1, 20, 20)); + gradePanel.setBorder(BorderFactory.createEmptyBorder(50, 100, 50, 100)); + + JButton primaryButton = createGradeButton("小学"); + primaryButton.addActionListener(e -> startExam("小学")); + gradePanel.add(primaryButton); + + JButton juniorButton = createGradeButton("初中"); + juniorButton.addActionListener(e -> startExam("初中")); + gradePanel.add(juniorButton); + + JButton seniorButton = createGradeButton("高中"); + seniorButton.addActionListener(e -> startExam("高中")); + gradePanel.add(seniorButton); + + panel.add(gradePanel, BorderLayout.CENTER); + + JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + + JButton changePasswordButton = createStyledButton("修改密码"); + changePasswordButton.addActionListener(e -> showPasswordChangePanel()); + bottomPanel.add(changePasswordButton); + + // 新增:删除账号按钮 + JButton deleteAccountButton = createStyledButton("删除账号"); + deleteAccountButton.setBackground(new Color(220, 20, 60)); // 红色背景提示危险操作 + deleteAccountButton.addActionListener(e -> deleteAccount()); + bottomPanel.add(deleteAccountButton); + + JButton logoutButton = createStyledButton("退出登录"); + logoutButton.addActionListener(e -> logout()); + bottomPanel.add(logoutButton); + + panel.add(bottomPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "GradeSelection"); + } + // 新增:删除当前账号功能 + private void deleteAccount() { + // 简单确认对话框 + int result = JOptionPane.showConfirmDialog( + this, + "确定要删除当前账号吗?\n用户名:" + currentUserId + "\n此操作不可恢复!", + "确认删除账号", + JOptionPane.YES_NO_OPTION, + JOptionPane.WARNING_MESSAGE + ); + + if (result == JOptionPane.YES_OPTION) { + // 直接使用当前用户名作为邮箱(根据您的系统设计,用户名可能就是邮箱) + // 或者让用户输入邮箱确认 + String userEmail = JOptionPane.showInputDialog( + this, + "请输入您的邮箱进行确认:", + "确认删除", + JOptionPane.QUESTION_MESSAGE + ); + + if (userEmail != null && !userEmail.trim().isEmpty()) { + // 调用User_service的Unregister函数删除账号 + String deleteResult = userService.Unregister(userEmail.trim()); + + if (deleteResult.equals("删除成功")) { + JOptionPane.showMessageDialog(this, "账号删除成功!"); + // 清除当前用户信息并返回登录界面 + currentUserId = null; + examService = null; + showLoginPanel(); + } else { + JOptionPane.showMessageDialog(this, "删除失败:" + deleteResult); + } + } + } + } + + private void createExamPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(20, 40, 20, 40)); + + // 顶部信息栏 + JPanel topPanel = new JPanel(new BorderLayout()); + questionNumberLabel = new JLabel("", JLabel.LEFT); + questionNumberLabel.setFont(new Font("微软雅黑", Font.BOLD, 16)); + + topPanel.add(questionNumberLabel, BorderLayout.WEST); + panel.add(topPanel, BorderLayout.NORTH); + + // 题目区域 + JPanel questionPanel = new JPanel(new BorderLayout(10, 20)); + questionPanel.setBorder(BorderFactory.createEmptyBorder(30, 50, 30, 50)); + + questionLabel = new JLabel("", JLabel.CENTER); + questionLabel.setFont(new Font("微软雅黑", Font.PLAIN, 18)); + questionPanel.add(questionLabel, BorderLayout.NORTH); + + // 选项区域 + JPanel optionsPanel = new JPanel(new GridLayout(4, 1, 10, 10)); + optionsPanel.setBorder(BorderFactory.createEmptyBorder(20, 100, 20, 100)); + + 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, 16)); + optionGroup.add(optionButtons[i]); + optionsPanel.add(optionButtons[i]); + } + + questionPanel.add(optionsPanel, BorderLayout.CENTER); + panel.add(questionPanel, BorderLayout.CENTER); + + // 导航按钮区域 + JPanel navPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + + prevButton = createStyledButton("上一题"); + prevButton.addActionListener(e -> goToPreviousQuestion()); + navPanel.add(prevButton); + + nextButton = createStyledButton("下一题"); + nextButton.addActionListener(e -> goToNextQuestion()); + navPanel.add(nextButton); + + submitButton = createStyledButton("提交试卷"); + submitButton.addActionListener(e -> submitExam()); + navPanel.add(submitButton); + + panel.add(navPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "Exam"); + } + + private void createResultPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(50, 100, 50, 100)); + + resultLabel = new JLabel("", JLabel.CENTER); + resultLabel.setFont(new Font("微软雅黑", Font.BOLD, 24)); + panel.add(resultLabel, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 20)); + + // 新增查看错题按钮 + JButton reviewButton = createStyledButton("查看错题"); + reviewButton.addActionListener(e -> showWrongQuestions()); + buttonPanel.add(reviewButton); + + JButton continueButton = createStyledButton("继续做题"); + continueButton.addActionListener(e -> showGradeSelectionPanel()); + buttonPanel.add(continueButton); + + JButton exitButton = createStyledButton("退出程序"); + exitButton.addActionListener(e -> System.exit(0)); + buttonPanel.add(exitButton); + + panel.add(buttonPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "Result"); + } + + private void createWrongQuestionsPanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(20, 40, 20, 40)); + + // 标题 + JLabel titleLabel = new JLabel("错题回顾", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + panel.add(titleLabel, BorderLayout.NORTH); + + // 错题内容区域(使用滚动面板) + JTextArea wrongQuestionsArea = new JTextArea(); + wrongQuestionsArea.setFont(new Font("微软雅黑", Font.PLAIN, 14)); + wrongQuestionsArea.setEditable(false); + wrongQuestionsArea.setLineWrap(true); + wrongQuestionsArea.setWrapStyleWord(true); + + JScrollPane scrollPane = new JScrollPane(wrongQuestionsArea); + scrollPane.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20)); + panel.add(scrollPane, BorderLayout.CENTER); + + // 存储错题文本区域引用 + panel.putClientProperty("wrongQuestionsArea", wrongQuestionsArea); + + // 返回按钮 + JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JButton backButton = createStyledButton("返回成绩"); + backButton.addActionListener(e -> showResultPanel()); + bottomPanel.add(backButton); + + panel.add(bottomPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "WrongQuestions"); + } + + private void createPasswordChangePanel() { + JPanel panel = new JPanel(new BorderLayout(10, 10)); + panel.setBorder(BorderFactory.createEmptyBorder(50, 100, 50, 100)); + + JLabel titleLabel = new JLabel("修改密码", JLabel.CENTER); + titleLabel.setFont(new Font("微软雅黑", Font.BOLD, 20)); + panel.add(titleLabel, BorderLayout.NORTH); + + JPanel formPanel = new JPanel(new GridLayout(4, 2, 15, 15)); + formPanel.setBorder(BorderFactory.createEmptyBorder(30, 80, 30, 80)); + + formPanel.add(new JLabel("邮箱:", JLabel.RIGHT)); + JTextField emailField = new JTextField(); + formPanel.add(emailField); + + formPanel.add(new JLabel("原密码:", JLabel.RIGHT)); + JPasswordField oldPasswordField = new JPasswordField(); + formPanel.add(oldPasswordField); + + formPanel.add(new JLabel("新密码:", JLabel.RIGHT)); + JPasswordField newPasswordField = new JPasswordField(); + formPanel.add(newPasswordField); + + formPanel.add(new JLabel("确认新密码:", JLabel.RIGHT)); + JPasswordField confirmPasswordField = new JPasswordField(); + formPanel.add(confirmPasswordField); + + panel.add(formPanel, BorderLayout.CENTER); + + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10)); + + JButton changeButton = createStyledButton("确认修改"); + changeButton.addActionListener(e -> { + String email = emailField.getText(); + String oldPassword = new String(oldPasswordField.getPassword()); + String newPassword = new String(newPasswordField.getPassword()); + String confirmPassword = new String(confirmPasswordField.getPassword()); + changePassword(email, oldPassword, newPassword, confirmPassword); + }); + buttonPanel.add(changeButton); + + JButton backButton = createStyledButton("返回"); + backButton.addActionListener(e -> showGradeSelectionPanel()); + buttonPanel.add(backButton); + + panel.add(buttonPanel, BorderLayout.SOUTH); + + mainPanel.add(panel, "PasswordChange"); + } + + private JButton createStyledButton(String text) { + JButton button = new JButton(text); + button.setFont(new Font("微软雅黑", Font.BOLD, 14)); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(10, 20, 10, 20)); + return button; + } + + private JButton createGradeButton(String text) { + JButton button = new JButton(text); + button.setFont(new Font("微软雅黑", Font.BOLD, 18)); + button.setFocusPainted(false); + button.setBorder(BorderFactory.createEmptyBorder(20, 0, 20, 0)); + return button; + } + + // 界面显示方法 + private void showLoginPanel() { + loginIdField.setText(""); + loginPasswordField.setText(""); + cardLayout.show(mainPanel, "Login"); + } + + private void showRegisterPanel() { + regEmailField.setText(""); + regCodeField.setText(""); + regPasswordField.setText(""); + regConfirmPasswordField.setText(""); + regUserIdField.setText(""); + cardLayout.show(mainPanel, "Register"); + } + + private void showGradeSelectionPanel() { + cardLayout.show(mainPanel, "GradeSelection"); + } + + private void showPasswordChangePanel() { + cardLayout.show(mainPanel, "PasswordChange"); + } + + private void showExamPanel() { + cardLayout.show(mainPanel, "Exam"); + } + + private void showResultPanel() { + cardLayout.show(mainPanel, "Result"); + } + + private void showWrongQuestionsPanel() { + cardLayout.show(mainPanel, "WrongQuestions"); + } + + // 业务逻辑方法 + private void login() { + String id = loginIdField.getText().trim(); + String password = new String(loginPasswordField.getPassword()); + + if (id.isEmpty() || password.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入用户名和密码"); + return; + } + + String result = userService.login(id, password); + if (result.equals("登陆成功")) { + currentUserId = id; + showGradeSelectionPanel(); + } else { + JOptionPane.showMessageDialog(this, result); + } + } + + private void sendVerificationCode() { + String email = regEmailField.getText().trim(); + if (email.isEmpty()) { + JOptionPane.showMessageDialog(this, "请输入邮箱地址"); + return; + } + + String result = userService.register(email); + JOptionPane.showMessageDialog(this, result); + } + + private void register() { + String email = regEmailField.getText().trim(); + String code = regCodeField.getText().trim(); + String password = new String(regPasswordField.getPassword()); + String confirmPassword = new String(regConfirmPasswordField.getPassword()); + String userId = regUserIdField.getText().trim(); + + if (email.isEmpty() || code.isEmpty() || password.isEmpty() || userId.isEmpty()) { + JOptionPane.showMessageDialog(this, "请填写所有字段"); + return; + } + + if (!password.equals(confirmPassword)) { + JOptionPane.showMessageDialog(this, "两次输入的密码不一致"); + return; + } + + String result = userService.check_register(email, code, password, userId); + JOptionPane.showMessageDialog(this, result); + + if (result.equals("注册成功")) { + showLoginPanel(); + } + } + + private void startExam(String gradeType) { + String input = JOptionPane.showInputDialog(this, + "请输入" + gradeType + "题目数量:", "题目数量", JOptionPane.QUESTION_MESSAGE); + + if (input == null) return; + + try { + int numQuestions = Integer.parseInt(input.trim()); + if (numQuestions < 10 || numQuestions > 30) { + JOptionPane.showMessageDialog(this, "题目数量范围是10-30"); + return; + } + + examService = new Exam_service(numQuestions, gradeType, currentUserId); + showCurrentQuestion(); + showExamPanel(); + + } catch (NumberFormatException e) { + JOptionPane.showMessageDialog(this, "请输入有效的数字"); + } + } + + private void showCurrentQuestion() { + Question question = examService.get_now_question(); + if (question != null) { + int currentIndex = examService.get_now_index(); + int totalQuestions = examService.get_paper().size(); + + questionNumberLabel.setText(String.format("第 %d 题 / 共 %d 题", + currentIndex + 1, totalQuestions)); + + questionLabel.setText("
" + + question.toString() + "
"); + + for (int i = 0; i < 4; i++) { + String optionText = question.getOptions(i); + optionButtons[i].setText((char)('A' + i) + ": " + optionText); + } + + Integer userAnswer = examService.get_user_answer(currentIndex); + optionGroup.clearSelection(); + if (userAnswer != null && userAnswer >= 0 && userAnswer < 4) { + optionButtons[userAnswer].setSelected(true); + } + + updateNavigationButtons(); + } + } + + private void updateNavigationButtons() { + int currentIndex = examService.get_now_index(); + int totalQuestions = examService.get_paper().size(); + + prevButton.setEnabled(currentIndex > 0); + nextButton.setEnabled(currentIndex < totalQuestions - 1); + submitButton.setEnabled(currentIndex == totalQuestions - 1); + } + + private void goToPreviousQuestion() { + if (examService.pre_one()) { + showCurrentQuestion(); + } + } + + private void goToNextQuestion() { + int selectedOption = getSelectedOption(); + if (selectedOption == -1) { + JOptionPane.showMessageDialog(this, "请选择一个答案"); + return; + } + + boolean hasNext = examService.next_one(selectedOption); + if (hasNext) { + showCurrentQuestion(); + } else { + showExamResult(); + } + } + + private void submitExam() { + int selectedOption = getSelectedOption(); + if (selectedOption == -1) { + JOptionPane.showMessageDialog(this, "请选择一个答案"); + return; + } + + examService.next_one(selectedOption); + showExamResult(); + } + + private int getSelectedOption() { + for (int i = 0; i < 4; i++) { + if (optionButtons[i].isSelected()) { + return i; + } + } + return -1; + } + + private void showExamResult() { + Exam_result result = examService.calculate_result(); + + String resultText = String.format( + "
" + + "

考试完成!

" + + "

%s数学测试

" + + "

总题数: %d

" + + "

答对题数: %d

" + + "

得分: %.1f

" + + "

用时: %s

" + + "

正确率: %s

" + + "
", + result.getExamType(), + result.getTotalQuestions(), + result.getCorrectAnswers(), + result.getScore(), + result.get_time(), + result.getCorrectRate() + ); + + resultLabel.setText(resultText); + showResultPanel(); + } + + // 新增:查看错题功能 + // 修正:查看错题功能 - 简洁显示 + private void showWrongQuestions() { + // 获取考试结果 + Exam_result result = examService.calculate_result(); + java.util.List wrongQuestionIndices = result.getWrongQuestions(); + + if (wrongQuestionIndices.isEmpty()) { + JOptionPane.showMessageDialog(this, "恭喜!本次考试没有错题!"); + return; + } + + // 获取错题面板和文本区域 + JPanel wrongQuestionsPanel = (JPanel) mainPanel.getComponent(5); // 第6个面板是错题面板 + JTextArea wrongQuestionsArea = (JTextArea) ((JScrollPane) wrongQuestionsPanel.getComponent(1)).getViewport().getView(); + + // 构建错题显示内容 + StringBuilder sb = new StringBuilder(); + sb.append("本次考试共有 ").append(wrongQuestionIndices.size()).append(" 道错题:\n\n"); + + java.util.ArrayList paper = examService.get_paper(); + + for (int i = 0; i < wrongQuestionIndices.size(); i++) { + int questionIndex = wrongQuestionIndices.get(i); + Question question = paper.get(questionIndex); + + // 只显示一个题号 + sb.append("第 ").append(questionIndex + 1).append(" 题:"); + sb.append(question.toString()).append("\n"); + + // 显示所有选项,在正确答案后打勾 + sb.append("选项:\n"); + for (int j = 0; j < 4; j++) { + char optionChar = (char) ('A' + j); + sb.append(" ").append(optionChar).append(". ").append(question.getOptions(j)); + + // 标记正确答案 + if (question.getOptions(j).equals(question.getAnswer())) { + sb.append(" √"); + } + + // 标记用户选择的错误答案 + Integer userAnswer = examService.get_user_answer(questionIndex); + if (userAnswer != null && userAnswer == j && !question.getOptions(j).equals(question.getAnswer())) { + sb.append(" X"); + } + + sb.append("\n"); + } + + sb.append("\n"); + sb.append("-".repeat(50)).append("\n\n"); + } + + wrongQuestionsArea.setText(sb.toString()); + showWrongQuestionsPanel(); + } + private void changePassword(String email, String oldPassword, String newPassword, String confirmPassword) { + if (email.isEmpty() || oldPassword.isEmpty() || newPassword.isEmpty()) { + JOptionPane.showMessageDialog(this, "请填写所有字段"); + return; + } + + if (!newPassword.equals(confirmPassword)) { + JOptionPane.showMessageDialog(this, "两次输入的新密码不一致"); + return; + } + + String result = userService.change_pwd(email, oldPassword, newPassword); + JOptionPane.showMessageDialog(this, result); + + if (result.equals("修改成功")) { + showGradeSelectionPanel(); + } + } + + private void logout() { + currentUserId = null; + examService = null; + showLoginPanel(); + } + + public void exitchoice(){ + System.exit(0); + } + + // 主函数 + public static void main(String[] args) { + SwingUtilities.invokeLater(() -> { + new MainFrame().setVisible(true); + }); + } +} \ No newline at end of file