ソースを参照

bug修复,多租户登录,token清除

lsb 3 ヶ月 前
コミット
0b766c17d6

+ 9 - 1
src/httpclient/HttpCardLogin.cpp

@@ -73,13 +73,15 @@ void HttpCardLogin::slotHttpResponseCardLogin(QByteArray data)
     // 修复:原逻辑判断条件错误,应该是解析失败时返回错误
     if (error.error != QJsonParseError::NoError) {
         qDebug() << "[HttpCardLogin] JSON解析失败:" << error.errorString();
+        GetInteractiveData()->clearToken();
         emit signalLoginReturnStat(-3, "JSON解析失败: " + error.errorString());
         return;
     }
-    
+
     // 修复:原逻辑判断条件错误,应该是文档为空时返回错误
     if (jsonDoc.isNull() || jsonDoc.isEmpty()) {
         qDebug() << "[HttpCardLogin] JSON文档为空";
+        GetInteractiveData()->clearToken();
         emit signalLoginReturnStat(-3, "服务器返回数据为空");
         return;
     }
@@ -99,11 +101,13 @@ void HttpCardLogin::slotHttpResponseCardLogin(QByteArray data)
                 QJsonObject vDataObj = rootObj.value("data").toObject();
                 if (vDataObj.empty()) {
                     qDebug() << "[HttpCardLogin] data对象为空";
+                    GetInteractiveData()->clearToken();
                     emit signalLoginReturnStat(-3, "登录失败:返回数据为空");
                     return;
                 }
                 if (!vDataObj.contains("accessToken")) {
                     qDebug() << "[HttpCardLogin] 缺少accessToken字段";
+                    GetInteractiveData()->clearToken();
                     emit signalLoginReturnStat(-3, "登录失败:缺少accessToken");
                     return;
                 }
@@ -149,11 +153,13 @@ void HttpCardLogin::slotHttpResponseCardLogin(QByteArray data)
                 }
                 else{
                     qDebug() << "[HttpCardLogin] accessToken类型不是字符串";
+                    GetInteractiveData()->clearToken();
                     emit signalLoginReturnStat(-3, "登录失败:accessToken格式错误");
                 }
             }
             else{
                 qDebug() << "[HttpCardLogin] 响应中缺少data字段";
+                GetInteractiveData()->clearToken();
                 emit signalLoginReturnStat(-3, "登录失败:响应缺少data字段");
             }
         }
@@ -164,11 +170,13 @@ void HttpCardLogin::slotHttpResponseCardLogin(QByteArray data)
                 errorMsg = rootObj.value("msg").toString();
             }
             qDebug() << "[HttpCardLogin] 登录失败,code:" << codeValue << ",msg:" << errorMsg;
+            GetInteractiveData()->clearToken();
             emit signalLoginReturnStat(-2, errorMsg);
         }
     }
     else{
         qDebug() << "[HttpCardLogin] 响应中缺少code字段";
+        GetInteractiveData()->clearToken();
         emit signalLoginReturnStat(-3, "登录失败:响应格式错误");
     }
 }

+ 5 - 0
src/httpclient/HttpClient.cpp

@@ -18,6 +18,11 @@
 QString HttpClient::sToken = QString();
 const int HttpClient::HTTP_REQUEST_TIMEOUT = 5000;
 
+void HttpClient::clearToken()
+{
+    sToken.clear();
+}
+
 HttpClient::HttpClient(QObject *parent)
     : QThread(parent)
 {

+ 2 - 0
src/httpclient/HttpClient.h

@@ -28,6 +28,8 @@ public:
     ~HttpClient() override; // Qt5.15 override关键字需显式写
 
     static QString sToken; // 全局静态Token
+    /** 清空静态 sToken(与 InteractiveData::clearToken 配合使用) */
+    static void clearToken();
 
     void slotSetThreadStop();
 

+ 9 - 4
src/httpclient/HttpFaceLogin.cpp

@@ -105,7 +105,9 @@ void HttpFaceLogin::httpRequestFaceLogin()
 
     // 人脸登录:本模块自建 QNAM 发 multipart POST,不占用 HttpClient
     QString fullUrl = QString("http://%1%2").arg(host).arg(path);
-    qDebug().noquote() << "[HttpFaceLogin] 请求人脸登录 API:" << fullUrl;
+    qDebug().noquote() << "[HttpFaceLogin] 请求人脸登录 API:" << fullUrl
+                       << " tenant-id=" << cfg->tenant_id
+                       << " Authorization=" << (token.isEmpty() ? "(空)" : "(已设置)");
     QUrl url(fullUrl);
     bool isPng = m_faceImageData.size() >= 8 && m_faceImageData.startsWith("\x89PNG\r\n\x1a\n");
     QString partContentType = isPng ? "image/png" : "image/jpeg";
@@ -126,7 +128,11 @@ void HttpFaceLogin::httpRequestFaceLogin()
     if (!authValue.isEmpty() && !authValue.startsWith("Bearer ", Qt::CaseInsensitive))
         authValue = "Bearer " + authValue;
     request.setRawHeader("Authorization", authValue.toUtf8());
-    request.setRawHeader("tenant-id", cfg->tenant_id.toUtf8());
+    QString tenantId = cfg->tenant_id.trimmed();
+    if (!tenantId.isEmpty())
+        request.setRawHeader("tenant-id", tenantId.toUtf8());
+    else
+        qWarning() << "[HttpFaceLogin] tenant_id 为空,未设置 tenant-id 请求头,可能导致 403 您无权访问该租户的数据";
 
     QNetworkAccessManager nam;
     QNetworkReply *reply = nam.post(request, multiPart);
@@ -174,8 +180,7 @@ void HttpFaceLogin::slotHttpResponseFaceLogin(QByteArray data)
         QString msg = obj.value("msg").toString();
         if (msg.isEmpty()) msg = obj.value("result").toString();
         if (msg.isEmpty()) msg = QString::fromUtf8(data).left(200);
-        qDebug().noquote() << "[HttpFaceLogin] API 返回 code:" << code << "msg:" << msg
-                           << (code == 902 || msg.contains("无法根据人脸确定您的身份") ? " [902-人脸识别失败,QML 会累计计数]" : "");
+        qDebug().noquote() << "[HttpFaceLogin] API 返回非200 code:" << code << "msg:" << msg << "(QML 将累计,满5次停止)";
         emit signalFaceLoginError(msg);
         return;
     }

