From 4417c53248be4ed255088d572a0e9fbaf2c25f9e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=AA=E7=94=B0=E9=87=8E?= Date: Fri, 1 May 2026 03:19:48 +0000 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server_encrypted.cpp | 1744 ++++++++++++++++++++++++++++++++++++++++++ tiny_aes.hpp | 293 +++++++ 2 files changed, 2037 insertions(+) create mode 100644 server_encrypted.cpp create mode 100644 tiny_aes.hpp diff --git a/server_encrypted.cpp b/server_encrypted.cpp new file mode 100644 index 0000000..4ff0771 --- /dev/null +++ b/server_encrypted.cpp @@ -0,0 +1,1744 @@ +/* + * 增强版考勤服务器 - 支持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; +} diff --git a/tiny_aes.hpp b/tiny_aes.hpp new file mode 100644 index 0000000..5066d71 --- /dev/null +++ b/tiny_aes.hpp @@ -0,0 +1,293 @@ +/* + * Tiny AES128 C++ 移植版本 + * 与STM32端使用相同的AES实现,保证兼容性 + */ + +#ifndef _TINY_AES_HPP_ +#define _TINY_AES_HPP_ + +#include +#include + +#define AES_BLOCKLEN 16 +#define AES_KEYLEN 16 +#define AES_keyExpSize 176 + +struct AES_ctx +{ + uint8_t RoundKey[AES_keyExpSize]; +}; + +// S-box +static const uint8_t sbox[256] = { + 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, + 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, + 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, + 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, + 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, + 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, + 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, + 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, + 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, + 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, + 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, + 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, + 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, + 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, + 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, + 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 +}; + +// Inverse S-box +static const uint8_t rsbox[256] = { + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d +}; + +// Rcon +static const uint8_t Rcon[11] = { + 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36 +}; + +#define getSBoxValue(num) (sbox[(num)]) +#define getSBoxInvert(num) (rsbox[(num)]) + +// 导出这些函数供外部使用 +void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key); +void InvCipher(uint8_t* state, const uint8_t* RoundKey); + +inline void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) +{ + unsigned i, j, k; + uint8_t tempa[4]; + + for (i = 0; i < 4; ++i) { + RoundKey[(i * 4) + 0] = Key[(i * 4) + 0]; + RoundKey[(i * 4) + 1] = Key[(i * 4) + 1]; + RoundKey[(i * 4) + 2] = Key[(i * 4) + 2]; + RoundKey[(i * 4) + 3] = Key[(i * 4) + 3]; + } + + for (i = 4; i < 44; ++i) { + k = (i - 1) * 4; + tempa[0] = RoundKey[k + 0]; + tempa[1] = RoundKey[k + 1]; + tempa[2] = RoundKey[k + 2]; + tempa[3] = RoundKey[k + 3]; + + if (i % 4 == 0) { + const uint8_t u8tmp = tempa[0]; + tempa[0] = tempa[1]; + tempa[1] = tempa[2]; + tempa[2] = tempa[3]; + tempa[3] = u8tmp; + + tempa[0] = getSBoxValue(tempa[0]); + tempa[1] = getSBoxValue(tempa[1]); + tempa[2] = getSBoxValue(tempa[2]); + tempa[3] = getSBoxValue(tempa[3]); + + tempa[0] = tempa[0] ^ Rcon[i / 4]; + } + + j = i * 4; k = (i - 4) * 4; + RoundKey[j + 0] = RoundKey[k + 0] ^ tempa[0]; + RoundKey[j + 1] = RoundKey[k + 1] ^ tempa[1]; + RoundKey[j + 2] = RoundKey[k + 2] ^ tempa[2]; + RoundKey[j + 3] = RoundKey[k + 3] ^ tempa[3]; + } +} + +static void AddRoundKey(uint8_t round, uint8_t* state, const uint8_t* RoundKey) +{ + uint8_t i, j; + for (i = 0; i < 4; ++i) { + for (j = 0; j < 4; ++j) { + state[i * 4 + j] ^= RoundKey[(round * 16) + (i * 4 + j)]; + } + } +} + +static void SubBytes(uint8_t* state) +{ + uint8_t i; + for (i = 0; i < 16; ++i) { + state[i] = getSBoxValue(state[i]); + } +} + +static void InvSubBytes(uint8_t* state) +{ + uint8_t i; + for (i = 0; i < 16; ++i) { + state[i] = getSBoxInvert(state[i]); + } +} + +static void ShiftRows(uint8_t* state) +{ + uint8_t temp; + temp = state[1]; + state[1] = state[5]; + state[5] = state[9]; + state[9] = state[13]; + state[13] = temp; + + temp = state[2]; + state[2] = state[10]; + state[10] = temp; + temp = state[6]; + state[6] = state[14]; + state[14] = temp; + + temp = state[15]; + state[15] = state[11]; + state[11] = state[7]; + state[7] = state[3]; + state[3] = temp; +} + +static void InvShiftRows(uint8_t* state) +{ + uint8_t temp; + temp = state[13]; + state[13] = state[9]; + state[9] = state[5]; + state[5] = state[1]; + state[1] = temp; + + temp = state[2]; + state[2] = state[10]; + state[10] = temp; + temp = state[6]; + state[6] = state[14]; + state[14] = temp; + + temp = state[3]; + state[3] = state[7]; + state[7] = state[11]; + state[11] = state[15]; + state[15] = temp; +} + +static uint8_t xtime(uint8_t x) +{ + return ((x << 1) ^ (((x >> 7) & 1) * 0x1b)); +} + +static void MixColumns(uint8_t* state) +{ + uint8_t i; + uint8_t Tmp, Tm, t; + for (i = 0; i < 4; ++i) { + t = state[i * 4 + 0]; + Tmp = state[i * 4 + 0] ^ state[i * 4 + 1] ^ state[i * 4 + 2] ^ state[i * 4 + 3]; + Tm = state[i * 4 + 0] ^ state[i * 4 + 1]; Tm = xtime(Tm); state[i * 4 + 0] ^= Tm ^ Tmp; + Tm = state[i * 4 + 1] ^ state[i * 4 + 2]; Tm = xtime(Tm); state[i * 4 + 1] ^= Tm ^ Tmp; + Tm = state[i * 4 + 2] ^ state[i * 4 + 3]; Tm = xtime(Tm); state[i * 4 + 2] ^= Tm ^ Tmp; + Tm = state[i * 4 + 3] ^ t; Tm = xtime(Tm); state[i * 4 + 3] ^= Tm ^ Tmp; + } +} + +static uint8_t Multiply(uint8_t x, uint8_t y) +{ + return (((y & 1) * x) ^ + ((y >> 1 & 1) * xtime(x)) ^ + ((y >> 2 & 1) * xtime(xtime(x))) ^ + ((y >> 3 & 1) * xtime(xtime(xtime(x)))) ^ + ((y >> 4 & 1) * xtime(xtime(xtime(xtime(x)))))); +} + +static void InvMixColumns(uint8_t* state) +{ + int i; + uint8_t a, b, c, d; + for (i = 0; i < 4; ++i) { + a = state[i * 4 + 0]; + b = state[i * 4 + 1]; + c = state[i * 4 + 2]; + d = state[i * 4 + 3]; + + state[i * 4 + 0] = Multiply(a, 0x0e) ^ Multiply(b, 0x0b) ^ Multiply(c, 0x0d) ^ Multiply(d, 0x09); + state[i * 4 + 1] = Multiply(a, 0x09) ^ Multiply(b, 0x0e) ^ Multiply(c, 0x0b) ^ Multiply(d, 0x0d); + state[i * 4 + 2] = Multiply(a, 0x0d) ^ Multiply(b, 0x09) ^ Multiply(c, 0x0e) ^ Multiply(d, 0x0b); + state[i * 4 + 3] = Multiply(a, 0x0b) ^ Multiply(b, 0x0d) ^ Multiply(c, 0x09) ^ Multiply(d, 0x0e); + } +} + +static void Cipher(uint8_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + AddRoundKey(0, state, RoundKey); + + for (round = 1; ; ++round) { + SubBytes(state); + ShiftRows(state); + if (round == 10) { + break; + } + MixColumns(state); + AddRoundKey(round, state, RoundKey); + } + AddRoundKey(10, state, RoundKey); +} + +inline void InvCipher(uint8_t* state, const uint8_t* RoundKey) +{ + uint8_t round = 0; + + AddRoundKey(10, state, RoundKey); + + for (round = 9; ; --round) { + InvShiftRows(state); + InvSubBytes(state); + AddRoundKey(round, state, RoundKey); + if (round == 0) { + break; + } + InvMixColumns(state); + } +} + +inline void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) +{ + KeyExpansion(ctx->RoundKey, key); + memcpy(ctx->RoundKey + AES_keyExpSize - AES_BLOCKLEN, iv, AES_BLOCKLEN); +} + +inline void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + uint8_t* Iv = ctx->RoundKey + AES_keyExpSize - AES_BLOCKLEN; + + for (i = 0; i < length; i += AES_BLOCKLEN) { + memcpy(storeNextIv, buf + i, AES_BLOCKLEN); + InvCipher(buf + i, ctx->RoundKey); + + uint8_t j; + for (j = 0; j < AES_BLOCKLEN; ++j) { + buf[i + j] ^= Iv[j]; + } + memcpy(Iv, storeNextIv, AES_BLOCKLEN); + } + + memcpy(ctx->RoundKey + AES_keyExpSize - AES_BLOCKLEN, Iv, AES_BLOCKLEN); +} + +#endif // _TINY_AES_HPP_