|
|
|
|
@ -0,0 +1,316 @@
|
|
|
|
|
package controller;
|
|
|
|
|
|
|
|
|
|
import model.*;
|
|
|
|
|
import service.*;
|
|
|
|
|
import view.*;
|
|
|
|
|
|
|
|
|
|
import javax.swing.*;
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.Random;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public class AppController {
|
|
|
|
|
// 模型和服务
|
|
|
|
|
private final UserManager userManager;
|
|
|
|
|
private final ExamManager examManager;
|
|
|
|
|
private final EmailService emailService;
|
|
|
|
|
|
|
|
|
|
// 视图
|
|
|
|
|
private final MainFrame mainFrame;
|
|
|
|
|
private final LoginView loginView;
|
|
|
|
|
private final RegisterView registerView;
|
|
|
|
|
private final PasswordSetView passwordSetView;
|
|
|
|
|
private final MainMenuView mainMenuView;
|
|
|
|
|
private final ExamView examView;
|
|
|
|
|
private final ScoreView scoreView;
|
|
|
|
|
|
|
|
|
|
// 应用状态
|
|
|
|
|
private User currentUser;
|
|
|
|
|
private String emailForRegistration;
|
|
|
|
|
private String usernameForRegistration;
|
|
|
|
|
private final Map<String, String> registrationCodes = new HashMap<>();
|
|
|
|
|
private List<Question> currentExam;
|
|
|
|
|
private int currentQuestionIndex;
|
|
|
|
|
private int score;
|
|
|
|
|
private int[] userAnswers; // 新增:用于存储用户每一题的答案
|
|
|
|
|
|
|
|
|
|
public AppController() {
|
|
|
|
|
this.mainFrame = new MainFrame();
|
|
|
|
|
this.userManager = new UserManager();
|
|
|
|
|
this.examManager = new ExamManager();
|
|
|
|
|
this.emailService = new EmailService();
|
|
|
|
|
|
|
|
|
|
this.loginView = new LoginView();
|
|
|
|
|
this.registerView = new RegisterView();
|
|
|
|
|
this.passwordSetView = new PasswordSetView();
|
|
|
|
|
this.mainMenuView = new MainMenuView();
|
|
|
|
|
this.examView = new ExamView();
|
|
|
|
|
this.scoreView = new ScoreView();
|
|
|
|
|
|
|
|
|
|
mainFrame.addPanel(loginView, "LOGIN");
|
|
|
|
|
mainFrame.addPanel(registerView, "REGISTER");
|
|
|
|
|
mainFrame.addPanel(passwordSetView, "PASSWORD_SET");
|
|
|
|
|
mainFrame.addPanel(mainMenuView, "MAIN_MENU");
|
|
|
|
|
mainFrame.addPanel(examView, "EXAM");
|
|
|
|
|
mainFrame.addPanel(scoreView, "SCORE");
|
|
|
|
|
|
|
|
|
|
attachListeners();
|
|
|
|
|
mainFrame.showPanel("LOGIN");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 附加所有UI组件的事件监听器。
|
|
|
|
|
*/
|
|
|
|
|
private void attachListeners() {
|
|
|
|
|
// --- 登录逻辑 ---
|
|
|
|
|
loginView.getLoginButton().addActionListener(e -> handleLogin());
|
|
|
|
|
loginView.getRegisterButton().addActionListener(e -> mainFrame.showPanel("REGISTER"));
|
|
|
|
|
|
|
|
|
|
// --- 注册逻辑 ---
|
|
|
|
|
registerView.getGetCodeButton().addActionListener(e -> handleGetCode());
|
|
|
|
|
registerView.getRegisterButton().addActionListener(e -> handleVerifyCode());
|
|
|
|
|
registerView.getBackButton().addActionListener(e -> mainFrame.showPanel("LOGIN"));
|
|
|
|
|
|
|
|
|
|
// --- 密码设置逻辑 ---
|
|
|
|
|
passwordSetView.getSubmitButton().addActionListener(e -> handleSetPassword());
|
|
|
|
|
|
|
|
|
|
// --- 主菜单逻辑 ---
|
|
|
|
|
mainMenuView.getStartExamButton().addActionListener(e -> handleStartExam());
|
|
|
|
|
mainMenuView.getChangePasswordButton().addActionListener(e -> handleChangePassword());
|
|
|
|
|
mainMenuView.getLogoutButton().addActionListener(e -> handleLogout());
|
|
|
|
|
|
|
|
|
|
// --- 考试逻辑 (已重构) ---
|
|
|
|
|
examView.getPrevButton().addActionListener(e -> handlePreviousQuestion());
|
|
|
|
|
examView.getNextButton().addActionListener(e -> handleNextQuestion());
|
|
|
|
|
examView.getFinishButton().addActionListener(e -> handleFinishExam());
|
|
|
|
|
|
|
|
|
|
// --- 分数界面逻辑 ---
|
|
|
|
|
scoreView.getContinueButton().addActionListener(e -> {
|
|
|
|
|
String welcomeName = currentUser.getUsername() != null && !currentUser.getUsername().isEmpty() ? currentUser.getUsername() : currentUser.getEmail();
|
|
|
|
|
mainMenuView.setWelcomeMessage("欢迎, " + welcomeName);
|
|
|
|
|
mainFrame.showPanel("MAIN_MENU");
|
|
|
|
|
});
|
|
|
|
|
scoreView.getExitButton().addActionListener(e -> handleLogout());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 核心处理方法 ---
|
|
|
|
|
|
|
|
|
|
private void handleLogin() {
|
|
|
|
|
User user = userManager.authenticate(loginView.getIdentifier(), loginView.getPassword());
|
|
|
|
|
if (user != null) {
|
|
|
|
|
currentUser = user;
|
|
|
|
|
String welcomeName = user.getUsername() != null && !user.getUsername().isEmpty() ? user.getUsername() : user.getEmail();
|
|
|
|
|
mainMenuView.setWelcomeMessage("欢迎, " + welcomeName);
|
|
|
|
|
mainFrame.showPanel("MAIN_MENU");
|
|
|
|
|
} else {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "邮箱/用户名或密码错误", "登录失败", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
}
|
|
|
|
|
loginView.clearFields();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handleGetCode() {
|
|
|
|
|
String email = registerView.getEmail();
|
|
|
|
|
String username = registerView.getUsername();
|
|
|
|
|
|
|
|
|
|
if (!ValidationService.isValidEmail(email)) {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "请输入有效的邮箱格式!", "格式错误", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (username.trim().isEmpty() || username.contains("|") || username.contains(" ")) {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "用户名不能为空,且不能包含'|'或空格!", "格式错误", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (userManager.emailExists(email)) {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "该邮箱已被注册!", "注册失败", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (userManager.usernameExists(username)) {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "该用户名已被使用!", "注册失败", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
emailForRegistration = email;
|
|
|
|
|
usernameForRegistration = username;
|
|
|
|
|
String code = String.format("%06d", new Random().nextInt(1000000));
|
|
|
|
|
registrationCodes.put(emailForRegistration, code);
|
|
|
|
|
|
|
|
|
|
final JButton getCodeButton = registerView.getGetCodeButton();
|
|
|
|
|
getCodeButton.setEnabled(false);
|
|
|
|
|
getCodeButton.setText("发送中...");
|
|
|
|
|
|
|
|
|
|
SwingWorker<Void, Void> worker = new SwingWorker<>() {
|
|
|
|
|
private Exception mailException = null;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected Void doInBackground() {
|
|
|
|
|
try {
|
|
|
|
|
emailService.sendVerificationCode(emailForRegistration, code);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
this.mailException = e;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected void done() {
|
|
|
|
|
getCodeButton.setEnabled(true);
|
|
|
|
|
getCodeButton.setText("发送验证邮件");
|
|
|
|
|
|
|
|
|
|
if (mailException == null) {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "一封包含验证码的【电子邮件】已发送至您的邮箱,请登录邮箱查收。", "发送成功", JOptionPane.INFORMATION_MESSAGE);
|
|
|
|
|
} else {
|
|
|
|
|
String errorMessage = "邮件发送失败,请检查:\n1. 网络连接是否正常。\n2. config.properties 中的邮箱和授权码是否正确。\n\n错误详情: " + mailException.getMessage();
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, errorMessage, "发送失败", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
mailException.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
worker.execute();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handleVerifyCode() {
|
|
|
|
|
String code = registerView.getCode();
|
|
|
|
|
if (code.equals(registrationCodes.get(emailForRegistration))) {
|
|
|
|
|
mainFrame.showPanel("PASSWORD_SET");
|
|
|
|
|
} else {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "注册码错误!", "验证失败", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handleSetPassword() {
|
|
|
|
|
String pass1 = passwordSetView.getPassword();
|
|
|
|
|
String pass2 = passwordSetView.getConfirmPassword();
|
|
|
|
|
|
|
|
|
|
if (!pass1.equals(pass2)) {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "两次输入的密码不一致!", "错误", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!ValidationService.isPasswordStrong(pass1)) {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "密码必须为6-10位,且包含大小写字母和数字。", "密码格式错误", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
userManager.registerUser(emailForRegistration, usernameForRegistration, pass1, UserType.PRIMARY);
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "注册成功!请返回登录。", "成功", JOptionPane.INFORMATION_MESSAGE);
|
|
|
|
|
mainFrame.showPanel("LOGIN");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handleStartExam() {
|
|
|
|
|
UserType type = mainMenuView.getSelectedType();
|
|
|
|
|
int count = mainMenuView.getQuestionCount();
|
|
|
|
|
|
|
|
|
|
if(currentUser.getUserType() != type) {
|
|
|
|
|
currentUser.setUserType(type);
|
|
|
|
|
userManager.updateUser(currentUser);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentExam = examManager.generateExam(currentUser, type, count);
|
|
|
|
|
if (currentExam.size() < count) {
|
|
|
|
|
JOptionPane.showMessageDialog(mainFrame, "抱歉,未能生成足够数量的不重复题目,实际生成 " + currentExam.size() + " 道。", "提示", JOptionPane.WARNING_MESSAGE);
|
|
|
|
|
}
|
|
|
|
|
if (currentExam.isEmpty()) return;
|
|
|
|
|
|
|
|
|
|
currentQuestionIndex = 0;
|
|
|
|
|
score = 0;
|
|
|
|
|
userAnswers = new int[currentExam.size()];
|
|
|
|
|
Arrays.fill(userAnswers, -1); // -1 代表未作答
|
|
|
|
|
|
|
|
|
|
examView.setupForExam(currentExam.size());
|
|
|
|
|
displayCurrentQuestion();
|
|
|
|
|
mainFrame.showPanel("EXAM");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void displayCurrentQuestion() {
|
|
|
|
|
Question q = currentExam.get(currentQuestionIndex);
|
|
|
|
|
int savedAnswer = userAnswers[currentQuestionIndex];
|
|
|
|
|
examView.displayQuestion(q, currentQuestionIndex + 1, currentExam.size(), savedAnswer);
|
|
|
|
|
examView.updateNavigationButtons(currentQuestionIndex, currentExam.size());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handlePreviousQuestion() {
|
|
|
|
|
saveCurrentAnswer();
|
|
|
|
|
if (currentQuestionIndex > 0) {
|
|
|
|
|
currentQuestionIndex--;
|
|
|
|
|
displayCurrentQuestion();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handleNextQuestion() {
|
|
|
|
|
saveCurrentAnswer();
|
|
|
|
|
if (currentQuestionIndex < currentExam.size() - 1) {
|
|
|
|
|
currentQuestionIndex++;
|
|
|
|
|
displayCurrentQuestion();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void saveCurrentAnswer() {
|
|
|
|
|
int selectedIndex = examView.getSelectedIndex();
|
|
|
|
|
userAnswers[currentQuestionIndex] = selectedIndex;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handleFinishExam() {
|
|
|
|
|
saveCurrentAnswer();
|
|
|
|
|
|
|
|
|
|
int choice = JOptionPane.showConfirmDialog(mainFrame,
|
|
|
|
|
"您确定要交卷吗?\n未完成的题目将按 0 分计算。",
|
|
|
|
|
"确认交卷",
|
|
|
|
|
JOptionPane.YES_NO_OPTION);
|
|
|
|
|
|
|
|
|
|
if (choice == JOptionPane.YES_OPTION) {
|
|
|
|
|
calculateScore();
|
|
|
|
|
int finalScore = (int) Math.round((double) score / currentExam.size() * 100);
|
|
|
|
|
scoreView.setScore(finalScore, score, currentExam.size());
|
|
|
|
|
mainFrame.showPanel("SCORE");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void calculateScore() {
|
|
|
|
|
score = 0;
|
|
|
|
|
for (int i = 0; i < currentExam.size(); i++) {
|
|
|
|
|
if (userAnswers[i] != -1 && userAnswers[i] == currentExam.get(i).getCorrectOptionIndex()) {
|
|
|
|
|
score++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handleChangePassword() {
|
|
|
|
|
ChangePasswordView dialog = new ChangePasswordView(mainFrame);
|
|
|
|
|
|
|
|
|
|
dialog.getConfirmButton().addActionListener(e -> {
|
|
|
|
|
String oldPass = dialog.getOldPassword();
|
|
|
|
|
String newPass = dialog.getNewPassword();
|
|
|
|
|
String confirmPass = dialog.getConfirmPassword();
|
|
|
|
|
|
|
|
|
|
if (userManager.authenticate(currentUser.getEmail(), oldPass) == null) {
|
|
|
|
|
JOptionPane.showMessageDialog(dialog, "原密码错误!", "验证失败", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!newPass.equals(confirmPass)) {
|
|
|
|
|
JOptionPane.showMessageDialog(dialog, "两次输入的新密码不一致!", "错误", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!ValidationService.isPasswordStrong(newPass)) {
|
|
|
|
|
JOptionPane.showMessageDialog(dialog, "新密码必须为6-10位,且包含大小写字母和数字。", "密码格式错误", JOptionPane.ERROR_MESSAGE);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
currentUser.setHashedPassword(newPass);
|
|
|
|
|
userManager.updateUser(currentUser);
|
|
|
|
|
|
|
|
|
|
JOptionPane.showMessageDialog(dialog, "密码修改成功!", "成功", JOptionPane.INFORMATION_MESSAGE);
|
|
|
|
|
dialog.dispose();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
dialog.getCancelButton().addActionListener(e -> dialog.dispose());
|
|
|
|
|
dialog.setVisible(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void handleLogout() {
|
|
|
|
|
currentUser = null;
|
|
|
|
|
loginView.clearFields();
|
|
|
|
|
mainFrame.showPanel("LOGIN");
|
|
|
|
|
}
|
|
|
|
|
}
|