第一次合并 #1

Closed
hndx202314010506 wants to merge 0 commits from <deleted>:zhanghao_branch into develop

@ -1,206 +1,2 @@
# Pair-Programming
## **项目概述**
数学学习系统是一个面向小初高学生的数学学习软件,旨在通过生成针对性的数学题目帮助学生进行练习和巩固数学知识。该系统采用前后端分离架构,前端基于 Electron 构建桌面应用,后端使用 Java 提供 API 服务。
## **项目结构**
pair-programming/
├── src/
│ ├── electron-frontend/ # Electron前端
│ │ ├── resources/
│ │ │ ├── renderer/ # 渲染进程页面
│ │ │ │ ├── main.html # 主页面
│ │ │ │ ├── login.html # 登录页面
│ │ │ │ └── result.html # 结果页面
│ │ │ │ └── quiz.html # 答题页面
│ │ │ │ └── register.html # 注册页面
│ │ │ ├── main.js # 主进程代码
│ │ │ └── preload.js # 预加载脚本
│ │ ├── package.json # 前端依赖配置
│ │ └── package-lock.json # 依赖版本锁定文件
│ └── java-backend/ # Java后端
│ └── math\_learn/ # 数学学习系统后端代码
│ ├── .idea/ # IntelliJ IDEA配置
│ ├── src/ # 源代码
│ │ └── com/mathlearn/
│ │ ├── Main.java # 主程序入口
│ │ ├── generator/ # 题目生成业务
│ │ │ ├── QuestionGenerator # 题目生成器接口
│ │ │ └── SeniorHighGenerator.java # 高中题目生成器
│ │ └──model/ # 实体类
│ │ ├── api/ # API响应类
│ │ ├── exam/ # 题目试卷类
│ │ └── user/ # 用户类
│ └── .gitignore # Git忽略文件
└──doc/ #说明文档
└── README.md
## **功能说明**
**核心功能**
1.用户认证:支持用户注册、登录、退出、修改密码功能
2.题目生成:根据用户选择的学段(小学、初中、高中)生成相应难度的数学题目
3.答题系统:提供交互式答题界面
4.成绩评估:答题完成后计算并展示得分和评价
## **题目生成**
系统根据不同学段生成相应难度的题目:
高中阶段包含对数等高级数学题目
题目数量可在 10-30 之间选择
生成过程包含详细日志记录,便于调试和跟踪
## **技术栈**
前端技术
Electron用于构建跨平台桌面应用
HTML/CSS页面结构和样式
JavaScript交互逻辑
node-fetch网络请求
后端技术
Java后端服务开发
JDK 21Java 开发环境
## **环境配置与运行**
后端运行:
1.确保安装 JDK 21
2.在 IDE如 IntelliJ IDEA中打开math\_learn项目
3.运行com.mathlearn.Main主类启动后端服务
## **API 接口说明**
生成试卷
端点:/generate-exam
方法POST
参数:
  sessionId用户会话 ID
  userType用户类型小学 / 初中 / 高中)
  questionCount题目数量10-30
返回:包含试卷信息和第一题的 JSON 响应
## **退出登录**
端点:/logout
方法POST
参数:
sessionId用户会话 ID
返回:退出结果
## **注意事项**
确保后端服务在本地 8080 端口运行,否则前端无法正常获取数据
题目生成可能需要一定时间,系统设置了 15 秒超时控制
若遇到连接问题,请检查:
1.后端 Java 程序是否运行
2.网络连接是否正常
3.端口 8080 是否被占用
## **使用教程**
1.后端启动
两种路径:
1安装mathlearn\_setup.exe安装包根据指示完成下载
确保端口8080未被占用
backend目录有jar和exe两种方式启动后端服务器
2在 IDE如 IntelliJ IDEA或其他编程软件中打开math\_learn项目
确保端口8080未被占用
运行com.mathlearn.Main主类启动后端服务
2.前端运行:
安装mathlearn\_setup.exe安装包根据指示完成下载
3.根据邮箱注册自己的账号并完成登录
4\.根据用户类型选择不同的题目类型并输入所需题目数量10-30题
5\.完成答题并查看所得分数
6\.安装目录下backend/math_questions文件夹里存储用户答题数据

@ -2,16 +2,15 @@
"name": "math-learning-app",
"version": "1.0.0",
"description": "小初高数学学习软件",
"main": "resources/main.js",
"main": "src/main.js",
"scripts": {
"start": "electron .",
"dev": "electron . --dev",
"build": "electron-builder",
"dist": "electron-builder --publish=never"
"build": "electron-builder"
},
"devDependencies": {
"electron": "^22.3.27",
"electron-builder": "^24.13.3"
"electron": "^22.0.0",
"electron-builder": "^24.0.0"
},
"build": {
"appId": "com.mathlearning.app",
@ -20,33 +19,16 @@
"output": "dist"
},
"files": [
"resources/**/*",
"!resources/node_modules",
"!**/*.map",
"!**/*.log"
"src/**/*",
"styles/**/*",
"assets/**/*"
],
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64"
]
}
],
"icon": "resources/icon.ico"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "数学学习系统"
},
"compression": "maximum",
"asar": true
"target": "nsis",
"icon": "assets/icon.ico"
}
},
"dependencies": {
"node-fetch": "^2.7.0"
}
}
}

@ -4,43 +4,24 @@ const path = require('path');
let mainWindow;
function createWindow(initialPage = 'login') {
// 如果已存在窗口,先关闭
if (mainWindow) {
mainWindow.close();
mainWindow = null;
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: path.join(__dirname, 'preload.js'),
webSecurity: false,
backgroundThrottling: false
preload: path.join(__dirname, 'preload.js'), // 确保路径正确
webSecurity: false // 开发时可以关闭避免CORS问题
},
show: false,
backgroundColor: '#ffffff'
icon: path.join(__dirname, 'assets/icon.png') // 可选
});
// 在窗口准备好后显示
mainWindow.once('ready-to-show', () => {
mainWindow.show();
mainWindow.focus();
});
// 加载登录页面
mainWindow.loadFile(path.join(__dirname, 'renderer/login.html'));
// 根据参数加载不同页面
if (initialPage === 'login') {
mainWindow.loadFile(path.join(__dirname, 'renderer/login.html'));
} else {
mainWindow.loadFile(path.join(__dirname, `renderer/${initialPage}.html`));
}
// 开发时打开开发者工具
mainWindow.webContents.openDevTools();
return mainWindow;
}
function createMenu() {
@ -99,22 +80,12 @@ ipcMain.handle('api-request', async (event, { endpoint, method = 'POST', data =
}
});
// 修改导航处理 - 对于登录页面完全重新创建窗口
// 页面导航
ipcMain.handle('navigate-to', (event, page) => {
if (page === 'login') {
// 对于登录页面,完全重新创建窗口
createWindow('login');
} else {
// 其他页面使用普通导航
mainWindow.loadFile(path.join(__dirname, `renderer/${page}.html`));
}
mainWindow.loadFile(path.join(__dirname, `renderer/${page}.html`));
});
// 修复:只有一个 app.whenReady() 调用
app.whenReady().then(() => {
createWindow('login');
createMenu(); // 在这里创建菜单
});
app.whenReady().then(createWindow);
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
@ -124,6 +95,6 @@ app.on('window-all-closed', () => {
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow('login');
createWindow();
}
});

