终版
This commit is contained in:
+228
-147
@@ -77,7 +77,8 @@ QNetworkRequest ApiClient::createRequest(const QString &url, bool encryptedRespo
|
||||
request.setRawHeader("X-Api-Token", m_apiToken.toUtf8());
|
||||
request.setRawHeader("Accept", "application/json");
|
||||
if (jsonBody) {
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream");
|
||||
request.setRawHeader("X-Encrypted", "AES128-CBC");
|
||||
}
|
||||
request.setTransferTimeout(NetworkTimeoutMs);
|
||||
return request;
|
||||
@@ -91,9 +92,9 @@ void ApiClient::fetchSyncState(qulonglong lastVersion)
|
||||
});
|
||||
qInfo() << "[ApiClient] GET sync state" << url;
|
||||
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(url));
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(url, true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("同步状态加载失败"), reply);
|
||||
reply->deleteLater();
|
||||
@@ -137,36 +138,70 @@ void ApiClient::fetchSyncState(qulonglong lastVersion)
|
||||
emit timeSlotsFetched(timeSlots);
|
||||
}
|
||||
|
||||
if (state.contains("mapping_config")) {
|
||||
const QJsonObject mappingConfig = state.value("mapping_config").toObject();
|
||||
if (state.contains("students") || state.contains("classes")) {
|
||||
QMap<QString, QString> studentMap;
|
||||
QMap<QString, QString> studentClassMap;
|
||||
QMap<QString, QString> classMap;
|
||||
|
||||
const QJsonObject studentObject = mappingConfig.value("student_name_map").toObject();
|
||||
for (auto it = studentObject.begin(); it != studentObject.end(); ++it) {
|
||||
studentMap[it.key()] = it.value().toString();
|
||||
const QJsonArray studentArray = state.value("students").toArray();
|
||||
for (const QJsonValue &value : studentArray) {
|
||||
const QJsonObject object = value.toObject();
|
||||
const QString studentId = object.value("student_id").toString().trimmed();
|
||||
if (studentId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
studentMap[studentId] = object.value("student_name").toString().trimmed();
|
||||
studentClassMap[studentId] = object.value("class_id").toString().trimmed();
|
||||
}
|
||||
|
||||
const QJsonObject studentClassObject = mappingConfig.value("student_class_map").toObject();
|
||||
for (auto it = studentClassObject.begin(); it != studentClassObject.end(); ++it) {
|
||||
studentClassMap[it.key()] = it.value().toString();
|
||||
const QJsonArray classes = state.value("classes").toArray();
|
||||
for (const QJsonValue &value : classes) {
|
||||
const QJsonObject object = value.toObject();
|
||||
const QString classId = object.value("class_id").toString().trimmed();
|
||||
if (!classId.isEmpty()) {
|
||||
classMap[classId] = object.value("class_name").toString().trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
const QJsonObject classObject = mappingConfig.value("class_name_map").toObject();
|
||||
for (auto it = classObject.begin(); it != classObject.end(); ++it) {
|
||||
classMap[it.key()] = it.value().toString();
|
||||
QVector<StudentProfileListModel::Item> studentItems;
|
||||
studentItems.reserve(studentMap.size());
|
||||
for (auto it = studentMap.cbegin(); it != studentMap.cend(); ++it) {
|
||||
const QString studentId = it.key();
|
||||
studentItems.push_back({
|
||||
studentId,
|
||||
it.value(),
|
||||
studentClassMap.value(studentId),
|
||||
classMap.value(studentClassMap.value(studentId), studentClassMap.value(studentId)),
|
||||
{},
|
||||
{}
|
||||
});
|
||||
}
|
||||
emit mappingConfigFetched(studentMap, studentClassMap, classMap);
|
||||
emit studentsFetched(studentItems);
|
||||
emit classesFetched(classMap);
|
||||
}
|
||||
|
||||
if (state.contains("face_mappings")) {
|
||||
const QJsonObject mappings = state.value("face_mappings").toObject();
|
||||
QMap<QString, QString> mapping;
|
||||
for (auto it = mappings.begin(); it != mappings.end(); ++it) {
|
||||
mapping[it.key()] = it.value().toString();
|
||||
if (state.contains("face_profiles")) {
|
||||
const QJsonArray profiles = state.value("face_profiles").toArray();
|
||||
QVector<FaceListModel::Item> faces;
|
||||
faces.reserve(profiles.size());
|
||||
for (const QJsonValue &value : profiles) {
|
||||
const QJsonObject object = value.toObject();
|
||||
const QString faceId = object.value("face_id").toString().trimmed();
|
||||
const QString studentId = object.value("student_id").toString().trimmed();
|
||||
if (faceId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
faces.push_back({
|
||||
faceId,
|
||||
studentId,
|
||||
object.value("has_photo").toBool(false),
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
object.value("status").toString().trimmed()
|
||||
});
|
||||
}
|
||||
emit faceIdMappingFetched(mapping);
|
||||
emit faceProfilesFetched(faces);
|
||||
}
|
||||
|
||||
if (state.contains("face_photos")) {
|
||||
@@ -178,15 +213,6 @@ void ApiClient::fetchSyncState(qulonglong lastVersion)
|
||||
emit facePhotosFetched(ids);
|
||||
}
|
||||
|
||||
if (state.contains("all_student_ids")) {
|
||||
QStringList ids;
|
||||
const QJsonArray array = state.value("all_student_ids").toArray();
|
||||
for (const QJsonValue &value : array) {
|
||||
ids.push_back(value.toString());
|
||||
}
|
||||
emit allStudentIdsFetched(ids);
|
||||
}
|
||||
|
||||
emit syncStateFetched(version, true);
|
||||
reply->deleteLater();
|
||||
});
|
||||
@@ -204,6 +230,18 @@ QByteArray ApiClient::maybeDecrypt(const QByteArray &data, bool encrypted) const
|
||||
return CryptoUtils::decryptAes128CbcBase64(trimmed);
|
||||
}
|
||||
|
||||
QByteArray ApiClient::decryptedReplyBody(QNetworkReply *reply) const
|
||||
{
|
||||
const QByteArray data = reply->readAll();
|
||||
const bool encrypted = reply->rawHeader("X-Encrypted").trimmed() == "AES128-CBC";
|
||||
return maybeDecrypt(data, encrypted);
|
||||
}
|
||||
|
||||
QByteArray ApiClient::encryptedJsonBody(const QByteArray &body) const
|
||||
{
|
||||
return CryptoUtils::encryptAes128CbcBase64(body);
|
||||
}
|
||||
|
||||
bool ApiClient::isConnectionError(QNetworkReply::NetworkError error) const
|
||||
{
|
||||
switch (error) {
|
||||
@@ -280,21 +318,20 @@ void ApiClient::fetchRecords(int limit, int offset)
|
||||
{"offset", QString::number(offset)}
|
||||
});
|
||||
qInfo() << "[ApiClient] GET" << url;
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(url));
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(url, true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray rawData = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qInfo() << "[ApiClient] records status=" << statusCode
|
||||
<< "error=" << reply->error()
|
||||
<< "bytes=" << rawData.size()
|
||||
<< "body=" << rawData.left(200);
|
||||
<< "bytes=" << data.size()
|
||||
<< "body=" << data.left(200);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("加载记录失败"), reply);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray data = maybeDecrypt(rawData, false);
|
||||
if (data.isEmpty()) {
|
||||
emit networkError("记录数据为空");
|
||||
reply->deleteLater();
|
||||
@@ -320,21 +357,20 @@ void ApiClient::fetchStatistics()
|
||||
{
|
||||
const QString url = buildUrl("/v1/attendance/statistics");
|
||||
qInfo() << "[ApiClient] GET" << url;
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(url));
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(url, true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray rawData = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qInfo() << "[ApiClient] statistics status=" << statusCode
|
||||
<< "error=" << reply->error()
|
||||
<< "bytes=" << rawData.size()
|
||||
<< "body=" << rawData.left(200);
|
||||
<< "bytes=" << data.size()
|
||||
<< "body=" << data.left(200);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("加载统计失败"), reply);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray data = maybeDecrypt(rawData, false);
|
||||
if (data.isEmpty()) {
|
||||
emit networkError("统计数据为空");
|
||||
reply->deleteLater();
|
||||
@@ -352,9 +388,10 @@ void ApiClient::fetchStatistics()
|
||||
|
||||
void ApiClient::clearAllRecords()
|
||||
{
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/attendance/records/clear"), false, true), QByteArray());
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/attendance/records/clear"), true, true),
|
||||
encryptedJsonBody("{}"));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (isConnectionError(reply->error())) {
|
||||
emit connectionUnavailable(QStringLiteral("清空失败: %1").arg(reply->errorString()));
|
||||
@@ -375,9 +412,9 @@ void ApiClient::deleteRecord(const QString ×tamp, const QString &studentId)
|
||||
QNetworkReply *reply = m_networkManager.deleteResource(createRequest(buildUrl("/v1/attendance/records", {
|
||||
{"timestamp", timestamp},
|
||||
{"student_id", studentId}
|
||||
})));
|
||||
}), true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (isConnectionError(reply->error())) {
|
||||
emit connectionUnavailable(QStringLiteral("记录删除失败: %1").arg(reply->errorString()));
|
||||
@@ -395,9 +432,9 @@ void ApiClient::deleteRecord(const QString ×tamp, const QString &studentId)
|
||||
|
||||
void ApiClient::fetchTimeSlots()
|
||||
{
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/config/time-slots")));
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/config/time-slots"), true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("加载时间段失败"), reply);
|
||||
reply->deleteLater();
|
||||
@@ -439,9 +476,10 @@ void ApiClient::uploadTimeSlots(const QVector<TimeSlot> &timeSlots)
|
||||
qInfo() << "[ApiClient] POST" << buildUrl("/v1/config/time-slots")
|
||||
<< "bytes=" << body.size()
|
||||
<< "body=" << body.left(300);
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/config/time-slots"), false, true), body);
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/config/time-slots"), true, true),
|
||||
encryptedJsonBody(body));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (isConnectionError(reply->error())) {
|
||||
emit connectionUnavailable(QStringLiteral("时间段同步失败: %1").arg(reply->errorString()));
|
||||
@@ -456,139 +494,203 @@ void ApiClient::uploadTimeSlots(const QVector<TimeSlot> &timeSlots)
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::fetchMappingConfig()
|
||||
void ApiClient::fetchStudents()
|
||||
{
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/config/mappings")));
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/students"), true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("加载映射配置失败"), reply);
|
||||
emitNetworkError(QStringLiteral("加载学生档案失败"), reply);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QVector<StudentProfileListModel::Item> students;
|
||||
const QJsonArray array = QJsonDocument::fromJson(data).object().value("students").toArray();
|
||||
students.reserve(array.size());
|
||||
for (const QJsonValue &value : array) {
|
||||
const QJsonObject object = value.toObject();
|
||||
students.push_back({
|
||||
object.value("student_id").toString().trimmed(),
|
||||
object.value("student_name").toString().trimmed(),
|
||||
object.value("class_id").toString().trimmed(),
|
||||
{},
|
||||
{},
|
||||
{}
|
||||
});
|
||||
}
|
||||
emit studentsFetched(students);
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::uploadStudents(const QVector<StudentProfileListModel::Item> &students)
|
||||
{
|
||||
QJsonArray array;
|
||||
for (const StudentProfileListModel::Item &student : students) {
|
||||
const QString studentId = student.studentId.trimmed();
|
||||
if (studentId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QJsonObject object;
|
||||
object["student_id"] = studentId;
|
||||
object["student_name"] = student.studentName.trimmed();
|
||||
object["class_id"] = student.classId.trimmed();
|
||||
array.append(object);
|
||||
}
|
||||
QJsonObject payload;
|
||||
payload["students"] = array;
|
||||
const QByteArray body = QJsonDocument(payload).toJson(QJsonDocument::Compact);
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/students"), true, true),
|
||||
encryptedJsonBody(body));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (isConnectionError(reply->error())) {
|
||||
emit connectionUnavailable(QStringLiteral("学生档案保存失败: %1").arg(reply->errorString()));
|
||||
}
|
||||
emit studentsUploaded(false, reply->errorString());
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
const QJsonObject object = QJsonDocument::fromJson(data).object();
|
||||
emit studentsUploaded(object.value("success").toBool(), object.value("message").toString());
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::fetchClasses()
|
||||
{
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/classes"), true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("加载班级配置失败"), reply);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject root = QJsonDocument::fromJson(data).object();
|
||||
QMap<QString, QString> studentMap;
|
||||
QMap<QString, QString> studentClassMap;
|
||||
QMap<QString, QString> classMap;
|
||||
|
||||
const QJsonObject studentObject = root.value("student_name_map").toObject();
|
||||
for (auto it = studentObject.begin(); it != studentObject.end(); ++it) {
|
||||
studentMap[it.key()] = it.value().toString();
|
||||
const QJsonArray array = QJsonDocument::fromJson(data).object().value("classes").toArray();
|
||||
for (const QJsonValue &value : array) {
|
||||
const QJsonObject object = value.toObject();
|
||||
const QString classId = object.value("class_id").toString().trimmed();
|
||||
if (!classId.isEmpty()) {
|
||||
classMap[classId] = object.value("class_name").toString().trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
const QJsonObject studentClassObject = root.value("student_class_map").toObject();
|
||||
for (auto it = studentClassObject.begin(); it != studentClassObject.end(); ++it) {
|
||||
studentClassMap[it.key()] = it.value().toString();
|
||||
}
|
||||
|
||||
const QJsonObject classObject = root.value("class_name_map").toObject();
|
||||
for (auto it = classObject.begin(); it != classObject.end(); ++it) {
|
||||
classMap[it.key()] = it.value().toString();
|
||||
}
|
||||
|
||||
emit mappingConfigFetched(studentMap, studentClassMap, classMap);
|
||||
emit classesFetched(classMap);
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::uploadMappingConfig(const QMap<QString, QString> &studentNameMap,
|
||||
const QMap<QString, QString> &studentClassMap,
|
||||
const QMap<QString, QString> &classNameMap)
|
||||
void ApiClient::uploadClasses(const QMap<QString, QString> &classNameMap)
|
||||
{
|
||||
QJsonObject studentObject;
|
||||
for (auto it = studentNameMap.begin(); it != studentNameMap.end(); ++it) {
|
||||
studentObject[it.key()] = it.value();
|
||||
QJsonArray array;
|
||||
for (auto it = classNameMap.cbegin(); it != classNameMap.cend(); ++it) {
|
||||
const QString classId = it.key().trimmed();
|
||||
if (classId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QJsonObject object;
|
||||
object["class_id"] = classId;
|
||||
object["class_name"] = it.value().trimmed();
|
||||
array.append(object);
|
||||
}
|
||||
|
||||
QJsonObject studentClassObject;
|
||||
for (auto it = studentClassMap.begin(); it != studentClassMap.end(); ++it) {
|
||||
studentClassObject[it.key()] = it.value();
|
||||
}
|
||||
|
||||
QJsonObject classObject;
|
||||
for (auto it = classNameMap.begin(); it != classNameMap.end(); ++it) {
|
||||
classObject[it.key()] = it.value();
|
||||
}
|
||||
|
||||
QJsonObject payload;
|
||||
payload["student_name_map"] = studentObject;
|
||||
payload["student_class_map"] = studentClassObject;
|
||||
payload["class_name_map"] = classObject;
|
||||
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/config/mappings"), false, true),
|
||||
QJsonDocument(payload).toJson(QJsonDocument::Compact));
|
||||
payload["classes"] = array;
|
||||
const QByteArray body = QJsonDocument(payload).toJson(QJsonDocument::Compact);
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/classes"), true, true),
|
||||
encryptedJsonBody(body));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (isConnectionError(reply->error())) {
|
||||
emit connectionUnavailable(QStringLiteral("映射保存失败: %1").arg(reply->errorString()));
|
||||
emit connectionUnavailable(QStringLiteral("班级配置保存失败: %1").arg(reply->errorString()));
|
||||
}
|
||||
emit mappingConfigUploaded(false, reply->errorString());
|
||||
emit classesUploaded(false, reply->errorString());
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
const QJsonObject object = QJsonDocument::fromJson(data).object();
|
||||
emit mappingConfigUploaded(object.value("success").toBool(), object.value("message").toString());
|
||||
emit classesUploaded(object.value("success").toBool(), object.value("message").toString());
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::fetchFaceIdMapping()
|
||||
void ApiClient::fetchFaceProfiles()
|
||||
{
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/faces/mappings")));
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/face_profiles"), true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("加载人脸映射失败"), reply);
|
||||
emitNetworkError(QStringLiteral("加载人脸档案失败"), reply);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject mappings = QJsonDocument::fromJson(data).object().value("mappings").toObject();
|
||||
QMap<QString, QString> mapping;
|
||||
for (auto it = mappings.begin(); it != mappings.end(); ++it) {
|
||||
mapping[it.key()] = it.value().toString();
|
||||
QVector<FaceListModel::Item> faces;
|
||||
const QJsonArray array = QJsonDocument::fromJson(data).object().value("face_profiles").toArray();
|
||||
faces.reserve(array.size());
|
||||
for (const QJsonValue &value : array) {
|
||||
const QJsonObject object = value.toObject();
|
||||
faces.push_back({
|
||||
object.value("face_id").toString().trimmed(),
|
||||
object.value("student_id").toString().trimmed(),
|
||||
object.value("has_photo").toBool(false),
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
object.value("status").toString().trimmed()
|
||||
});
|
||||
}
|
||||
emit faceIdMappingFetched(mapping);
|
||||
emit faceProfilesFetched(faces);
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::uploadFaceIdMapping(const QMap<QString, QString> &mapping)
|
||||
void ApiClient::uploadFaceProfiles(const QVector<FaceListModel::Item> &faces)
|
||||
{
|
||||
QJsonObject mappings;
|
||||
for (auto it = mapping.begin(); it != mapping.end(); ++it) {
|
||||
if (!it.value().trimmed().isEmpty()) {
|
||||
mappings[it.key()] = it.value();
|
||||
QJsonArray array;
|
||||
for (const FaceListModel::Item &face : faces) {
|
||||
const QString faceId = face.faceId.trimmed();
|
||||
if (faceId.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
QJsonObject object;
|
||||
object["face_id"] = faceId;
|
||||
object["student_id"] = face.studentId.trimmed();
|
||||
object["status"] = face.studentId.trimmed().isEmpty() ? QStringLiteral("unbound") : QStringLiteral("active");
|
||||
array.append(object);
|
||||
}
|
||||
|
||||
QJsonObject payload;
|
||||
payload["mappings"] = mappings;
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/faces/mappings"), false, true),
|
||||
QJsonDocument(payload).toJson(QJsonDocument::Compact));
|
||||
payload["face_profiles"] = array;
|
||||
const QByteArray body = QJsonDocument(payload).toJson(QJsonDocument::Compact);
|
||||
QNetworkReply *reply = m_networkManager.post(createRequest(buildUrl("/v1/face_profiles"), true, true),
|
||||
encryptedJsonBody(body));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (isConnectionError(reply->error())) {
|
||||
emit connectionUnavailable(QStringLiteral("人脸映射保存失败: %1").arg(reply->errorString()));
|
||||
emit connectionUnavailable(QStringLiteral("人脸档案保存失败: %1").arg(reply->errorString()));
|
||||
}
|
||||
emit faceIdMappingUploaded(false, reply->errorString());
|
||||
emit faceProfilesUploaded(false, reply->errorString());
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
const QJsonObject object = QJsonDocument::fromJson(data).object();
|
||||
emit faceIdMappingUploaded(object.value("success").toBool(), object.value("message").toString());
|
||||
emit faceProfilesUploaded(object.value("success").toBool(), object.value("message").toString());
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::fetchFacePhotos()
|
||||
{
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/faces/photos")));
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/faces/photos"), true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("加载照片列表失败"), reply);
|
||||
reply->deleteLater();
|
||||
@@ -634,27 +736,6 @@ void ApiClient::fetchFacePhoto(const QString &faceId)
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::fetchAllStudentIds()
|
||||
{
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/faces/student-ids")));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emitNetworkError(QStringLiteral("加载学生ID失败"), reply);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList ids;
|
||||
const QJsonArray array = QJsonDocument::fromJson(data).object().value("student_ids").toArray();
|
||||
for (const QJsonValue &value : array) {
|
||||
ids.push_back(value.toString());
|
||||
}
|
||||
emit allStudentIdsFetched(ids);
|
||||
reply->deleteLater();
|
||||
});
|
||||
}
|
||||
|
||||
void ApiClient::deleteFace(const QString &studentId, bool deleteRecords)
|
||||
{
|
||||
QString baseUrl = m_serverUrl.trimmed();
|
||||
@@ -665,9 +746,9 @@ void ApiClient::deleteFace(const QString &studentId, bool deleteRecords)
|
||||
if (deleteRecords) {
|
||||
url += "?delete_records=true";
|
||||
}
|
||||
QNetworkReply *reply = m_networkManager.deleteResource(createRequest(url));
|
||||
QNetworkReply *reply = m_networkManager.deleteResource(createRequest(url, true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, studentId]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
if (isConnectionError(reply->error())) {
|
||||
emit connectionUnavailable(QStringLiteral("删除失败: %1").arg(reply->errorString()));
|
||||
@@ -688,9 +769,9 @@ void ApiClient::deleteFace(const QString &studentId, bool deleteRecords)
|
||||
|
||||
void ApiClient::checkHealth()
|
||||
{
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/system/health")));
|
||||
QNetworkReply *reply = m_networkManager.get(createRequest(buildUrl("/v1/system/health"), true));
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
const QByteArray data = reply->readAll();
|
||||
const QByteArray data = decryptedReplyBody(reply);
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
emit healthChecked(false, reply->errorString());
|
||||
reply->deleteLater();
|
||||
|
||||
Reference in New Issue
Block a user