zhuizhu hace 10 meses
padre
commit
5f626c8ef8
Se han modificado 10 ficheros con 577 adiciones y 49 borrados
  1. 6 0
      CPPWebFramework/cwf/modelbasicoperation.cpp
  2. 95 22
      api/gradesapi.cpp
  3. 37 17
      api/gradesapi.h
  4. 150 0
      api/requestthreadpool.cpp
  5. 78 0
      api/requestthreadpool.h
  6. 95 1
      api/tapi.cpp
  7. 41 0
      main.cpp
  8. 61 2
      tcontroller.cpp
  9. 2 0
      teacherServer.pro
  10. 12 7
      tmodel.h

+ 6 - 0
CPPWebFramework/cwf/modelbasicoperation.cpp

@@ -1,4 +1,5 @@
 #include "modelbasicoperation.h"
+#include "qobject.h"
 
 CWF_BEGIN_NAMESPACE
 
@@ -149,6 +150,11 @@ QString ModelBasicOperation::convertQVariantTypeToSQLType(const QVariant::Type t
         output = "INTEGER";
         break;
     }
+    case QVariant::Char:
+    case QMetaType::SChar: {
+        output = "INTEGER";
+        break;
+    }
     case QVariant::Bool: {
         output = "INTEGER";
         break;

+ 95 - 22
api/gradesapi.cpp

@@ -13,33 +13,36 @@
 namespace TC {
 
 extern std::optional<QJsonValue> sendRequest(QNetworkAccessManager::Operation op,
-                                      const QString &url,
-                                      const QByteArray &postData);
+                                             const QString &url,
+                                             const QByteArray &postData);
+
+extern std::optional<QJsonValue> sendRawRequest(QNetworkAccessManager::Operation op,
+                                                const QString &url,
+                                                const QByteArray &postData);
 
 GradesUpdate::GradesUpdate()
     : QObject()
-    , url("/score-api/grades/update")
+    , url("/score-api/grades/create")
 {}
 
-GradesUpdate::GradesUpdate(const QString &answerFileName, 
-                         const QString &checkinNumber,
-                         int endTime,
-                         const QString &examRoom,
-                         const QString &examSessions,
-                         const QString &examText,
-                         const QString &examineNumber,
-                         const QString &groupName,
-                         const QString &id,
-                         const QString &name,
-                         int onlineStatus,
-                         const QString &schoolAddress,
-                         const QString &schoolName,
-                         int sex,
-                         int startTime,
-                         int status,
-                         const QString &swId)
+GradesUpdate::GradesUpdate(const QString &answerFileName,
+                           const QString &checkinNumber,
+                           int endTime,
+                           const QString &examRoom,
+                           const QString &examSessions,
+                           const QString &examText,
+                           const QString &examineNumber,
+                           const QString &groupName,
+                           const QString &name,
+                           int onlineStatus,
+                           const QString &schoolAddress,
+                           const QString &schoolName,
+                           int sex,
+                           int startTime,
+                           int status,
+                           const QString &swId)
     : QObject()
-    , url("/score-api/grades/update")
+    , url("/score-api/grades/create")
 {
     QJsonObject json;
     json["answerFileName"] = answerFileName;
@@ -50,7 +53,6 @@ GradesUpdate::GradesUpdate(const QString &answerFileName,
     json["examText"] = examText;
     json["examineNumber"] = examineNumber;
     json["groupName"] = groupName;
-    json["id"] = id;
     json["name"] = name;
     json["onlineStatus"] = onlineStatus;
     json["schoolAddress"] = schoolAddress;
@@ -90,4 +92,75 @@ GradesUpdate::Data GradesUpdate::post()
     return ret;
 }
 
+// 添加异步版本的 post 方法
+void GradesUpdate::postAsync(std::function<void(const Data&)> callback, int maxRetries)
+{
+    // 创建任务并添加到线程池
+    std::shared_ptr<GradesUpdateTask> task = std::make_shared<GradesUpdateTask>(url, postData, callback);
+    task->setMaxRetries(maxRetries);
+    RequestThreadPool::instance()->addTask(task);
+}
+
+// GradesUpdateTask 实现
+GradesUpdateTask::GradesUpdateTask(const QString &url, const QByteArray &postData, std::function<void(const GradesUpdate::Data&)> callback)
+    : m_url(url)
+    , m_postData(postData)
+    , m_callback(callback)
+{
+}
+
+void GradesUpdateTask::run()
+{
+    // 执行请求
+    std::optional<QJsonValue> data = sendRawRequest(QNetworkAccessManager::PostOperation,
+                                                    m_url,
+                                                    m_postData);
+
+    GradesUpdate::Data ret;
+    ret.success = false;
+    ret.message = "Failed to update grades";
+    
+    bool success = false;
+    
+    if (data.has_value()) {
+        const QJsonObject &object = data.value().toObject();
+        qDebug() << object << object.isEmpty() << object.empty();
+
+        if (object.contains("code")) {
+            int code = object.value("code").toInt();
+            if (code == 0) {
+                ret.success = true;
+            }
+        }
+        if (object.contains("msg")) {
+            ret.message = object.value("msg").toString();
+        }
+
+        // 如果成功,触发考试更新事件
+        if (ret.success) {
+            AppEvent::instance()->examsTestUpdate();
+            success = true;
+        }
+    }
+    
+    // 调用回调函数
+    if (m_callback) {
+        m_callback(ret);
+    }
+    
+    // 发送任务完成信号
+    emit taskCompleted(success);
+    
+    // 如果失败,发送失败信号,可能会触发重试
+    if (!success) {
+        emit taskFailed();
+    }
+}
+
+bool GradesUpdateTask::retry()
+{
+    // 这里可以添加重试前的准备工作,如果需要的话
+    return true; // 返回 true 表示可以重试
+}
+
 } // namespace TC

+ 37 - 17
api/gradesapi.h

@@ -2,6 +2,7 @@
 #define GRADESAPI_H
 
 #include <QObject>
+#include "requestthreadpool.h"
 
 namespace TC {
 
@@ -16,30 +17,49 @@ public:
     };
     
     GradesUpdate();
-    GradesUpdate(const QString &answerFileName, 
-                const QString &checkinNumber,
-                int endTime,
-                const QString &examRoom,
-                const QString &examSessions,
-                const QString &examText,
-                const QString &examineNumber,
-                const QString &groupName,
-                const QString &id,
-                const QString &name,
-                int onlineStatus,
-                const QString &schoolAddress,
-                const QString &schoolName,
-                int sex,
-                int startTime,
-                int status,
-                const QString &swId);
+    GradesUpdate(const QString &answerFileName,
+                 const QString &checkinNumber,
+                 int endTime,
+                 const QString &examRoom,
+                 const QString &examSessions,
+                 const QString &examText,
+                 const QString &examineNumber,
+                 const QString &groupName,
+                 const QString &name,
+                 int onlineStatus,
+                 const QString &schoolAddress,
+                 const QString &schoolName,
+                 int sex,
+                 int startTime,
+                 int status,
+                 const QString &swId);
 
+    // 同步版本
     Data post();
+    
+    // 异步版本 - 添加到线程池中执行
+    void postAsync(std::function<void(const Data&)> callback = nullptr, int maxRetries = 3);
 
     QString url;
     QByteArray postData;
 };
 
