diff --git a/mainwindow.h b/mainwindow.h index d288e99..890b44f 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -12,7 +12,6 @@ #include #include -#include #include #include diff --git a/newCardPage.cpp b/newCardPage.cpp index e43b7ae..2a55cbe 100644 --- a/newCardPage.cpp +++ b/newCardPage.cpp @@ -91,6 +91,12 @@ void MainWindow::on_newCardButton_clicked() return; } + if (!device.is_depositAllowed()) + { + QMessageBox::warning(this, QString("提示"), QString("本设备不支持开卡。")); + return; + } + if (!newCardUserIdFilled) { QMessageBox::warning(this, "提示", "请填写学/工号。"); @@ -164,7 +170,19 @@ void MainWindow::on_newCardButton_clicked() query.prepare("insert into card " "values (:cardId, 0, 0.00, null);"); query.bindValue(":cardId", cardIdSelected); - query.exec(); + success = query.exec(); + if (!success) + { + QMessageBox::warning(this, "提示", QString("数据库异常。开卡失败,请重试。")); + return; + } + + success = reader.initCard(cardIdSelected); // 初始化卡 + if (!success) + { + QMessageBox::warning(this, "提示", QString("读卡器设备异常。写卡失败,请重试。")); + return; + } } // 检查是否是新用户 @@ -195,6 +213,14 @@ void MainWindow::on_newCardButton_clicked() return; } QMessageBox::information(this, "提示", "新用户开卡成功。"); + + success = reader.initCard(cardIdSelected); // 初始化卡 + if (!success) + { + QMessageBox::warning(this, "提示", QString("读卡器设备异常。写卡失败,请重试。")); + return; + } + return; } else // 库中有用户记录 @@ -221,7 +247,7 @@ void MainWindow::on_newCardButton_clicked() } else if (userCardStatus == -1) // 用户有挂失卡,需要移资 { - /// @todo 弹出验证用户界面,要求用户输入密码;将挂失卡的信息和消费记录移到新卡 + /// @todo 弹出验证用户界面,要求用户输入密码;在数据库中将挂失卡的信息和消费记录移到新卡 QString info, prompt = QString("如需将挂失卡移资到本卡,请输入密码。"); bool success = verifyUser(userId, prompt, info); if (!success) @@ -238,6 +264,13 @@ void MainWindow::on_newCardButton_clicked() return; } + success = reader.initCard(cardIdSelected); // 初始化卡 + if (!success) + { + QMessageBox::warning(this, "提示", QString("读卡器设备异常。写卡失败,请重试。")); + return; + } + QMessageBox::information(this, "提示", "移资成功。"); return; } @@ -251,6 +284,14 @@ void MainWindow::on_newCardButton_clicked() QMessageBox::warning(this, "提示", info + QString("\n注册失败,请重新开卡。")); return; } + + success = reader.initCard(cardIdSelected); // 初始化卡 + if (!success) + { + QMessageBox::warning(this, "提示", QString("读卡器设备异常。写卡失败,请重试。")); + return; + } + QMessageBox::information(this, "提示", "新用户开卡成功。"); return; } diff --git a/readerAPI.cpp b/readerAPI.cpp index c9f797c..5790d7a 100644 --- a/readerAPI.cpp +++ b/readerAPI.cpp @@ -74,6 +74,10 @@ int Reader::getComNumber() * 通过读卡器的inventory方法获取最大数量为maxViccNum的卡片UID,并将其转换为QStringList返回。 * @param maxViccNum 最大卡片数量 * @return 包含卡片UID的QStringList + * @details + * 通过读卡器的inventory方法获取最大数量为maxViccNum的卡片UID,并将其转换为QStringList返回。 + * 该函数分配内存来存储卡片的UID,然后调用CVCDOurs::inventory来获取卡片信息。 + * 获取到的UID会转换为字符串并添加到QStringList中,最后返回该列表。 * @author 柯劲帆 * @date 2024-07-29 */ @@ -100,3 +104,198 @@ QStringList Reader::inventory(int maxViccNum) free(aucUID); return uidList; } + + +/** + * @brief 读取记录数量和最近一条记录的index + * 该函数用于从卡片的block0中读取记录数量和最近一条记录的索引。 + * @param recordNum 记录数量,通过引用返回,类型为 int + * @param recordIndex 最近一条记录的索引,通过引用返回,类型为 int + * @param cardId 要读取记录的卡片ID,类型为 QString + * @return bool 是否读取成功 + * - true 读取成功 + * - false 读取失败 + * @details + * 函数首先将卡片ID转换为 `uchar_t` 类型的字符数组,然后调用 `readBlocks` 函数从卡片的block0中读取记录数量和索引。 + * 如果 `readBlocks` 返回的hex数量为0,则表示读取失败,函数返回 `false`。 + * 否则,函数将读取的结果转换为整数,并通过引用参数 `recordNum` 和 `recordIndex` 返回。 + * @author 柯劲帆 + * @date 2024-07-31 + */ +bool Reader::readRecordNumber(int &recordNum, int &recordIndex, QString cardId) +{ + // 记录数量在block0的byte0的bit0-3中 + // 最近一条记录的index在block0的byte0的bit4-7中 + + // 分配内存用于存储读取结果字符串 + 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); + + int hexNum = readBlocks(0, 1, recordIndexHex, nullptr, cardIdHex); + if (hexNum == 0) return false; + + recordNum = (int)(recordIndexHex[0] & 0x0F); + recordIndex = (int)(recordIndexHex[1] >> 4); + + return true; +} + + +/** + * @brief 写入记录数量和最近一条记录的index + * 该函数用于将记录数量和最近一条记录的索引写入到卡片的block0中。 + * @param recordNum 记录数量,类型为 int + * @param recordIndex 最近一条记录的索引,类型为 int + * @param cardId 要写入记录的卡片ID,类型为 QString + * @return bool 是否写入成功 + * - true 写入成功 + * - false 写入失败 + * @details + * 函数首先将记录数量和记录索引转换为 `uchar_t` 类型,并存储在 `recordIndexStr` 数组中。 + * 然后,函数将卡片ID转换为 `uchar_t` 类型的字符数组,并调用 `writeBlock` 函数将记录数量和索引写入到卡片的block0中。 + * 如果 `writeBlock` 函数返回的写入行数为1,则表示写入成功,函数返回 `true`;否则,函数返回 `false`。 + * @author 柯劲帆 + * @date 2024-07-31 + */ +bool Reader::writeRecordNumber(int recordNum, int recordIndex, QString cardId) +{ + // 记录数量在block0的byte0的bit0-3中 + // 最近一条记录的index在block0的byte0的bit4-7中 + + // 分配内存用于存储读取结果字符串 + uchar_t recordIndexStr[4] = {0}; + recordIndexStr[0] = (uchar_t)(recordIndex << 4); + recordIndexStr[0] += (uchar_t)(recordNum); + + uchar_t cardIdHex[8] = {0}; + QByteArray ba = cardId.toLatin1(); + StringToHex(ba.data(), cardIdHex); + + int success = writeBlock(0, recordIndexStr, cardIdHex); + return success == 1; +} + + +/** + * @brief 插入一条交易记录 + * 该函数用于将一条新的交易记录插入到卡片中。 + * @param record 要插入的交易记录字符串,类型为 QString + * @param cardId 卡片ID,类型为 QString + * @return bool 是否插入成功 + * - true 插入成功 + * - false 插入失败 + * @details + * 函数首先读取卡片的当前记录数量和记录索引。如果读取失败,函数将返回 `false`。 + * 然后,函数更新记录数量和记录索引,并将新的交易记录转换为 `uchar_t` 类型的字符数组。 + * 最后,函数调用 `writeBlocks` 将记录写入卡片。如果写入行数不为0,则表示插入成功,函数返回 `true`;否则,函数返回 `false`。 + * @author 柯劲帆 + * @date 2024-07-31 + */ +bool Reader::insertRecord(QString record, QString cardId) +{ + int recordNum, recordIndex; + bool success = readRecordNumber(recordNum, recordIndex, cardId); + if (!success) return false; + recordNum = std::min(maxRecordNum, ++recordNum); + recordIndex = (recordIndex + 1) % maxRecordNum; + success = writeRecordNumber(recordNum, recordIndex, cardId); + if (!success) return false; + + int blockIndex = 1 + 4 * recordIndex; + + uchar_t cardIdHex[8] = {0}; + QByteArray ba = cardId.toLatin1(); + StringToHex(ba.data(), cardIdHex); + + uchar_t recordHex[4 * 4] = {0}; + ba = record.toLatin1(); + StringToHex(ba.data(), recordHex); + + int writeLineNumber = writeBlocks(blockIndex, 4, recordHex, cardIdHex); + + return writeLineNumber != 0; +} + + +/** + * @brief 获取全部记录 + * 该函数用于从卡片中获取所有记录。 + * @param cardId 要读取记录的卡片ID,类型为 QString + * @param ok 操作是否成功的标志,类型为 bool 的引用 + * @return QStringList 包含所有记录的字符串列表 + * @details + * 函数首先读取卡片的记录数量和起始索引。如果读取失败,函数将设置 `ok` 为 `false` 并返回空列表。 + * 然后,函数将卡片ID转换为 `uchar_t` 类型的字符数组,并逐个读取记录块。 + * 每条记录由4个block组成,每个block包含8个hex字符。函数将每个block的内容转换为字符串并拼接成完整的记录字符串。 + * 最终,函数将所有记录字符串添加到 `QStringList` 中并返回。 + * @author 柯劲帆 + * @date 2024-07-31 + */ +QStringList Reader::getRecords(QString cardId, bool &ok) +{ + QStringList recordList; + + int recordNum, recordStartIndex; + bool success = readRecordNumber(recordNum, recordStartIndex, cardId); + if (!success) + { + ok = false; + return recordList; + } + + uchar_t cardIdHex[8] = {0}; + QByteArray ba = cardId.toLatin1(); + StringToHex(ba.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++) + { + 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; + } + + char blockStr[9] = {0}; + HexToString(blockHex, 4, blockStr); + + recordStr += QString(blockStr); + } + recordList.push_back(recordStr); + } + return recordList; +} + + +/** + * @brief 卡初始化 + * 将第1个block初始化为全0。 + * @param cardId 要初始化的卡片ID,类型为 QString + * @return bool 是否初始化成功 + * - true 初始化成功 + * - false 初始化失败 + * @details + * 函数首先将传入的 `QString` 类型的卡片ID转换为 `uchar_t` 类型的字符数组。 + * 然后,函数定义一个全为0的字符数组,并调用 `writeBlocks` 函数将其写入卡片的第1个block。 + * 如果 `writeBlocks` 函数返回的写入行数不为0,则表示初始化成功,函数返回 `true`;否则,函数返回 `false`。 + * @author 柯劲帆 + * @date 2024-07-31 + */ +bool Reader::initCard(QString cardId) +{ + uchar_t cardIdHex[8] = {0}; + QByteArray ba = cardId.toLatin1(); + StringToHex(ba.data(), cardIdHex); + + uchar_t allZeroHex[4] = {0}; + int writeLineNumber = writeBlock(0, allZeroHex, cardIdHex); + return writeLineNumber != 0; +} diff --git a/readerAPI.h b/readerAPI.h index dac8431..c931cfc 100644 --- a/readerAPI.h +++ b/readerAPI.h @@ -20,6 +20,10 @@ class Reader : private CVCDOurs { private: int comNumber = -1; ///< 硬件连接com口号,若未连接为-1 + int maxRecordNum = 6; ///< 每条记录4个block,最多28-1=27个block,所以最多支持6条记录 + + bool readRecordNumber(int &recordNum, int &recordIndex, QString cardId); + bool writeRecordNumber(int recordNum, int recordIndex, QString cardId); public: void setComNumber(int comNumber); @@ -29,6 +33,10 @@ public: bool connect(); QStringList inventory(int maxViccNum); + bool insertRecord(QString record, QString cardId); + bool writeRecords(QStringList recordList, QString cardId); + QStringList getRecords(QString cardId, bool &ok); + bool initCard(QString cardId); }; #endif // READERAPI_H