+ 106 - 100
src/httpclient/HttpPasswordLogin.cpp

@@ -1,100 +1,106 @@
-#include "HttpPasswordLogin.h"
-
-#include "../usr/config.h"
-#include "../interactive/InteractiveData.h"
-#include "../interactive/InteractiveHttp.h"
-
-#include <QJsonObject>
-
-HttpPasswordLogin::HttpPasswordLogin(QObject *parent)
-    : QThread(parent)
-{
-}
-
-void HttpPasswordLogin::run()
-{
-    httpRequestUsernameLogin();
-
-    //qDebug() << "HttpPasswordLogin thread exit!";
-}
-
-void HttpPasswordLogin::httpRequestUsernameLogin()
-{
-    QJsonObject jsonRoot;
-    QDateTime currentDateTime = QDateTime::currentDateTime();
-    qint64 timestampSeconds = currentDateTime.toMSecsSinceEpoch();
-
-    QString url = Config()->usernameLogin_url;
-    jsonRoot.insert("username", this->m_username);
-    jsonRoot.insert("password", this->m_password);
-
-    QJsonDocument jsonDoc(jsonRoot);
-    QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);
-    qDebug() << "json=" << QString::fromUtf8(jsonData);
-
-    emit signalPostRequestData(timestampSeconds, url, jsonData, NULL, m_accessToken);
-}
-
-void HttpPasswordLogin::slotHttpResponseUsernameLogin(QByteArray data)
-{
-    QJsonParseError error;
-    QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
-    if (error.error != QJsonParseError::NoError) {
-        emit signalLoginReturnStat(-3, "登录失败");
-        return;
-    }
-    if(jsonDoc.isNull() || jsonDoc.isEmpty()) {
-        emit signalLoginReturnStat(-3, "登录失败");
-        return;
-    }
-
-    QJsonObject rootObj = jsonDoc.object();
-    if(rootObj.contains("code"))
-    {
-        int codeValue = rootObj.value("code").toInt();
-        if(codeValue == 200 || codeValue == 0)
-        {
-            if(rootObj.contains("data"))
-            {
-                QJsonObject vDataObj = rootObj.value("data").toObject();
-                if (vDataObj.empty()) {
-                    emit signalLoginReturnStat(-3, "登录失败");
-                    return;
-                }
-                if (!vDataObj.contains("accessToken")) {
-                    emit signalLoginReturnStat(-3, "登录失败");
-                    return;
-                }
-                QJsonValue value = vDataObj.value("accessToken");
-                if(value.type() == QJsonValue::String)
-                {
-                    GetInteractiveData()->m_token = value.toString();
-                    InteractiveHttp::strToken = value.toString();
-                    m_accessToken = value.toString();
-                    if (vDataObj.contains("refreshToken") && vDataObj.value("refreshToken").type() == QJsonValue::String) {
-                        m_refreshToken = vDataObj.value("refreshToken").toString();
-                    }
-                    QString username = "未知用户";
-                    if(vDataObj.contains("nickname"))
-                    {
-                        QJsonValue name = vDataObj.value("nickname");
-                        if(name.type() == QJsonValue::String)
-                        {
-                            username = name.toString();
-                        }
-                    }
-                    Config()->username = username;
-                    GetInteractiveData()->m_userName = username;
-                    emit signalLoginReturnStat(0, "登录成功");
-                    emit signalLoginReturnParam(username, 0);
-                }
-                else {
-                    emit signalLoginReturnStat(-3, "登录失败");
-                }
-            }
-        }
-        else{
-            emit signalLoginReturnStat(-1, "用户名或者密码错误");
-        }
-    }
-}
+#include "HttpPasswordLogin.h"
+
+#include "../usr/config.h"
+#include "../interactive/InteractiveData.h"
+#include "../interactive/InteractiveHttp.h"
+
+#include <QJsonObject>
+
+HttpPasswordLogin::HttpPasswordLogin(QObject *parent)
+    : QThread(parent)
+{
+}
+
+void HttpPasswordLogin::run()
+{
+    httpRequestUsernameLogin();
+
+    //qDebug() << "HttpPasswordLogin thread exit!";
+}
+
+void HttpPasswordLogin::httpRequestUsernameLogin()
+{
+    QJsonObject jsonRoot;
+    QDateTime currentDateTime = QDateTime::currentDateTime();
+    qint64 timestampSeconds = currentDateTime.toMSecsSinceEpoch();
+
+    QString url = Config()->usernameLogin_url;
+    jsonRoot.insert("username", this->m_username);
+    jsonRoot.insert("password", this->m_password);
+
+    QJsonDocument jsonDoc(jsonRoot);
+    QByteArray jsonData = jsonDoc.toJson(QJsonDocument::Compact);
+    qDebug() << "json=" << QString::fromUtf8(jsonData);
+
+    emit signalPostRequestData(timestampSeconds, url, jsonData, NULL, m_accessToken);
+}
+
+void HttpPasswordLogin::slotHttpResponseUsernameLogin(QByteArray data)
+{
+    QJsonParseError error;
+    QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
+    if (error.error != QJsonParseError::NoError) {
+        GetInteractiveData()->clearToken();
+        emit signalLoginReturnStat(-3, "登录失败");
+        return;
+    }
+    if(jsonDoc.isNull() || jsonDoc.isEmpty()) {
+        GetInteractiveData()->clearToken();
+        emit signalLoginReturnStat(-3, "登录失败");
+        return;
+    }
+
+    QJsonObject rootObj = jsonDoc.object();
+    if(rootObj.contains("code"))
+    {
+        int codeValue = rootObj.value("code").toInt();
+        if(codeValue == 200 || codeValue == 0)
+        {
+            if(rootObj.contains("data"))
+            {
+                QJsonObject vDataObj = rootObj.value("data").toObject();
+                if (vDataObj.empty()) {
+                    GetInteractiveData()->clearToken();
+                    emit signalLoginReturnStat(-3, "登录失败");
+                    return;
+                }
+                if (!vDataObj.contains("accessToken")) {
+                    GetInteractiveData()->clearToken();
+                    emit signalLoginReturnStat(-3, "登录失败");
+                    return;
+                }
+                QJsonValue value = vDataObj.value("accessToken");
+                if(value.type() == QJsonValue::String)
+                {
+                    GetInteractiveData()->m_token = value.toString();
+                    InteractiveHttp::strToken = value.toString();
+                    m_accessToken = value.toString();
+                    if (vDataObj.contains("refreshToken") && vDataObj.value("refreshToken").type() == QJsonValue::String) {
+                        m_refreshToken = vDataObj.value("refreshToken").toString();
+                    }
+                    QString username = "未知用户";
+                    if(vDataObj.contains("nickname"))
+                    {
+                        QJsonValue name = vDataObj.value("nickname");
+                        if(name.type() == QJsonValue::String)
+                        {
+                            username = name.toString();
+                        }
+                    }
+                    Config()->username = username;
+                    GetInteractiveData()->m_userName = username;
+                    emit signalLoginReturnStat(0, "登录成功");
+                    emit signalLoginReturnParam(username, 0);
+                }
+                else {
+                    GetInteractiveData()->clearToken();
+                    emit signalLoginReturnStat(-3, "登录失败");
+                }
+            }
+        }
+        else{
+            GetInteractiveData()->clearToken();
+            emit signalLoginReturnStat(-1, "用户名或者密码错误");
+        }
+    }
+}