+// GradesUpdate 请求任务类
+class GradesUpdateTask : public RequestTask
+{
+    Q_OBJECT
+public:
+    GradesUpdateTask(const QString &url, const QByteArray &postData, std::function<void(const GradesUpdate::Data&)> callback = nullptr);
+    
+    void run() override;
+    bool retry() override;
+    
+private:
+    QString m_url;
+    QByteArray m_postData;
+    std::function<void(const GradesUpdate::Data&)> m_callback;
+};
+
 } // namespace TC
 
 #endif // GRADESAPI_H

+ 150 - 0
api/requestthreadpool.cpp

@@ -0,0 +1,150 @@
+#include "requestthreadpool.h"
+#include <QTimer>
+#include <QDebug>
+
+namespace TC {
+
+RequestThreadPool* RequestThreadPool::s_instance = nullptr;
+
+RequestThreadPool* RequestThreadPool::instance()
+{
+    if (!s_instance) {
+        s_instance = new RequestThreadPool();
+    }
+    return s_instance;
+}
+
+RequestThreadPool::RequestThreadPool(QObject* parent)
+    : QObject(parent)
+{
+    // 默认设置为4个线程
+    m_threadPool.setMaxThreadCount(4);
+}
+
+RequestThreadPool::~RequestThreadPool()
+{
+    m_threadPool.waitForDone();
+}
+
+void RequestThreadPool::setMaxThreadCount(int count)
+{
+    m_threadPool.setMaxThreadCount(count);
+}
+
+int RequestThreadPool::activeThreadCount() const
+{
+    return m_threadPool.activeThreadCount();
+}
+
+void RequestThreadPool::addTask(std::shared_ptr<RequestTask> task)
+{
+    if (!task) {
+        return;
+    }
+    
+    // 连接信号槽,传递任务指针作为参数
+    connect(task.get(), &RequestTask::taskCompleted, this, &RequestThreadPool::onTaskCompleted);
+    connect(task.get(), &RequestTask::taskFailed, this, [this, task]() {
+        this->onTaskFailed(task.get());
+    });
+    
+    QMutexLocker locker(&m_mutex);
+    m_taskQueue.enqueue(task);
+    
+    // 如果线程池有空闲线程,则立即处理任务
+    if (m_threadPool.activeThreadCount() < m_threadPool.maxThreadCount()) {
+        std::shared_ptr<RequestTask> nextTask = nullptr;
+        if (!m_taskQueue.isEmpty()) {
+            nextTask = m_taskQueue.dequeue();
+            m_activeTaskCount++;
+        }
+        
+        // 在锁释放后启动任务,避免死锁
+        if (nextTask) {
+            m_threadPool.start(nextTask.get());
+        }
+    }
+}
+
+void RequestThreadPool::processNextTask()
+{
+    std::shared_ptr<RequestTask> nextTask = nullptr;
+    
+    {
+        QMutexLocker locker(&m_mutex);
+        if (!m_taskQueue.isEmpty()) {
+            nextTask = m_taskQueue.dequeue();
+            m_activeTaskCount++;
+        }
+    }
+    
+    // 在锁释放后启动任务,避免死锁
+    if (nextTask) {
+        m_threadPool.start(nextTask.get());
+    }
+}
+
+void RequestThreadPool::onTaskCompleted(bool success)
+{
+    {
+        QMutexLocker locker(&m_mutex);
+        m_activeTaskCount--;
+    }
+    
+    // 在锁释放后处理下一个任务
+    processNextTask();
+}
+
+// 修改方法签名,直接接收任务指针作为参数
+void RequestThreadPool::onTaskFailed(RequestTask* task)
+{
+    if (!task) {
+        return;
+    }
+    
+    bool canRetry = false;
+    int retryCount = 0;
+    
+    {
+        QMutexLocker locker(&m_mutex);
+        m_activeTaskCount--;
+        
+        // 在锁内检查是否可以重试,但不执行重试操作
+        canRetry = task->canRetry();
+        if (canRetry) {
+            task->incrementRetryCount();
+            retryCount = task->retryCount();
+        }
+    }
+    
+    if (canRetry) {
+        qDebug() << "Task failed, retrying..." << retryCount << "of" << task->maxRetries();
+        
+        // 延迟一段时间后重试 (500ms * 重试次数)
+        QTimer::singleShot(500 * retryCount, [this, task]() {
+            bool shouldRetry = task->retry();
+            
+            if (shouldRetry) {
+                // 重新加入队列
+                bool startTask = false;
+                {
+                    QMutexLocker locker(&m_mutex);
+                    m_activeTaskCount++;
+                    startTask = true;
+                }
+                
+                if (startTask) {
+                    m_threadPool.start(task);
+                }
+            } else {
+                // 重试失败,处理下一个任务
+                processNextTask();
+            }
+        });
+    } else {
+        // 达到最大重试次数,处理下一个任务
+        processNextTask();
+    }
+}
+
+} // namespace TC

