backend #2

Merged
hnu202326010419 merged 1 commits from zhaowenqi_branch into develop 3 months ago

@ -0,0 +1,172 @@
package mathquiz.backend;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import java.util.Random;
/**
* Email Service Class - Handles email verification code sending
*/
public class EmailService {
// Email configuration - loaded from properties file
private String smtpHost;
private String smtpPort;
private String emailUsername;
private String emailPassword;
private String fromEmail;
public EmailService() {
loadEmailConfig();
}
/**
* Load email configuration from properties file
*/
private void loadEmailConfig() {
Properties config = new Properties();
try {
config.load(new FileInputStream("email_config.properties"));
smtpHost = config.getProperty("smtp.host", "smtp.gmail.com");
smtpPort = config.getProperty("smtp.port", "587");
emailUsername = config.getProperty("email.username", "your-email@gmail.com");
emailPassword = config.getProperty("email.password", "your-app-password");
fromEmail = config.getProperty("email.from", "your-email@gmail.com");
} catch (IOException e) {
System.err.println("Warning: Could not load email configuration, using default settings");
// Use default settings
smtpHost = "smtp.gmail.com";
smtpPort = "587";
emailUsername = "your-email@gmail.com";
emailPassword = "your-app-password";
fromEmail = "your-email@gmail.com";
}
}
/**
* Send verification code to email
*/
public String sendVerificationCode(String email) throws Exception {
// Generate 6-digit numeric verification code
String verificationCode = generateVerificationCode();
// Send real email
sendRealEmail(email, verificationCode);
// Also save to file for backup verification
saveVerificationCodeToFile(email, verificationCode);
return verificationCode;
}
/**
* Generate verification code
*/
private String generateVerificationCode() {
Random random = new Random();
StringBuilder code = new StringBuilder();
for (int i = 0; i < 6; i++) {
code.append(random.nextInt(10));
}
return code.toString();
}
/**
* Send real email using SMTP protocol
*/
private void sendRealEmail(String email, String verificationCode) throws Exception {
try {
// Create email content
String subject = "Math Quiz System - Registration Verification Code";
String content = createEmailContent(verificationCode);
// Use SimpleEmailSender
SimpleEmailSender sender = new SimpleEmailSender(
smtpHost,
Integer.parseInt(smtpPort),
emailUsername,
emailPassword,
fromEmail
);
sender.sendEmail(email, subject, content);
System.out.println("Verification code sent successfully to: " + email);
System.out.println("Verification code: " + verificationCode);
} catch (Exception e) {
System.err.println("Failed to send email: " + e.getMessage());
// Fall back to console output
System.out.println("=== EMAIL VERIFICATION CODE ===");
System.out.println("To: " + email);
System.out.println("Subject: Math Quiz System - Registration Verification Code");
System.out.println("Verification Code: " + verificationCode);
System.out.println("Please check your email or use the code above.");
System.out.println("================================");
}
}
/**
* Create email content with HTML formatting
*/
private String createEmailContent(String verificationCode) {
return "<html>" +
"<head><meta charset='UTF-8'></head>" +
"<body style='font-family: Arial, sans-serif; line-height: 1.6; color: #333;'>" +
"<div style='max-width: 600px; margin: 0 auto; padding: 20px;'>" +
"<h2 style='color: #0066cc; text-align: center;'>Math Quiz System</h2>" +
"<h3 style='color: #333;'>Registration Verification</h3>" +
"<p>Hello,</p>" +
"<p>Thank you for registering with Math Quiz System. Please use the following verification code to complete your registration:</p>" +
"<div style='background-color: #f0f8ff; border: 2px solid #0066cc; border-radius: 8px; padding: 20px; text-align: center; margin: 20px 0;'>" +
"<h1 style='color: #0066cc; font-size: 32px; margin: 0; letter-spacing: 5px;'>" + verificationCode + "</h1>" +
"</div>" +
"<p><strong>Important:</strong></p>" +
"<ul>" +
"<li>This verification code will expire in 5 minutes</li>" +
"<li>Please enter this code exactly as shown</li>" +
"<li>If you did not request this code, please ignore this email</li>" +
"</ul>" +
"<p>Best regards,<br>Math Quiz System Team</p>" +
"<hr style='border: none; border-top: 1px solid #eee; margin: 20px 0;'>" +
"<p style='font-size: 12px; color: #666; text-align: center;'>This is an automated message, please do not reply.</p>" +
"</div>" +
"</body>" +
"</html>";
}
/**
* Save verification code to file (for demonstration only)
*/
private void saveVerificationCodeToFile(String email, String verificationCode) {
try {
java.nio.file.Files.createDirectories(java.nio.file.Paths.get("data"));
java.nio.file.Files.write(
java.nio.file.Paths.get("data/verification_codes.txt"),
(email + ":" + verificationCode + "\n").getBytes(),
java.nio.file.StandardOpenOption.CREATE,
java.nio.file.StandardOpenOption.APPEND
);
} catch (Exception e) {
// Ignore file save errors
}
}
/**
* Validate email format
*/
public boolean isValidEmail(String email) {
if (email == null || email.trim().isEmpty()) {
return false;
}
String trimmedEmail = email.trim();
return trimmedEmail.contains("@") &&
trimmedEmail.contains(".") &&
trimmedEmail.length() > 5;
}
}

