diff --git a/VCDOurs.cpp b/VCDOurs.cpp index 3f9499c..fdbb40c 100644 --- a/VCDOurs.cpp +++ b/VCDOurs.cpp @@ -1,5 +1,6 @@ #include #include "VCDOurs.h" +#include "qlogging.h" CVCDOurs::CVCDOurs(void) { @@ -422,7 +423,7 @@ int CVCDOurs::writeBlocks( int nFirstBlock, int nBlockNum, uchar_t *pDat, uchar_ int k = 0; for( int i = 0; i < nBlockNum; i++ ) { - k += writeBlock( nFirstBlock + i, pDat+i*8, pucUID ); + k += writeBlock( nFirstBlock + i, pDat+i*4, pucUID ); } return k; } diff --git a/VCDOurs.h b/VCDOurs.h index 4e3a32c..e5a4411 100644 --- a/VCDOurs.h +++ b/VCDOurs.h @@ -71,12 +71,12 @@ public: virtual int readMultipleBlocks( uchar_t *pucUID, // NULL表示select模式 uchar_t optional_flag, // optional bit = 1, 要求返回block的安全状态 - uchar_t ucBlkno, // 开始block no - uchar_t blocknum, // 实际读的block是:blocknum+1; + uchar_t ucBlkno, // 开始block no + uchar_t blocknum, // 实际读的block是:blocknum+1; uchar_t buf[], // 返回读取的数据 uchar_t aucSecurity[] // 如果optional_flag = 1, 返回该block的安全状态:1表示locked. ) - { + { t15bOption = optional_flag; return readBlocks( ucBlkno, blocknum+1, buf, aucSecurity, pucUID ); } diff --git a/consumePage.cpp b/consumePage.cpp index 81b1185..74984d5 100644 --- a/consumePage.cpp +++ b/consumePage.cpp @@ -172,6 +172,12 @@ void MainWindow::on_consumeButton_clicked() return; } + success = reader.insertRecord(recordId, cardId); + if (!success) + { + QMessageBox::warning(this, "提示", "消费完成。写卡失败。本交易不记录在卡上。"); + } + QString consumeResultMessage = QString("消费成功:") + QString::number(deductValue) + QString("元\n"); consumeResultMessage += QString("原余额:") + QString::number(originalBalance) + QString("元\n"); consumeResultMessage += QString("消费后余额:") + QString::number(finalBalance) + QString("元\n"); @@ -263,8 +269,6 @@ bool MainWindow::deductCard(QString cardId, double deductValue, double &original query.next(); finalBalance = query.value("@newBalance").toDouble(); - /// @todo 写卡 - return true; } diff --git a/depositPage.cpp b/depositPage.cpp index 9ad142f..fb0b72b 100644 --- a/depositPage.cpp +++ b/depositPage.cpp @@ -118,6 +118,12 @@ void MainWindow::on_depositByCardIdButton_clicked() return; } + success = reader.insertRecord(recordId, cardId); + if (!success) + { + QMessageBox::warning(this, "提示", "充值成功。写卡失败。本交易不记录在卡上。"); + } + QString depositResultMessage = QString("充值成功:") + QString::number(topUpValue) + QString("元\n"); depositResultMessage += QString("原余额:") + QString::number(originalBalance) + QString("元\n"); depositResultMessage += QString("充值后余额:") + QString::number(finalBalance) + QString("元\n"); @@ -223,9 +229,10 @@ void MainWindow::on_depositByUserIdButton_clicked() * @param finalBalance 充值后的余额,通过引用返回 * @param recordId 交易编号,通过引用返回 * @param info 如果出现异常,填入异常信息,通过引用返回 - * @return bool 是否充值成功 - * - true 成功 - * - false 失败 + * @return int 是否充值成功 + * - 0 失败 + * - 1 成功 + * - 2 充值成功但写卡失败 * @details * 函数首先检查设备是否支持充值。如果设备不支持,函数返回失败并设置错误信息。 * 接着,函数查询数据库获取卡片的当前状态和余额。如果卡片不存在、已挂失或未启用,函数返回失败并设置相应的错误信息。 @@ -299,8 +306,6 @@ bool MainWindow::topUpCard(QString cardId, double topUpValue, double &originalBa query.next(); finalBalance = query.value("@newBalance").toDouble(); - /// @todo 写卡 - return true; } @@ -327,5 +332,5 @@ QString MainWindow::getRecordId(QDateTime currentTime, int userId, int recordTyp QString typeStr = QString::number(recordType); // 第25位:记录类型 QString randomHex = QString::number(QRandomGenerator::global()->bounded(0x10000), 16).rightJustified(4, '0'); // 第26-29位:随机十六进制数 QString recordId = timeStr + userIdStr + typeStr + randomHex; // 共30位 - return recordId; + return recordId.toUpper(); } diff --git a/mainwindow.cpp b/mainwindow.cpp index 496cb78..9af617a 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -83,7 +83,6 @@ MainWindow::MainWindow(QWidget *parent) ui->queryResultTable->setColumnWidth(5, 240); - // 设置启动页面 ui->stackedWidget->setCurrentWidget(ui->settingPage); } @@ -93,4 +92,3 @@ MainWindow::~MainWindow() { delete ui; } - diff --git a/mainwindow.h b/mainwindow.h index 890b44f..f595755 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -71,6 +71,7 @@ private slots: void on_queryInventoryButton_clicked(); void on_userRecordQueryButton_clicked(); void on_userIdRecordQueryButton_clicked(); + void on_cardRecordQueryButton_clicked(); private: Ui::MainWindow *ui; diff --git a/newCardPage.cpp b/newCardPage.cpp index 2a55cbe..0d1dc91 100644 --- a/newCardPage.cpp +++ b/newCardPage.cpp @@ -247,7 +247,7 @@ void MainWindow::on_newCardButton_clicked() } else if (userCardStatus == -1) // 用户有挂失卡,需要移资 { - /// @todo 弹出验证用户界面,要求用户输入密码;在数据库中将挂失卡的信息和消费记录移到新卡 + // 弹出验证用户界面,要求用户输入密码;在数据库中将挂失卡的信息和消费记录移到新卡 QString info, prompt = QString("如需将挂失卡移资到本卡,请输入密码。"); bool success = verifyUser(userId, prompt, info); if (!success) @@ -333,8 +333,6 @@ bool MainWindow::bindUserWithCard(int userId, QString cardId, QString &info) return false; } - /// @todo 写卡 - return true; } @@ -570,8 +568,6 @@ bool MainWindow::transferCard(int userId, QString newCardId, QString oldCardId, return false; } - /// @todo 将数据库上的记录写到新卡上 - return true; } @@ -606,7 +602,5 @@ bool MainWindow::reopenCard(QString cardId, QString &info) return false; } - /// @todo 看看是否有写卡需求 - return true; } diff --git a/queryPage.cpp b/queryPage.cpp index 008fcb2..73275c7 100644 --- a/queryPage.cpp +++ b/queryPage.cpp @@ -283,6 +283,136 @@ void MainWindow::on_userIdRecordQueryButton_clicked() } +/** + * @brief 查询卡内记录 + * @param void + * @return void + * @author 柯劲帆 + * @date 2024-07-31 + */ +void MainWindow::on_cardRecordQueryButton_clicked() +{ + if (!databaseReady()) + { + QMessageBox::warning(this, QString("提示"), QString("数据库未连接,请设置。")); + if (ui->stackedWidget->currentWidget() != ui->settingPage) + { + ui->stackedWidget->setCurrentWidget(ui->settingPage); + } + return; + } + + if (ui->queryCardIdBox->currentIndex() == -1) + { + QMessageBox::warning(this, "提示", "请放置卡片并点击查询按钮。"); + return; + } + QString cardId = ui->queryCardIdBox->currentText(); + int cardStatus; + double balance; + + // 检查和获取绑定用户 + QSqlQuery query(db->getDatabase()); + query.prepare(QString("select userId, `status`, balance from card " + "where id = :cardId;")); + query.bindValue(":cardId", cardId); + bool success = false; + success = query.exec(); + if (!success) + { + QMessageBox::warning(this, QString("提示"), QString("数据库异常。查询失败。")); + queryPageInitContent(); + return; + } + if (query.next()) // 卡已被注册,获取用户ID + { + cardStatus = query.value("status").toInt(); + if (cardStatus == 0) + { + QMessageBox::warning(this, QString("提示"), QString("此卡未被启用。")); + queryPageInitContent(); + return; + } + if (cardStatus == -1) + { + ui->queryCardStatusLabel->setText(QString("已被挂失")); + } + balance = query.value("balance").toDouble(); + ui->queryBalanceShowEdit->setText(QString::number(balance, 'f', 2)); + } + else // 卡没有注册 + { + query.finish(); + query.prepare(QString("insert into card " + "values (:cardId, 0, 0.0, null);")); + query.bindValue(":cardId", cardId); + success = query.exec(); + if (!success) + { + QMessageBox::warning(this, QString("提示"), QString("数据库异常。查询失败。")); + queryPageInitContent(); + return; + } + + QMessageBox::warning(this, QString("提示"), QString("此卡未被启用。")); + queryPageInitContent(); + return; + } + + QStringList cardRecordIdList = reader.readAllRecords(cardId, success); + if (!success) + { + QMessageBox::warning(this, QString("提示"), QString("读卡器异常。查询失败。")); + queryPageInitContent(); + return; + } + + query.finish(); + query.prepare(QString("select time, type, value, balance, device, id from record_view " + "where cardId = :cardId and id = :recordId;")); + query.bindValue(":cardId", cardId); + + + std::vector transactionRecordList; + for (int i = 0; i < cardRecordIdList.size(); i++) + { + QString recordId = cardRecordIdList[i]; + + query.bindValue(":recordId", recordId); + success = query.exec(); + if (!success) + { + QMessageBox::warning(this, QString("提示"), QString("数据库异常。查询失败。")); + queryPageInitContent(); + return; + } + if (!query.next()) { + // 原来的代码 + // QMessageBox::warning(this, QString("提示"), QString("数据库异常。查询失败。")); + // queryPageInitContent(); + // return; + + // 这里本来应该报错退出,但是读卡器经常发疯,见Reader::readAllRecords(QString cardId, bool &ok) + // 所以发现有不在数据库中的记录号,直接跳过 + continue; + } + + QStringList transactionRecord = transactionRecord2QStringList + ( + query.value("time").toDateTime(), + query.value("type").toInt(), + query.value("value").toDouble(), + query.value("balance").toDouble(), + query.value("device").toString(), + query.value("id").toString() + ); + transactionRecordList.push_back(transactionRecord); + } + + displayInTableWidget(transactionRecordList); +} + + /** * @brief 将数据库查询返回的数据转化为QStringList * 该函数用于将数据库查询返回的交易记录数据转换为一个 `QStringList`,以便在界面上显示。 @@ -374,4 +504,3 @@ void MainWindow::displayInTableWidget(std::vector transactionRecord } } } - diff --git a/quitAppPage.cpp b/quitAppPage.cpp index b561c16..bb2f1b4 100644 --- a/quitAppPage.cpp +++ b/quitAppPage.cpp @@ -18,7 +18,8 @@ void MainWindow::on_quitAppAction_triggered() /** * @brief 退出程序 - * 在退出页面点击“确认”按钮触发 + * 在退出页面点击“确认”按钮触发 + * 关闭读卡器连接,销毁数据库对象(内部关闭数据库连接) * @param void * @return void * @author 柯劲帆 @@ -26,5 +27,7 @@ void MainWindow::on_quitAppAction_triggered() */ void MainWindow::on_confirmQuitButton_clicked() { + if (reader.is_connected()) reader.disconnect(); + free(db); this->close(); } diff --git a/readerAPI.cpp b/readerAPI.cpp index bccac73..3531c8f 100644 --- a/readerAPI.cpp +++ b/readerAPI.cpp @@ -1,4 +1,5 @@ #include "readerAPI.h" +#include "qdebug.h" /** @@ -29,7 +30,7 @@ bool Reader::is_connected() */ bool Reader::connect() { - if (CVCDOurs::connectReaderByCOM(comNumber)) + if (connectReaderByCOM(comNumber)) { return true; } @@ -41,6 +42,19 @@ bool Reader::connect() } +/** + * @brief 关闭连接 + * @param void + * @return void + * @author 柯劲帆 + * @date 2024-07-31 + */ +void Reader::disconnect() +{ + disconnectReaderByCOM(); +} + + /** * @brief 设置COM口号 * @param comNumber 要设置的COM口号 @@ -131,14 +145,13 @@ bool Reader::readRecordNumber(int &recordNum, int &recordIndex, QString cardId) uchar_t recordIndexHex[4] = {0}; // 一个block有4个byte,1个byte有两个hex,会返回8个hex存在4个uchar_t中 uchar_t cardIdHex[8] = {0}; - QByteArray ba = cardId.toLatin1(); - StringToHex(ba.data(), cardIdHex); + StringToHex(cardId.toLatin1().data(), cardIdHex); - int hexNum = readBlocks(0, 1, recordIndexHex, nullptr, cardIdHex); + int hexNum = readSingleBlock(cardIdHex, 0, 0, recordIndexHex, nullptr); if (hexNum == 0) return false; recordNum = (int)(recordIndexHex[0] & 0x0F); - recordIndex = (int)(recordIndexHex[1] >> 4); + recordIndex = (int)(recordIndexHex[0] >> 4); return true; } @@ -171,10 +184,9 @@ bool Reader::writeRecordNumber(int recordNum, int recordIndex, QString cardId) recordIndexStr[0] += (uchar_t)(recordNum); uchar_t cardIdHex[8] = {0}; - QByteArray ba = cardId.toLatin1(); - StringToHex(ba.data(), cardIdHex); + StringToHex(cardId.toLatin1().data(), cardIdHex); - int success = writeBlock(0, recordIndexStr, cardIdHex); + int success = writeSingleBlock(cardIdHex, 0, 4, recordIndexStr); return success == 1; } @@ -207,14 +219,12 @@ bool Reader::insertRecord(QString record, QString cardId) int blockIndex = 1 + 4 * recordIndex; uchar_t cardIdHex[8] = {0}; - QByteArray ba = cardId.toLatin1(); - StringToHex(ba.data(), cardIdHex); + StringToHex(cardId.toLatin1().data(), cardIdHex); uchar_t recordHex[4 * 4] = {0}; - ba = record.toLatin1(); - StringToHex(ba.data(), recordHex); + StringToHex(record.toLatin1().data(), recordHex); - int writeLineNumber = writeBlocks(blockIndex, 4, recordHex, cardIdHex); + int writeLineNumber = writeMultipleBlocks(cardIdHex, blockIndex, 4, 4, recordHex); return writeLineNumber != 0; } @@ -234,7 +244,7 @@ bool Reader::insertRecord(QString record, QString cardId) * @author 柯劲帆 * @date 2024-07-31 */ -QStringList Reader::getRecords(QString cardId, bool &ok) +QStringList Reader::readAllRecords(QString cardId, bool &ok) { QStringList recordList; @@ -247,37 +257,43 @@ QStringList Reader::getRecords(QString cardId, bool &ok) } uchar_t cardIdHex[8] = {0}; - QByteArray ba = cardId.toLatin1(); - StringToHex(ba.data(), cardIdHex); + StringToHex(cardId.toLatin1().data(), cardIdHex); for (int i = 0; i < recordNum; i++) { - QString recordStr = ""; - int recordIndex = 1 + 4 * ((recordStartIndex + i) % maxRecordNum); - for (int j = 0; j < 4; j++) + StringToHex(cardId.toLatin1().data(), cardIdHex); // readMultipleBlocks不知为何会改变cardIdHex的值,要重新赋值 + int recordBlockIndex = 1 + 4 * ((recordStartIndex + i) % maxRecordNum); + uchar_t recordHex[4 * 4] = {0}; + int hexNum = readMultipleBlocks(cardIdHex, 0, recordBlockIndex, 4, recordHex, nullptr); + if (hexNum == 0) { - uchar_t blockHex[4] = {0}; // 一个block有4个byte,1个byte有两个hex,会返回8个hex存在4个uchar_t中 - int hexNum = readBlocks(recordIndex + j, 1, blockHex, nullptr, cardIdHex); - if (hexNum == 0) - { - ok = false; - return recordList; - } + // 原来的处理代码 + // ok = false; + // return recordList; - char blockStr[9] = {0}; - HexToString(blockHex, 4, blockStr); - - recordStr += QString(blockStr); + // 这里经常出现hexNum == 0,循环终止的情况,原因是返回的reply为空值: “[]” + // 一旦出现[],后面的值大概率会出错。 + // 出现主要集中在第二遍循环时。 + // 尝试解决方案:每一遍循环重新inventory重置一下环境(怀疑某些变量被改了)、每一遍循环睡眠2秒,都没有解决这个问题 + // 因此在发生错误时,直接返回能够正确读取的记录ID。 + break; } - recordList.push_back(recordStr); + + char recordStr[33] = {0}; + HexToString(recordHex, 4 * 4, recordStr); + recordStr[30] = '\0'; + QString qRecordStr = QString(recordStr).toUpper(); + + recordList.push_back(qRecordStr); } + ok = true; return recordList; } /** * @brief 卡初始化 - * 将第1个block初始化为全0。 + * 将第1个block的记录数量设置为0,最近一条交易记录下标设置为maxRecordNum-1。 * @param cardId 要初始化的卡片ID,类型为 QString * @return bool 是否初始化成功 * - true 初始化成功 @@ -292,10 +308,10 @@ QStringList Reader::getRecords(QString cardId, bool &ok) bool Reader::initCard(QString cardId) { uchar_t cardIdHex[8] = {0}; - QByteArray ba = cardId.toLatin1(); - StringToHex(ba.data(), cardIdHex); + StringToHex(cardId.toLatin1().data(), cardIdHex); - uchar_t allZeroHex[4] = {0}; - int writeLineNumber = writeBlock(0, allZeroHex, cardIdHex); + uchar_t initHex[4] = {0}; + initHex[0] = (char)(maxRecordNum - 1) << 4; + int writeLineNumber = writeSingleBlock(cardIdHex, 0, 4, initHex); return writeLineNumber != 0; } diff --git a/readerAPI.h b/readerAPI.h index c931cfc..8dd6cfd 100644 --- a/readerAPI.h +++ b/readerAPI.h @@ -2,9 +2,11 @@ #define READERAPI_H #include +#include + #include #include -#include +#include typedef unsigned char uchar_t; @@ -29,13 +31,15 @@ public: void setComNumber(int comNumber); int getComNumber(); + bool is_connected(); bool connect(); + void disconnect(); QStringList inventory(int maxViccNum); bool insertRecord(QString record, QString cardId); bool writeRecords(QStringList recordList, QString cardId); - QStringList getRecords(QString cardId, bool &ok); + QStringList readAllRecords(QString cardId, bool &ok); bool initCard(QString cardId); };