+ 76 - 66
src/interactive/InteractiveData.cpp

@@ -1,66 +1,76 @@
-#include "InteractiveData.h"
-
-InteractiveData* InteractiveData::pInstance = nullptr;
-
-InteractiveData::InteractiveData()
-{
-}
-
-InteractiveData *InteractiveData::instance()
-{
-    if (!pInstance) {
-        pInstance = new InteractiveData;
-    }
-    return pInstance;
-}
-
-InteractiveData *InteractiveData::create(QQmlEngine *, QJSEngine *)
-{
-    return instance();
-}
-
-bool InteractiveData::isHavePower(const QString &operation)
-{
-    // 如果角色包含超级管理员
-    if (m_roles.contains(QString("admin")))
-    {
-        return true;
-    }
-    // 如果是其他用户,判断是否有执行该操作的权利
-    else if (m_permissions.contains(operation))
-    {
-        return true;
-    }
-    // 如果没有权利执行该操作
-    else
-    {
-        return false;
-    }
-}
-
-QSet<QString> InteractiveData::roles()
-{
-    return m_roles;
-}
-
-void InteractiveData::setRoles(const QSet<QString> &roles)
-{
-    m_roles = roles;
-    emit rolesChanged();
-}
-
-QSet<QString> InteractiveData::permissions()
-{
-    return m_permissions;
-}
-
-void InteractiveData::setPermissions(const QSet<QString> &permissions)
-{
-    m_permissions = permissions;
-    emit permissionsChanged();
-}
-
-InteractiveData::~InteractiveData()
-{
-}
-
+#include "InteractiveData.h"
+#include "InteractiveHttp.h"
+#include "../httpclient/HttpClient.h"
+
+InteractiveData* InteractiveData::pInstance = nullptr;
+
+InteractiveData::InteractiveData()
+{
+    clearToken();
+}
+
+void InteractiveData::clearToken()
+{
+    m_token.clear();
+    InteractiveHttp::strToken.clear();
+    HttpClient::clearToken();
+}
+
+InteractiveData *InteractiveData::instance()
+{
+    if (!pInstance) {
+        pInstance = new InteractiveData;
+    }
+    return pInstance;
+}
+
+InteractiveData *InteractiveData::create(QQmlEngine *, QJSEngine *)
+{
+    return instance();
+}
+
+bool InteractiveData::isHavePower(const QString &operation)
+{
+    // 如果角色包含超级管理员
+    if (m_roles.contains(QString("admin")))
+    {
+        return true;
+    }
+    // 如果是其他用户,判断是否有执行该操作的权利
+    else if (m_permissions.contains(operation))
+    {
+        return true;
+    }
+    // 如果没有权利执行该操作
+    else
+    {
+        return false;
+    }
+}
+
+QSet<QString> InteractiveData::roles()
+{
+    return m_roles;
+}
+
+void InteractiveData::setRoles(const QSet<QString> &roles)
+{
+    m_roles = roles;
+    emit rolesChanged();
+}
+
+QSet<QString> InteractiveData::permissions()
+{
+    return m_permissions;
+}
+
+void InteractiveData::setPermissions(const QSet<QString> &permissions)
+{
+    m_permissions = permissions;
+    emit permissionsChanged();
+}
+
+InteractiveData::~InteractiveData()
+{
+}
+