@ -0,0 +1,58 @@
package mathquiz.backend;
/**
* Question Class - Represents a multiple choice question
*/
public class Question {
private final String question;
private final String optionA;
private final String optionB;
private final String optionC;
private final String optionD;
private final String correctAnswer;
public Question(String question, String optionA, String optionB, String optionC, String optionD, String correctAnswer) {
this.question = question;
this.optionA = optionA;
this.optionB = optionB;
this.optionC = optionC;
this.optionD = optionD;
this.correctAnswer = correctAnswer;
}
public String getQuestion() {
return question;
}
public String getOptionA() {
return optionA;
}
public String getOptionB() {
return optionB;
}
public String getOptionC() {
return optionC;
}
public String getOptionD() {
return optionD;
}
public String getCorrectAnswer() {
return correctAnswer;
}
@Override
public String toString() {
return "Question{" +
"question='" + question + '\'' +
", optionA='" + optionA + '\'' +
", optionB='" + optionB + '\'' +
", optionC='" + optionC + '\'' +
", optionD='" + optionD + '\'' +
", correctAnswer='" + correctAnswer + '\'' +
'}';
}
}

@ -0,0 +1,298 @@
package mathquiz.backend;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import mathquiz.AccountType;
import mathquiz.Deduplicator;
import mathquiz.GeneratorFactory;
import mathquiz.ProblemGenerator;
/**
* Quiz Service Class - Handles question generation and quiz logic
*/
public class QuizService {
/**
* Generate multiple choice questions
*/
public List<Question> generateQuestions(AccountType accountType, int count) {
try {
// Get question generator
ProblemGenerator generator = GeneratorFactory.getGenerator(accountType);
// Generate original questions
List<String> rawQuestions = generator.generate(count);
// Deduplication processing - use default username if current user is null
String username = getCurrentUsername();
if (username == null || username.trim().isEmpty()) {
username = "default_user";
}
Deduplicator deduplicator = new Deduplicator(username);
List<String> uniqueQuestions = deduplicator.ensureUnique(rawQuestions, count, generator);
// Convert to multiple choice questions
List<Question> questions = new ArrayList<>();
for (String questionText : uniqueQuestions) {
Question question = convertToMultipleChoice(questionText, accountType);
questions.add(question);
}
return questions;
} catch (Exception e) {
System.err.println("Error in generateQuestions: " + e.getMessage());
e.printStackTrace();
return new ArrayList<>(); // Return empty list instead of null
}
}
/**
* Convert expression questions to multiple choice questions
*/
private Question convertToMultipleChoice(String expression, AccountType accountType) {
try {
// Calculate correct answer
double correctAnswer = calculateExpression(expression, accountType);
// Handle invalid answers
if (Double.isNaN(correctAnswer) || Double.isInfinite(correctAnswer)) {
System.out.println("Invalid answer for expression: " + expression + ", using 0.0");
correctAnswer = 0.0;
}
System.out.println("Expression: " + expression + ", Answer: " + correctAnswer);
// Generate wrong options
List<Double> options = generateOptions(correctAnswer);
System.out.println("Generated " + options.size() + " wrong options: " + options);
// Randomly arrange options
Random random = new Random();
String[] optionTexts = new String[4];
String correctAnswerText = "";
// Create a list with correct answer and wrong options
List<Double> allOptions = new ArrayList<>();
allOptions.add(correctAnswer);
allOptions.addAll(options);
System.out.println("All options before shuffle: " + allOptions);
// Shuffle the options
Collections.shuffle(allOptions);
System.out.println("All options after shuffle: " + allOptions);
// Find the correct answer index after shuffling
int correctAnswerIndex = allOptions.indexOf(correctAnswer);
System.out.println("Correct answer index: " + correctAnswerIndex);
for (int i = 0; i < 4; i++) {
optionTexts[i] = formatAnswer(allOptions.get(i));
if (i == correctAnswerIndex) {
correctAnswerText = getOptionLetter(i);
}
}
System.out.println("Final options: " + java.util.Arrays.toString(optionTexts));
System.out.println("Correct answer: " + correctAnswerText);
return new Question(
"Calculate: " + expression + " = ?",
optionTexts[0], optionTexts[1], optionTexts[2], optionTexts[3],
correctAnswerText
);
} catch (Exception e) {
System.err.println("Error in convertToMultipleChoice: " + e.getMessage());
e.printStackTrace();
// Return a default question
return new Question(
"Calculate: 1 + 1 = ?",
"1", "2", "3", "4",
"B"
);
}
}
/**
* Calculate expression result
*/
private double calculateExpression(String expression, AccountType accountType) {
try {
// Handle special functions
String processedExpression = preprocessExpression(expression, accountType);
// Simple expression calculation (in actual application should use more comprehensive expression parser)
return evaluateExpression(processedExpression);
} catch (Exception e) {
// If calculation fails, return a default value
return 0.0;
}
}
/**
* Preprocess expression, handle special functions
*/
private String preprocessExpression(String expression, AccountType accountType) {
String result = expression;
// Handle square root
result = result.replaceAll("sqrt\\(([^)]+)\\)", "Math.sqrt($1)");
// Handle square
result = result.replaceAll("\\(([^)]+)\\)\\^2", "($1) * ($1)");
// Handle trigonometric functions
result = result.replaceAll("sin\\(([^)]+)\\)", "Math.sin($1)");
result = result.replaceAll("cos\\(([^)]+)\\)", "Math.cos($1)");
result = result.replaceAll("tan\\(([^)]+)\\)", "Math.tan($1)");
// Handle operators
result = result.replace("×", "*");
result = result.replace("÷", "/");
return result;
}
/**
* Simple expression calculation
*/
private double evaluateExpression(String expression) {
try {
// Simplified expression calculation, only handle basic arithmetic operations
return evaluateSimpleExpression(expression);
} catch (Exception e) {
// If calculation fails, return 0
return 0.0;
}
}
/**
* Simplified expression calculation
*/
private double evaluateSimpleExpression(String expression) {
// Implement a simple expression calculator here
// For simplification, only handle basic number operations
try {
// Remove all spaces
expression = expression.replaceAll("\\s+", "");
// Handle parentheses
while (expression.contains("(")) {
int start = expression.lastIndexOf("(");
int end = expression.indexOf(")", start);
if (end == -1) break;
String subExpr = expression.substring(start + 1, end);
double result = evaluateSimpleExpression(subExpr);
expression = expression.substring(0, start) + result + expression.substring(end + 1);
}
// Handle multiplication and division
while (expression.contains("*") || expression.contains("/")) {
expression = processOperators(expression, new String[]{"*", "/"});
}
// Handle addition and subtraction
while (expression.contains("+") || (expression.contains("-") && !expression.startsWith("-"))) {
expression = processOperators(expression, new String[]{"+", "-"});
}
return Double.parseDouble(expression);
} catch (Exception e) {
return 0.0;
}
}
/**
* Process operators
*/
private String processOperators(String expression, String[] operators) {
for (String op : operators) {
int index = expression.indexOf(op);
if (index > 0) {
// Find left operand
int leftStart = index - 1;
while (leftStart >= 0 && (Character.isDigit(expression.charAt(leftStart)) || expression.charAt(leftStart) == '.')) {
leftStart--;
}
leftStart++;
// Find right operand
int rightEnd = index + 1;
while (rightEnd < expression.length() && (Character.isDigit(expression.charAt(rightEnd)) || expression.charAt(rightEnd) == '.')) {
rightEnd++;
}
double left = Double.parseDouble(expression.substring(leftStart, index));
double right = Double.parseDouble(expression.substring(index + 1, rightEnd));
double result = 0;
switch (op) {
case "+": result = left + right; break;
case "-": result = left - right; break;
case "*": result = left * right; break;
case "/": result = right != 0 ? left / right : 0; break;
}
expression = expression.substring(0, leftStart) + result + expression.substring(rightEnd);
break;
}
}
return expression;
}
/**
* Generate wrong options
*/
private List<Double> generateOptions(double correctAnswer) {
List<Double> options = new ArrayList<>();
Random random = new Random();
// Generate 3 wrong options
for (int i = 0; i < 3; i++) {
double wrongAnswer;
do {
// Generate wrong answers near correct answer
double variation = (random.nextDouble() - 0.5) * Math.abs(correctAnswer) * 0.5;
wrongAnswer = correctAnswer + variation;
// Ensure wrong answer is not 0 and different from correct answer
if (wrongAnswer == 0) {
wrongAnswer = random.nextDouble() * 10 + 1;
}
} while (Math.abs(wrongAnswer - correctAnswer) < 0.01);
options.add(wrongAnswer);
}
return options;
}
/**
* Format answer
*/
private String formatAnswer(double answer) {
if (answer == (long) answer) {
return String.valueOf((long) answer);
} else {
return String.format("%.2f", answer);
}
}
/**
* Get option letter
*/
private String getOptionLetter(int index) {
return String.valueOf((char) ('A' + index));
}
/**
* Get current username
*/
private String getCurrentUsername() {
return mathquiz.gui.QuizSession.getInstance().getCurrentUser();
}
}