+ 78 - 0
api/requestthreadpool.h

@@ -0,0 +1,78 @@
+#ifndef REQUESTTHREADPOOL_H
+#define REQUESTTHREADPOOL_H
+
+#include <QObject>
+#include <QQueue>
+#include <QMutex>
+#include <QWaitCondition>
+#include <QThreadPool>
+#include <QRunnable>
+#include <functional>
+#include <memory>
+
+namespace TC {
+
+// 请求任务基类
+class RequestTask : public QObject, public QRunnable
+{
+    Q_OBJECT
+public:
+    RequestTask() : QObject(), QRunnable() {
+        setAutoDelete(false);
+    }
+    virtual ~RequestTask() {}
+    
+    virtual void run() = 0;
+    virtual bool retry() = 0;
+    
+    int retryCount() const { return m_retryCount; }
+    void incrementRetryCount() { m_retryCount++; }
+    
+    void setMaxRetries(int maxRetries) { m_maxRetries = maxRetries; }
+    int maxRetries() const { return m_maxRetries; }
+    
+    bool canRetry() const { return m_retryCount < m_maxRetries; }
+    
+signals:
+    void taskCompleted(bool success);
+    void taskFailed();
+    
+private:
+    int m_retryCount = 0;
+    int m_maxRetries = 3; // 默认最多重试3次
+};
+
+// 线程池管理类
+class RequestThreadPool : public QObject
+{
+    Q_OBJECT
+public:
+    static RequestThreadPool* instance();
+    
+    void addTask(std::shared_ptr<RequestTask> task);
+    void setMaxThreadCount(int count);
+    int activeThreadCount() const;
+    
+private:
+    RequestThreadPool(QObject* parent = nullptr);
+    ~RequestThreadPool();
+    
+    void processNextTask();
+    
+// 在 private slots: 部分修改
+private slots:
+    void onTaskCompleted(bool success);
+    void onTaskFailed(RequestTask* task); // 修改方法签名,接收任务指针作为参数
+
+private:
+    static RequestThreadPool* s_instance;
+    QThreadPool m_threadPool;
+    QQueue<std::shared_ptr<RequestTask>> m_taskQueue;
+    QMutex m_mutex;
+    QWaitCondition m_condition;
+    int m_activeTaskCount = 0;
+};
+
+} // namespace TC
+
+#endif // REQUESTTHREADPOOL_H

