Files
bishe-Server/server_encrypted.cpp
T
2026-05-01 03:19:48 +00:00

1745 lines
62 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 增强版考勤服务器 - 支持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 <iostream>
#include <string>
#include <fstream>
#include <sstream>
#include <thread>
#include <vector>
#include <map>
#include <set>
#include <ctime>
#include <algorithm>
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#include <direct.h>
#pragma comment(lib, "ws2_32.lib")
typedef int socklen_t;
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <cstring>
#include <dirent.h>
#include <sys/stat.h>
#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 <openssl/evp.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>
#include <openssl/aes.h>
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<unsigned char> 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<unsigned char> 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(&ltm, &now);
#else
localtime_r(&now, &ltm);
#endif
char buffer[80];
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &ltm);
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<char>(value);
i += 2;
} else {
result += str[i];
}
} else if (str[i] == '+') {
result += ' ';
} else {
result += str[i];
}
}
return result;
}
std::map<std::string, std::string> parseQueryParams(const std::string& query) {
std::map<std::string, std::string> 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<std::string, std::string> parseJSON(const std::string& json) {
std::map<std::string, std::string> 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<std::string> 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<std::string> 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<std::string, std::string> 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<std::pair<std::string, std::string>> 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<std::string> 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<char>(file)), std::istreambuf_iterator<char>());
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<std::string> 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<std::string> 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<std::string> 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<std::string> 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<long long>(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/<id>
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/<face_id>
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/<student_id>?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;
}