@ -0,0 +1,184 @@
package mathquiz.backend;
import java.io.*;
import java.net.*;
import javax.net.ssl.*;
/**
* Simple Email Sender using SMTP protocol
* This class provides a basic SMTP client implementation
*/
public class SimpleEmailSender {
private String smtpHost;
private int smtpPort;
private String username;
private String password;
private String fromEmail;
public SimpleEmailSender(String smtpHost, int smtpPort, String username, String password, String fromEmail) {
this.smtpHost = smtpHost;
this.smtpPort = smtpPort;
this.username = username;
this.password = password;
this.fromEmail = fromEmail;
}
/**
* Send email using SMTP protocol with SSL/TLS support
*/
public void sendEmail(String toEmail, String subject, String content) throws Exception {
Socket socket = null;
BufferedReader reader = null;
PrintWriter writer = null;
try {
System.out.println("Connecting to SMTP server: " + smtpHost + ":" + smtpPort);
// Connect to SMTP server
socket = new Socket(smtpHost, smtpPort);
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new PrintWriter(socket.getOutputStream(), true);
// Read server greeting
String response = reader.readLine();
System.out.println("Server greeting: " + response);
if (!response.startsWith("220")) {
throw new Exception("SMTP server error: " + response);
}
// Send EHLO command
String hostname = InetAddress.getLocalHost().getHostName();
if (hostname == null || hostname.isEmpty()) {
hostname = "localhost";
}
writer.println("EHLO " + hostname);
response = reader.readLine();
System.out.println("EHLO response: " + response);
if (!response.startsWith("250")) {
throw new Exception("EHLO failed: " + response);
}
// Read additional EHLO responses
while (response.startsWith("250-")) {
response = reader.readLine();
System.out.println("EHLO continuation: " + response);
}
// Start TLS if supported
writer.println("STARTTLS");
response = reader.readLine();
System.out.println("STARTTLS response: " + response);
if (response.startsWith("220")) {
// Upgrade to SSL socket
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, smtpHost, smtpPort, true);
sslSocket.startHandshake();
// Update streams to use SSL socket
reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
writer = new PrintWriter(sslSocket.getOutputStream(), true);
// Send EHLO again after TLS
writer.println("EHLO " + hostname);
response = reader.readLine();
System.out.println("EHLO after TLS: " + response);
if (!response.startsWith("250")) {
throw new Exception("EHLO after TLS failed: " + response);
}
// Read additional EHLO responses
while (response.startsWith("250-")) {
response = reader.readLine();
System.out.println("EHLO continuation after TLS: " + response);
}
}
// Authenticate using AUTH LOGIN
writer.println("AUTH LOGIN");
response = reader.readLine();
System.out.println("AUTH LOGIN response: " + response);
if (!response.startsWith("334")) {
throw new Exception("AUTH LOGIN failed: " + response);
}
// Send username (base64 encoded)
String encodedUsername = java.util.Base64.getEncoder().encodeToString(username.getBytes());
writer.println(encodedUsername);
response = reader.readLine();
System.out.println("Username auth response: " + response);
if (!response.startsWith("334")) {
throw new Exception("Username authentication failed: " + response);
}
// Send password (base64 encoded)
String encodedPassword = java.util.Base64.getEncoder().encodeToString(password.getBytes());
writer.println(encodedPassword);
response = reader.readLine();
System.out.println("Password auth response: " + response);
if (!response.startsWith("235")) {
throw new Exception("Password authentication failed: " + response);
}
// Send MAIL FROM
writer.println("MAIL FROM:<" + fromEmail + ">");
response = reader.readLine();
System.out.println("MAIL FROM response: " + response);
if (!response.startsWith("250")) {
throw new Exception("MAIL FROM failed: " + response);
}
// Send RCPT TO
writer.println("RCPT TO:<" + toEmail + ">");
response = reader.readLine();
System.out.println("RCPT TO response: " + response);
if (!response.startsWith("250")) {
throw new Exception("RCPT TO failed: " + response);
}
// Send DATA
writer.println("DATA");
response = reader.readLine();
System.out.println("DATA response: " + response);
if (!response.startsWith("354")) {
throw new Exception("DATA failed: " + response);
}
// Send email headers and content
writer.println("From: " + fromEmail);
writer.println("To: " + toEmail);
writer.println("Subject: " + subject);
writer.println("MIME-Version: 1.0");
writer.println("Content-Type: text/html; charset=UTF-8");
writer.println();
writer.println(content);
writer.println(".");
response = reader.readLine();
System.out.println("Email send response: " + response);
if (!response.startsWith("250")) {
throw new Exception("Email sending failed: " + response);
}
// Quit
writer.println("QUIT");
response = reader.readLine();
System.out.println("QUIT response: " + response);
System.out.println("Email sent successfully to: " + toEmail);
} catch (Exception e) {
System.err.println("Failed to send email: " + e.getMessage());
e.printStackTrace();
throw e;
} finally {
try {
if (writer != null) writer.close();
if (reader != null) reader.close();
if (socket != null) socket.close();
} catch (IOException e) {
// Ignore cleanup errors
}
}
}
}