+ 95 - 1
api/tapi.cpp

@@ -120,6 +120,50 @@ static bool InterceptorsResponse(QNetworkReply *reply, QJsonValue &data)
     return false;
 }
 
+static bool InterceptorsResponse2(QNetworkReply *reply, QJsonValue &data)
+{
+    if (reply->error() == QNetworkReply::NoError) {
+        const QByteArray allData = reply->readAll();
+
+        QJsonParseError jsonError;
+        QJsonDocument jsonDoc = QJsonDocument::fromJson(allData, &jsonError);
+        if (jsonError.error != QJsonParseError::NoError) {
+            qDebug() << data;
+            return false;
+        }
+#ifdef QT_DEBUG
+        qDebug() << QString::fromUtf8(jsonDoc.toJson());
+#endif
+        if (jsonDoc.isObject()) {
+            const QJsonObject &object = jsonDoc.object();
+
+            data = object;
+            // if (object.contains(scCode)) {
+            //     int code = object.value(scCode).toInt();
+            //     if (code != 0) {
+            //         return false;
+            //     }
+            // }
+            // if (object.contains(scMessage)) {
+            //     const QString &message = object.value(scMessage).toString();
+            //     // Core::MessageManager::writeFlashing(message);
+            // }
+            // if (object.contains(scData)) {
+            //     data = object.value(scData);
+            // }
+        }
+        return true;
+    } else if (reply->error() == QNetworkReply::InternalServerError) { //权限错误
+        qDebug() << "0" << reply->error();
+        AppEvent::instance()->setJwtToken(QString());
+        return false;
+    } else { //其他错误
+        qDebug() << reply->error() << reply->errorString();
+        AppEvent::instance()->setJwtToken(QString());
+        return false;
+    }
+    return false;
+}
 }; // namespace TC
 
 namespace TC {
@@ -186,7 +230,11 @@ QNetworkReply *NetworkAccessManager::createRequest(Operation op,
     }
 
     //这里考虑处理 JWT 数据
-    const QString &token = AppEvent::instance()->jwtToken();
+    const QString &token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
+                           "eyJkZXB0SWQiOjEsImV4cCI6MTc0ODU5NDg2MCwiaWF0IjoxNzQ4MzM1NjYwLCJyb2xlSWQ"
+                           "iOiIwMDEiLCJ1c2VySWQiOiIwMTk2ZWRjZi05NjgwLTdlNTgtYjIxZC0wMmYyYmVhNmY0Zm"
+                           "IifQ.lc9M-47c1C4-A0mvED39tra_TzhuyKStDE_3uQ4Q5Qg";
+    AppEvent::instance()->jwtToken();
     // 加个事件校验 防止token 失效导致异常
     if (!token.isEmpty()) {
         req.setRawHeader("Authorization", "Bearer " + token.toUtf8());
@@ -242,6 +290,52 @@ std::optional<QJsonValue> sendRequest(QNetworkAccessManager::Operation op,
     }
     return data;
 }
+
+std::optional<QJsonValue> sendRawRequest(QNetworkAccessManager::Operation op,
+                                         const QString &url,
+                                         const QByteArray &postData = QByteArray())
+{
+    QNetworkAccessManager *manager = NetworkAccessManager::instance();
+    manager->setTransferTimeout(3000);
+    QNetworkReply *reply = nullptr;
+
+    QUrl fullUrl(QString(base_url).append(url));
+    switch (op) {
+    case QNetworkAccessManager::HeadOperation:
+        reply = manager->head(QNetworkRequest(fullUrl));
+        break;
+    case QNetworkAccessManager::GetOperation:
+        reply = manager->get(QNetworkRequest(fullUrl));
+        break;
+    case QNetworkAccessManager::PutOperation:
+        reply = manager->put(QNetworkRequest(fullUrl), postData);
+        break;
+    case QNetworkAccessManager::PostOperation:
+        reply = manager->post(QNetworkRequest(fullUrl), postData);
+        break;
+    case QNetworkAccessManager::DeleteOperation:
+        reply = manager->deleteResource(QNetworkRequest(fullUrl));
+        break;
+    case QNetworkAccessManager::CustomOperation:
+        break;
+    default:
+        break;
+    }
+    if (!reply) {
+        qDebug() << "sendRequest Operation error";
+    }
+
+    //等待请求结束
+    QEventLoop loop;
+    QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
+    loop.exec();
+
+    QJsonValue data;
+    if (!InterceptorsResponse2(reply, data)) {
+        return std::nullopt;
+    }
+    return data;
+}
 bool downRequest(const QString &url,
                  const QByteArray &fileName,
                  std::function<bool(QNetworkReply *)> callback)

+ 41 - 0
main.cpp

@@ -20,6 +20,8 @@
 
 #include "qtsingleapplication.h"
 
+#include <api/gradesapi.h>
+
 void customMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
 {
     QString txt;
@@ -126,6 +128,45 @@ int main(int argc, char *argv[])
     server.addController<TableDataController>("/api/v1/tableData");
     server.addController<TableDataExamRoomController>("/api/v1/tableDataRoom");
 
+    // {
+    //     const QString answerFileName = "测试";
+    //     const QString checkinNumber = "checkinNumber";
+    //     int endTime = 10;
+    //     const QString examRoom = "examRoom";         // 考试房间
+    //     const QString examSessions = "examSessions"; //考试场次
+    //     const QString examText = "examText";         //考试内容
+    //     const QString examineNumber;                 // 考试编号
+    //     const QString groupName = "groupName";
+    //     const QString name = "name";
+    //     int onlineStatus = 1;
+    //     const QString schoolAddress = "";
+    //     const QString schoolName = "schoolName";
+    //     int sex = 0;
+    //     int startTime = 0;
+    //     int status = 1;
+    //     const QString swId = "swId";
+
+    //     TC::GradesUpdate gradesUpdate(answerFileName,
+    //                                   checkinNumber,
+    //                                   endTime,
+    //                                   examRoom,
+    //                                   examSessions,
+    //                                   examText,
+    //                                   examineNumber,
+    //                                   groupName,
+    //                                   name,
+    //                                   onlineStatus,
+    //                                   schoolAddress,
+    //                                   schoolName,
+    //                                   sex,
+    //                                   startTime,
+    //                                   status,
+    //                                   swId);
+    //     gradesUpdate.postAsync([](const TC::GradesUpdate::Data& data) {
+    //         qDebug() << "Update result:" << data.success << data.message;
+    //     });
+    // }
+
     TLogin login;
     std::unique_ptr<MainWindow> mainWindow = nullptr;
 

+ 61 - 2
tcontroller.cpp

@@ -16,6 +16,8 @@
 #include <cwf/filemanager.h>
 #include <cwf/sqlquery.h>
 
+#include <api/gradesapi.h>
+
 static CWF::SqlDatabaseStorage storage("QSQLITE", "localhost", "data.db", "", "");
 static const QLatin1String tokenSecret("mydirtysecret");
 static QJsonObject getJwtToken(qint64 userid)
@@ -668,9 +670,10 @@ void ExamsAnswerTimeController::doPost(CWF::Request &request, CWF::Response &res
 
             {
                 int examID = 0;
+                QString examText = "";
 
                 QString sql = QString(R"__(
-SELECT eq.id
+SELECT eq.id, eq.name
 FROM user u
 JOIN exams_question eq
 ON (
@@ -688,6 +691,7 @@ WHERE u.id = %1;
                     if (!array.isEmpty()) {
                         QJsonObject object = array[0].toObject();
                         examID = object["id"].toInt();
+                        examText = object["name"].toString();
                     }
                 }
 
@@ -708,6 +712,60 @@ WHERE u.id = %1;
                 examsTestModel.setAnswerFileName(fileName);
                 examsTestModel.save();
 
+                // 这里通知去发送到服务器 通过指定id
+                // GradesUpdate(const QString &answerFileName,
+                //              const QString &checkinNumber,
+                //              int endTime,
+                //              const QString &examRoom,
+                //              const QString &examSessions,
+                //              const QString &examText,
+                //              const QString &examineNumber,
+                //              const QString &groupName,
+                //              const QString &id,
+                //              const QString &name,
+                //              int onlineStatus,
+                //              const QString &schoolAddress,
+                //              const QString &schoolName,
+                //              int sex,
+                //              int startTime,
+                //              int status,
+                //              const QString &swId)
+                {
+                    const QString answerFileName = fileName;
+                    const QString checkinNumber = user.getCheckinNumber();
+                    int endTime = time;
+                    // const QString examRoom = examRoom; // 考试房间
+                    const QString examSessions = examNumber; //考试场次
+                    // const QString examText;                  //考试内容
+                    const QString examineNumber = user.getExamineeNumber(); // 考试编号
+                    const QString groupName = user.getGroup();
+                    const QString name = user.getName();
+                    int onlineStatus = 1;
+                    const QString schoolAddress = "";
+                    const QString schoolName = user.getSchool();
+                    int sex = 0;
+                    int startTime = 0;
+                    int status = 1;
+                    const QString swId = user.getSwID();
+
+                    TC::GradesUpdate gradesUpdate(answerFileName,
+                                                  checkinNumber,
+                                                  endTime,
+                                                  examRoom,
+                                                  examSessions,
+                                                  examText,
+                                                  examineNumber,
+                                                  groupName,
+                                                  name,
+                                                  onlineStatus,
+                                                  schoolAddress,
+                                                  schoolName,
+                                                  sex,
+                                                  startTime,
+                                                  status,
+                                                  swId);
+                    gradesUpdate.postAsync();
+                }
                 qDebug() << " AppEvent::instance()->examsTestUpdate();";
                 AppEvent::instance()->examsTestUpdate();
             }
@@ -771,7 +829,8 @@ SELECT
     e.end_time AS exam_time,
     e.exam_room,
     e.exam_sessions,
-		eq.name AS examName
+    e.isUpload,
+	eq.name AS examName
 FROM
     user s
 JOIN

+ 2 - 0
teacherServer.pro

@@ -10,6 +10,7 @@ CONFIG += c++17
 
 SOURCES += \
     api/gradesapi.cpp \
+    api/requestthreadpool.cpp \
     api/tapi.cpp \
     api/tloginapi.cpp \
     appevent.cpp \
@@ -27,6 +28,7 @@ SOURCES += \
 
 HEADERS += \
     api/gradesapi.h \
+    api/requestthreadpool.h \
     api/tapi.h \
     api/tloginapi.h \
     appevent.h \

+ 12 - 7
tmodel.h

@@ -118,11 +118,12 @@ class ExamsTestModel : public CWF::Model
     Q_PROPERTY(QString exam_sessions READ getExamSessions WRITE setExamSessions)
 
     Q_PROPERTY(QString answerFileName READ getAnswerFileName WRITE setAnswerFileName)
+
+    Q_PROPERTY(qint8 isUpload READ getIsUpload WRITE setIsUpload)
 public:
     explicit ExamsTestModel(CWF::SqlDatabaseStorage &connection)
         : CWF::Model(connection, "exams_test")
     {}
-
 public slots:
     quint64 getUserId() const { return user_id; }
     void setUserId(const quint64 &value) { user_id = value; }
@@ -145,14 +146,18 @@ public slots:
     QString getAnswerFileName() const { return answerFileName; }
     void setAnswerFileName(const QString &value) { answerFileName = value; }
 
+    qint8 getIsUpload() const { return isUpload; }
+    void setIsUpload(const qint8 &value) { isUpload = value; }
+
 private:
-    quint64 user_id;       // 用户id
-    quint64 exams_id;      // 考试id
-    quint64 start_time;    // 开始时间 目前弃用
-    quint64 end_time;      // 结束时间 当作考试时间使用
-    QString exam_room;     // 考试教师
-    QString exam_sessions; // 考试场次
+    quint64 user_id;        // 用户id
+    quint64 exams_id;       // 考试id
+    quint64 start_time;     // 开始时间 目前弃用
+    quint64 end_time;       // 结束时间 当作考试时间使用
+    QString exam_room;      // 考试教师
+    QString exam_sessions;  // 考试场次
     QString answerFileName; // 答案名称
+    qint8 isUpload;
 };
 
 #endif // TMODEL_H