添加注册功能并完善邮箱验证

pull/6/head
杨默涵 2 months ago
parent eb5c8a061d
commit 4ebc155285

@ -0,0 +1,186 @@
package com.example.mathsystemtogether;
import jakarta.mail.*;
import jakarta.mail.internet.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import java.util.Random;
/**
*
*
*/
public class EmailService {
private String smtpHost;
private String smtpPort;
private String fromEmail;
private String fromPassword;
private Session session;
private boolean isConfigured = false;
public EmailService() {
loadConfiguration();
if (isConfigured) {
initializeSession();
}
}
/**
*
*/
private void loadConfiguration() {
try (InputStream input = getClass().getClassLoader().getResourceAsStream("mail.properties")) {
if (input == null) {
return;
}
Properties props = new Properties();
props.load(input);
smtpHost = props.getProperty("mail.smtp.host");
smtpPort = props.getProperty("mail.smtp.port");
fromEmail = props.getProperty("mail.from.email");
fromPassword = props.getProperty("mail.from.password");
// 检查配置是否完整
if (smtpHost != null && smtpPort != null && fromEmail != null && fromPassword != null) {
isConfigured = true;
}
} catch (IOException e) {
// 配置加载失败,静默处理
}
}
/**
*
*/
private void initializeSession() {
if (!isConfigured) {
return;
}
Properties props = new Properties();
props.put("mail.smtp.host", smtpHost);
props.put("mail.smtp.port", smtpPort);
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.ssl.trust", smtpHost);
// 创建认证器
Authenticator authenticator = new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(fromEmail, fromPassword);
}
};
session = Session.getInstance(props, authenticator);
}
/**
* 6
*/
public String generateVerificationCode() {
Random random = new Random();
return String.format("%06d", random.nextInt(1000000));
}
/**
*
* @param toEmail
* @param verificationCode
* @return
*/
public boolean sendVerificationCode(String toEmail, String verificationCode) {
if (!isConfigured) {
return false;
}
try {
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(fromEmail));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
message.setSubject("数学考试系统 - 邮箱验证码");
// 创建邮件内容
String htmlContent = createEmailContent(verificationCode);
message.setContent(htmlContent, "text/html; charset=UTF-8");
// 发送邮件
Transport.send(message);
return true;
} catch (MessagingException e) {
return false;
}
}
/**
* HTML
*/
private String createEmailContent(String verificationCode) {
StringBuilder html = new StringBuilder();
html.append("<!DOCTYPE html>");
html.append("<html>");
html.append("<head>");
html.append("<meta charset=\"UTF-8\">");
html.append("<title>邮箱验证码</title>");
html.append("<style>");
html.append("body { font-family: Arial, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px; }");
html.append(".container { max-width: 600px; margin: 0 auto; background-color: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); overflow: hidden; }");
html.append(".header { background: linear-gradient(135deg, #8A2BE2, #9932CC); color: white; padding: 30px; text-align: center; }");
html.append(".header h1 { margin: 0; font-size: 24px; }");
html.append(".content { padding: 30px; }");
html.append(".verification-code { background-color: #f8f9fa; border: 2px solid #8A2BE2; border-radius: 8px; padding: 20px; text-align: center; margin: 20px 0; }");
html.append(".code { font-size: 32px; font-weight: bold; color: #8A2BE2; letter-spacing: 5px; margin: 10px 0; }");
html.append(".footer { background-color: #f8f9fa; padding: 20px; text-align: center; color: #666; font-size: 14px; }");
html.append(".warning { background-color: #fff3cd; border: 1px solid #ffeaa7; border-radius: 5px; padding: 15px; margin: 20px 0; color: #856404; }");
html.append("</style>");
html.append("</head>");
html.append("<body>");
html.append("<div class=\"container\">");
html.append("<div class=\"header\">");
html.append("<h1>数学考试系统</h1>");
html.append("<p>邮箱验证码</p>");
html.append("</div>");
html.append("<div class=\"content\">");
html.append("<h2>您好!</h2>");
html.append("<p>您正在注册数学考试系统,请使用以下验证码完成注册:</p>");
html.append("<div class=\"verification-code\">");
html.append("<p><strong>验证码:</strong></p>");
html.append("<div class=\"code\">").append(verificationCode).append("</div>");
html.append("</div>");
html.append("<div class=\"warning\">");
html.append("<p><strong>重要提示:</strong></p>");
html.append("<ul>");
html.append("<li>验证码有效期为 <strong>5分钟</strong></li>");
html.append("<li>请勿将验证码泄露给他人</li>");
html.append("<li>如非本人操作,请忽略此邮件</li>");
html.append("</ul>");
html.append("</div>");
html.append("<p>如果您没有注册数学考试系统,请忽略此邮件。</p>");
html.append("<p>祝您使用愉快!</p>");
html.append("<p><strong>数学考试系统团队</strong></p>");
html.append("</div>");
html.append("<div class=\"footer\">");
html.append("<p>此邮件由系统自动发送,请勿回复</p>");
html.append("<p>2024 数学考试系统. All rights reserved.</p>");
html.append("</div>");
html.append("</div>");
html.append("</body>");
html.append("</html>");
return html.toString();
}
/**
*
*/
public boolean isAvailable() {
return isConfigured && session != null;
}
}