@ -0,0 +1,209 @@
package mathquiz.backend;
import java.io.*;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* User Service Class - Handles user registration, login, password management
*/
public class UserService {
private static final String USERS_FILE = "data/users.txt";
private static final String PASSWORDS_FILE = "data/passwords.txt";
private Map<String, String> userPasswords;
private Map<String, String> userEmails;
public UserService() {
loadUserData();
}
/**
* User registration
*/
public void registerUser(String email, String username) throws IOException {
if (userEmails.containsValue(email)) {
throw new IllegalArgumentException("This email has already been registered");
}
userEmails.put(username, email);
// Save user data
saveUserData();
}
/**
* Set password
*/
public void setPassword(String email, String password) throws IOException {
if (!isValidPassword(password)) {
throw new IllegalArgumentException("Password does not meet requirements");
}
String username = getUsernameByEmail(email);
if (username == null) {
throw new IllegalArgumentException("User does not exist");
}
userPasswords.put(username, password);
savePasswordData();
}
/**
* User login
*/
public boolean login(String email, String password) {
String username = getUsernameByEmail(email);
if (username == null) {
return false;
}
String storedPassword = userPasswords.get(username);
if (storedPassword == null) {
return false;
}
if (Objects.equals(storedPassword, password)) {
// Set current user (use email for session)
mathquiz.gui.QuizSession.getInstance().setCurrentUser(email);
return true;
}
return false;
}
/**
* Change password
*/
public boolean changePassword(String username, String oldPassword, String newPassword) throws IOException {
if (!isValidPassword(newPassword)) {
throw new IllegalArgumentException("New password does not meet requirements");
}
String storedPassword = userPasswords.get(username);
if (!Objects.equals(storedPassword, oldPassword)) {
return false;
}
userPasswords.put(username, newPassword);
savePasswordData();
return true;
}
/**
* Validate password format
*/
private boolean isValidPassword(String password) {
if (password.length() < 6 || password.length() > 10) {
return false;
}
boolean hasUpperCase = false;
boolean hasLowerCase = false;
boolean hasDigit = false;
for (char c : password.toCharArray()) {
if (Character.isUpperCase(c)) {
hasUpperCase = true;
} else if (Character.isLowerCase(c)) {
hasLowerCase = true;
} else if (Character.isDigit(c)) {
hasDigit = true;
}
}
return hasUpperCase && hasLowerCase && hasDigit;
}
/**
* Get username by email
*/
private String getUsernameByEmail(String email) {
for (Map.Entry<String, String> entry : userEmails.entrySet()) {
if (entry.getValue().equals(email)) {
return entry.getKey();
}
}
return null;
}
/**
* Generate username
*/
private String generateUsername(String email) {
String baseName = email.split("@")[0];
String username = baseName;
int counter = 1;
while (userEmails.containsKey(username)) {
username = baseName + counter;
counter++;
}
return username;
}
/**
* Load user data
*/
private void loadUserData() {
userPasswords = new HashMap<>();
userEmails = new HashMap<>();
try {
// Create data directory
Files.createDirectories(Paths.get("data"));
// Load user email mapping
if (Files.exists(Paths.get(USERS_FILE))) {
for (String line : Files.readAllLines(Paths.get(USERS_FILE))) {
String[] parts = line.split(",");
if (parts.length == 2) {
userEmails.put(parts[0], parts[1]);
}
}
}
// Load user passwords
if (Files.exists(Paths.get(PASSWORDS_FILE))) {
for (String line : Files.readAllLines(Paths.get(PASSWORDS_FILE))) {
String[] parts = line.split(",");
if (parts.length == 2) {
userPasswords.put(parts[0], parts[1]);
}
}
}
} catch (IOException e) {
// If loading fails, use empty data
userPasswords = new HashMap<>();
userEmails = new HashMap<>();
}
}
/**
* Save user data
*/
private void saveUserData() throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(USERS_FILE))) {
for (Map.Entry<String, String> entry : userEmails.entrySet()) {
writer.write(entry.getKey() + "," + entry.getValue());
writer.newLine();
}
}
}
/**
* Save password data
*/
private void savePasswordData() throws IOException {
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(PASSWORDS_FILE))) {
for (Map.Entry<String, String> entry : userPasswords.entrySet()) {
writer.write(entry.getKey() + "," + entry.getValue());
writer.newLine();
}
}
}
}
Loading…
Cancel
Save