@ -1,8 +1,60 @@
const { contextBridge, ipcRenderer } = require('electron');
// 统一的会话管理器
const SessionManager = {
// 获取当前用户会话(强制从存储读取)
getCurrentUser() {
try {
const session = localStorage.getItem('userSession');
return session ? JSON.parse(session) : null;
} catch (error) {
console.error('获取用户会话失败:', error);
return null;
}
},
// 获取当前考试会话
getCurrentExam() {
try {
const exam = localStorage.getItem('currentExam');
return exam ? JSON.parse(exam) : null;
} catch (error) {
console.error('获取考试会话失败:', error);
return null;
}
},
// 清除所有会话
clearAllSessions() {
localStorage.removeItem('userSession');
localStorage.removeItem('currentExam');
localStorage.removeItem('examResult');
console.log('所有会话已清除');
},
// 验证会话是否有效
validateUserSession() {
const user = this.getCurrentUser();
if (!user || !user.sessionId) {
return false;
}
// 可以添加更多验证逻辑,比如检查过期时间等
return true;
},
// 刷新用户会话(在修改密码等操作后)
refreshUserSession(updatedUserData) {
if (updatedUserData) {
localStorage.setItem('userSession', JSON.stringify(updatedUserData));
}
}
};
// 暴露安全的API给渲染进程
contextBridge.exposeInMainWorld('electronAPI', {
// 简单的导航功能
// 页面导航
navigateTo: (page) => ipcRenderer.invoke('navigate-to', page),
// 存储会话数据
@ -17,13 +69,34 @@ contextBridge.exposeInMainWorld('electronAPI', {
removeSession: (key) => {
localStorage.removeItem(key);
},
// 新增:获取当前会话状态
getSessionState: () => {
return SessionManager.validateSession();
},
// 新增:强制清除所有会话
clearAllSessions: () => {
SessionManager.clearAllSessions();
},
// 新增会话验证方法
validateSession: () => {
return SessionManager.validateUserSession();
},
// 新增会话刷新方法
refreshSession: (userData) => {
SessionManager.refreshUserSession(userData);
}
});
// API 请求
// 直接在渲染进程中暴露 API 调用函数
contextBridge.exposeInMainWorld('api', {
request: async (endpoint, method = 'POST', data = {}) => {
try {
console.log('API Request:', endpoint, method, data);
const url = `http://localhost:8080/api${endpoint}`;
const formData = new URLSearchParams();
@ -40,8 +113,10 @@ contextBridge.exposeInMainWorld('api', {
});
const result = await response.json();
console.log('API Response:', result);
return result;
} catch (error) {
console.error('API Error:', error);
return {
success: false,
message: `网络错误: ${error.message}`,
@ -49,4 +124,6 @@ contextBridge.exposeInMainWorld('api', {
};
}
}
});
});
console.log('Preload script loaded successfully');

@ -27,12 +27,6 @@
font-size: 16px;
}
input:focus {
border-color: #007acc;
outline: none;
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2);
}
button {
width: 100%;
padding: 12px;
@ -63,10 +57,10 @@
<h2>数学试卷自动生成系统</h2>
<form id="loginForm">
<div class="form-group">
<input type="text" id="username" placeholder="用户名" required autocomplete="username">
<input type="text" id="username" placeholder="用户名" required>
</div>
<div class="form-group">
<input type="password" id="password" placeholder="密码" required autocomplete="current-password">
<input type="password" id="password" placeholder="密码" required>
</div>
<button type="submit">登录</button>
</form>
@ -75,18 +69,24 @@
</div>
<script>
// 简单的焦点设置
// 页面加载完成后自动聚焦到用户名输入框
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
const usernameInput = document.getElementById('username');
if (usernameInput) {
// 强制触发重绘
usernameInput.style.display = 'none';
usernameInput.offsetHeight; // 触发回流
usernameInput.style.display = '';
// 设置焦点
usernameInput.focus();
}
}, 100);
});
// 原有的表单提交逻辑
document.getElementById('loginForm').addEventListener('submit', async (e) => {
// 原有登录逻辑保持不变...
e.preventDefault();
const username = document.getElementById('username').value;

@ -4,274 +4,69 @@
<head>
<meta charset="UTF-8">
<title>数学学习系统 - 主页</title>
<link rel="stylesheet" href="../style/main.css">
<link rel="stylesheet" href="../style/common.css">
<style>
.main-container {
max-width: 800px;
margin: 20px auto;
padding: 0;
overflow: hidden;
}
.hero-section {
text-align: center;
padding: 40px 0 20px;
}
.welcome-text {
font-size: 1.1rem;
color: var(--light-text);
margin-bottom: 10px;
}
.user-card {
display: flex;
align-items: center;
gap: 20px;
padding: 24px;
margin-bottom: 30px;
}
.user-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
font-weight: bold;
max-width: 600px;
margin: 50px auto;
padding: 40px;
background: white;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
}
.user-info {
flex: 1;
}
.user-name {
font-size: 1.4rem;
font-weight: 700;
color: var(--dark-text);
margin-bottom: 4px;
}
.user-type {
color: var(--light-text);
font-size: 0.95rem;
}
.section {
margin-bottom: 40px;
background: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-bottom: 30px;
}
.section-header {
.btn-group {
display: flex;
align-items: center;
gap: 12px;
gap: 15px;
margin-bottom: 20px;
}
.section-icon {
width: 40px;
height: 40px;
border-radius: 12px;
background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.exam-card {
padding: 30px;
text-align: center;
}
.exam-icon {
font-size: 3rem;
margin-bottom: 16px;
}
.action-buttons {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-top: 30px;
}
/* 题目类型按钮特殊样式 */
.level-btn {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 24px 16px;
min-height: 140px;
}
.level-icon {
font-size: 2.5rem;
margin-bottom: 8px;
}
.level-title {
font-size: 1.1rem;
font-weight: 600;
}
.level-desc {
font-size: 0.85rem;
color: rgba(255, 255, 255, 0.8);
text-align: center;
}
/* 考试设置区域 */
.exam-settings {
background: linear-gradient(135deg, #f0f9ff, #e0f2fe);
padding: 30px;
border-radius: 20px;
border-left: 6px solid var(--info-color);
margin-top: 20px;
}
.settings-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
}
.settings-icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--info-color);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.5rem;
}
.input-group {
display: flex;
gap: 12px;
align-items: end;
}
.input-with-label {
flex: 1;
}
.input-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--dark-text);
margin-top: 30px;
}
/* 动画效果 */
.fade-in {
animation: fadeInUp 0.6s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
input {
width: 100%;
padding: 12px;
border: 1px solid #ddd;
border-radius: 5px;
font-size: 16px;
margin-bottom: 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="main-container card fade-in">
<!-- 顶部英雄区域 -->
<div class="hero-section">
<h1 class="page-title">数学学习系统</h1>
<p class="welcome-text">开启您的数学学习之旅</p>
</div>
<div class="main-container">
<h2>数学学习系统</h2>
<!-- 用户信息卡片 -->
<div class="user-card card">
<div class="user-avatar" id="userAvatar">
<!-- 用户首字母将通过JavaScript填充 -->
</div>
<div class="user-info">
<div class="user-name" id="userName">加载中...</div>
<div class="user-type" id="userType">用户类型</div>
</div>
</div>
<!-- 题目类型选择 -->
<div class="section">
<div class="section-header">
<div class="section-icon">📚</div>
<h2 class="section-title">选择题目类型</h2>
</div>
<div class="btn-group">
<button class="btn btn-primary level-btn" data-type="小学">
<div class="level-icon">🧒</div>
<div class="level-title">小学题目</div>
<div class="level-desc">基础算术</div>
</button>
<button class="btn btn-success level-btn" data-type="初中">
<div class="level-icon">👦</div>
<div class="level-title">初中题目</div>
<div class="level-desc">指数运算</div>
</button>
<button class="btn btn-warning level-btn" data-type="高中">
<div class="level-icon">👨‍🎓</div>
<div class="level-title">高中题目</div>
<div class="level-desc">对数与三角函数</div>
</button>
</div>
</div>
<div class="user-info" id="userInfo">
<!-- 用户信息将通过JavaScript填充 -->
</div>
<!-- 考试设置区域 -->
<div class="exam-settings" id="examSettings" style="display: none;">
<div class="settings-header">
<div class="settings-icon">📝</div>
<div>
<h3 style="margin: 0; color: var(--dark-text);">生成 <span id="selectedType"
style="color: var(--info-color);">题目</span></h3>
<p style="margin: 4px 0 0; color: var(--light-text);">自定义您的考试设置</p>
</div>
</div>
<div class="btn-group">
<button class="btn btn-primary" data-type="小学">小学题目</button>
<button class="btn btn-success" data-type="初中">初中题目</button>
<button class="btn btn-warning" data-type="高中">高中题目</button>
</div>
<div class="input-group">
<div class="input-with-label">
<label class="input-label">题目数量</label>
<input type="number" id="questionCount" class="form-control" placeholder="输入 10-30 之间的数字" min="10" max="30">
</div>
<button class="btn btn-primary" id="startExam" style="min-width: 120px;">
<span id="startExamText">开始答题</span>
<span id="startExamLoading" style="display: none;" class="loading"></span>
</button>
</div>
</div>
<div class="exam-settings" id="examSettings" style="display: none;">
<h3>生成 <span id="selectedType"></span> 题目</h3>
<input type="number" id="questionCount" placeholder="题目数量 (10-30)" min="10" max="30">
<button class="btn btn-primary" id="startExam">开始答题</button>
</div>
<!-- 操作按钮 -->
<div class="action-buttons">
<button class="btn btn-secondary" id="changePasswordBtn">
<span style="margin-right: 8px;">🔒</span>
修改密码
</button>
<button class="btn btn-danger" id="logoutBtn">
<span style="margin-right: 8px;">🚪</span>
退出登录
</button>
</div>
<div style="margin-top: 30px;">
<button class="btn btn-danger" id="logoutBtn">退出登录</button>
<button class="btn btn-warning" id="changePasswordBtn">修改密码</button>
</div>
</div>
@ -289,22 +84,17 @@
}
// 显示用户信息
document.getElementById('userName').textContent = currentUser.username;
document.getElementById('userType').textContent = `${currentUser.userType} 用户`;
document.getElementById('userAvatar').textContent = currentUser.username.charAt(0).toUpperCase();
document.getElementById('userInfo').innerHTML = `
<strong>用户名:</strong> ${currentUser.username}<br>
<strong>用户类型:</strong> ${currentUser.userType}
`;
// 绑定题目类型按钮事件
document.querySelectorAll('.level-btn').forEach(btn => {
document.querySelectorAll('.btn-group button').forEach(btn => {
btn.addEventListener('click', (e) => {
selectedType = e.currentTarget.dataset.type;
selectedType = e.target.dataset.type;
document.getElementById('selectedType').textContent = selectedType;
document.getElementById('examSettings').style.display = 'block';
// 滚动到考试设置区域
document.getElementById('examSettings').scrollIntoView({
behavior: 'smooth',
block: 'center'
});
});
});
@ -325,30 +115,20 @@
if (changePasswordBtn) {
changePasswordBtn.addEventListener('click', showChangePasswordModal);
}
// 初始化统计数据(模拟数据)
initializeStats();
});
function initializeStats() {
// 这里可以连接后端获取真实数据,目前使用模拟数据
document.getElementById('totalQuestions').textContent = '128';
document.getElementById('correctRate').textContent = '87%';
document.getElementById('studyDays').textContent = '15';
}
async function startExam() {
console.log('=== 开始答题流程开始 ===');
const questionCount = parseInt(document.getElementById('questionCount').value);
if (!selectedType) {
showNotification('请先选择题目类型', 'error');
alert('请先选择题目类型');
return;
}
if (isNaN(questionCount) || questionCount < 10 || questionCount > 30) {
showNotification('题目数量应在10-30之间', 'error');
alert('题目数量应在10-30之间');
return;
}
@ -358,175 +138,216 @@
console.log('当前用户会话:', latestUser);
if (!latestUser || !latestUser.sessionId) {
showNotification('用户会话已过期,请重新登录', 'error');
alert('用户会话已过期,请重新登录');
await window.electronAPI.navigateTo('login');
return;
}
// 显示加载状态
const startExamBtn = document.getElementById('startExam');
const startExamText = document.getElementById('startExamText');
const startExamLoading = document.getElementById('startExamLoading');
startExamText.style.display = 'none';
startExamLoading.style.display = 'inline-block';
startExamBtn.disabled = true;
const result = await window.api.request('/generate-exam', 'POST', {
console.log('开始生成试卷请求:', {
sessionId: latestUser.sessionId,
userType: selectedType,
questionCount: questionCount
});
console.log('生成试卷API响应:', result);
// 显示加载状态
const startExamBtn = document.getElementById('startExam');
const originalText = startExamBtn.textContent;
startExamBtn.textContent = '生成中...';
startExamBtn.disabled = true;
if (result.success) {
// 保存考试信息
const examData = {
examId: result.data.examId,
totalQuestions: result.data.totalQuestions,
currentQuestion: result.data.currentQuestion || 0,
userType: selectedType,
firstQuestion: {
question: result.data.question,
options: result.data.options
}
};
console.log('发送生成试卷请求...');
console.log('保存考试数据:', examData);
window.electronAPI.setSession('currentExam', examData);
// 添加超时控制
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15秒超时
showNotification('试卷生成成功!正在跳转...', 'success');
try {
const result = await window.api.request('/generate-exam', 'POST', {
sessionId: latestUser.sessionId,
userType: selectedType,
questionCount: questionCount
});
setTimeout(async () => {
await window.electronAPI.navigateTo('quiz');
}, 1000);
} else {
showNotification('生成试卷失败: ' + result.message, 'error');
if (result.message.includes('会话') || result.message.includes('登录')) {
window.electronAPI.removeSession('userSession');
await window.electronAPI.navigateTo('login');
clearTimeout(timeoutId);
console.log('生成试卷API响应:', result);
if (result.success) {
// 保存考试信息 - 确保包含所有必要字段
const examData = {
examId: result.data.examId,
totalQuestions: result.data.totalQuestions,
currentQuestion: result.data.currentQuestion || 0,
userType: selectedType,
// 保存第一题的详细信息,确保跳转后能立即显示
firstQuestion: {
question: result.data.question,
options: result.data.options
}
};
console.log('保存考试数据:', examData);
window.electronAPI.setSession('currentExam', examData);
// 确保数据保存完成后再跳转
setTimeout(async () => {
console.log('考试数据已保存,准备跳转到答题页面');
await window.electronAPI.navigateTo('quiz');
}, 100);
} else {
alert('生成试卷失败: ' + result.message);
// 如果是会话问题,清除会话
if (result.message.includes('会话') || result.message.includes('登录')) {
window.electronAPI.removeSession('userSession');
await window.electronAPI.navigateTo('login');
}
}
} catch (fetchError) {
clearTimeout(timeoutId);
throw fetchError;
}
} catch (error) {
console.error('生成试卷错误:', error);
showNotification('生成试卷时发生错误: ' + error.message, 'error');
if (error.name === 'AbortError') {
alert('请求超时!请检查:\n1. 后端Java程序是否运行\n2. 网络连接是否正常\n3. 端口8080是否被占用');
} else {
alert('生成试卷时发生错误: ' + error.message);
}
} finally {
// 恢复按钮状态
const startExamBtn = document.getElementById('startExam');
const startExamText = document.getElementById('startExamText');
const startExamLoading = document.getElementById('startExamLoading');
if (startExamBtn) {
startExamText.style.display = 'inline-block';
startExamLoading.style.display = 'none';
startExamBtn.textContent = '开始答题';
startExamBtn.disabled = false;
}
}
}
// 显示通知函数
function showNotification(message, type) {
// 移除现有通知
const existingNotification = document.querySelector('.notification');
if (existingNotification) {
existingNotification.remove();
}
console.log('=== 开始答题流程结束 ===');
}
const notification = document.createElement('div');
notification.className = `message ${type} notification`;
notification.textContent = message;
notification.style.position = 'fixed';
notification.style.top = '20px';
notification.style.right = '20px';
notification.style.zIndex = '1001';
notification.style.maxWidth = '300px';
// 网络连接测试函数
async function testBackendConnectivity() {
try {
console.log('测试后端连接...');
// 使用更简单的请求测试连接
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000);
const response = await fetch('http://localhost:8080/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'username=test&password=test',
signal: controller.signal
});
document.body.appendChild(notification);
clearTimeout(timeoutId);
// 3秒后自动移除
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateX(100%)';
setTimeout(() => notification.remove(), 300);
}, 3000);
return {
connected: true,
status: response.status
};
} catch (error) {
console.error('网络连接测试失败:', error);
return {
connected: false,
error: error.message
};
}
}
async function logout() {
if (confirm('确定要退出登录吗?')) {
try {
// 获取最新用户会话
const latestUser = window.electronAPI.getSession('userSession');
console.log('退出登录,用户信息:', latestUser);
if (latestUser && latestUser.sessionId) {
await window.api.request('/logout', 'POST', {
console.log('发送退出登录请求sessionId:', latestUser.sessionId);
const result = await window.api.request('/logout', 'POST', {
sessionId: latestUser.sessionId
});
console.log('退出登录响应:', result);
} else {
console.log('没有有效的用户会话,直接清除本地数据');
}
} catch (error) {
console.error('退出登录API错误:', error);
// 即使API失败也继续清除本地数据
} finally {
// 清除所有本地存储
window.electronAPI.removeSession('userSession');
window.electronAPI.removeSession('currentExam');
window.electronAPI.removeSession('examResult');
console.log('本地会话已清除,跳转到登录页面');
// 直接跳转
await window.electronAPI.navigateTo('login');
}
}
}
// 显示修改密码模态框(使用新的样式)
// 显示修改密码模态框
function showChangePasswordModal() {
// 创建修改密码模态框
const modalHtml = `
<div class="modal-overlay" id="passwordModal">
<div class="modal-content">
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 24px;">
<div style="width: 40px; height: 40px; border-radius: 10px; background: var(--primary-color); display: flex; align-items: center; justify-content: center; color: white;">🔒</div>
<h3 style="margin: 0; color: var(--dark-text);">修改密码</h3>
</div>
<div class="form-group">
<label class="form-label">原密码</label>
<input type="password" id="oldPassword" class="form-control" placeholder="请输入原密码">
</div>
<div class="form-group">
<label class="form-label">新密码</label>
<input type="password" id="newPassword" class="form-control" placeholder="请输入新密码">
</div>
<div class="form-group">
<label class="form-label">确认新密码</label>
<input type="password" id="confirmPassword" class="form-control" placeholder="请再次输入新密码">
</div>
<div id="passwordMessage" style="margin-bottom: 20px;"></div>
<div style="display: flex; gap: 12px;">
<button class="btn btn-primary" id="confirmChangePassword" style="flex: 1;">确认修改</button>
<button class="btn btn-secondary" id="cancelChangePassword" style="flex: 1;">取消</button>
</div>
</div>
<div id="passwordModal" style="position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:1000;">
<div style="background:white;padding:30px;border-radius:10px;width:400px;" id="passwordModalContent">
<h3>修改密码</h3>
<div class="form-group">
<input type="password" id="oldPassword" placeholder="原密码" style="width:100%;padding:10px;margin:10px 0;">
</div>
`;
<div class="form-group">
<input type="password" id="newPassword" placeholder="新密码" style="width:100%;padding:10px;margin:10px 0;">
</div>
<div class="form-group">
<input type="password" id="confirmPassword" placeholder="确认新密码" style="width:100%;padding:10px;margin:10px 0;">
</div>
<div style="display:flex;gap:10px;margin-top:20px;">
<button id="confirmChangePassword" class="btn btn-primary" style="flex:1;">确认修改</button>
<button id="cancelChangePassword" class="btn btn-secondary" style="flex:1;">取消</button>
</div>
<div id="passwordMessage" style="margin-top:10px;"></div>
</div>
</div>
`;
// 添加模态框到页面
document.body.insertAdjacentHTML('beforeend', modalHtml);
// 绑定事件
// 绑定模态框事件 - 阻止内容区域点击事件冒泡
const modalContent = document.getElementById('passwordModalContent');
modalContent.addEventListener('click', function (e) {
e.stopPropagation();
});
// 绑定按钮事件
document.getElementById('confirmChangePassword').addEventListener('click', changePassword);
document.getElementById('cancelChangePassword').addEventListener('click', closePasswordModal);
// 点击背景关闭
// 点击模态框背景关闭
document.getElementById('passwordModal').addEventListener('click', function (e) {
if (e.target === this) {
closePasswordModal();
}
});
// 自动聚焦到第一个输入框
setTimeout(() => {
document.getElementById('oldPassword').focus();
}, 100);
}
// 关闭修改密码模态框
function closePasswordModal() {
const modal = document.getElementById('passwordModal');
if (modal) {
modal.style.opacity = '0';
setTimeout(() => modal.remove(), 300);
modal.remove();
}
}
@ -536,23 +357,30 @@
const confirmPassword = document.getElementById('confirmPassword').value;
const messageEl = document.getElementById('passwordMessage');
// 清空之前的消息
messageEl.textContent = '';
messageEl.className = '';
messageEl.style.color = '';
if (!oldPassword || !newPassword || !confirmPassword) {
showMessage('请填写所有字段', 'error', messageEl);
messageEl.textContent = '请填写所有字段';
messageEl.style.color = 'red';
return;
}
if (newPassword !== confirmPassword) {
showMessage('两次输入的新密码不一致', 'error', messageEl);
messageEl.textContent = '两次输入的新密码不一致';
messageEl.style.color = 'red';
return;
}
try {
// 重新获取最新用户会话
const latestUser = window.electronAPI.getSession('userSession');
console.log('修改密码,用户信息:', latestUser);
if (!latestUser || !latestUser.sessionId) {
showMessage('用户会话已过期,请重新登录', 'error', messageEl);
messageEl.textContent = '用户会话已过期,请重新登录';
messageEl.style.color = 'red';
setTimeout(() => {
closePasswordModal();
window.electronAPI.navigateTo('login');
@ -567,23 +395,37 @@
confirmPassword: confirmPassword
});
console.log('修改密码响应:', result);
if (result.success) {
showMessage('密码修改成功', 'success', messageEl);
messageEl.textContent = '密码修改成功';
messageEl.style.color = 'green';
// 更新本地会话中的密码信息(如果需要)
// 注意:这里只更新本地显示,实际密码已在后端更新
setTimeout(() => {
closePasswordModal();
}, 1500);
}, 2000);
} else {
showMessage(result.message, 'error', messageEl);
messageEl.textContent = result.message;
messageEl.style.color = 'red';
// 如果是会话问题,清除会话
if (result.message.includes('会话') || result.message.includes('登录')) {
window.electronAPI.removeSession('userSession');
setTimeout(() => {
closePasswordModal();
window.electronAPI.navigateTo('login');
}, 2000);
}
}
} catch (error) {
showMessage('修改密码时发生错误: ' + error.message, 'error', messageEl);
console.error('修改密码错误:', error);
messageEl.textContent = '修改密码时发生错误: ' + error.message;
messageEl.style.color = 'red';
}
}
function showMessage(message, type, element) {
element.textContent = message;
element.className = `message ${type}`;
}
</script>
</body>

@ -147,7 +147,7 @@
getCodeBtn.textContent = '发送中...';
// 调用后端API获取验证码 - 使用正确的路径
const result = await window.api.request('/send-registration-code', 'POST', {
const result = await window.api.request('/api/send-registration-code', 'POST', {
email: email
});
@ -197,7 +197,7 @@
}
try {
const result = await window.api.request('/register', 'POST', {
const result = await window.api.request('/api/register', 'POST', {
email, registrationCode, username, password, confirmPassword, userType
});

@ -36,66 +36,83 @@ body {
border: 1px solid #bee5eb;
}
/* 专门修复输入框渲染问题 */
input[type="text"],
input[type="password"],
input[type="number"] {
-webkit-appearance: none !important;
appearance: none !important;
border: 1px solid #ddd !important;
background-color: white !important;
box-shadow: none !important;
outline: none !important;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000;
}
/* 强制焦点状态 */
input:focus {
border-color: #007acc !important;
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2) !important;
outline: none !important;
}
/* 修复可能的透明问题 */
input:not([disabled]):not([readonly]) {
opacity: 1 !important;
visibility: visible !important;
}
/* 强制重绘的动画 */
@keyframes forceRepaint {
0% {
opacity: 0.999;
}
100% {
opacity: 1;
}
}
.force-repaint {
animation: forceRepaint 0.001s;
}
/* 专门为模态框输入框添加样式 */
#passwordModal input[type="password"] {
-webkit-appearance: none !important;
appearance: none !important;
border: 1px solid #ddd !important;
background-color: white !important;
box-shadow: none !important;
outline: none !important;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000;
opacity: 1 !important;
visibility: visible !important;
}
#passwordModal input[type="password"]:focus {
border-color: #007acc !important;
box-shadow: 0 0 0 2px rgba(0, 122, 204, 0.2) !important;
outline: none !important;
/* 基础按钮样式 */
.btn {
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
/* 按钮交互效果 */
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.btn:active {
transform: translateY(0);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
/* 主按钮 - 用于主要操作 */
.btn-primary {
background: #007acc;
color: white;
}
.btn-primary:hover {
background: #005fa3;
}
/* 次要按钮 - 用于辅助操作 */
.btn-secondary {
background: #6c757d;
color: white;
}
.btn-secondary:hover {
background: #5a6268;
}
/* 成功按钮 - 用于提交、确认等操作 */
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover {
background: #218838;
}
/* 危险按钮 - 用于删除、退出等操作 */
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover {
background: #c82333;
}
/* 禁用状态 */
.btn:disabled {
opacity: 0.7;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
/* 图标按钮样式(如果需要) */
.btn-icon-left i {
margin-right: 8px;
}
.btn-icon-right i {
margin-left: 8px;
}

@ -1,389 +0,0 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #4f46e5;
--primary-dark: #4338ca;
--success-color: #10b981;
--warning-color: #f59e0b;
--danger-color: #ef4444;
--info-color: #3b82f6;
--light-bg: #f8fafc;
--dark-text: #1e293b;
--light-text: #64748b;
--border-color: #e2e8f0;
--shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
--shadow-lg: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
body {
font-family: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
line-height: 1.6;
color: var(--dark-text);
}
/* 容器样式 */
.container {
max-width: 1200px;
margin: 0 auto;
}
/* 卡片样式 */
.card {
background: white;
border-radius: 16px;
box-shadow: var(--shadow);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
}
.card-header {
padding: 24px;
border-bottom: 1px solid var(--border-color);
}
.card-body {
padding: 24px;
}
/* 按钮样式 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 24px;
border: none;
border-radius: 12px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
text-decoration: none;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
transition: left 0.5s;
}
.btn:hover::before {
left: 100%;
}
.btn-primary {
background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
color: white;
box-shadow: 0 4px 14px 0 rgba(79, 70, 229, 0.4);
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px 0 rgba(79, 70, 229, 0.5);
}
.btn-success {
background: linear-gradient(135deg, var(--success-color), #059669);
color: white;
box-shadow: 0 4px 14px 0 rgba(16, 185, 129, 0.4);
}
.btn-success:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px 0 rgba(16, 185, 129, 0.5);
}
.btn-warning {
background: linear-gradient(135deg, var(--warning-color), #d97706);
color: white;
box-shadow: 0 4px 14px 0 rgba(245, 158, 11, 0.4);
}
.btn-warning:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px 0 rgba(245, 158, 11, 0.5);
}
.btn-danger {
background: linear-gradient(135deg, var(--danger-color), #dc2626);
color: white;
box-shadow: 0 4px 14px 0 rgba(239, 68, 68, 0.4);
}
.btn-danger:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px 0 rgba(239, 68, 68, 0.5);
}
.btn-secondary {
background: #64748b;
color: white;
}
.btn-secondary:hover {
background: #475569;
transform: translateY(-1px);
}
/* 输入框样式 */
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: var(--dark-text);
}
.form-control {
width: 100%;
padding: 14px 16px;
border: 2px solid var(--border-color);
border-radius: 12px;
font-size: 16px;
transition: all 0.3s ease;
background: white;
color: var(--dark-text);
}
.form-control:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1);
transform: translateY(-1px);
}
/* 消息样式 */
.message {
padding: 16px;
margin-top: 20px;
border-radius: 12px;
text-align: center;
font-weight: 500;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.message.success {
background: #d1fae5;
color: #065f46;
border: 1px solid #a7f3d0;
}
.message.error {
background: #fee2e2;
color: #991b1b;
border: 1px solid #fecaca;
}
.message.info {
background: #dbeafe;
color: #1e40af;
border: 1px solid #bfdbfe;
}
/* 用户信息卡片 */
.user-card {
background: linear-gradient(135deg, #f8fafc, #e2e8f0);
padding: 24px;
border-radius: 16px;
border-left: 4px solid var(--primary-color);
margin-bottom: 30px;
}
.user-card h3 {
margin-bottom: 8px;
color: var(--dark-text);
}
.user-card p {
color: var(--light-text);
margin-bottom: 4px;
}
/* 按钮组 */
.btn-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 30px;
}
/* 模态框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(4px);
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.modal-content {
background: white;
padding: 32px;
border-radius: 20px;
box-shadow: var(--shadow-lg);
max-width: 500px;
width: 90%;
animation: scaleIn 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
/* 标题样式 */
.page-title {
font-size: 2.5rem;
font-weight: 700;
text-align: center;
margin-bottom: 30px;
background: linear-gradient(135deg, var(--primary-color), #7c3aed);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.section-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 20px;
color: var(--dark-text);
}
/* 输入框修复 */
input[type="text"],
input[type="password"],
input[type="number"],
input[type="email"],
select {
-webkit-appearance: none !important;
appearance: none !important;
border: 2px solid var(--border-color) !important;
background-color: white !important;
box-shadow: none !important;
outline: none !important;
transform: translateZ(0);
backface-visibility: hidden;
perspective: 1000;
}
input:focus,
select:focus {
border-color: var(--primary-color) !important;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important;
outline: none !important;
}
/* 加载动画 */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid rgba(255, 255, 255, .3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* 响应式设计 */
@media (max-width: 768px) {
body {
padding: 10px;
}
.btn-group {
grid-template-columns: 1fr;
}
.page-title {
font-size: 2rem;
}
.modal-content {
padding: 24px;
margin: 20px;
}
}
/* 特殊效果 */
.glass-effect {
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.18);
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(79, 70, 229, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(79, 70, 229, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(79, 70, 229, 0);
}
}

@ -1,13 +0,0 @@
<component name="ArtifactManager">
<artifact type="jar" name="math-learning-system:jar">
<output-path>$PROJECT_DIR$/../../../../../../electronPro/MATHLEARN/backend</output-path>
<root id="archive" name="math-learning-system.jar">
<element id="directory" name="META-INF">
<element id="file-copy" path="$PROJECT_DIR$/META-INF/MANIFEST.MF" />
</element>
<element id="module-output" name="math-learning-system" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/com/sun/mail/javax.mail/1.6.2/javax.mail-1.6.2.jar" path-in-jar="/" />
<element id="extracted-dir" path="$MAVEN_REPOSITORY$/javax/activation/activation/1.1.1/activation-1.1.1.jar" path-in-jar="/" />
</root>
</artifact>
</component>

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="math-learning-system" />
</profile>
</annotationProcessing>
</component>
</project>

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component>
</project>

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

@ -1,17 +0,0 @@
<component name="libraryTable">
<library name="Java EE 6-Java EE 6">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/javax.transaction.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javax.jms.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javax.servlet.jsp.jstl.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javax.ejb.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javax.resource.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javax.servlet.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javax.servlet.jsp.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javax.annotation.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javax.persistence.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES />
</library>
</component>

@ -1,13 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/math_learn.iml" filepath="$PROJECT_DIR$/math_learn.iml" />
</modules>
</component>
</project>

@ -1,3 +0,0 @@
Manifest-Version: 1.0
Main-Class: com.mathlearn.Main

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module version="4">
<component name="AdditionalModuleElements">
<content url="file://$MODULE_DIR$" dumb="true">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
</content>
</component>
</module>

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mathlearn</groupId>
<artifactId>math-learning-system</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- JavaMail -->
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
<!-- 激活框架 -->
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>9</source>
<target>9</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

@ -8,15 +8,13 @@ import com.mathlearn.model.user.UserAccount;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import java.io.*;
import java.net.InetSocketAddress;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.mail.*;
import javax.mail.internet.*;
import java.util.Properties;
// 主系统类 - 改造为->HTTP服务器
// 主系统类 - 改造为HTTP服务器
public class Main {
private Map<String, UserAccount> accounts;
private Map<String, UserAccount> pendingRegistrations; // 待完成注册的用户
@ -25,13 +23,6 @@ public class Main {
private String baseDirectory = "math_questions";
private Random random = new Random();
// 邮件配置 - 请修改为你的QQ邮箱信息
private static final String EMAIL_HOST = "smtp.qq.com";
private static final String EMAIL_PORT = "587";
private static final String EMAIL_USERNAME = "2944528150@qq.com"; // QQ邮箱
private static final String EMAIL_PASSWORD = "kxfeaqqfyxysdhfh"; // QQ邮箱授权码不是密码
private static final String EMAIL_FROM = "2944528150@qq.com"; // 发件人邮箱
public Main() {
initializeAccounts();
pendingRegistrations = new HashMap<>();
@ -112,7 +103,7 @@ public class Main {
System.out.println("注册成功: " + username);
return new ApiResponse(true, "注册成功", null);
}
//添加发送注册码的方法
// 在math_question类中添加发送注册码的方法
public ApiResponse sendRegistrationCode(String email) {
System.out.println("收到发送注册码请求,邮箱: " + email);
@ -131,19 +122,18 @@ public class Main {
// 生成注册码
String code = generateRegistrationCode(email);
// 发送邮件
boolean emailSent = sendEmail(email, "数学学习系统 - 注册验证码",
buildEmailContent(code));
// 模拟发送邮件
System.out.println("=== 邮件发送模拟 ===");
System.out.println("收件人: " + email);
System.out.println("验证码: " + code);
System.out.println("=== 邮件发送完成 ===");
if (emailSent) {
Map<String, Object> data = new HashMap<>();
data.put("email", email);
// 实际生产环境中不应该返回验证码,这里仅用于测试
data.put("debugCode", code);
return new ApiResponse(true, "验证码已发送到您的邮箱", data);
} else {
return new ApiResponse(false, "邮件发送失败,请检查邮箱地址或稍后重试", null);
}
Map<String, Object> data = new HashMap<>();
data.put("email", email);
// 注意:实际生产中不应该返回验证码,这里仅用于演示和测试
data.put("debugCode", code);
return new ApiResponse(true, "验证码已发送到您的邮箱", data);
}
// 验证邮箱格式
@ -154,75 +144,7 @@ public class Main {
}
// 构建邮件内容
private String buildEmailContent(String code) {
return "<!DOCTYPE html>" +
"<html>" +
"<head>" +
"<meta charset=\"UTF-8\">" +
"<style>" +
"body { font-family: Arial, sans-serif; background-color: #f5f5f5; padding: 20px; }" +
".container { background: white; padding: 30px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }" +
".code { font-size: 24px; color: #e74c3c; font-weight: bold; padding: 10px; background: #f8f9fa; border-radius: 5px; text-align: center; margin: 20px 0; }" +
".footer { margin-top: 20px; padding-top: 20px; border-top: 1px solid #eee; color: #666; font-size: 12px; }" +
"</style>" +
"</head>" +
"<body>" +
"<div class=\"container\">" +
"<h2>数学学习系统 - 注册验证码</h2>" +
"<p>您好!</p>" +
"<p>您正在注册数学学习系统账号,验证码为:</p>" +
"<div class=\"code\">" + code + "</div>" +
"<p>验证码有效期15分钟请尽快完成注册。</p>" +
"<p>如非本人操作,请忽略此邮件。</p>" +
"<div class=\"footer\">" +
"<p>数学学习系统团队</p>" +
"<p>此为系统邮件,请勿回复</p>" +
"</div>" +
"</div>" +
"</body>" +
"</html>";
}
// 发送邮件方法
private boolean sendEmail(String toEmail, String subject, String content) {
try {
// 配置邮件服务器属性
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", EMAIL_HOST);
props.put("mail.smtp.port", EMAIL_PORT);
props.put("mail.smtp.ssl.trust", EMAIL_HOST);
// 创建会话
Session session = Session.getInstance(props, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(EMAIL_USERNAME, EMAIL_PASSWORD);
}
});
// 创建邮件
Message message = new MimeMessage(session);
message.setFrom(new InternetAddress(EMAIL_FROM));
message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(toEmail));
message.setSubject(subject);
// 设置邮件内容为HTML格式
message.setContent(content, "text/html; charset=utf-8");
// 发送邮件
Transport.send(message);
System.out.println("邮件发送成功 - 收件人: " + toEmail);
return true;
} catch (Exception e) {
System.err.println("邮件发送失败: " + e.getMessage());
e.printStackTrace();
return false;
}
}
// 验证密码格式
private boolean isValidPassword(String password) {
@ -549,24 +471,13 @@ public class Main {
server.createContext("/api/submit-answer", new SubmitAnswerHandler());
server.createContext("/api/finish-exam", new FinishExamHandler());
server.createContext("/api/logout", new LogoutHandler());
// 添加这一行 - 注册发送注册码的端点
// 添加新的注册码相关端点
server.createContext("/api/send-registration-code", new SendRegistrationCodeHandler());
server.setExecutor(null);
server.start();
System.out.println("数学学习系统服务器已启动,端口: " + port);
System.out.println("已注册的端点:");
System.out.println(" - /api/register");
System.out.println(" - /api/login");
System.out.println(" - /api/change-password");
System.out.println(" - /api/generate-exam");
System.out.println(" - /api/get-question");
System.out.println(" - /api/submit-answer");
System.out.println(" - /api/finish-exam");
System.out.println(" - /api/logout");
System.out.println(" - /api/send-registration-code"); // 确认这个端点已注册
}
// 内部HTTP处理器类

@ -16,9 +16,8 @@ public class JuniorHighGenerator implements QuestionGenerator {
switch (type) {
case 0: // 基本四则运算(考虑优先级)
ExpressionResult exprResult = generateArithmeticQuestion();
questionText = exprResult.expression;
correctAnswerValue = exprResult.value;
questionText = generateArithmeticQuestion();
correctAnswerValue = evaluateExpression(questionText.replace(" = ?", ""));
isIntegerAnswer = (correctAnswerValue == (int)correctAnswerValue);
break;
@ -62,143 +61,48 @@ public class JuniorHighGenerator implements QuestionGenerator {
return new Question(questionText, options, correctIndex);
}
// 表达式和结果封装类
private static class ExpressionResult {
String expression;
double value;
ExpressionResult(String expression, double value) {
this.expression = expression;
this.value = value;
}
}
// 生成考虑优先级的算术题目并同时计算结果
private ExpressionResult generateArithmeticQuestion() {
// 使用预定义的简单表达式模板,确保可计算
String[][] templates = {
{"%d + %d × %d", "先乘后加"},
{"%d × %d + %d", "先乘后加"},
{"%d - %d × %d", "先乘后减"},
{"%d × %d - %d", "先乘后减"},
{"%d + %d ÷ %d", "先除后加"},
{"%d ÷ %d + %d", "先除后加"},
{"%d - %d ÷ %d", "先除后减"},
{"%d ÷ %d - %d", "先除后减"},
{"%d × (%d + %d)", "先加后乘"},
{"(%d + %d) × %d", "先加后乘"},
{"%d × (%d - %d)", "先减后乘"},
{"(%d - %d) × %d", "先减后乘"}
};
String[] template = templates[random.nextInt(templates.length)];
String pattern = template[0];
String description = template[1];
// 生成合适的数字,确保计算有效
int a, b, c = 1;
double result = 0;
// 根据模板类型生成合适的数字
if (pattern.contains("÷")) {
// 除法模板确保除数不为0且能整除
if (pattern.startsWith("%d ÷")) {
// 第一个操作数是除法确保b能整除a
b = random.nextInt(9) + 1; // 1-9
a = b * (random.nextInt(5) + 1); // a是b的倍数
c = random.nextInt(10) + 1;
result = (double)a / b + (pattern.contains("+") ? c : -c);
} else if (pattern.contains("÷ %d +") || pattern.contains("÷ %d -")) {
// 其他位置的除法确保除数不为0
a = random.nextInt(10) + 1;
b = random.nextInt(9) + 1; // 1-9
c = random.nextInt(10) + 1;
result = (double)a / b + (pattern.contains("+") ? c : -c);
} else {
// 中间的除法
a = random.nextInt(10) + 1;
b = random.nextInt(9) + 1; // 1-9
c = random.nextInt(10) + 1;
if (pattern.contains("+ %d ÷")) {
result = a + (double)b / c;
} else if (pattern.contains("- %d ÷")) {
result = a - (double)b / c;
// 生成考虑优先级的算术题目
private String generateArithmeticQuestion() {
int operandCount = random.nextInt(3) + 2; // 2-4个操作数
StringBuilder question = new StringBuilder();
List<String> tokens = new ArrayList<>();
// 生成操作数
for (int i = 0; i < operandCount; i++) {
if (i > 0) {
// 随机选择运算符,考虑优先级
String[] availableOps;
if (i == 1 && operandCount > 2) {
// 第一个运算符倾向于用乘除,确保优先级测试
availableOps = new String[]{"×", "÷", "+", "-"};
} else {
availableOps = new String[]{"+", "-", "×", "÷"};
}
String op = availableOps[random.nextInt(availableOps.length)];
tokens.add(op);
question.append(" ").append(op).append(" ");
}
} else if (pattern.contains("(")) {
// 括号模板
a = random.nextInt(10) + 1;
b = random.nextInt(10) + 1;
c = random.nextInt(10) + 1;
if (pattern.contains("× (") && pattern.contains(" + ")) {
result = a * (b + c);
} else if (pattern.contains("× (") && pattern.contains(" - ")) {
result = a * (b - c);
} else if (pattern.contains(" + ") && pattern.contains(") ×")) {
result = (a + b) * c;
} else if (pattern.contains(" - ") && pattern.contains(") ×")) {
result = (a - b) * c;
}
} else {
// 基本四则运算
a = random.nextInt(10) + 1;
b = random.nextInt(10) + 1;
c = random.nextInt(10) + 1;
// 根据运算符计算结果
if (pattern.equals("%d + %d × %d")) {
result = a + b * c;
} else if (pattern.equals("%d × %d + %d")) {
result = a * b + c;
} else if (pattern.equals("%d - %d × %d")) {
result = a - b * c;
} else if (pattern.equals("%d × %d - %d")) {
result = a * b - c;
}
}
// 如果还没有计算结果,使用安全的表达式计算
if (result == 0) {
result = safeEvaluateExpression(pattern, a, b, c);
// 生成合适的操作数
int num;
if (tokens.size() > 0 && (tokens.get(tokens.size()-1).equals("÷"))) {
// 除法:生成能整除的数
num = generateDivisibleNumber();
} else {
num = random.nextInt(30) + 1; // 1-30
}
tokens.add(String.valueOf(num));
question.append(num);
}
question.append(" = ?");
String expression = String.format(pattern, a, b, c) + " = ?";
return new ExpressionResult(expression, result);
return question.toString();
}
// 安全的表达式计算
private double safeEvaluateExpression(String pattern, int a, int b, int c) {
try {
if (pattern.equals("%d + %d × %d")) {
return a + b * c;
} else if (pattern.equals("%d × %d + %d")) {
return a * b + c;
} else if (pattern.equals("%d - %d × %d")) {
return a - b * c;
} else if (pattern.equals("%d × %d - %d")) {
return a * b - c;
} else if (pattern.equals("%d + %d ÷ %d")) {
return a + (double)b / c;
} else if (pattern.equals("%d ÷ %d + %d")) {
return (double)a / b + c;
} else if (pattern.equals("%d - %d ÷ %d")) {
return a - (double)b / c;
} else if (pattern.equals("%d ÷ %d - %d")) {
return (double)a / b - c;
} else if (pattern.equals("%d × (%d + %d)")) {
return a * (b + c);
} else if (pattern.equals("(%d + %d) × %d")) {
return (a + b) * c;
} else if (pattern.equals("%d × (%d - %d)")) {
return a * (b - c);
} else if (pattern.equals("(%d - %d) × %d")) {
return (a - b) * c;
}
} catch (Exception e) {
System.err.println("计算表达式失败: " + String.format(pattern, a, b, c) + ", 错误: " + e.getMessage());
}
return 0;
// 生成能整除的数(用于除法运算)
private int generateDivisibleNumber() {
int[] smallNumbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
return smallNumbers[random.nextInt(smallNumbers.length)];
}
// 计算简单运算(支持负数)
@ -211,8 +115,66 @@ public class JuniorHighGenerator implements QuestionGenerator {
}
}
// 计算表达式(考虑运算优先级)- 使用自定义解析器避免ScriptEngine问题
private double evaluateExpression(String expression) {
try {
// 移除空格
expression = expression.replaceAll(" ", "");
// 使用递归下降解析器计算表达式
return evaluate(expression);
} catch (Exception e) {
System.err.println("计算表达式失败: " + expression + ", 错误: " + e.getMessage());
return 0;
}
}
// 递归计算表达式
private double evaluate(String expr) {
if (expr.isEmpty()) return 0;
// 处理加减法
String[] plusMinus = expr.split("(?=[+-])", 2);
if (plusMinus.length > 1) {
double left = evaluate(plusMinus[0]);
double right = evaluate(plusMinus[1].substring(1));
return plusMinus[1].charAt(0) == '+' ? left + right : left - right;
}
// 处理乘除法
String[] multDiv = expr.split("(?=[×÷])", 2);
if (multDiv.length > 1) {
double left = evaluateTerm(multDiv[0]);
double right = evaluateTerm(multDiv[1].substring(1));
return multDiv[1].charAt(0) == '×' ? left * right : left / right;
}
// 处理基本项(数字或括号表达式)
return evaluateTerm(expr);
}
// 计算基本项
private double evaluateTerm(String term) {
if (term.startsWith("(") && term.endsWith(")")) {
return evaluate(term.substring(1, term.length() - 1));
}
// 处理平方符号
if (term.endsWith("²")) {
double base = evaluateTerm(term.substring(0, term.length() - 1));
return base * base;
}
try {
return Double.parseDouble(term);
} catch (NumberFormatException e) {
System.err.println("解析数字失败: " + term);
return 0;
}
}
private int getPerfectSquare() {
int[] perfectSquares = {1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144};
int[] perfectSquares = {1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400};
return perfectSquares[random.nextInt(perfectSquares.length)];
}
@ -228,51 +190,32 @@ public class JuniorHighGenerator implements QuestionGenerator {
used.add(correctStr);
// 生成3个错误选项
int attempts = 0;
while (options.size() < 4 && attempts < 50) {
attempts++;
while (options.size() < 4) {
double wrongValue;
String wrongStr;
// 根据正确答案调整变化范围
double baseRange = Math.max(2, Math.abs(correctAnswer) * 0.3);
double variation = (random.nextDouble() * baseRange) + 1;
boolean positive = random.nextBoolean();
wrongValue = correctAnswer + (positive ? variation : -variation);
if (isInteger) {
wrongValue = Math.round(wrongValue);
wrongStr = String.valueOf((int)wrongValue);
} else {
wrongValue = Math.round(wrongValue * 100) / 100.0;
wrongStr = String.format("%.2f", wrongValue);
}
do {
// 根据正确答案的大小调整变化范围
double range = Math.max(5, Math.abs(correctAnswer) * 0.3);
double variation = (random.nextDouble() * range) + 1;
boolean positive = random.nextBoolean();
if (!used.contains(wrongStr)) {
options.add(wrongStr);
used.add(wrongStr);
}
}
wrongValue = correctAnswer + (positive ? variation : -variation);
// 如果选项不够,添加一些固定选项
while (options.size() < 4) {
String extraOption;
if (isInteger) {
int extraValue = (int)correctAnswer + options.size() * 2 + 1;
extraOption = String.valueOf(extraValue);
} else {
double extraValue = correctAnswer + options.size() * 0.5 + 0.1;
extraOption = String.format("%.2f", extraValue);
}
if (isInteger) {
wrongValue = Math.round(wrongValue);
wrongStr = String.valueOf((int)wrongValue);
} else {
wrongValue = Math.round(wrongValue * 100) / 100.0;
wrongStr = String.format("%.2f", wrongValue);
}
} while (used.contains(wrongStr) || Double.isNaN(wrongValue) || Double.isInfinite(wrongValue));
if (!used.contains(extraOption)) {
options.add(extraOption);
used.add(extraOption);
}
options.add(wrongStr);
used.add(wrongStr);
}
Collections.shuffle(options);
return options;
}
}
}

Loading…
Cancel
Save