+ 58 - 56
src/interactive/InteractiveData.h

@@ -1,56 +1,58 @@
-#ifndef INTERACTIVEDATA_H
-#define INTERACTIVEDATA_H
-#include <QMap>
-#include <QList>
-#include <QVector>
-#include <QString>
-
-#include <QReadWriteLock>
-#include <QReadLocker>
-#include <QWriteLocker>
-#include <QtQml>
-
-#define MATERIALS_TYPE_ALL "0"
-
-class InteractiveData : public QObject
-{
-    Q_OBJECT
-    QML_SINGLETON
-    QML_NAMED_ELEMENT(InteractiveData)
-
-    Q_PROPERTY(QSet<QString> roles READ roles WRITE setRoles NOTIFY rolesChanged)
-    Q_PROPERTY(QSet<QString> permissions READ permissions WRITE setPermissions NOTIFY permissionsChanged)
-signals:
-    void rolesChanged();
-    void permissionsChanged();
-
-public:
-    static InteractiveData* pInstance;
-    static InteractiveData* instance();
-    static InteractiveData* create(QQmlEngine*, QJSEngine*);
-
-    Q_INVOKABLE bool isHavePower(const QString &operation);
-
-    QSet<QString> roles();
-    void setRoles(const QSet<QString> &roles);
-    QSet<QString> permissions();
-    void setPermissions(const QSet<QString> &permissions);
-
-    void cleanJobTicketsModel();
-
-    ~InteractiveData();
-private:
-    explicit InteractiveData();
-public:
-    QString m_userName;                     // 用户名
-    QSet<QString> m_roles;                  // 用户角色
-    QSet<QString> m_permissions;            // 用户许可
-
-    QString m_token = "ab3938d75cf84f02a2fb7fa62f9e4397";
-};
-
-inline InteractiveData* GetInteractiveData()
-{
-    return InteractiveData::instance();
-}
-#endif // INTERACTIVEDATA_H
+#ifndef INTERACTIVEDATA_H
+#define INTERACTIVEDATA_H
+#include <QMap>
+#include <QList>
+#include <QVector>
+#include <QString>
+
+#include <QReadWriteLock>
+#include <QReadLocker>
+#include <QWriteLocker>
+#include <QtQml>
+
+#define MATERIALS_TYPE_ALL "0"
+
+class InteractiveData : public QObject
+{
+    Q_OBJECT
+    QML_SINGLETON
+    QML_NAMED_ELEMENT(InteractiveData)
+
+    Q_PROPERTY(QSet<QString> roles READ roles WRITE setRoles NOTIFY rolesChanged)
+    Q_PROPERTY(QSet<QString> permissions READ permissions WRITE setPermissions NOTIFY permissionsChanged)
+signals:
+    void rolesChanged();
+    void permissionsChanged();
+
+public:
+    static InteractiveData* pInstance;
+    static InteractiveData* instance();
+    static InteractiveData* create(QQmlEngine*, QJSEngine*);
+
+    Q_INVOKABLE bool isHavePower(const QString &operation);
+    /** 清空 m_token(系统启动、登录失败、退出登录时调用),同时清空 InteractiveHttp::strToken 与 HttpClient::sToken */
+    Q_INVOKABLE void clearToken();
+
+    QSet<QString> roles();
+    void setRoles(const QSet<QString> &roles);
+    QSet<QString> permissions();
+    void setPermissions(const QSet<QString> &permissions);
+
+    void cleanJobTicketsModel();
+
+    ~InteractiveData();
+private:
+    explicit InteractiveData();
+public:
+    QString m_userName;                     // 用户名
+    QSet<QString> m_roles;                  // 用户角色
+    QSet<QString> m_permissions;            // 用户许可
+
+    QString m_token;
+};
+
+inline InteractiveData* GetInteractiveData()
+{
+    return InteractiveData::instance();
+}
+#endif // INTERACTIVEDATA_H

+ 4 - 0
src/main.cpp

@@ -4,6 +4,7 @@
 #include <QDateTime>
 
 #include "./interactive/InteractiveCAN.h"
+#include "./interactive/InteractiveData.h"
 #include "./interactive/InteractiveFace.h"
 #include "./usr/LotoQmlPlugin.h"
 #include "./usr/config.h"
@@ -28,6 +29,9 @@ int main(int argc, char **argv)
     // 注册C++类/对象到QML
     LotoQmlTypes::registerTypes();
 
+    // 系统启动时清空 token(未登录状态)
+    GetInteractiveData()->clearToken();
+
     // 人脸采集 ImageProvider,供 QML 中 Image source="image://InteractiveFaceImage/xxx" 使用
     engine.addImageProvider(QStringLiteral("InteractiveFaceImage"), GetInteractiveFace());
 

+ 24 - 36
src/qml/Login.qml