@ -0,0 +1,310 @@
package com.example.mathsystemtogether;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.stage.Stage;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import java.util.*;
import java.util.regex.Pattern;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
/**
*
*/
public class RegisterController {
@FXML private TextField usernameField;
@FXML private PasswordField passwordField;
@FXML private PasswordField confirmPasswordField;
@FXML private TextField emailField;
@FXML private TextField verificationCodeField;
@FXML private Button sendCodeButton;
@FXML private Button registerButton;
@FXML private Button backToLoginButton;
@FXML private ComboBox<String> levelComboBox;
@FXML private Label statusLabel;
// 验证码相关
private String generatedCode;
private long codeGenerationTime;
private static final int CODE_VALIDITY_MINUTES = 5;
private static final String EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$";
private static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX);
// 用户数据文件路径
private static final String USER_DATA_FILE = "user_data.txt";
// 邮件服务
private EmailService emailService;
@FXML
public void initialize() {
setupLevelComboBox();
// 初始化时禁用注册按钮
registerButton.setDisable(true);
// 初始化邮件服务
emailService = new EmailService();
// 检查邮件服务配置
if (!emailService.isAvailable()) {
showStatus("⚠️ 邮件服务未配置请检查mail.properties文件", true);
}
}
private void setupLevelComboBox() {
ObservableList<String> levels = FXCollections.observableArrayList("小学", "初中", "高中");
levelComboBox.setItems(levels);
levelComboBox.setValue("小学");
}
@FXML
private void handleSendCode() {
String email = emailField.getText().trim();
if (email.isEmpty()) {
showStatus("请输入邮箱地址", true);
return;
}
if (!isValidEmail(email)) {
showStatus("请输入有效的邮箱地址", true);
return;
}
// 检查邮件服务是否可用
if (!emailService.isAvailable()) {
showStatus("❌ 邮件服务不可用,请检查配置", true);
return;
}
// 生成6位数字验证码
generatedCode = emailService.generateVerificationCode();
codeGenerationTime = System.currentTimeMillis();
// 发送真实邮件
showStatus("📤 正在发送验证码到邮箱:" + email + ",请稍候...", false);
// 在后台线程中发送邮件
new Thread(() -> {
boolean success = emailService.sendVerificationCode(email, generatedCode);
javafx.application.Platform.runLater(() -> {
if (success) {
showStatus("✅ 验证码已发送到邮箱:" + email + ",请查收邮件", false);
// 启用注册按钮
registerButton.setDisable(false);
} else {
showStatus("❌ 邮件发送失败,请检查邮箱地址或稍后重试", true);
// 显示验证码用于调试(仅开发环境)
showStatus("❌ 邮件发送失败,请检查邮箱地址或稍后重试。验证码:" + generatedCode + "(调试用)", true);
}
});
}).start();
// 禁用发送按钮60秒
sendCodeButton.setDisable(true);
sendCodeButton.setText("60秒后可重发");
// 启动倒计时
startCountdown();
}
@FXML
private void handleRegister() {
String username = usernameField.getText().trim();
String password = passwordField.getText();
String confirmPassword = confirmPasswordField.getText();
String email = emailField.getText().trim();
String verificationCode = verificationCodeField.getText().trim();
String level = levelComboBox.getValue();
// 验证输入
if (username.isEmpty() || password.isEmpty() || confirmPassword.isEmpty() ||
email.isEmpty() || verificationCode.isEmpty()) {
showStatus("请填写所有字段", true);
return;
}
if (username.length() < 3) {
showStatus("用户名至少需要3个字符", true);
return;
}
if (password.length() < 6) {
showStatus("密码至少需要6个字符", true);
return;
}
if (!password.equals(confirmPassword)) {
showStatus("两次输入的密码不一致", true);
return;
}
if (!isValidEmail(email)) {
showStatus("请输入有效的邮箱地址", true);
return;
}
// 验证验证码
if (!verifyCode(verificationCode)) {
showStatus("验证码错误或已过期", true);
return;
}
// 检查用户名是否已存在
if (isUserExists(username)) {
showStatus("用户名已存在,请选择其他用户名", true);
return;
}
// 检查邮箱是否已注册
if (isEmailExists(email)) {
showStatus("该邮箱已被注册", true);
return;
}
// 保存用户信息
if (saveUserData(username, password, email, level)) {
showStatus("注册成功!正在跳转到登录界面...", false);
// 延迟跳转到登录界面
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
javafx.application.Platform.runLater(() -> {
handleBackToLogin();
});
}
}, 2000);
} else {
showStatus("注册失败,请重试", true);
}
}
@FXML
private void handleBackToLogin() {
try {
// 关闭当前窗口
Stage currentStage = (Stage) backToLoginButton.getScene().getWindow();
currentStage.close();
// 打开登录界面
FXMLLoader loader = new FXMLLoader(getClass().getResource("exam-view.fxml"));
Scene scene = new Scene(loader.load(), 900, 900);
Stage loginStage = new Stage();
loginStage.setTitle("数学考试系统 - 登录");
loginStage.setScene(scene);
loginStage.setResizable(true);
loginStage.show();
} catch (Exception e) {
showStatus("跳转失败:" + e.getMessage(), true);
}
}
private boolean verifyCode(String inputCode) {
if (generatedCode == null) {
return false;
}
// 检查验证码是否过期
long currentTime = System.currentTimeMillis();
long elapsedMinutes = (currentTime - codeGenerationTime) / (1000 * 60);
if (elapsedMinutes > CODE_VALIDITY_MINUTES) {
return false;
}
return generatedCode.equals(inputCode);
}
private boolean isValidEmail(String email) {
return EMAIL_PATTERN.matcher(email).matches();
}
private boolean isUserExists(String username) {
try {
if (!Files.exists(Paths.get(USER_DATA_FILE))) {
return false;
}
List<String> lines = Files.readAllLines(Paths.get(USER_DATA_FILE));
for (String line : lines) {
if (line.startsWith(username + "|")) {
return true;
}
}
} catch (IOException e) {
System.err.println("检查用户存在性时出错:" + e.getMessage());
}
return false;
}
private boolean isEmailExists(String email) {
try {
if (!Files.exists(Paths.get(USER_DATA_FILE))) {
return false;
}
List<String> lines = Files.readAllLines(Paths.get(USER_DATA_FILE));
for (String line : lines) {
String[] parts = line.split("\\|");
if (parts.length >= 3 && parts[2].equals(email)) {
return true;
}
}
} catch (IOException e) {
System.err.println("检查邮箱存在性时出错:" + e.getMessage());
}
return false;
}
private boolean saveUserData(String username, String password, String email, String level) {
try {
String userData = username + "|" + password + "|" + email + "|" + level + "|" + System.currentTimeMillis() + "\n";
Files.write(Paths.get(USER_DATA_FILE), userData.getBytes(), StandardOpenOption.CREATE, StandardOpenOption.APPEND);
return true;
} catch (IOException e) {
System.err.println("保存用户数据时出错:" + e.getMessage());
return false;
}
}
private void showStatus(String message, boolean isError) {
statusLabel.setText(message);
if (isError) {
statusLabel.setStyle("-fx-text-fill: #DC143C; -fx-font-weight: bold; -fx-font-size: 14;");
} else {
statusLabel.setStyle("-fx-text-fill: #228B22; -fx-font-weight: bold; -fx-font-size: 14;");
}
}
private void startCountdown() {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
int countdown = 60;
@Override
public void run() {
javafx.application.Platform.runLater(() -> {
if (countdown > 0) {
sendCodeButton.setText(countdown + "秒后可重发");
countdown--;
} else {
sendCodeButton.setDisable(false);
sendCodeButton.setText("📤 发送验证码");
timer.cancel();
}
});
}
}, 0, 1000);
}
}

