/* * 增强版考勤服务器 - 支持AES加密数据 * * 功能: * 1. HTTP端口8082 - 接收STM32加密签到数据 * 2. HTTP API - 提供Qt客户端接口 * 3. AES-128-CBC解密支持 * * 编译:g++ -o server_encrypted.exe server_encrypted.cpp -lws2_32 -lcrypto -lssl -std=c++11 */ #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #include #include #include #pragma comment(lib, "ws2_32.lib") typedef int socklen_t; #else #include #include #include #include #include #include #include #define SOCKET int #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #define closesocket(s) close(s) #define _mkdir(d) mkdir(d, 0755) #endif // OpenSSL库用于AES解密和Base64 #include #include #include #include const int HTTP_PORT = 8082; const int BUFFER_SIZE = 4096; const std::string LOG_FILE = "attendance.log"; const std::string DB_FILE = "attendance_database.csv"; const std::string TIMESLOTS_FILE = "timeslots_config.json"; // 时间段配置文件 const std::string MAPPING_CONFIG_FILE = "mapping_config.json"; // 映射配置文件 const std::string FACE_ID_MAPPING_FILE = "face_id_mapping.json"; // 人脸ID映射文件 const std::string FACES_DIR = "faces"; // 人脸照片目录 const std::string PENDING_DELETE_FILE = "pending_delete.json"; // 待删除人脸列表 const std::string SYNC_REQUEST_FILE = "sync_request.json"; // K210同步请求标志文件 // ⚠️ 重要:密钥和IV必须与STM32端完全一致! static const unsigned char MY_AES_KEY[16] = { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; static const unsigned char MY_AES_IV[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f }; // ============= Base64解码 ============= std::string base64_decode(const std::string& encoded_string) { BIO *bio, *b64; int decode_len = encoded_string.length(); char* buffer = new char[decode_len]; bio = BIO_new_mem_buf(encoded_string.c_str(), -1); b64 = BIO_new(BIO_f_base64()); bio = BIO_push(b64, bio); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); int length = BIO_read(bio, buffer, decode_len); BIO_free_all(bio); std::string result(buffer, length); delete[] buffer; return result; } // ============= 添加PKCS#7填充 ============= std::string add_pkcs7_padding(const std::string& data) { size_t padding_len = 16 - (data.length() % 16); std::string result = data; for (size_t i = 0; i < padding_len; i++) { result += (char)padding_len; } return result; } // ============= 手动移除PKCS#7填充 ============= std::string remove_pkcs7_padding(const std::string& data) { if (data.empty()) return ""; // 获取最后一个字节(填充长度) unsigned char padding_len = (unsigned char)data[data.length() - 1]; // 验证填充长度 if (padding_len < 1 || padding_len > 16 || padding_len > data.length()) { std::cerr << "[CRYPTO] Invalid padding length: " << (int)padding_len << std::endl; return data; // 返回原数据 } // 验证所有填充字节都相同 for (size_t i = data.length() - padding_len; i < data.length(); i++) { if ((unsigned char)data[i] != padding_len) { std::cerr << "[CRYPTO] Invalid padding at position " << i << std::endl; return data; // 返回原数据 } } // 移除填充 return data.substr(0, data.length() - padding_len); } // ============= AES-CBC加密 (用于响应Qt客户端) ============= std::string aes_encrypt_cbc(const std::string& plaintext) { // 添加PKCS#7填充 std::string padded = add_pkcs7_padding(plaintext); EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) { std::cerr << "[CRYPTO] Failed to create context" << std::endl; return ""; } // 初始化加密 if (EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, MY_AES_KEY, MY_AES_IV) != 1) { std::cerr << "[CRYPTO] EncryptInit failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return ""; } // 禁用自动填充(我们已手动添加) EVP_CIPHER_CTX_set_padding(ctx, 0); // 加密数据 std::vector ciphertext(padded.length() + 16); int len; int ciphertext_len = 0; if (EVP_EncryptUpdate(ctx, ciphertext.data(), &len, (unsigned char*)padded.c_str(), padded.length()) != 1) { std::cerr << "[CRYPTO] EncryptUpdate failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return ""; } ciphertext_len = len; // 完成加密 if (EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len) != 1) { std::cerr << "[CRYPTO] EncryptFinal failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return ""; } ciphertext_len += len; EVP_CIPHER_CTX_free(ctx); return std::string((char*)ciphertext.data(), ciphertext_len); } // ============= AES-CBC解密 (使用OpenSSL标准库) ============= std::string aes_decrypt_cbc(const std::string& encrypted_data) { if (encrypted_data.empty() || encrypted_data.length() % 16 != 0) { std::cerr << "[CRYPTO] Invalid encrypted data length: " << encrypted_data.length() << std::endl; return ""; } EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); if (!ctx) { std::cerr << "[CRYPTO] Failed to create context" << std::endl; return ""; } // 初始化解密(使用标准AES-128-CBC) if (EVP_DecryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, MY_AES_KEY, MY_AES_IV) != 1) { std::cerr << "[CRYPTO] DecryptInit failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return ""; } // 禁用自动填充,我们手动处理PKCS#7 EVP_CIPHER_CTX_set_padding(ctx, 0); // 解密数据 std::vector plaintext(encrypted_data.length() + 16); int len; int plaintext_len = 0; if (EVP_DecryptUpdate(ctx, plaintext.data(), &len, (unsigned char*)encrypted_data.c_str(), encrypted_data.length()) != 1) { std::cerr << "[CRYPTO] DecryptUpdate failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return ""; } plaintext_len = len; // 完成解密 if (EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len) != 1) { std::cerr << "[CRYPTO] DecryptFinal failed" << std::endl; EVP_CIPHER_CTX_free(ctx); return ""; } plaintext_len += len; EVP_CIPHER_CTX_free(ctx); std::string result((char*)plaintext.data(), plaintext_len); // 手动移除PKCS#7填充 result = remove_pkcs7_padding(result); return result; } // ============= 十六进制输出辅助函数 ============= void print_hex(const std::string& label, const unsigned char* data, size_t len) { std::cout << label << " (" << len << " bytes): "; for (size_t i = 0; i < len && i < 32; i++) { printf("%02x ", data[i]); } if (len > 32) std::cout << "..."; std::cout << std::endl; } // ============= Base64编码 ============= std::string base64_encode(const std::string& input) { BIO *bio, *b64; BUF_MEM *bufferPtr; b64 = BIO_new(BIO_f_base64()); bio = BIO_new(BIO_s_mem()); bio = BIO_push(b64, bio); BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL); BIO_write(bio, input.c_str(), input.length()); BIO_flush(bio); BIO_get_mem_ptr(bio, &bufferPtr); std::string result(bufferPtr->data, bufferPtr->length); BIO_free_all(bio); return result; } // ============= 加密JSON数据(用于响应Qt客户端)============= std::string encryptJSON(const std::string& json_data) { std::cout << "[CRYPTO] Encrypting response..." << std::endl; // AES加密 std::string encrypted = aes_encrypt_cbc(json_data); if (encrypted.empty()) { std::cerr << "[CRYPTO] Encryption failed!" << std::endl; return ""; } // Base64编码 std::string encrypted_base64 = base64_encode(encrypted); std::cout << "[CRYPTO] ✓ Response encrypted" << std::endl; return encrypted_base64; } // ============= 解密JSON数据 ============= std::string decryptJSON(const std::string& encrypted_base64) { try { std::cout << "[CRYPTO] Decrypting data..." << std::endl; std::cout << "[CRYPTO] Base64 input: " << encrypted_base64 << std::endl; std::cout << "[CRYPTO] Base64 length: " << encrypted_base64.length() << std::endl; // 1. Base64解码 std::string encrypted_binary = base64_decode(encrypted_base64); std::cout << "[CRYPTO] Binary length: " << encrypted_binary.length() << std::endl; print_hex("[CRYPTO] Encrypted binary", (unsigned char*)encrypted_binary.c_str(), encrypted_binary.length()); // 调试:打印密钥和IV print_hex("[CRYPTO] Using KEY", MY_AES_KEY, 16); print_hex("[CRYPTO] Using IV", MY_AES_IV, 16); // 2. AES解密 std::string decrypted = aes_decrypt_cbc(encrypted_binary); if (decrypted.empty()) { std::cerr << "[CRYPTO] Decryption failed!" << std::endl; return ""; } std::cout << "[CRYPTO] ✓ Decryption successful" << std::endl; std::cout << "[CRYPTO] Decrypted length: " << decrypted.length() << std::endl; print_hex("[CRYPTO] Decrypted hex", (unsigned char*)decrypted.c_str(), decrypted.length()); std::cout << "[CRYPTO] Decrypted text: " << decrypted << std::endl; return decrypted; } catch (const std::exception& e) { std::cerr << "[CRYPTO] Exception: " << e.what() << std::endl; return ""; } } // ============= 工具函数 ============= std::string getCurrentTimestamp() { time_t now = time(0); tm ltm; #ifdef _WIN32 localtime_s(<m, &now); #else localtime_r(&now, <m); #endif char buffer[80]; strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", <m); return std::string(buffer); } // 清理字符串中的控制字符(\r, \n等) std::string cleanString(const std::string& str) { std::string result; result.reserve(str.length()); for (char c : str) { // 只保留可打印字符和空格,过滤掉\r\n\t等控制字符 if (c >= 32 && c <= 126) { result += c; } } return result; } std::string urlDecode(const std::string& str) { std::string result; for (size_t i = 0; i < str.length(); ++i) { if (str[i] == '%' && i + 2 < str.length()) { int value; std::istringstream is(str.substr(i + 1, 2)); if (is >> std::hex >> value) { result += static_cast(value); i += 2; } else { result += str[i]; } } else if (str[i] == '+') { result += ' '; } else { result += str[i]; } } return result; } std::map parseQueryParams(const std::string& query) { std::map params; std::istringstream stream(query); std::string pair; while (std::getline(stream, pair, '&')) { size_t pos = pair.find('='); if (pos != std::string::npos) { std::string key = urlDecode(pair.substr(0, pos)); std::string value = urlDecode(pair.substr(pos + 1)); params[key] = value; } } return params; } // 简单的JSON解析 std::map parseJSON(const std::string& json) { std::map result; std::string content = json; size_t start = content.find('{'); size_t end = content.rfind('}'); if (start != std::string::npos && end != std::string::npos) { content = content.substr(start + 1, end - start - 1); } std::istringstream ss(content); std::string token; while (std::getline(ss, token, ',')) { size_t colon = token.find(':'); if (colon != std::string::npos) { std::string key = token.substr(0, colon); std::string value = token.substr(colon + 1); // 移除引号和空格 key.erase(std::remove_if(key.begin(), key.end(), [](char c) { return c == '\"' || c == ' ' || c == '\t'; }), key.end()); value.erase(std::remove_if(value.begin(), value.end(), [](char c) { return c == '\"'; }), value.end()); // 保留timestamp中的空格 if (key != "timestamp") { value.erase(std::remove(value.begin(), value.end(), ' '), value.end()); } result[key] = value; } } return result; } // ============= 数据库操作(与原server_with_api.cpp相同)============= std::string readDatabase(int limit = -1, int offset = 0) { std::ifstream db(DB_FILE); if (!db.is_open()) { return "[]"; } std::vector lines; std::string line; std::getline(db, line); // 跳过表头 while (std::getline(db, line)) { if (!line.empty()) { lines.push_back(line); } } db.close(); std::reverse(lines.begin(), lines.end()); std::ostringstream json; json << "["; int start = offset; int end = (limit > 0) ? std::min(start + limit, (int)lines.size()) : lines.size(); for (int i = start; i < end; ++i) { if (i > start) json << ","; std::istringstream ss(lines[i]); std::string timestamp, student_id, name, conf, status, class_id; std::getline(ss, timestamp, ','); std::getline(ss, student_id, ','); std::getline(ss, name, ','); std::getline(ss, conf, ','); std::getline(ss, status, ','); std::getline(ss, class_id, ','); // 兼容旧数据:如果没有class_id字段,默认为"0" if (class_id.empty()) class_id = "0"; json << "{" << "\"timestamp\":\"" << timestamp << "\"," << "\"student_id\":\"" << student_id << "\"," << "\"name\":\"" << name << "\"," << "\"confidence\":" << conf << "," << "\"status\":\"" << status << "\"," << "\"class_id\":\"" << class_id << "\"" << "}"; } json << "]"; return json.str(); } std::string getStatistics() { std::ifstream db(DB_FILE); if (!db.is_open()) { return "{\"total\":0,\"today\":0}"; } std::string line; std::getline(db, line); int total = 0; int today = 0; std::string todayDate = getCurrentTimestamp().substr(0, 10); while (std::getline(db, line)) { if (!line.empty()) { total++; if (line.find(todayDate) == 0) { today++; } } } db.close(); std::ostringstream json; json << "{" << "\"total\":" << total << "," << "\"today\":" << today << "," << "\"server_time\":\"" << getCurrentTimestamp() << "\"" << "}"; return json.str(); } bool clearDatabase() { std::ofstream db(DB_FILE); if (!db.is_open()) return false; db << "Timestamp,Student_ID,Name,Confidence,Status\n"; db.close(); return true; } // ============= 时间段配置管理 ============= bool saveTimeSlots(const std::string& json_data) { std::cout << "[TIMESLOTS] Attempting to save " << json_data.size() << " bytes" << std::endl; if (json_data.empty()) { std::cerr << "[TIMESLOTS] ERROR: json_data is empty!" << std::endl; return false; } std::ofstream file(TIMESLOTS_FILE); if (!file.is_open()) { std::cerr << "[TIMESLOTS] Failed to open file for writing: " << TIMESLOTS_FILE << std::endl; return false; } file << json_data; file.flush(); file.close(); std::cout << "[TIMESLOTS] ✓ Configuration saved successfully to " << TIMESLOTS_FILE << std::endl; std::cout << "[TIMESLOTS] Preview: " << json_data.substr(0, 100) << "..." << std::endl; return true; } std::string readTimeSlots() { std::ifstream file(TIMESLOTS_FILE); if (!file.is_open()) { std::cout << "[TIMESLOTS] File not found, returning default config" << std::endl; return "{\"time_slots\":[]}"; } std::stringstream buffer; buffer << file.rdbuf(); file.close(); std::string content = buffer.str(); std::cout << "[TIMESLOTS] Read " << content.size() << " bytes from config file" << std::endl; return content; } // ============= 检查重复签到 ============= // 参数: student_id - 学号, slot_start - 时段开始时间(HH:MM), slot_end - 时段结束时间(HH:MM) // 返回: JSON格式 {"duplicate": true/false, "record_time": "..."} std::string checkDuplicate(const std::string& student_id, const std::string& slot_start, const std::string& slot_end) { std::ifstream db(DB_FILE); if (!db.is_open()) { return "{\"duplicate\":false}"; } // 获取今天的日期 std::string todayDate = getCurrentTimestamp().substr(0, 10); // 解析时段开始和结束时间 int start_hour = 0, start_minute = 0, end_hour = 23, end_minute = 59; if (slot_start.length() >= 5) { start_hour = std::stoi(slot_start.substr(0, 2)); start_minute = std::stoi(slot_start.substr(3, 2)); } if (slot_end.length() >= 5) { end_hour = std::stoi(slot_end.substr(0, 2)); end_minute = std::stoi(slot_end.substr(3, 2)); } std::string line; std::getline(db, line); // 跳过表头 while (std::getline(db, line)) { if (line.empty()) continue; // 解析CSV: Timestamp,Student_ID,Name,Confidence,Status std::istringstream ss(line); std::string timestamp, record_student_id, name, conf, status; std::getline(ss, timestamp, ','); std::getline(ss, record_student_id, ','); std::getline(ss, name, ','); std::getline(ss, conf, ','); std::getline(ss, status, ','); // 检查是否是同一个学生 if (record_student_id != student_id) continue; // 检查是否是今天的记录 if (timestamp.substr(0, 10) != todayDate) continue; // 解析记录的时间 (格式: YYYY-MM-DD HH:MM:SS) if (timestamp.length() >= 16) { int record_hour = std::stoi(timestamp.substr(11, 2)); int record_minute = std::stoi(timestamp.substr(14, 2)); int record_total_minutes = record_hour * 60 + record_minute; int start_total_minutes = start_hour * 60 + start_minute; int end_total_minutes = end_hour * 60 + end_minute; // 检查记录是否在指定时段内 if (record_total_minutes >= start_total_minutes && record_total_minutes <= end_total_minutes) { db.close(); std::cout << "[CHECK] Duplicate found: " << student_id << " at " << timestamp << std::endl; return "{\"duplicate\":true,\"record_time\":\"" + timestamp + "\"}"; } } } db.close(); return "{\"duplicate\":false}"; } // ============= 删除记录 ============= bool deleteRecord(const std::string& timestamp, const std::string& student_id) { std::ifstream inFile(DB_FILE); if (!inFile.is_open()) return false; std::vector lines; std::string line; std::getline(inFile, line); lines.push_back(line); bool found = false; while (std::getline(inFile, line)) { if (line.empty()) continue; if (line.find(timestamp) == 0 && line.find(student_id) != std::string::npos) { found = true; std::cout << "[DELETE] 删除记录: " << line << std::endl; continue; } lines.push_back(line); } inFile.close(); if (!found) return false; std::ofstream outFile(DB_FILE); if (!outFile.is_open()) return false; for (const auto& l : lines) { outFile << l << "\n"; } outFile.close(); return true; } // ============= 人脸照片管理函数 ============= // 保存人脸ID映射配置 bool saveFaceIdMapping(const std::string& json_data) { std::cout << "[FACE_MAPPING] Saving " << json_data.size() << " bytes" << std::endl; std::ofstream file(FACE_ID_MAPPING_FILE); if (!file.is_open()) return false; file << json_data; file.close(); return true; } // 根据映射重命名照片文件 void renameFacePhotos(const std::string& json_data) { std::cout << "[FACE_PHOTO] Renaming photos based on mapping..." << std::endl; // 解析JSON: {"mappings":{"1":"20210001","2":"20210002"}} size_t mappings_start = json_data.find("\"mappings\":{"); if (mappings_start == std::string::npos) return; mappings_start += 12; // 跳过 "mappings":{ size_t mappings_end = json_data.find("}", mappings_start); if (mappings_end == std::string::npos) return; std::string mappings_str = json_data.substr(mappings_start, mappings_end - mappings_start); // 解析每个映射对 std::map mappings; size_t pos = 0; while (pos < mappings_str.length()) { // 找到键 size_t key_start = mappings_str.find("\"", pos); if (key_start == std::string::npos) break; size_t key_end = mappings_str.find("\"", key_start + 1); if (key_end == std::string::npos) break; // 找到值 size_t val_start = mappings_str.find("\"", key_end + 1); if (val_start == std::string::npos) break; size_t val_end = mappings_str.find("\"", val_start + 1); if (val_end == std::string::npos) break; std::string key = mappings_str.substr(key_start + 1, key_end - key_start - 1); std::string value = mappings_str.substr(val_start + 1, val_end - val_start - 1); if (!key.empty() && !value.empty()) { mappings[key] = value; } pos = val_end + 1; } // 遍历faces目录,重命名匹配的照片 std::vector> rename_list; #ifdef _WIN32 WIN32_FIND_DATAA findData; std::string searchPath = FACES_DIR + "\\*.jpg"; HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData); if (hFind != INVALID_HANDLE_VALUE) { do { std::string filename = findData.cFileName; std::string base_name = filename.substr(0, filename.length() - 4); // 去掉.jpg // 检查是否以某个face_id开头 for (const auto& m : mappings) { // 匹配 "face_id.jpg" 或 "face_id_xxx.jpg" if (base_name == m.first || (base_name.find(m.first + "_") == 0)) { // 直接使用学号作为新文件名(不保留时间戳) std::string new_filename = m.second + ".jpg"; if (filename != new_filename) { rename_list.push_back({filename, new_filename}); } break; } } } while (FindNextFileA(hFind, &findData)); FindClose(hFind); } #else DIR* dir = opendir(FACES_DIR.c_str()); if (dir) { struct dirent* entry; while ((entry = readdir(dir)) != NULL) { std::string filename = entry->d_name; if (filename.size() > 4 && filename.substr(filename.size() - 4) == ".jpg") { std::string base_name = filename.substr(0, filename.length() - 4); for (const auto& m : mappings) { if (base_name == m.first || (base_name.find(m.first + "_") == 0)) { std::string new_filename = m.second + ".jpg"; if (filename != new_filename) { rename_list.push_back({filename, new_filename}); } break; } } } } closedir(dir); } #endif // 执行重命名 for (const auto& r : rename_list) { std::string old_path = FACES_DIR + "/" + r.first; std::string new_path = FACES_DIR + "/" + r.second; // 如果目标文件已存在,先删除旧照片 std::remove(new_path.c_str()); if (std::rename(old_path.c_str(), new_path.c_str()) == 0) { std::cout << "[FACE_PHOTO] Renamed: " << r.first << " -> " << r.second << std::endl; } else { std::cerr << "[FACE_PHOTO] Rename failed: " << r.first << std::endl; } } std::cout << "[FACE_PHOTO] Renamed " << rename_list.size() << " photos" << std::endl; } // 读取人脸ID映射配置 std::string readFaceIdMapping() { std::ifstream file(FACE_ID_MAPPING_FILE); if (!file.is_open()) return "{\"mappings\":{}}"; std::stringstream buffer; buffer << file.rdbuf(); file.close(); std::string content = buffer.str(); return content.empty() ? "{\"mappings\":{}}" : content; } // 获取人脸照片列表 std::string getFacePhotosList() { std::ostringstream json; json << "{\"photos\":["; std::vector photos; #ifdef _WIN32 WIN32_FIND_DATAA findData; std::string searchPath = FACES_DIR + "\\*.jpg"; HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData); if (hFind != INVALID_HANDLE_VALUE) { do { photos.push_back(findData.cFileName); } while (FindNextFileA(hFind, &findData)); FindClose(hFind); } #else DIR* dir = opendir(FACES_DIR.c_str()); if (dir) { struct dirent* entry; while ((entry = readdir(dir)) != NULL) { std::string name = entry->d_name; if (name.size() > 4 && name.substr(name.size() - 4) == ".jpg") { photos.push_back(name); } } closedir(dir); } #endif std::sort(photos.begin(), photos.end()); for (size_t i = 0; i < photos.size(); ++i) { if (i > 0) json << ","; std::string id = photos[i].substr(0, photos[i].size() - 4); json << "{\"id\":\"" << id << "\",\"filename\":\"" << photos[i] << "\"}"; } json << "]}"; return json.str(); } // 保存人脸照片 bool saveFacePhoto(const std::string& face_id, const std::string& photo_data) { _mkdir(FACES_DIR.c_str()); std::string filepath = FACES_DIR + "/" + face_id + ".jpg"; std::ofstream file(filepath, std::ios::binary); if (!file.is_open()) { std::cerr << "[FACE_PHOTO] Failed to create: " << filepath << std::endl; return false; } file.write(photo_data.c_str(), photo_data.size()); file.close(); std::cout << "[FACE_PHOTO] Saved: " << filepath << " (" << photo_data.size() << " bytes)" << std::endl; return true; } // 读取人脸照片 std::string readFacePhoto(const std::string& face_id) { std::string filepath = FACES_DIR + "/" + face_id + ".jpg"; std::ifstream file(filepath, std::ios::binary); if (!file.is_open()) return ""; std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); file.close(); return content; } // 删除人脸照片 bool deleteFacePhoto(const std::string& face_id) { std::string filepath = FACES_DIR + "/" + face_id + ".jpg"; if (std::remove(filepath.c_str()) == 0) { std::cout << "[FACE_PHOTO] Deleted: " << filepath << std::endl; return true; } return false; } // 从人脸ID映射中删除指定ID bool removeFaceIdFromMapping(const std::string& face_id) { std::ifstream inFile(FACE_ID_MAPPING_FILE); if (!inFile.is_open()) return false; std::stringstream buffer; buffer << inFile.rdbuf(); inFile.close(); std::string content = buffer.str(); // 简单的JSON处理:移除 "face_id":"xxx" 条目 std::string search_pattern1 = "\"" + face_id + "\":\""; size_t pos = content.find(search_pattern1); if (pos != std::string::npos) { // 找到值的结束位置 size_t value_start = pos + search_pattern1.length(); size_t value_end = content.find("\"", value_start); if (value_end != std::string::npos) { // 检查是否需要删除前面的逗号 size_t delete_start = pos; size_t delete_end = value_end + 1; // 检查后面是否有逗号 if (delete_end < content.length() && content[delete_end] == ',') { delete_end++; } else if (delete_start > 0 && content[delete_start - 1] == ',') { delete_start--; } content = content.substr(0, delete_start) + content.substr(delete_end); // 保存修改后的内容 std::ofstream outFile(FACE_ID_MAPPING_FILE); if (outFile.is_open()) { outFile << content; outFile.close(); std::cout << "[FACE_MAPPING] Removed face_id: " << face_id << std::endl; return true; } } } return false; } // 删除指定学生ID的所有考勤记录 int deleteRecordsByStudentId(const std::string& student_id) { std::ifstream inFile(DB_FILE); if (!inFile.is_open()) return 0; std::vector lines; std::string line; // 读取表头 std::getline(inFile, line); lines.push_back(line); int deleted_count = 0; while (std::getline(inFile, line)) { if (line.empty()) continue; // 解析CSV找到student_id字段 std::istringstream ss(line); std::string timestamp, record_student_id; std::getline(ss, timestamp, ','); std::getline(ss, record_student_id, ','); if (record_student_id == student_id) { deleted_count++; std::cout << "[DELETE] 删除考勤记录: " << line << std::endl; continue; } lines.push_back(line); } inFile.close(); if (deleted_count == 0) return 0; std::ofstream outFile(DB_FILE); if (!outFile.is_open()) return 0; for (const auto& l : lines) { outFile << l << "\n"; } outFile.close(); return deleted_count; } // 获取所有学生ID(从考勤记录提取,包括没有照片的) std::string getAllStudentIds() { std::set student_ids; // 从考勤记录中提取所有学生ID std::ifstream db(DB_FILE); if (db.is_open()) { std::string line; std::getline(db, line); // 跳过表头 while (std::getline(db, line)) { if (line.empty()) continue; std::istringstream ss(line); std::string timestamp, student_id; std::getline(ss, timestamp, ','); std::getline(ss, student_id, ','); if (!student_id.empty()) { student_ids.insert(student_id); } } db.close(); } // 构建JSON响应 std::ostringstream json; json << "{\"student_ids\":["; bool first = true; for (const auto& id : student_ids) { if (!first) json << ","; json << "\"" << id << "\""; first = false; } json << "]}"; return json.str(); } // 添加人脸ID到待删除列表 bool addToPendingDelete(const std::string& face_id) { std::set pending_ids; // 读取现有列表 std::ifstream inFile(PENDING_DELETE_FILE); if (inFile.is_open()) { std::stringstream buffer; buffer << inFile.rdbuf(); inFile.close(); std::string content = buffer.str(); // 简单解析JSON数组 size_t start = content.find('['); size_t end = content.rfind(']'); if (start != std::string::npos && end != std::string::npos) { std::string arr = content.substr(start + 1, end - start - 1); std::istringstream ss(arr); std::string token; while (std::getline(ss, token, ',')) { token.erase(std::remove(token.begin(), token.end(), '"'), token.end()); token.erase(std::remove(token.begin(), token.end(), ' '), token.end()); if (!token.empty()) { pending_ids.insert(token); } } } } // 添加新ID pending_ids.insert(face_id); // 写回文件 std::ofstream outFile(PENDING_DELETE_FILE); if (!outFile.is_open()) return false; outFile << "{\"pending_delete\":["; bool first = true; for (const auto& id : pending_ids) { if (!first) outFile << ","; outFile << "\"" << id << "\""; first = false; } outFile << "]}"; outFile.close(); std::cout << "[PENDING_DELETE] Added: " << face_id << std::endl; return true; } // 获取待删除列表 std::string getPendingDeleteList() { std::ifstream file(PENDING_DELETE_FILE); if (!file.is_open()) { return "{\"pending_delete\":[]}"; } std::stringstream buffer; buffer << file.rdbuf(); file.close(); std::string content = buffer.str(); return content.empty() ? "{\"pending_delete\":[]}" : content; } // 从待删除列表中移除指定ID(K210确认删除后调用) bool removeFromPendingDelete(const std::string& face_id) { std::set pending_ids; // 读取现有列表 std::ifstream inFile(PENDING_DELETE_FILE); if (inFile.is_open()) { std::stringstream buffer; buffer << inFile.rdbuf(); inFile.close(); std::string content = buffer.str(); size_t start = content.find('['); size_t end = content.rfind(']'); if (start != std::string::npos && end != std::string::npos) { std::string arr = content.substr(start + 1, end - start - 1); std::istringstream ss(arr); std::string token; while (std::getline(ss, token, ',')) { token.erase(std::remove(token.begin(), token.end(), '"'), token.end()); token.erase(std::remove(token.begin(), token.end(), ' '), token.end()); if (!token.empty() && token != face_id) { pending_ids.insert(token); } } } } // 写回文件 std::ofstream outFile(PENDING_DELETE_FILE); if (!outFile.is_open()) return false; outFile << "{\"pending_delete\":["; bool first = true; for (const auto& id : pending_ids) { if (!first) outFile << ","; outFile << "\"" << id << "\""; first = false; } outFile << "]}"; outFile.close(); std::cout << "[PENDING_DELETE] Removed: " << face_id << std::endl; return true; } // ============= K210同步请求管理 ============= // 设置同步请求标志(QT端调用) bool setSyncRequest(bool sync) { std::ofstream file(SYNC_REQUEST_FILE); if (!file.is_open()) return false; file << "{\"sync\":" << (sync ? "true" : "false") << "}"; file.close(); std::cout << "[SYNC] Sync request set to: " << (sync ? "true" : "false") << std::endl; return true; } // 获取同步请求状态(K210轮询调用) std::string getSyncRequest() { std::ifstream file(SYNC_REQUEST_FILE); if (!file.is_open()) { return "{\"sync\":false}"; } std::stringstream buffer; buffer << file.rdbuf(); file.close(); std::string content = buffer.str(); return content.empty() ? "{\"sync\":false}" : content; } // 清除同步请求标志(K210确认后调用) bool clearSyncRequest() { return setSyncRequest(false); } // 根据K210的face_id查询真实学号 std::string getMappedStudentId(const std::string& face_id) { std::cout << "[MAPPING] Looking up face_id: " << face_id << std::endl; std::ifstream file(FACE_ID_MAPPING_FILE); if (!file.is_open()) { std::cout << "[MAPPING] File not found: " << FACE_ID_MAPPING_FILE << ", using original ID" << std::endl; return face_id; // 没有映射文件,返回原ID } std::stringstream buffer; buffer << file.rdbuf(); file.close(); std::string content = buffer.str(); std::cout << "[MAPPING] File content: " << content << std::endl; // 简单解析JSON查找 "face_id":"student_id" // 格式: {"mappings":{"1":"20210001","2":"20210002"}} std::string search_key = "\"" + face_id + "\":\""; size_t pos = content.find(search_key); if (pos != std::string::npos) { size_t start = pos + search_key.length(); size_t end = content.find("\"", start); if (end != std::string::npos) { std::string mapped_id = content.substr(start, end - start); if (!mapped_id.empty()) { std::cout << "[MAPPING] ✓ Face ID " << face_id << " -> Student ID " << mapped_id << std::endl; return mapped_id; } } } std::cout << "[MAPPING] No mapping found for face_id: " << face_id << std::endl; return face_id; // 没找到映射,返回原ID } // ============= HTTP请求处理 ============= void handleHttpRequest(SOCKET client_socket) { char buffer[BUFFER_SIZE] = {0}; int bytes = recv(client_socket, buffer, BUFFER_SIZE - 1, 0); if (bytes <= 0) { closesocket(client_socket); return; } std::string request(buffer, bytes); std::istringstream stream(request); std::string method, path, version; stream >> method >> path >> version; std::cout << "[HTTP] " << method << " " << path << std::endl; std::string response_body; std::string content_type = "application/json"; int status_code = 200; std::string status_text = "OK"; std::string endpoint = path; std::string query; size_t q_pos = path.find('?'); if (q_pos != std::string::npos) { endpoint = path.substr(0, q_pos); query = path.substr(q_pos + 1); } // 提取请求体 std::string request_body; if (method == "POST") { size_t content_length = 0; size_t cl_pos = request.find("Content-Length:"); if (cl_pos != std::string::npos) { size_t cl_end = request.find("\r\n", cl_pos); std::string cl_str = request.substr(cl_pos + 15, cl_end - cl_pos - 15); cl_str.erase(0, cl_str.find_first_not_of(" \t")); content_length = std::stoi(cl_str); } size_t header_end = request.find("\r\n\r\n"); if (header_end != std::string::npos) { size_t body_start = header_end + 4; size_t current_body_size = request.size() - body_start; if (current_body_size >= content_length) { request_body = request.substr(body_start, content_length); } else { request_body = request.substr(body_start); size_t remaining = content_length - current_body_size; while (remaining > 0 && remaining < BUFFER_SIZE * 10) { char extra_buffer[BUFFER_SIZE] = {0}; int extra_bytes = recv(client_socket, extra_buffer, std::min(remaining, (size_t)BUFFER_SIZE - 1), 0); if (extra_bytes <= 0) break; request_body.append(extra_buffer, extra_bytes); remaining -= extra_bytes; } } } std::cout << "[HTTP] POST body size: " << request_body.size() << " bytes (expected: " << content_length << ")" << std::endl; } // ============= 路由处理 ============= // 新增:处理加密签到数据 if (method == "POST" && endpoint == "/api/checkin") { std::cout << "[CHECKIN] Processing encrypted checkin data..." << std::endl; // 检查是否是加密数据 if (request.find("X-Encrypted: AES128-CBC") != std::string::npos) { std::cout << "[CHECKIN] Encrypted data detected" << std::endl; // 解密数据 std::string decrypted_json = decryptJSON(request_body); if (decrypted_json.empty()) { response_body = "{\"success\":false,\"message\":\"解密失败\"}"; status_code = 400; status_text = "Bad Request"; } else { // 解析JSON auto json_data = parseJSON(decrypted_json); if (json_data.find("student_id") != json_data.end()) { // 构建考勤记录 std::string timestamp = getCurrentTimestamp(); std::string student_id = json_data["student_id"]; // K210已发送真实学号 std::string name = json_data["name"]; std::string confidence = json_data["confidence"]; std::string status = json_data["status"]; std::string class_id = json_data.count("class_id") ? json_data["class_id"] : "0"; std::cout << "[CHECKIN] Student ID: " << student_id << std::endl; // 保存到CSV(包含班级编号) std::ofstream db(DB_FILE, std::ios::app); if (db.is_open()) { db << timestamp << "," << student_id << "," << name << "," << confidence << "," << status << "," << class_id << "\n"; db.close(); response_body = "{\"success\":true,\"message\":\"签到成功\",\"timestamp\":\"" + timestamp + "\"}"; std::cout << "[CHECKIN] ✓ " << name << " (ID:" << student_id << ", Class:" << class_id << ")" << std::endl; } else { response_body = "{\"success\":false,\"message\":\"数据库写入失败\"}"; status_code = 500; status_text = "Internal Server Error"; } } else { response_body = "{\"success\":false,\"message\":\"缺少必要字段\"}"; status_code = 400; status_text = "Bad Request"; } } } else { response_body = "{\"success\":false,\"message\":\"未加密数据\"}"; status_code = 400; status_text = "Bad Request"; } } // API端点:返回加密数据 else if (method == "GET" && endpoint == "/api/records") { auto params = parseQueryParams(query); int limit = params.count("limit") ? std::stoi(params["limit"]) : 100; int offset = params.count("offset") ? std::stoi(params["offset"]) : 0; std::string json_data = readDatabase(limit, offset); // 🔒 强制加密所有响应(不再是可选) response_body = encryptJSON(json_data); if (response_body.empty()) { response_body = "{\\\"error\\\":\\\"Encryption failed\\\"}"; status_code = 500; status_text = "Internal Server Error"; } } else if (method == "GET" && endpoint == "/api/statistics") { std::string json_data = getStatistics(); // 🔒 强制加密所有响应 response_body = encryptJSON(json_data); if (response_body.empty()) { response_body = "{\\\"error\\\":\\\"Encryption failed\\\"}"; status_code = 500; status_text = "Internal Server Error"; } } else if (method == "POST" && endpoint == "/api/clear") { if (clearDatabase()) { response_body = "{\"success\":true,\"message\":\"Database cleared\"}"; } else { response_body = "{\"success\":false,\"message\":\"Failed to clear database\"}"; status_code = 500; status_text = "Internal Server Error"; } } else if (method == "DELETE" && endpoint == "/api/record") { auto params = parseQueryParams(query); if (params.count("timestamp") && params.count("student_id")) { std::string timestamp = params["timestamp"]; std::string student_id = params["student_id"]; if (deleteRecord(timestamp, student_id)) { response_body = "{\"success\":true,\"message\":\"Record deleted\"}"; } else { response_body = "{\"success\":false,\"message\":\"Record not found\"}"; status_code = 404; status_text = "Not Found"; } } else { response_body = "{\"success\":false,\"message\":\"Missing parameters\"}"; status_code = 400; status_text = "Bad Request"; } } else if (method == "GET" && endpoint == "/api/health") { response_body = "{\"status\":\"healthy\",\"time\":\"" + getCurrentTimestamp() + "\"}"; } else if (method == "GET" && endpoint == "/api/time") { // 返回服务器当前Unix时间戳(UTC秒)和可读时间 std::ostringstream json; json << "{" << "\"epoch\":" << static_cast(time(0)) << "," << "\"server_time\":\"" << getCurrentTimestamp() << "\"" << "}"; response_body = json.str(); } else if (method == "POST" && endpoint == "/api/timeslots") { // 上传时间段配置: POST /api/timeslots std::cout << "[TIMESLOTS] Received upload request, data size: " << request_body.size() << " bytes" << std::endl; if (saveTimeSlots(request_body)) { response_body = "{\"success\":true,\"message\":\"时间段配置已更新\"}"; std::cout << "[TIMESLOTS] ✓ Configuration uploaded successfully" << std::endl; } else { response_body = "{\"success\":false,\"message\":\"保存时间段配置失败\"}"; status_code = 500; status_text = "Internal Server Error"; } } else if (method == "GET" && endpoint == "/api/timeslots") { // 获取时间段配置: GET /api/timeslots std::cout << "[TIMESLOTS] Reading configuration" << std::endl; response_body = readTimeSlots(); } else if (method == "GET" && endpoint == "/api/check_duplicate") { // 检查重复签到: GET /api/check_duplicate?student_id=xxx&slot_start=08:00&slot_end=09:30 auto params = parseQueryParams(query); if (params.count("student_id") && params.count("slot_start") && params.count("slot_end")) { response_body = checkDuplicate(params["student_id"], params["slot_start"], params["slot_end"]); std::cout << "[CHECK] student_id=" << params["student_id"] << ", slot=" << params["slot_start"] << "-" << params["slot_end"] << ", result=" << response_body << std::endl; } else { response_body = "{\"error\":\"Missing parameters: student_id, slot_start, slot_end\"}"; status_code = 400; status_text = "Bad Request"; } } else if (method == "GET" && endpoint == "/api/today_records") { // 获取今日已签到记录(供STM32开机下载) // GET /api/today_records?slot_start=08:00&slot_end=09:30 auto params = parseQueryParams(query); std::string slot_start = params.count("slot_start") ? params["slot_start"] : "00:00"; std::string slot_end = params.count("slot_end") ? params["slot_end"] : "23:59"; std::ifstream db(DB_FILE); std::string todayDate = getCurrentTimestamp().substr(0, 10); // 解析时段 int start_hour = 0, start_minute = 0, end_hour = 23, end_minute = 59; if (slot_start.length() >= 5) { start_hour = std::stoi(slot_start.substr(0, 2)); start_minute = std::stoi(slot_start.substr(3, 2)); } if (slot_end.length() >= 5) { end_hour = std::stoi(slot_end.substr(0, 2)); end_minute = std::stoi(slot_end.substr(3, 2)); } std::ostringstream json; json << "{\"date\":\"" << todayDate << "\",\"records\":["; if (db.is_open()) { std::string line; std::getline(db, line); // 跳过表头 bool first = true; while (std::getline(db, line)) { if (line.empty()) continue; // 检查是否是今天的记录 if (line.substr(0, 10) != todayDate) continue; // 解析时间 if (line.length() >= 16) { int record_hour = std::stoi(line.substr(11, 2)); int record_minute = std::stoi(line.substr(14, 2)); int record_total = record_hour * 60 + record_minute; int start_total = start_hour * 60 + start_minute; int end_total = end_hour * 60 + end_minute; if (record_total >= start_total && record_total <= end_total) { // 提取学号 std::istringstream ss(line); std::string timestamp, student_id; std::getline(ss, timestamp, ','); std::getline(ss, student_id, ','); if (!first) json << ","; json << "\"" << student_id << "\""; first = false; } } } db.close(); } json << "]}"; response_body = json.str(); std::cout << "[TODAY] Returning today's records: " << response_body << std::endl; } else if (method == "POST" && endpoint == "/api/mapping_config") { // 上传映射配置: POST /api/mapping_config std::cout << "[MAPPING] Received upload request, data size: " << request_body.size() << " bytes" << std::endl; std::ofstream file(MAPPING_CONFIG_FILE); if (file.is_open()) { file << request_body; file.flush(); file.close(); response_body = "{\"success\":true,\"message\":\"映射配置已保存\"}"; std::cout << "[MAPPING] ✓ Configuration saved successfully" << std::endl; } else { response_body = "{\"success\":false,\"message\":\"保存映射配置失败\"}"; status_code = 500; status_text = "Internal Server Error"; } } else if (method == "GET" && endpoint == "/api/mapping_config") { // 获取映射配置: GET /api/mapping_config std::cout << "[MAPPING] Reading configuration" << std::endl; std::ifstream file(MAPPING_CONFIG_FILE); if (file.is_open()) { std::stringstream buffer; buffer << file.rdbuf(); file.close(); response_body = buffer.str(); std::cout << "[MAPPING] Returning config: " << response_body.size() << " bytes" << std::endl; } else { // 返回空配置 response_body = "{\"student_name_map\":{},\"class_name_map\":{}}"; std::cout << "[MAPPING] Config file not found, returning empty config" << std::endl; } // ============= 人脸照片管理 API ============= } else if (method == "GET" && endpoint == "/api/face_photos") { // 获取人脸照片列表 _mkdir(FACES_DIR.c_str()); response_body = getFacePhotosList(); std::cout << "[FACE_PHOTO] Photos list fetched" << std::endl; } else if (method == "GET" && endpoint.find("/api/face_photo/") == 0) { // 获取指定人脸照片: GET /api/face_photo/ std::string face_id = endpoint.substr(16); std::string photo_data = readFacePhoto(face_id); if (!photo_data.empty()) { response_body = photo_data; content_type = "image/jpeg"; std::cout << "[FACE_PHOTO] Photo fetched for id: " << face_id << std::endl; } else { response_body = "{\"error\":\"Photo not found\"}"; status_code = 404; status_text = "Not Found"; } } else if (method == "POST" && endpoint.find("/api/face_photo_simple/") == 0) { // 简化版人脸照片上传: POST /api/face_photo_simple/ std::string face_id = endpoint.substr(23); std::cout << "[FACE_PHOTO] Simple upload for face_id: " << face_id << ", size: " << request_body.size() << " bytes" << std::endl; if (!face_id.empty() && !request_body.empty()) { if (saveFacePhoto(face_id, request_body)) { response_body = "{\"success\":true,\"message\":\"Photo saved\",\"face_id\":\"" + face_id + "\"}"; } else { response_body = "{\"success\":false,\"message\":\"Failed to save photo\"}"; status_code = 500; status_text = "Internal Server Error"; } } else { response_body = "{\"success\":false,\"message\":\"Invalid face_id or empty data\"}"; status_code = 400; status_text = "Bad Request"; } } else if (method == "POST" && endpoint == "/api/face_id_mapping") { // 保存人脸ID映射 std::cout << "[FACE_MAPPING] Saving mapping, size: " << request_body.size() << " bytes" << std::endl; if (saveFaceIdMapping(request_body)) { // 根据新映射重命名照片文件 renameFacePhotos(request_body); // 自动触发K210同步 setSyncRequest(true); std::cout << "[FACE_MAPPING] Auto-triggered K210 sync" << std::endl; response_body = "{\"success\":true,\"message\":\"Face ID mapping saved, K210 sync triggered\"}"; } else { response_body = "{\"success\":false,\"message\":\"Failed to save mapping\"}"; status_code = 500; status_text = "Internal Server Error"; } } else if (method == "GET" && endpoint == "/api/face_id_mapping") { // 获取人脸ID映射 response_body = readFaceIdMapping(); std::cout << "[FACE_MAPPING] Mapping fetched" << std::endl; } else if (method == "GET" && endpoint == "/api/all_student_ids") { // 获取所有学生ID(从考勤记录提取) response_body = getAllStudentIds(); std::cout << "[STUDENT_IDS] All student IDs fetched" << std::endl; } else if (method == "DELETE" && endpoint.find("/api/face/") == 0) { // 删除人脸信息: DELETE /api/face/?delete_records=true std::string student_id = endpoint.substr(10); // 去掉 "/api/face/" auto params = parseQueryParams(query); bool delete_records = (params.count("delete_records") && params["delete_records"] == "true"); std::cout << "[DELETE_FACE] Deleting face for student_id: " << student_id << ", delete_records: " << (delete_records ? "yes" : "no") << std::endl; bool photo_deleted = deleteFacePhoto(student_id); bool mapping_deleted = removeFaceIdFromMapping(student_id); int records_deleted = 0; if (delete_records) { records_deleted = deleteRecordsByStudentId(student_id); } // 添加到待删除列表,等待K210同步删除 addToPendingDelete(student_id); std::ostringstream result; result << "{\"success\":true," << "\"photo_deleted\":" << (photo_deleted ? "true" : "false") << "," << "\"mapping_deleted\":" << (mapping_deleted ? "true" : "false") << "," << "\"records_deleted\":" << records_deleted << "," << "\"k210_pending\":true}"; response_body = result.str(); std::cout << "[DELETE_FACE] Result: " << response_body << std::endl; } else if (method == "GET" && endpoint == "/api/pending_delete") { // K210查询待删除列表 response_body = getPendingDeleteList(); std::cout << "[PENDING_DELETE] List fetched" << std::endl; } else if (method == "POST" && endpoint == "/api/confirm_delete") { // K210确认删除完成,从待删除列表移除 // 请求体格式: {"face_id":"xxx"} std::string face_id; size_t pos = request_body.find("\"face_id\":\""); if (pos != std::string::npos) { size_t start = pos + 11; size_t end = request_body.find("\"", start); if (end != std::string::npos) { face_id = request_body.substr(start, end - start); } } if (!face_id.empty() && removeFromPendingDelete(face_id)) { response_body = "{\"success\":true,\"message\":\"Confirmed delete: " + face_id + "\"}"; std::cout << "[PENDING_DELETE] K210 confirmed delete: " << face_id << std::endl; } else { response_body = "{\"success\":false,\"message\":\"Invalid face_id or not in pending list\"}"; status_code = 400; status_text = "Bad Request"; } // ============= K210同步请求 API ============= } else if (method == "GET" && endpoint == "/api/sync_request") { // K210查询是否需要同步 response_body = getSyncRequest(); std::cout << "[SYNC] K210 checking sync request: " << response_body << std::endl; } else if (method == "POST" && endpoint == "/api/sync_ack") { // K210确认收到同步请求,清除标志 if (clearSyncRequest()) { response_body = "{\"success\":true,\"message\":\"Sync acknowledged\"}"; std::cout << "[SYNC] K210 acknowledged sync request" << std::endl; } else { response_body = "{\"success\":false,\"message\":\"Failed to clear sync request\"}"; status_code = 500; status_text = "Internal Server Error"; } } else if (method == "POST" && endpoint == "/api/trigger_sync") { // QT端触发K210同步 if (setSyncRequest(true)) { response_body = "{\"success\":true,\"message\":\"Sync request sent to K210\"}"; std::cout << "[SYNC] QT triggered K210 sync" << std::endl; } else { response_body = "{\"success\":false,\"message\":\"Failed to set sync request\"}"; status_code = 500; status_text = "Internal Server Error"; } } else { response_body = "{\"error\":\"Not Found\"}"; status_code = 404; status_text = "Not Found"; } // 构建HTTP响应 std::ostringstream response; response << "HTTP/1.1 " << status_code << " " << status_text << "\r\n"; response << "Content-Type: " << content_type << "; charset=utf-8\r\n"; response << "Content-Length: " << response_body.length() << "\r\n"; response << "Access-Control-Allow-Origin: *\r\n"; response << "Connection: close\r\n"; response << "\r\n"; response << response_body; std::string resp_str = response.str(); send(client_socket, resp_str.c_str(), resp_str.length(), 0); closesocket(client_socket); } // ============= HTTP服务器线程 ============= void httpServerThread() { SOCKET server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket == INVALID_SOCKET) { std::cerr << "[HTTP] Failed to create socket" << std::endl; return; } int opt = 1; setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt)); sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(HTTP_PORT); if (bind(server_socket, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR) { std::cerr << "[HTTP] Bind failed" << std::endl; closesocket(server_socket); return; } listen(server_socket, 10); std::cout << "[HTTP] Server listening on port " << HTTP_PORT << std::endl; while (true) { sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); SOCKET client_socket = accept(server_socket, (sockaddr*)&client_addr, &client_len); if (client_socket != INVALID_SOCKET) { std::thread(handleHttpRequest, client_socket).detach(); } } } int main() { #ifdef _WIN32 WSADATA wsa; WSAStartup(MAKEWORD(2, 2), &wsa); #endif std::cout << "========================================" << std::endl; std::cout << " Face Recognition Attendance Server" << std::endl; std::cout << " with AES-128-CBC Encryption" << std::endl; std::cout << "========================================" << std::endl; std::cout << "HTTP Port " << HTTP_PORT << " - Encrypted API" << std::endl; std::cout << "Database: " << DB_FILE << std::endl; std::cout << "----------------------------------------" << std::endl; // 确保CSV文件存在 std::ifstream test(DB_FILE); if (!test.good()) { std::ofstream create(DB_FILE); create << "Timestamp,Student_ID,Name,Confidence,Status,Class_ID\n"; create.close(); } test.close(); // 启动HTTP服务器 std::thread http_thread(httpServerThread); http_thread.join(); #ifdef _WIN32 WSACleanup(); #endif return 0; }