|
|
#include "backend_interface.h"
|
|
|
#include <random>
|
|
|
#include <algorithm>
|
|
|
#include <sstream>
|
|
|
#include <iomanip>
|
|
|
#include <curl/curl.h>
|
|
|
|
|
|
namespace exam_system {
|
|
|
|
|
|
// 常量配置
|
|
|
namespace {
|
|
|
constexpr int kVerificationCodeLength = 6;
|
|
|
constexpr int kVerificationCodeExpirySeconds = 300; // 5分钟
|
|
|
constexpr int kOptionsCount = 4;
|
|
|
|
|
|
// libcurl 写回调函数
|
|
|
size_t WriteCallback(void* contents, size_t size, size_t nmemb, std::string* response) {
|
|
|
size_t total_size = size * nmemb;
|
|
|
response->append((char*)contents, total_size);
|
|
|
return total_size;
|
|
|
}
|
|
|
} // namespace
|
|
|
|
|
|
BackendImpl::BackendImpl() {
|
|
|
// 初始化 ExamSystem
|
|
|
exam_system_ = std::make_unique<ExamSystem>();
|
|
|
|
|
|
// 配置邮箱设置
|
|
|
email_config_.smtp_server = "smtp.163.com";
|
|
|
email_config_.smtp_port = 587; // 使用STARTTLS
|
|
|
email_config_.username = "your_email@163.com";
|
|
|
email_config_.password = "your_authorization_code"; // 注意:这是授权码,不是登录密码
|
|
|
email_config_.use_curl = true;
|
|
|
|
|
|
// 初始化libcurl
|
|
|
curl_global_init(CURL_GLOBAL_DEFAULT);
|
|
|
|
|
|
Logger::Log(Logger::Level::INFO, "BackendImpl初始化完成");
|
|
|
}
|
|
|
|
|
|
BackendImpl::~BackendImpl() {
|
|
|
curl_global_cleanup();
|
|
|
}
|
|
|
|
|
|
bool BackendImpl::SendVerificationCode(const std::string& email) {
|
|
|
if (email.empty() || email.find('@') == std::string::npos) {
|
|
|
Logger::Log(Logger::Level::ERROR, "邮箱地址格式无效: " + email);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 清理过期的验证码
|
|
|
CleanExpiredVerificationCodes();
|
|
|
|
|
|
// 生成验证码
|
|
|
std::string code = GenerateVerificationCode();
|
|
|
|
|
|
// 保存验证码
|
|
|
VerificationCode vc;
|
|
|
vc.code = code;
|
|
|
vc.generate_time = std::time(nullptr);
|
|
|
vc.email = email;
|
|
|
verification_codes_[email] = vc;
|
|
|
|
|
|
// 发送邮件
|
|
|
std::string subject = "数学考试系统 - 验证码";
|
|
|
std::string body = "您的验证码是: " + code + "\n\n";
|
|
|
body += "该验证码有效期为5分钟,请尽快使用。\n";
|
|
|
body += "如果不是您本人操作,请忽略此邮件。\n\n";
|
|
|
body += "数学考试系统团队";
|
|
|
|
|
|
bool send_success = SendEmail(email, subject, body);
|
|
|
|
|
|
if (send_success) {
|
|
|
Logger::Log(Logger::Level::INFO,
|
|
|
"验证码发送成功至: " + email + ", 验证码: " + code);
|
|
|
return true;
|
|
|
} else {
|
|
|
Logger::Log(Logger::Level::ERROR, "验证码发送失败: " + email);
|
|
|
verification_codes_.erase(email);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool BackendImpl::UserRegister(const std::string& username,
|
|
|
const std::string& password,
|
|
|
const std::string& email,
|
|
|
const std::string& code) {
|
|
|
// 输入验证
|
|
|
if (username.empty() || password.empty() || email.empty() || code.empty()) {
|
|
|
Logger::Log(Logger::Level::ERROR, "注册信息不完整");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 验证邮箱验证码
|
|
|
if (!ValidateVerificationCode(email, code)) {
|
|
|
Logger::Log(Logger::Level::ERROR, "验证码错误或已过期");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 创建用户信息并添加到ExamSystem
|
|
|
UserInfo new_user;
|
|
|
new_user.username = username;
|
|
|
new_user.password = password;
|
|
|
new_user.email = email;
|
|
|
new_user.user_type = "小学"; // 默认类型
|
|
|
|
|
|
bool success = exam_system_->AddUser(new_user);
|
|
|
|
|
|
if (success) {
|
|
|
// 注册成功后清除验证码
|
|
|
verification_codes_.erase(email);
|
|
|
Logger::Log(Logger::Level::INFO,
|
|
|
"用户注册成功: " + username + ", 邮箱: " + email);
|
|
|
} else {
|
|
|
Logger::Log(Logger::Level::ERROR, "用户注册失败: " + username);
|
|
|
}
|
|
|
|
|
|
return success;
|
|
|
}
|
|
|
|
|
|
bool BackendImpl::UserLogin(const std::string& username,
|
|
|
const std::string& password) {
|
|
|
if (username.empty() || password.empty()) {
|
|
|
Logger::Log(Logger::Level::ERROR, "用户名或密码为空");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 使用ExamSystem进行登录
|
|
|
LoginRequest request;
|
|
|
request.username = username;
|
|
|
request.password = password;
|
|
|
|
|
|
LoginResponse response = exam_system_->Login(request);
|
|
|
|
|
|
if (response.success) {
|
|
|
current_user_ = username;
|
|
|
Logger::Log(Logger::Level::INFO, "用户登录成功: " + username);
|
|
|
return true;
|
|
|
} else {
|
|
|
Logger::Log(Logger::Level::ERROR,
|
|
|
"用户登录失败: " + username + ", 错误: " + response.error_message);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void BackendImpl::UserLogout() {
|
|
|
if (!current_user_.empty()) {
|
|
|
exam_system_->Logout(current_user_);
|
|
|
current_user_.clear();
|
|
|
user_questions_.erase(current_user_);
|
|
|
current_correct_answers_.clear();
|
|
|
Logger::Log(Logger::Level::INFO, "用户登出: " + current_user_);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool BackendImpl::ChangePassword(const std::string& old_password,
|
|
|
const std::string& new_password) {
|
|
|
if (current_user_.empty()) {
|
|
|
Logger::Log(Logger::Level::ERROR, "用户未登录,无法修改密码");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
if (new_password.empty()) {
|
|
|
Logger::Log(Logger::Level::ERROR, "新密码不能为空");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 使用ExamSystem修改密码
|
|
|
ChangePasswordRequest request;
|
|
|
request.username = current_user_;
|
|
|
request.old_password = old_password;
|
|
|
request.new_password = new_password;
|
|
|
|
|
|
ChangePasswordResponse response = exam_system_->ChangePassword(request);
|
|
|
|
|
|
if (response.success) {
|
|
|
Logger::Log(Logger::Level::INFO, "密码修改成功: " + current_user_);
|
|
|
return true;
|
|
|
} else {
|
|
|
Logger::Log(Logger::Level::ERROR,
|
|
|
"密码修改失败: " + current_user_ + ", 错误: " + response.error_message);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void BackendImpl::UserExit() {
|
|
|
UserLogout();
|
|
|
Logger::Log(Logger::Level::INFO, "用户退出系统");
|
|
|
}
|
|
|
|
|
|
std::vector<QuestionInfo> BackendImpl::GenerateQuestions(
|
|
|
const std::string& difficulty, int count) {
|
|
|
std::vector<QuestionInfo> questions;
|
|
|
|
|
|
if (current_user_.empty()) {
|
|
|
Logger::Log(Logger::Level::ERROR, "用户未登录,无法生成题目");
|
|
|
return questions;
|
|
|
}
|
|
|
|
|
|
// 使用ExamSystem生成题目
|
|
|
GenerateRequest request;
|
|
|
request.username = current_user_;
|
|
|
request.difficulty = difficulty;
|
|
|
request.count = count;
|
|
|
|
|
|
GenerateResponse response = exam_system_->GenerateProblems(request);
|
|
|
|
|
|
if (!response.success) {
|
|
|
Logger::Log(Logger::Level::ERROR,
|
|
|
"生成题目失败: " + response.error_message);
|
|
|
return questions;
|
|
|
}
|
|
|
|
|
|
// 转换为前端需要的格式
|
|
|
current_correct_answers_.clear();
|
|
|
|
|
|
for (size_t i = 0; i < response.problems.size(); ++i) {
|
|
|
QuestionInfo question;
|
|
|
question.id = i + 1;
|
|
|
|
|
|
// 提取题目内容(去除题号)
|
|
|
std::string problem_content = response.problems[i];
|
|
|
size_t dot_pos = problem_content.find('.');
|
|
|
if (dot_pos != std::string::npos) {
|
|
|
problem_content = problem_content.substr(dot_pos + 2); // 跳过". "
|
|
|
}
|
|
|
|
|
|
question.content = problem_content;
|
|
|
|
|
|
// 计算正确答案
|
|
|
std::string correct_answer = CalculateSimpleAnswer(problem_content);
|
|
|
current_correct_answers_.push_back(correct_answer);
|
|
|
|
|
|
// 生成选项
|
|
|
question.options = GenerateOptions(correct_answer);
|
|
|
|
|
|
// 设置正确答案索引
|
|
|
for (size_t j = 0; j < question.options.size(); ++j) {
|
|
|
if (question.options[j] == correct_answer) {
|
|
|
question.correct_answer = j;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
questions.push_back(question);
|
|
|
}
|
|
|
|
|
|
// 缓存用户题目
|
|
|
user_questions_[current_user_] = questions;
|
|
|
|
|
|
// 保存题目到文件
|
|
|
exam_system_->SaveProblems(current_user_, response.problems);
|
|
|
|
|
|
Logger::Log(Logger::Level::INFO,
|
|
|
"为用户 " + current_user_ + " 生成 " +
|
|
|
std::to_string(count) + " 道 " + difficulty + " 难度题目");
|
|
|
|
|
|
return questions;
|
|
|
}
|
|
|
|
|
|
TestResult BackendImpl::SubmitAnswers(const std::vector<int>& user_answers) {
|
|
|
TestResult result;
|
|
|
result.total_questions = 0;
|
|
|
result.correct_answers = 0;
|
|
|
result.score = 0.0;
|
|
|
result.difficulty = "unknown";
|
|
|
|
|
|
if (current_user_.empty()) {
|
|
|
Logger::Log(Logger::Level::ERROR, "用户未登录,无法提交答案");
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// 获取用户当前题目
|
|
|
auto it = user_questions_.find(current_user_);
|
|
|
if (it == user_questions_.end()) {
|
|
|
Logger::Log(Logger::Level::ERROR, "用户没有生成的题目");
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
const std::vector<QuestionInfo>& questions = it->second;
|
|
|
result.total_questions = questions.size();
|
|
|
|
|
|
if (user_answers.size() != questions.size()) {
|
|
|
Logger::Log(Logger::Level::ERROR,
|
|
|
"答案数量与题目数量不匹配: " +
|
|
|
std::to_string(user_answers.size()) + " vs " +
|
|
|
std::to_string(questions.size()));
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
// 统计正确答案
|
|
|
result.correct_answers = 0;
|
|
|
for (size_t i = 0; i < questions.size(); ++i) {
|
|
|
if (user_answers[i] >= 0 && user_answers[i] < static_cast<int>(questions[i].options.size()) &&
|
|
|
user_answers[i] == questions[i].correct_answer) {
|
|
|
++result.correct_answers;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 计算得分
|
|
|
result.score = CalculateScore(result.correct_answers, result.total_questions);
|
|
|
|
|
|
// 设置难度(从最后生成的题目中推断)
|
|
|
if (!questions.empty()) {
|
|
|
// 这里可以根据题目内容推断难度,简化实现使用primary
|
|
|
result.difficulty = "primary";
|
|
|
}
|
|
|
|
|
|
Logger::Log(Logger::Level::INFO,
|
|
|
"用户 " + current_user_ + " 提交答案,正确: " +
|
|
|
std::to_string(result.correct_answers) + "/" +
|
|
|
std::to_string(result.total_questions) +
|
|
|
", 得分: " + std::to_string(result.score));
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
std::string BackendImpl::GetCurrentUser() const {
|
|
|
return current_user_;
|
|
|
}
|
|
|
|
|
|
// ==================== 私有方法实现 ====================
|
|
|
|
|
|
bool BackendImpl::SendEmail(const std::string& recipient,
|
|
|
const std::string& subject,
|
|
|
const std::string& body) {
|
|
|
if (email_config_.use_curl) {
|
|
|
return SendEmailViaCurl(recipient, subject, body);
|
|
|
} else {
|
|
|
// 备用邮件发送方法
|
|
|
Logger::Log(Logger::Level::ERROR, "仅支持libcurl邮件发送");
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
bool BackendImpl::SendEmailViaCurl(const std::string& recipient,
|
|
|
const std::string& subject,
|
|
|
const std::string& body) {
|
|
|
CURL* curl;
|
|
|
CURLcode res = CURLE_OK;
|
|
|
struct curl_slist* recipients = nullptr;
|
|
|
std::string response_string;
|
|
|
|
|
|
curl = curl_easy_init();
|
|
|
if (!curl) {
|
|
|
Logger::Log(Logger::Level::ERROR, "libcurl初始化失败");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 设置SMTP服务器
|
|
|
curl_easy_setopt(curl, CURLOPT_URL, ("smtp://" + email_config_.smtp_server + ":" + std::to_string(email_config_.smtp_port)).c_str());
|
|
|
|
|
|
// 设置用户名和密码
|
|
|
curl_easy_setopt(curl, CURLOPT_USERNAME, email_config_.username.c_str());
|
|
|
curl_easy_setopt(curl, CURLOPT_PASSWORD, email_config_.password.c_str());
|
|
|
|
|
|
// 设置发件人和收件人
|
|
|
curl_easy_setopt(curl, CURLOPT_MAIL_FROM, email_config_.username.c_str());
|
|
|
recipients = curl_slist_append(recipients, recipient.c_str());
|
|
|
curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients);
|
|
|
|
|
|
// 设置邮件内容
|
|
|
std::string email_data =
|
|
|
"To: " + recipient + "\r\n"
|
|
|
"From: " + email_config_.username + "\r\n"
|
|
|
"Subject: " + subject + "\r\n"
|
|
|
"\r\n" + body + "\r\n";
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_READFUNCTION, [](char* buffer, size_t size, size_t nitems, void* instream) -> size_t {
|
|
|
std::string* email_data = static_cast<std::string*>(instream);
|
|
|
size_t buffer_size = size * nitems;
|
|
|
|
|
|
if (email_data->empty()) {
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
size_t copy_size = std::min(buffer_size, email_data->size());
|
|
|
memcpy(buffer, email_data->c_str(), copy_size);
|
|
|
email_data->erase(0, copy_size);
|
|
|
|
|
|
return copy_size;
|
|
|
});
|
|
|
|
|
|
curl_easy_setopt(curl, CURLOPT_READDATA, &email_data);
|
|
|
curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
|
|
|
|
|
|
// 启用TLS
|
|
|
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
|
|
|
|
|
|
// 设置服务器证书验证(生产环境应该设为1)
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
|
|
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
|
|
|
|
|
// 设置响应回调
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
|
|
|
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_string);
|
|
|
|
|
|
// 设置超时
|
|
|
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
|
|
|
|
|
// 发送邮件
|
|
|
res = curl_easy_perform(curl);
|
|
|
|
|
|
// 清理
|
|
|
curl_slist_free_all(recipients);
|
|
|
curl_easy_cleanup(curl);
|
|
|
|
|
|
if (res != CURLE_OK) {
|
|
|
Logger::Log(Logger::Level::ERROR,
|
|
|
"邮件发送失败: " + std::string(curl_easy_strerror(res)));
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
Logger::Log(Logger::Level::INFO, "邮件发送成功: " + recipient);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
std::string BackendImpl::GenerateVerificationCode() {
|
|
|
std::random_device rd;
|
|
|
std::mt19937 gen(rd());
|
|
|
std::uniform_int_distribution<int> dis(0, 9);
|
|
|
|
|
|
std::string code;
|
|
|
for (int i = 0; i < kVerificationCodeLength; ++i) {
|
|
|
code += std::to_string(dis(gen));
|
|
|
}
|
|
|
return code;
|
|
|
}
|
|
|
|
|
|
bool BackendImpl::ValidateVerificationCode(const std::string& email,
|
|
|
const std::string& code) {
|
|
|
auto it = verification_codes_.find(email);
|
|
|
if (it == verification_codes_.end()) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
const VerificationCode& vc = it->second;
|
|
|
std::time_t current_time = std::time(nullptr);
|
|
|
|
|
|
// 检查验证码是否过期
|
|
|
if (current_time - vc.generate_time > kVerificationCodeExpirySeconds) {
|
|
|
verification_codes_.erase(it);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 检查验证码是否匹配
|
|
|
if (vc.code != code) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
void BackendImpl::CleanExpiredVerificationCodes() {
|
|
|
std::time_t current_time = std::time(nullptr);
|
|
|
std::vector<std::string> expired_emails;
|
|
|
|
|
|
for (const auto& pair : verification_codes_) {
|
|
|
if (current_time - pair.second.generate_time > kVerificationCodeExpirySeconds) {
|
|
|
expired_emails.push_back(pair.first);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
for (const auto& email : expired_emails) {
|
|
|
verification_codes_.erase(email);
|
|
|
Logger::Log(Logger::Level::INFO, "清理过期验证码: " + email);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
std::string BackendImpl::CalculateSimpleAnswer(const std::string& problem) {
|
|
|
// 简化实现,根据题目类型返回示例答案
|
|
|
// 实际项目中应该使用完整的表达式计算库
|
|
|
|
|
|
if (problem.find('+') != std::string::npos) {
|
|
|
return "25";
|
|
|
} else if (problem.find('-') != std::string::npos) {
|
|
|
return "12";
|
|
|
} else if (problem.find('*') != std::string::npos) {
|
|
|
return "36";
|
|
|
} else if (problem.find('/') != std::string::npos) {
|
|
|
return "4";
|
|
|
} else if (problem.find('²') != std::string::npos) {
|
|
|
return "64";
|
|
|
} else if (problem.find('√') != std::string::npos) {
|
|
|
return "8";
|
|
|
} else if (problem.find("sin") != std::string::npos) {
|
|
|
return "0.50";
|
|
|
} else if (problem.find("cos") != std::string::npos) {
|
|
|
return "0.87";
|
|
|
} else if (problem.find("tan") != std::string::npos) {
|
|
|
return "1.00";
|
|
|
}
|
|
|
|
|
|
return "42"; // 默认答案
|
|
|
}
|
|
|
|
|
|
std::vector<std::string> BackendImpl::GenerateOptions(const std::string& correct_answer) {
|
|
|
std::vector<std::string> options(kOptionsCount);
|
|
|
|
|
|
// 设置正确答案到一个随机位置
|
|
|
std::random_device rd;
|
|
|
std::mt19937 gen(rd());
|
|
|
std::uniform_int_distribution<int> pos_dist(0, kOptionsCount - 1);
|
|
|
int correct_pos = pos_dist(gen);
|
|
|
options[correct_pos] = correct_answer;
|
|
|
|
|
|
// 生成错误答案
|
|
|
try {
|
|
|
double correct_value = std::stod(correct_answer);
|
|
|
std::uniform_real_distribution<double> offset_dist(1.0, 10.0);
|
|
|
std::uniform_int_distribution<int> sign_dist(0, 1);
|
|
|
|
|
|
for (int i = 0; i < kOptionsCount; ++i) {
|
|
|
if (i == correct_pos) continue;
|
|
|
|
|
|
double offset = offset_dist(gen);
|
|
|
if (sign_dist(gen) == 0) {
|
|
|
offset = -offset;
|
|
|
}
|
|
|
|
|
|
double wrong_value = correct_value + offset;
|
|
|
|
|
|
// 格式化输出
|
|
|
std::stringstream ss;
|
|
|
if (std::abs(wrong_value - std::round(wrong_value)) < 1e-6) {
|
|
|
ss << static_cast<int>(wrong_value);
|
|
|
} else {
|
|
|
ss << std::fixed << std::setprecision(2) << wrong_value;
|
|
|
}
|
|
|
options[i] = ss.str();
|
|
|
}
|
|
|
} catch (const std::exception& e) {
|
|
|
// 如果转换失败,使用简单选项
|
|
|
for (int i = 0; i < kOptionsCount; ++i) {
|
|
|
if (i == correct_pos) continue;
|
|
|
options[i] = "选项" + std::to_string(i + 1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return options;
|
|
|
}
|
|
|
|
|
|
double BackendImpl::CalculateScore(int correct_count, int total_count) {
|
|
|
if (total_count == 0) return 0.0;
|
|
|
return (static_cast<double>(correct_count) / total_count) * 100.0;
|
|
|
}
|
|
|
|
|
|
} // namespace exam_system
|