@@ -47,9 +47,9 @@ Rectangle {
     // 人脸登录错误弹窗:API 返回错误时弹窗,确认按钮 10 秒倒计时自动关闭
     property var faceLoginErrorDialog: null
     property int faceLoginErrorCountdown: 10
-    // 累计「无法根据人脸确定您的身份」(902) 次数,满 5 次则停止采集并弹窗
+    // 累计人脸 API 非 200 次数(任意非 200 都统计),满 5 次则停止视频、停止识别并弹窗
     property int faceLoginError902Count: 0
-    // 已达 5 次 902 错误,弹窗已显示,停止 API/采集定时器
+    // 已达 5 次非 200,弹窗已显示,停止 API/采集定时器
     property bool faceLoginError902Reached: false
 
     Component.onCompleted: {
@@ -225,24 +225,22 @@ Rectangle {
             faceLoginSuccessCloseTimer.start()
         }
         function onSignalFaceLoginError(msg) {
-            // 902「无法根据人脸确定您的身份」累计 5 次则停止采集并弹窗
-            if (msg && msg.indexOf("无法根据人脸确定您的身份") >= 0) {
-                var prevCount = control.faceLoginError902Count || 0;
-                control.faceLoginError902Count = prevCount + 1;
-                var curCount = control.faceLoginError902Count;
-                console.log("[Login.qml] 人脸 902 错误,当前累计次数:", curCount, "/5, msg:", msg);
-                if (curCount >= 5) {
-                    console.log("[Login.qml] 人脸 902 已达 5 次,停止采集并弹窗");
-                    control.faceLoginError902Count = 0;
-                    control.faceLoginError902Reached = true;  // 停止 API/采集定时器
-                    InteractiveFace.cameraImageStop();
-                    if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
-                        httpFaceLogin.clearFaceQueue();
-                    if (typeof control.clearFaceLoginResources === "function")
-                        control.clearFaceLoginResources();
-                    control.showFaceLoginErrorDialog("人脸识别登录失败,请重试");
-                    return;
-                }
+            // 非 200 状态都统计:API 返回非 200 即累计一次,满 5 次则停止视频、停止识别并弹窗
+            var prevCount = control.faceLoginError902Count || 0;
+            control.faceLoginError902Count = prevCount + 1;
+            var curCount = control.faceLoginError902Count;
+            console.log("[Login.qml] 人脸 API 非200,累计:", curCount, "/5, msg:", msg);
+            if (curCount >= 5) {
+                console.log("[Login.qml] 非200 已达 5 次,停止视频与识别并弹窗");
+                control.faceLoginError902Count = 0;
+                control.faceLoginError902Reached = true;
+                InteractiveFace.cameraImageStop();
+                if (typeof httpFaceLogin !== "undefined" && httpFaceLogin.clearFaceQueue)
+                    httpFaceLogin.clearFaceQueue();
+                if (typeof control.clearFaceLoginResources === "function")
+                    control.clearFaceLoginResources();
+                control.showFaceLoginErrorDialog("人脸识别登录失败,请重试");
+                return;
             }
             console.log("[Login.qml] 人脸登录 API 错误,继续识别:", msg);
         }
@@ -552,7 +550,7 @@ Rectangle {
             }
         }
 
-        // 圆心摄像头影像:用 ShaderEffectSource + OpacityMask 实现圆形裁剪,确保不超出圆形
+        // 摄像头影像:只保留一块圆形区域(方形内容先被捕获再裁成圆形,不直接显示方形)
         Item {
             id: cameraCircleArea
             width: faceDiameter
@@ -561,11 +559,13 @@ Rectangle {
             anchors.verticalCenter: parent.verticalCenter
             anchors.verticalCenterOffset: -30  // 向上偏移,为下方提示文字留出空间
 
-            // 待裁剪内容(ShaderEffectSource 的 sourceItem,hideSource 会隐藏其直接显示)
+            // 视频内容(方形)放在可视区外,仅供 ShaderEffectSource 采样,界面只显示下方裁成圆形的结果
             Item {
                 id: cameraContentItem
                 width: faceDiameter
                 height: faceDiameter
+                x: -faceDiameter - 10
+                y: -faceDiameter - 10
                 visible: true
 
                 Rectangle {
@@ -617,7 +617,7 @@ Rectangle {
                 }
             }
 
-            // 捕获内容到纹理,hideSource 确保 sourceItem 不直接显示;须 visible 以便 OpacityMask 能采样
+            // 将内容捕获为纹理(source 已 visible:false,此处仅用于采样;若平台仍绘制 source 可改用 layer)
             ShaderEffectSource {
                 id: cameraEffectSource
                 width: faceDiameter
@@ -638,6 +638,7 @@ Rectangle {
                 visible: false
             }
 
+            // 唯一可见的显示:方形纹理裁成圆形
             OpacityMask {
                 anchors.fill: parent
                 source: cameraEffectSource
@@ -669,19 +670,6 @@ Rectangle {
             horizontalAlignment: Text.AlignHCenter
             z: 10
         }
-        // 识别错误计数:累计 902「无法根据人脸确定您的身份」时显示,满 5 次弹窗并停止
-        Text {
-            id: faceErrorCountText
-            anchors.horizontalCenter: parent.horizontalCenter
-            anchors.top: faceTipText.bottom
-            anchors.topMargin: 8
-            text: control.faceLoginError902Count > 0 ? ("识别错误 " + control.faceLoginError902Count + "/5 次") : ""
-            color: "#FFB74D"
-            font.pixelSize: 18
-            horizontalAlignment: Text.AlignHCenter
-            z: 10
-            visible: text !== ""
-        }
 
         property string faceCameraUrl: ""
         property bool hasValidCameraImage: false

+ 1 - 0
src/qml/main.qml

@@ -638,6 +638,7 @@ ApplicationWindow {
                 }
             }
         } else {
+            InteractiveData.clearToken();
             stackView.clear();
             stackView.push(adPage.createObject(appWindow, {adImageSource: advertisementImageModel[0]}));
             homeMouseArea.visible = true;

+ 1 - 0
src/usr/LotoQmlPlugin.cpp

@@ -92,6 +92,7 @@ void LotoQmlTypes::registerTypes()
     qmlRegisterSingletonType<JobTicketModel>(uri, majorVersion, minorVersion, "JobTicketModel", &JobTicketModel::create);
     qmlRegisterSingletonType<UserInfoModel>(uri, majorVersion, minorVersion, "UserInfoModel", &UserInfoModel::create);
     qmlRegisterSingletonType<WorkNodeFormModel>(uri, majorVersion, minorVersion, "WorkNodeFormModel", &WorkNodeFormModel::create);
+    qmlRegisterSingletonType<InteractiveData>(uri, majorVersion, minorVersion, "InteractiveData", &InteractiveData::create);
 
     qmlRegisterSingletonType<InteractiveCAN>(uri, majorVersion, minorVersion, "InteractiveCAN", &InteractiveCAN::create);
     qmlRegisterSingletonType<InteractiveFace>(uri, majorVersion, minorVersion, "InteractiveFace", &InteractiveFace::create);

+ 202 - 202
src/usr/config.h

@@ -1,202 +1,202 @@
-#ifndef CONFIG_H_
-#define CONFIG_H_
-#include <QString>
-#include <QObject>
-#include <QHash>
-#include <QVariantMap>
-#include <QtQml/qqml.h>
-
-class config : public QObject
-{
-    Q_OBJECT
-    QML_SINGLETON
-    QML_NAMED_ELEMENT(Config)
-
-    Q_PROPERTY(QString suserId READ suserId WRITE setSuserId)
-    Q_PROPERTY(QString sdevUuid READ sdevUuid WRITE setSdevUuid NOTIFY sdevUuidChanged)
-    Q_PROPERTY(QString shttpHost READ shttpHost WRITE setShttpHost NOTIFY shttpHostChanged)
-    Q_PROPERTY(int sloginTimeout READ sloginTimeout WRITE setSloginTimeout NOTIFY sloginTimeoutChanged)
-    Q_PROPERTY(QString susername READ susername)
-    Q_PROPERTY(QStringList sserialPortList READ sserialPortList)
-
-    Q_PROPERTY(int scurrentPlanId READ scurrentPlanId WRITE setScurrentPlanId)
-private:
-    config();
-
-    Q_DISABLE_COPY(config);
-public:
-    static config* pInstance;
-    static config* instance();
-    static config* create(QQmlEngine*, QJSEngine*);
-
-    void configWrite(void);
-    bool configRead(void);
-    QString getDeviceUUID(void);
-
-    enum xmlType {
-        XML_NULL = 0,
-        XML_CONFIG,
-        XML_DEVICE,
-        XML_SERVER,
-        XML_PARAM,
-    };
-    Q_ENUM(xmlType)
-#ifdef Q_OS_WIN
-    QString configfile = "config.xml";
-#else
-    QString configpath = "/storage/emulated/0/Android/data/com.cabinet/";
-    QString configfile = configpath + "config.xml";
-#endif
-    bool devInit = false;
-    QString devUuid = "CABINET_016";
-    bool logfileStat = false;
-    bool readBee = false;
-    int heartTime = 60;
-    int lockCloseTimeout = 10 * 60;
-    bool rfidInit = false;
-    bool lockInit = false;
-
-    int currentPlanId = 0;
-
-    const char* lotoQmlModuleName = "Loto";
-    int lotoQmlModuleMajorVersion = 1;
-    int lotoQmlModuleMinorVersion = 0;
-
-#ifdef Q_OS_WIN
-    QString rfidPort = "COM1";
-    QString lockPort = "COM2";
-#else
-    QString rfidPort = "/dev/ttyUSB1";
-    QString lockPort = "/dev/ttyUSB2";
-#endif
-    QStringList serialPortList;
-
-    QString m_systemMACAddr;
-
-//    QString httpHost = "120.27.232.27:9292";
-    QString httpHost = "192.168.0.10:48080";
-    QString tenant_id = "149";
-
-    QString userInfoUrl = "/admin-api/system/user/profile/get";                                             // 用户中心
-    QString updateUserInfoUrl = "/admin-api/system/user/profile/update";                                    // 更新用户信息
-    QString updatePasswordUrl = "/admin-api/system/user/profile/update-password";                           // 更新用户密码
-    QString userInfoByIdUrl = "/admin-api/system/user/get";                                                 // 根据用户ID获取用户头像等信息
-
-    QString usernameLogin_url = "/admin-api/system/auth/login";                                             //用户名登陆接口
-    QString cardLogin_url = "/admin-api/iscs/job-card/loginByCard";                                          //卡号登陆接口
-    QString logout_url = "/logout";                                                                         //登出接口
-
-    QString getInfo_url = "/getInfo";                                                                       // 获取当前用户信息
-    QString getSysUserCharacteristicPage_url = "/system/user/characteristic/getSysUserCharacteristicPage";  //获取当前用户特征
-    QString insertUserFace_url = "/system/user/characteristic/insertUserFace";   // 新增面部信息
-
-    QString loginByFace_url = "/loginByArcFace";   // 人脸登录
-
-    QString jobTicketsUrl = "/admin-api/iscs/workflow-work/getMyWorkPage";                                    // 获取我的作业页面
-    QString workNodeDetail = "/admin-api/iscs/workflow-work/getMyWorkNodeDetail";                               // 表单详情页面
-    QString workNodeDetailForm = "/admin-api/bpm/form/get";                                                   // 表单详情页中的组件信息
-    QString updateNodeApprovalUrl = "/admin-api/iscs/workflow-work/updateNodeApproval";                        // 表单提交
-
-    QString keyMACByNFC = "/admin-api/iscs/key/selectKeyByNfc";                                                 // 根据NFC信息获取钥匙MAC地址
-
-    QString isolationPointById = "/admin-api/iscs/isolation-point/selectIsolationPointById";                   // 根据隔离点ID获取隔离点具体信息
-
-    QString uploadJobTicketUrl = "/admin-api/isc/work-handle/insertWorkTicket";
-    QString uploadPositionInfoUrl = "/admin-api/isc/work-handle/updatePointLock";
-
-    QString updateColockUrl = "/admin-api/isc/work-handle/updateUserLock";
-    QString updateUncolockUrl = "/admin-api/isc/work-handle/updateUserUnlock";
-    QString updatePointUnlock = "/admin-api/isc/work-handle/updatePointUnlock";
-    QString updateBackLock = "/admin-api/isc/work-handle/updateBackLock";
-
-    QString workTicketByNodeId = "/admin-api/isc/work-handle/getWorkTicketByNodeId";
-
-    QString ip = "0.0.0.0";
-    QString mask = "255.255.255.0";
-    QString dns = "0.0.0.0";
-
-    int loginTimeout = 99;
-
-    QString userId;
-    QString username;
-    QString nickName;
-    QString cardNo;
-    QString devId;
-    QString devName;
-
-    QString suserId()
-    {
-        return userId;
-    }
-    void setSuserId(const QString& suserId)
-    {
-        userId = suserId;
-    }
-
-    QString sdevUuid() const {
-        return devUuid;
-    }
-
-    void setSdevUuid(const QString &sdevUuid) {
-        if (sdevUuid == devUuid)
-            return;
-        devUuid = sdevUuid;
-        emit sdevUuidChanged();
-    }
-
-    QString shttpHost() const {
-        return httpHost;
-    }
-
-    void setShttpHost(const QString &shttpHost) {
-        if (shttpHost == httpHost)
-            return;
-        httpHost = shttpHost;
-        emit shttpHostChanged();
-    }
-
-    int sloginTimeout() const {
-        return loginTimeout;
-    }
-
-    void setSloginTimeout(const int &sloginTimeout) {
-        if (sloginTimeout == loginTimeout)
-            return;
-        loginTimeout = sloginTimeout;
-        emit sloginTimeoutChanged();
-    }
-
-    QString susername() const {
-        return username;
-    }
-
-    QStringList sserialPortList() const {
-        return serialPortList;
-    }
-
-    int scurrentPlanId()
-    {
-        return currentPlanId;
-    }
-
-    Q_INVOKABLE void setScurrentPlanId(int scurrentPlanId)
-    {
-        currentPlanId = scurrentPlanId;
-    }
-private:
-    void getDeviceValue(QString key, QString value);
-    void getParamValue(QString key, QString value);
-    void getServerValue(QString key, QString value);
-
-signals:
-    void sdevUuidChanged();
-    void shttpHostChanged();
-    void shttpPortChanged();
-    void sloginTimeoutChanged();
-};
-
-inline config* Config() {
-    return config::instance();
-}
-
-#endif // CONFIG_H
+#ifndef CONFIG_H_
+#define CONFIG_H_
+#include <QString>
+#include <QObject>
+#include <QHash>
+#include <QVariantMap>
+#include <QtQml/qqml.h>
+
+class config : public QObject
+{
+    Q_OBJECT
+    QML_SINGLETON
+    QML_NAMED_ELEMENT(Config)
+
+    Q_PROPERTY(QString suserId READ suserId WRITE setSuserId)
+    Q_PROPERTY(QString sdevUuid READ sdevUuid WRITE setSdevUuid NOTIFY sdevUuidChanged)
+    Q_PROPERTY(QString shttpHost READ shttpHost WRITE setShttpHost NOTIFY shttpHostChanged)
+    Q_PROPERTY(int sloginTimeout READ sloginTimeout WRITE setSloginTimeout NOTIFY sloginTimeoutChanged)
+    Q_PROPERTY(QString susername READ susername)
+    Q_PROPERTY(QStringList sserialPortList READ sserialPortList)
+
+    Q_PROPERTY(int scurrentPlanId READ scurrentPlanId WRITE setScurrentPlanId)
+private:
+    config();
+
+    Q_DISABLE_COPY(config);
+public:
+    static config* pInstance;
+    static config* instance();
+    static config* create(QQmlEngine*, QJSEngine*);
+
+    void configWrite(void);
+    bool configRead(void);
+    QString getDeviceUUID(void);
+
+    enum xmlType {
+        XML_NULL = 0,
+        XML_CONFIG,
+        XML_DEVICE,
+        XML_SERVER,
+        XML_PARAM,
+    };
+    Q_ENUM(xmlType)
+#ifdef Q_OS_WIN
+    QString configfile = "config.xml";
+#else
+    QString configpath = "/storage/emulated/0/Android/data/com.cabinet/";
+    QString configfile = configpath + "config.xml";
+#endif
+    bool devInit = false;
+    QString devUuid = "CABINET_016";
+    bool logfileStat = false;
+    bool readBee = false;
+    int heartTime = 60;
+    int lockCloseTimeout = 10 * 60;
+    bool rfidInit = false;
+    bool lockInit = false;
+
+    int currentPlanId = 0;
+
+    const char* lotoQmlModuleName = "Loto";
+    int lotoQmlModuleMajorVersion = 1;
+    int lotoQmlModuleMinorVersion = 0;
+
+#ifdef Q_OS_WIN
+    QString rfidPort = "COM1";
+    QString lockPort = "COM2";
+#else
+    QString rfidPort = "/dev/ttyUSB1";
+    QString lockPort = "/dev/ttyUSB2";
+#endif
+    QStringList serialPortList;
+
+    QString m_systemMACAddr;
+
+    QString httpHost = "120.27.232.27:9292";
+    //QString httpHost = "192.168.0.10:48080";
+    QString tenant_id = "149";
+
+    QString userInfoUrl = "/admin-api/system/user/profile/get";                                             // 用户中心
+    QString updateUserInfoUrl = "/admin-api/system/user/profile/update";                                    // 更新用户信息
+    QString updatePasswordUrl = "/admin-api/system/user/profile/update-password";                           // 更新用户密码
+    QString userInfoByIdUrl = "/admin-api/system/user/get";                                                 // 根据用户ID获取用户头像等信息
+
+    QString usernameLogin_url = "/admin-api/system/auth/login";                                             //用户名登陆接口
+    QString cardLogin_url = "/admin-api/iscs/job-card/loginByCard";                                          //卡号登陆接口
+    QString logout_url = "/logout";                                                                         //登出接口
+
+    QString getInfo_url = "/getInfo";                                                                       // 获取当前用户信息
+    QString getSysUserCharacteristicPage_url = "/system/user/characteristic/getSysUserCharacteristicPage";  //获取当前用户特征
+    QString insertUserFace_url = "/system/user/characteristic/insertUserFace";   // 新增面部信息
+
+    QString loginByFace_url = "/admin-api/system/auth/loginByArcFace";   // 人脸登录
+
+    QString jobTicketsUrl = "/admin-api/iscs/workflow-work/getMyWorkPage";                                    // 获取我的作业页面
+    QString workNodeDetail = "/admin-api/iscs/workflow-work/getMyWorkNodeDetail";                               // 表单详情页面
+    QString workNodeDetailForm = "/admin-api/bpm/form/get";                                                   // 表单详情页中的组件信息
+    QString updateNodeApprovalUrl = "/admin-api/iscs/workflow-work/updateNodeApproval";                        // 表单提交
+
+    QString keyMACByNFC = "/admin-api/iscs/key/selectKeyByNfc";                                                 // 根据NFC信息获取钥匙MAC地址
+
+    QString isolationPointById = "/admin-api/iscs/isolation-point/selectIsolationPointById";                   // 根据隔离点ID获取隔离点具体信息
+
+    QString uploadJobTicketUrl = "/admin-api/isc/work-handle/insertWorkTicket";
+    QString uploadPositionInfoUrl = "/admin-api/isc/work-handle/updatePointLock";
+
+    QString updateColockUrl = "/admin-api/isc/work-handle/updateUserLock";
+    QString updateUncolockUrl = "/admin-api/isc/work-handle/updateUserUnlock";
+    QString updatePointUnlock = "/admin-api/isc/work-handle/updatePointUnlock";
+    QString updateBackLock = "/admin-api/isc/work-handle/updateBackLock";
+
+    QString workTicketByNodeId = "/admin-api/isc/work-handle/getWorkTicketByNodeId";
+
+    QString ip = "0.0.0.0";
+    QString mask = "255.255.255.0";
+    QString dns = "0.0.0.0";
+
+    int loginTimeout = 99;
+
+    QString userId;
+    QString username;
+    QString nickName;
+    QString cardNo;
+    QString devId;
+    QString devName;
+
+    QString suserId()
+    {
+        return userId;
+    }
+    void setSuserId(const QString& suserId)
+    {
+        userId = suserId;
+    }
+
+    QString sdevUuid() const {
+        return devUuid;
+    }
+
+    void setSdevUuid(const QString &sdevUuid) {
+        if (sdevUuid == devUuid)
+            return;
+        devUuid = sdevUuid;
+        emit sdevUuidChanged();
+    }
+
+    QString shttpHost() const {
+        return httpHost;
+    }
+
+    void setShttpHost(const QString &shttpHost) {
+        if (shttpHost == httpHost)
+            return;
+        httpHost = shttpHost;
+        emit shttpHostChanged();
+    }
+
+    int sloginTimeout() const {
+        return loginTimeout;
+    }
+
+    void setSloginTimeout(const int &sloginTimeout) {
+        if (sloginTimeout == loginTimeout)
+            return;
+        loginTimeout = sloginTimeout;
+        emit sloginTimeoutChanged();
+    }
+
+    QString susername() const {
+        return username;
+    }
+
+    QStringList sserialPortList() const {
+        return serialPortList;
+    }
+
+    int scurrentPlanId()
+    {
+        return currentPlanId;
+    }
+
+    Q_INVOKABLE void setScurrentPlanId(int scurrentPlanId)
+    {
+        currentPlanId = scurrentPlanId;
+    }
+private:
+    void getDeviceValue(QString key, QString value);
+    void getParamValue(QString key, QString value);
+    void getServerValue(QString key, QString value);
+
+signals:
+    void sdevUuidChanged();
+    void shttpHostChanged();
+    void shttpPortChanged();
+    void sloginTimeoutChanged();
+};
+
+inline config* Config() {
+    return config::instance();
+}
+
+#endif // CONFIG_H