@ -0,0 +1,131 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.Font?>
<?import javafx.scene.effect.DropShadow?>
<?import javafx.scene.paint.LinearGradient?>
<?import javafx.scene.paint.Stop?>
<BorderPane xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.example.mathsystemtogether.RegisterController" style="-fx-background-color: linear-gradient(to bottom, #6A0DAD, #4B0082, #2E0854);">
<top>
<VBox spacing="15.0" style="-fx-background-color: linear-gradient(to right, #8A2BE2, #9932CC, #8B008B); -fx-background-radius: 15; -fx-padding: 20;">
<padding>
<Insets bottom="15.0" left="20.0" right="20.0" top="20.0"/>
</padding>
<Label text="🎓 用户注册" textFill="white" textAlignment="CENTER" style="-fx-effect: dropshadow(gaussian, rgba(0,0,0,0.5), 10, 0, 0, 2);">
<font>
<Font name="System Bold" size="28.0"/>
</font>
</Label>
<Label text="✨ 创建新账户 · 邮箱验证 · 安全可靠" textFill="#E6E6FA" textAlignment="CENTER">
<font>
<Font name="System" size="14.0"/>
</font>
</Label>
</VBox>
</top>
<center>
<VBox spacing="10.0">
<padding>
<Insets bottom="10.0" left="15.0" right="15.0" top="10.0"/>
</padding>
<!-- 注册区域 -->
<VBox spacing="15.0" style="-fx-background-color: linear-gradient(to bottom, #E6E6FA, #D8BFD8, #DDA0DD); -fx-padding: 20; -fx-background-radius: 15; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.3), 15, 0, 0, 5);">
<Label text="📝 填写注册信息" textFill="#4B0082" textAlignment="CENTER">
<font>
<Font name="System Bold" size="18.0"/>
</font>
</Label>
<!-- 用户名输入 -->
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="👤 用户名:" minWidth="100.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<TextField fx:id="usernameField" promptText="请输入用户名" prefWidth="200.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<!-- 密码输入 -->
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="🔑 密码:" minWidth="100.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<PasswordField fx:id="passwordField" promptText="请输入密码" prefWidth="200.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<!-- 确认密码输入 -->
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="🔒 确认密码:" minWidth="100.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<PasswordField fx:id="confirmPasswordField" promptText="请再次输入密码" prefWidth="200.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<!-- 邮箱输入 -->
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="📧 邮箱:" minWidth="100.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<TextField fx:id="emailField" promptText="请输入邮箱地址" prefWidth="200.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
</HBox>
<!-- 验证码输入 -->
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="🔢 验证码:" minWidth="100.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<TextField fx:id="verificationCodeField" promptText="请输入验证码" prefWidth="120.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2; -fx-padding: 8;"/>
<Button fx:id="sendCodeButton" text="📤 发送验证码" onAction="#handleSendCode" style="-fx-background-color: linear-gradient(to right, #32CD32, #228B22); -fx-text-fill: white; -fx-background-radius: 8; -fx-padding: 8 15; -fx-font-size: 12; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 3, 0, 0, 1);"/>
</HBox>
<!-- 难度级别选择 -->
<HBox spacing="15.0" alignment="CENTER_LEFT">
<Label text="📚 难度级别:" minWidth="100.0" textFill="#6A0DAD">
<font>
<Font name="System Bold" size="14.0"/>
</font>
</Label>
<ComboBox fx:id="levelComboBox" prefWidth="140.0" style="-fx-background-color: white; -fx-background-radius: 8; -fx-border-color: #8A2BE2; -fx-border-radius: 8; -fx-border-width: 2;"/>
</HBox>
<!-- 按钮区域 -->
<HBox spacing="15.0" alignment="CENTER">
<Button fx:id="registerButton" text="🚀 注册" onAction="#handleRegister" style="-fx-background-color: linear-gradient(to right, #8A2BE2, #9932CC); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 12 25; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
<Button fx:id="backToLoginButton" text="🔙 返回登录" onAction="#handleBackToLogin" style="-fx-background-color: linear-gradient(to right, #DC143C, #B22222); -fx-text-fill: white; -fx-background-radius: 10; -fx-padding: 12 25; -fx-font-size: 14; -fx-font-weight: bold; -fx-effect: dropshadow(gaussian, rgba(0,0,0,0.3), 5, 0, 0, 2);"/>
</HBox>
<!-- 状态标签 -->
<Label fx:id="statusLabel" textFill="#8B0000" style="-fx-font-weight: bold; -fx-font-size: 14;" textAlignment="CENTER"/>
</VBox>
<!-- 注册说明 -->
<VBox spacing="15.0" style="-fx-background-color: linear-gradient(to bottom, #F8F0FF, #E6E6FA, #D8BFD8); -fx-padding: 20; -fx-background-radius: 15; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.2), 10, 0, 0, 3);">
<Label text="📖 注册说明" textFill="#4B0082" textAlignment="CENTER">
<font>
<Font name="System Bold" size="16.0"/>
</font>
</Label>
<VBox spacing="8.0" style="-fx-padding: 10; -fx-background-color: rgba(255,255,255,0.8); -fx-background-radius: 10; -fx-effect: dropshadow(gaussian, rgba(139,0,139,0.1), 5, 0, 0, 2);">
<Label text="① 填写用户名、密码和邮箱信息" textFill="#6A0DAD" style="-fx-font-weight: bold;"/>
<Label text="② 点击'发送验证码'获取邮箱验证码" textFill="#6A0DAD" style="-fx-font-weight: bold;"/>
<Label text="③ 输入收到的验证码完成验证" textFill="#6A0DAD" style="-fx-font-weight: bold;"/>
<Label text="④ 选择适合的难度级别" textFill="#6A0DAD" style="-fx-font-weight: bold;"/>
<Label text="⑤ 点击'注册'完成账户创建" textFill="#6A0DAD" style="-fx-font-weight: bold;"/>
</VBox>
</VBox>
</VBox>
</center>
</BorderPane>

@ -0,0 +1,43 @@
# 邮件服务配置
# QQ邮箱配置
# QQ邮箱SMTP配置
mail.smtp.host=smtp.qq.com
mail.smtp.port=587
mail.smtp.auth=true
mail.smtp.starttls.enable=true
mail.smtp.ssl.trust=smtp.qq.com
# 发送方邮箱配置
mail.from.email=1961004835@qq.com
mail.from.password=nixyzbpbcrekhgfe
# 其他邮箱服务商配置示例:
# Gmail配置
# mail.smtp.host=smtp.gmail.com
# mail.smtp.port=587
# mail.smtp.auth=true
# mail.smtp.starttls.enable=true
# mail.smtp.ssl.trust=smtp.gmail.com
# 163邮箱配置
# mail.smtp.host=smtp.163.com
# mail.smtp.port=587
# mail.smtp.auth=true
# mail.smtp.starttls.enable=true
# mail.smtp.ssl.trust=smtp.163.com
# 126邮箱配置
# mail.smtp.host=smtp.126.com
# mail.smtp.port=587
# mail.smtp.auth=true
# mail.smtp.starttls.enable=true
# mail.smtp.ssl.trust=smtp.126.com
# 企业邮箱配置示例
# mail.smtp.host=smtp.exmail.qq.com
# mail.smtp.port=587
# mail.smtp.auth=true
# mail.smtp.starttls.enable=true
# mail.smtp.ssl.trust=smtp.exmail.qq.com

@ -0,0 +1 @@
wgll|123456|ymhlovesLQX@163.com|小学|1760162416490
Loading…
Cancel
Save