diff --git a/.gitignore b/.gitignore index 4a0b530..864361a 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,4 @@ CMakeLists.txt.user* *.dll *.exe +*.drawio \ No newline at end of file diff --git a/CardManageSystem.pro b/CardManageSystem.pro index 6f3e1aa..68c4db0 100644 --- a/CardManageSystem.pro +++ b/CardManageSystem.pro @@ -51,8 +51,10 @@ DISTFILES += \ HF15693.dll \ HF15693.lib +# LIBS += -L$$PWD/. -LHF15693 + win32:CONFIG(release, debug|release): LIBS += -L$$PWD/../release/ -lHF15693 else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/../debug/ -lHF15693 -else:unix: LIBS += -L$$PWD/../ -lHF15693 -LIBS += -L. -lHF15693 +INCLUDEPATH += $$PWD/. +DEPENDPATH += $$PWD/. diff --git a/build/Desktop_Qt_6_7_2_MinGW_64_bit-Release/release/qmake_qmake_qm_files.qrc b/build/Desktop_Qt_6_7_2_MinGW_64_bit-Release/release/qmake_qmake_qm_files.qrc new file mode 100644 index 0000000..3efb7b7 --- /dev/null +++ b/build/Desktop_Qt_6_7_2_MinGW_64_bit-Release/release/qmake_qmake_qm_files.qrc @@ -0,0 +1,5 @@ + + +D:/Courseware/Integrated_Practice_of_IoT_System/CardManageSystem/build/Desktop_Qt_6_7_2_MinGW_64_bit-Release/release/CardManageSystem_zh_CN.qm + + diff --git a/database/initDb.sql b/database/initDb.sql index 0d63bf6..3048abb 100644 --- a/database/initDb.sql +++ b/database/initDb.sql @@ -13,7 +13,8 @@ DROP PROCEDURE IF EXISTS sp_consumeCard; CREATE TABLE device ( id INT AUTO_INCREMENT PRIMARY KEY, `name` VARCHAR(255) UNIQUE, - depositAllowed TINYINT NOT NULL + depositAllowed TINYINT NOT NULL, + CONSTRAINT chk_device_depositAllowed CHECK (depositAllowed IN (0, 1)) ) AUTO_INCREMENT=1000; CREATE TABLE `user` ( @@ -28,24 +29,26 @@ CREATE TABLE card ( balance DECIMAL(6, 2) NOT NULL, userId INT, FOREIGN KEY (userId) REFERENCES `user`(id), + CONSTRAINT chk_card_status CHECK (`status` IN (-1, 0, 1)), CONSTRAINT chk_card_balance CHECK (balance >= 0) ); CREATE TABLE record ( id VARCHAR(255) PRIMARY KEY, - cardId VARCHAR(16), + cardId VARCHAR(16) NOT NULL, `time` DATETIME NOT NULL, `type` TINYINT NOT NULL, `value` DECIMAL(6, 2) NOT NULL, originalBalance DECIMAL(6, 2) NOT NULL, balance DECIMAL(6, 2) NOT NULL, - deviceId INT, + deviceId INT NOT NULL, FOREIGN KEY (cardId) REFERENCES card(id), FOREIGN KEY (deviceId) REFERENCES device(id), CONSTRAINT chk_value CHECK (`value` >= -300), CONSTRAINT chk_record CHECK (originalBalance + `value` = balance), CONSTRAINT chk_record_originalBalance CHECK (originalBalance >= 0), - CONSTRAINT chk_record_balance CHECK (balance >= 0) + CONSTRAINT chk_record_balance CHECK (balance >= 0), + CONSTRAINT chk_record_type CHECK (`type` IN (0, 1)) ); diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..0f8afb3 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,523 @@ +

一卡通管理系统说明

+ +# 1 系统概述 + +## 1.1 系统简介 + +本系统是一个一卡通管理系统,该系统面向学校应用、基于 RFID 技术的构建。该系统基于 ISO/IEC15693 协议的 RFID 芯片( NXP ICS20 或 SL2S2002 )进行卡内数据读写,并实现与数据库交互记录的功能。 + +## 1.2 系统组成 + +### 1.2.1 系统架构 + + ![architecture.drawio](image\architecture.drawio.svg) + +本系统由一卡通管理系统软件、数据库以及读卡器硬件组成。 + +其中: + +- 一卡通管理系统软件 + - 为用户提供操作界面,实现读写数据库和硬件的逻辑; + - 运行在 PC 机中,调用数据库连接插件与数据库通过网络交互,调用硬件驱动接口与读卡器进行交互; +- 数据库软件 + - 存储一卡通管理系统的数据; + - 运行在服务器中,通过网络与 PC 机连接; +- 读卡器硬件 + - 读写卡片; + - 通过 USB 串口与 PC 机连接。 + +使用软件前,用户需要将 PC 机连接网络,并将读卡器连接到 PC 机的 USB 口上。 + +### 1.2.2 系统功能 + +本系统的功能包括: + +- 设置:连接和读写数据库、HF15693硬件; +- 开卡:创建新用户、挂失重开卡、挂失移资新卡; +- 挂失卡; +- 充值:远程充值、线下充值; +- 消费; +- 查询:查询卡内记录及数据库内记录。 + +## 1.3 运行环境及开发工具 + +本软件运行在 Windows 操作系统中。 + +本软件基于 Qt 6.7.2,使用 Qt Creator 14.0.0 开发,使用了 MySQL 8.0.37 作为数据库软件。 + +本项目使用 Git 作为开发版本管理工具。 + +在开发完毕后,使用 Inno Setup 软件将程序及其动态链接库文件打包为安装程序。 + + + +# 2 功能设计 + +## 2.1 设置功能 + +### 2.1.1 设置读卡器连接 + +**内容** + +该功能用于连接读卡器硬件。用户在输入框内输入连接的 COM 口号,并点击“连接”按钮连接。 + +- 连接成功后,软件窗口下方任务栏的读卡器连接状态勾选框显示勾选,同时显示连接的串口号; + +- 若连接不成功,软件弹出提示框,提示用户该串口上未识别到读卡器,同时软件窗口下方任务栏勾选框取消勾选,显示“当前无连接”。 + +**流程** + +首先获取 COM 口输入框组件的 COM 口号,调用读卡器 API 进行连接,得到连接结果。如果连接不成功,弹出提示框。最后根据连接结果更新状态栏信息。 + +![setting-setReader.drawio](image\setting-setReader.drawio.svg) + +### 2.1.2 设置数据库连接 + +**内容** + +该功能用于连接数据库,以及设备名认证。 + +> **设备名**用于在交易记录中记录操作设备的名称。 +> +> 对于每个设备名,数据库中设置了其操作权限: +> +> - 可充值:使用该设备名的设备拥有开卡、充值和消费权限(在管理员 PC 中使用); +> - 仅可消费:使用该设备名的设备仅有消费权限(在食堂等消费场所的终端机中使用)。 + +用户首先要填写数据库的 IP 地址和端口,输入数据库的密码和要使用的设备名,点击连接按钮。 + +- 如果数据库连接不成功,软件弹出提示框,提示用户数据库连接失败,同时软件窗口下方任务栏的数据库连接状态勾选框取消勾选,显示“数据库未连接”、“未指定设备名”; + +- 如果数据库连接成功,软件窗口下方任务栏的数据库连接状态勾选框显示勾选,同时显示连接的 IP 地址和端口; + +连接成功后,将会进行设备名认证。 + +- 若设备名验证成功,软件窗口下方任务栏显示设备名及其操作权限; + +- 若验证失败,软件弹出提示框,提示用户该设备名无效,同时软件窗口下方任务栏显示“未指定设备名”。 + +**流程** + +从输入组件获取 IP 地址、端口、密码,连接数据库并更新任务栏显示信息。如果连接不成功,弹出提示框;如果连接成功,继续进行设备名认证。 + +查询数据库进行设备名认证,按认证结果更新任务栏显示信息。如果认证不成功,还要弹出提示框。 + +![setting-setDatabase.drawio](image\setting-setDatabase.drawio.svg) + +以下功能中,除了仅需输学/工号的功能仅需连接数据库外,其他所有功能都需同时连接读卡器和数据库。 + +## 2.2 开卡功能 + +### 2.2.1 新用户开卡 + +**内容** + +本功能的使用条件是:用户使用了未启用卡和新学/工号。 + +> 本系统采用卡和用户一对一绑定的机制,即:一个学/工号在任一时刻只能绑定和使用 1 张卡。 + +> **卡**有三个状态: +> +> - 未启用:该卡未绑定任何用户,仅可用于开卡,不可用于查询、充值或消费。未启用的卡进行开卡操作后将绑定一个用户,进入“启用中”状态; +> - 启用中:该卡绑定了一个用户,可以用于查询、充值和消费,不可用于开卡。如果该卡绑定的用户进行了挂失操作,该卡进入“已被挂失”状态; +> - 已被挂失:该卡绑定了一个用户但在数据库中被标记为挂失,仅可用于查询和重开,不可用于充值或消费。如果绑定该卡的用户使用了另一卡号进行开卡,本卡在数据库中的余额和所有记录将会被移动到新卡,本卡在数据库中的数据被删除,再次被读取时将处于“未启用”状态。 +> +> 实体卡内存储只存最多 6 条交易编号,其他信息全部存在数据库中。 + +>**用户**以学/工号唯一标识,拥有属性“姓名”和“密码”。 +> +>- 学/工号支持4到8位数字 +>- 密码在创建用户时要求输入并二次确认。用于查询用户的所有交易记录和出现大额交易时验证用户身份。 + +用户需要在有开卡(充值)权限的设备上操作。 + +用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号,然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。 + +如果卡号和学/工号满足本功能条件,则在弹出的窗口中输入姓名、密码和二次确认密码。信息无误后,用户信息和卡信息会被写入数据库,卡片会被初始化,开卡成功。此时卡内余额为零。 + +**流程** + +当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。 + +在数据库中查询卡号: + +- 如果查询不到卡片,则在数据库中插入该卡片信息,设置为未启用状态并继续; +- 如果查询到卡片处于未启用状态,继续; +- 否则提示用户卡片已被启用并退出开卡功能,或进入挂失重开功能。 + +在数据库中查询学/工号: + +- 如果查询不到用户信息(未注册),弹出信息输入框获取数据,并检查二次确认密码。将用户信息写入数据库并继续; +- 如果查询到用户,则在卡表中查询用户: + - 如果查询不到数据,则将刚才插入的卡片记录绑定上用户; + - 如果查询到数据,则视卡的状态进入挂失移资功能或提示用户已经绑定了卡,不可再开卡,并退出开卡功能。 + +流程图见 *2.2.3* 节。 + +### 2.2.2 重开卡 + +**内容** + +本功能的使用条件是:卡号处于挂失状态。 + +用户需要在有开卡(充值)权限的设备上操作。 + +用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号(尽管程序没有用到),然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。 + +如果卡号满足本功能条件,则在弹出的窗口中输入密码验证用户身份。信息无误后,卡会被重新置为”启用中“状态,此时卡内余额为挂失前的余额。 + +**流程** + +当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。 + +在数据库中查询卡号: + +- 如果已被挂失,查询数据库中绑定的用户学/工号和姓名。弹出输入窗口,显示绑定的用户姓名,询问用户是否输入密码并重新开卡: + - 如果用户选择取消则退出; + - 如果用户输入密码并点击确定,则验证用户: + - 验证成功则修改数据库,重新启用该卡; + - 验证失败则弹出提示框并退出。 +- 如果是其他状态,则对应进入挂失卡移资或新用户开卡功能。 + +流程图见 *2.2.3* 节。 + +### 2.2.3 挂失卡移资 + +**内容** + +本功能的使用条件是:卡号处于未启用状态,且用户学/工号绑定的卡处于挂失状态。 + +用户需要在有开卡(充值)权限的设备上操作。 + +用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号,然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。 + +如果卡号满足本功能条件,则在弹出的窗口中输入密码验证用户身份。信息无误后,用户挂失的旧卡的记录将会被移植给新卡,此时新卡处于”启用中“状态,卡内余额为旧卡挂失前的余额。旧卡记录会被删除。 + +**流程** + +当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。 + +在数据库中查询卡号: + +- 如果查询不到卡片,则在数据库中插入该卡片信息,设置为未启用状态并继续; +- 如果查询到卡片处于未启用状态,继续; +- 否则提示用户卡片已被启用并退出开卡功能,或进入挂失重开功能。 + +在数据库中查询学/工号: + +- 如果查询不到用户信息(未注册),则进入新用户开卡功能; + +- 如果查询到用户,则继续在卡表中查询用户: + - 如果查询到用户,且用户绑定的卡处于挂失状态,则弹出输入框询问用户是否输入密码并重开新卡并移资。用户取消或验证失败则提示或退出;验证成功则修改数据库,将旧卡信息移植到新卡,删除旧卡信息。 + - 如果查询不到用户则进入新用户开卡功能;如果查询到用户但用户绑定的卡处于启用状态则弹出提示框,退出。 + +整个开卡功能的流程图如下: + +![newCard.drawio](image\newCard.drawio.svg) + +其中绿色部分对应新用户开卡功能,橙色部分对应重开卡部分,蓝色部分对应挂失卡移资部分。 + +## 2.3 充值功能 + +### 2.3.1 远程充值 + +**内容** + +本功能用于远程为卡余额充值。 + +> **卡余额**为小数点后两位精度的小数。金额不得超过 9999.99 元,不得低于 0.00 元。 + +本功能使用学/工号为卡充值,无需使用实体卡。 + +用户需要在有充值权限的设备上操作。首先输入金额(限制大于 0.00 元且小于 9999.99 元),然后输入学/工号。如果学/工号对应的卡处于”已启用“状态,且充值金额加上原金额不超过余额限制 9999.99 元,则充值成功。否则弹窗提示。 + +**流程** + +首先检查设备是否具有充值权限。如果没有,弹出提示窗口。 + +获取金额和学/工号,检查金额是否超出限制,若超出限制弹出提示窗口并退出。 + +在数据库中查询与该学/工号绑定的卡。如果未查询到记录或卡的状态不是”启用中“,弹出提示窗口并退出。 + +检查查询到卡的余额与金额之和是否超过余额限制,若超出限制弹出提示窗口并退出。 + +生成交易号,将交易记录插入数据库,将交易号写入卡中。 + +> **交易号**为 30 位十六进制数: +> +> - 第 0 - 3 位:随机数; +> - 第 4 位:0 或 1,1 表示该交易类型为充值,0 表示该交易类型为消费; +> - 第 5 - 15 位:学/工号,将学/工号(十进制)的每一位数字依次填入(不转换为十六进制); +> - 第 16 - 29 位:时间,从高到低分别是 yyyyMMddhhmmss ; + +![deposit-useId.drawio](image\deposit-useId.drawio.svg) + +### 2.3.2 线下充值 + +**内容** + +本功能用于使用卡片进行余额充值。 + +用户需要在有充值权限的设备上操作。首先输入金额(限制大于 0.00 元且小于 9999.99 元),然后点击”查询“扫描卡号并选择充值卡号。如果卡处于”已启用“状态,且充值金额加上原金额不超过余额限制 9999.99 元,则充值成功。否则弹窗提示。 + +**流程** + +首先检查设备是否具有充值权限。如果没有,弹出提示窗口。 + +获取金额和卡号,检查金额是否超出限制,若超出限制弹出提示窗口并退出。 + +在数据库中查询卡号。如果未查询到记录或卡的状态不是”启用中“,弹出提示窗口并退出。 + +检查查询到卡的余额与金额之和是否超过余额限制,若超出限制弹出提示窗口并退出。 + +生成交易号,将交易记录插入数据库,将交易号写入卡中。 + +![deposit-useCard.drawio](image\deposit-useCard.drawio.svg) + +## 2.4 消费功能 + +**内容** + +本功能用于使用卡片进行消费。 + +首先输入金额(限制不超过 300.00 元),然后点击”查询“扫描卡号并选择消费卡号。 + +- 如果卡处于”已启用“状态: + - 消费金额不超过不超过余额: + - 消费金额不超过 50.00 元,则消费成功; + - 消费金额超过 50.00 元且小于 300.00 元,需要验证用户身份。验证通过后消费成功; + - 消费金额超过限制 300.00 元,消费失败; + - 消费金额超出余额,消费失败; +- 卡片处于异常状态,消费失败。 + +**流程** + +获取金额和卡号,在数据库中查询卡号。如果未查询到记录或卡的状态不是”启用中“,弹出提示窗口并退出。 + +检查金额是否超出余额、超出单次消费限制 300.00 元,若超出限制弹出提示窗口并退出。 + +检查金额是否超出 50.00 元,如果超出需要弹出输入窗口获取密码验证用户身份。 + +生成交易号,将交易记录插入数据库,将交易号写入卡中。 + +![consume.drawio](image\consume.drawio.svg) + +## 2.5 查询功能 + +### 2.5.1 查询卡内记录 + +**内容** + +本功能用于查询卡内交易记录。 + +用户首先点击”读卡“并选择卡号,然后点击”查询卡内记录“按钮。 + +如果卡片处于未启用状态,则会弹出提示窗口。 + +如果卡片处于已启用或已被挂失状态,则余额框内显示余额,卡状态框内会显示卡状态,表格组件内显示卡内存储的交易编号(最多 6 条)对应的交易记录。 + +**流程** + +程序获取卡号,查询数据库查看卡片状态。如果卡片未启用,弹出提示窗口并退出; + +如果卡片已启用或已被挂失,在组件中显示余额、卡状态。 + +调用读卡器 API 读取卡内的交易编号,对每一条交易编号,查询数据库中的交易记录,并显示在表格组件中。 + +![query-inCard.drawio](image\query-inCard.drawio.svg) + +### 2.5.2 查询所有记录 + +**内容** + +本功能用于查询用户所有交易记录。 + +用户可以通过两种方式触发此功能: + +- 点击”读卡“并选择卡号,然后点击”查询所有记录“按钮; +- 输入学/工号,然后点击”查询学/工号记录“按钮; + +如果卡片处于未启用状态,则会弹出提示窗口。 + +如果卡片处于已启用或已被挂失状态,则余额框内显示余额,卡状态框内会显示卡状态,表格组件内显示该用户的所有交易记录。 + +**流程** + +程序获取卡号或用户学/工号(通过学/工号查到卡号),查询数据库查看卡片状态。如果卡片未启用,弹出提示窗口并退出; + +如果卡片已启用或已被挂失,在组件中显示余额、卡状态。 + +查询该用户在数据库内的所有交易记录,并显示在表格组件中。 + +![query-all.drawio](image\query-all.drawio.svg) + +## 2.6 挂失功能 + +**内容** + +本功能用于挂失用户的卡。 + +用户输入学/工号,然后点击”挂失“按钮; + +如果学/工号未绑定卡或绑定的卡已经被挂失,则会弹出提示窗口并退出。 + +将该用户的卡设置为已被挂失状态。 + +**流程** + +程序获取用户学/工号,查询数据库查看卡片状态。 + +如果学/工号未绑定卡或绑定的卡已经被挂失,弹出提示窗口并退出。 + +修改数据库,将学/工号绑定的卡设置为挂失状态。 + +![reportLoss.drawio](image\reportLoss.drawio.svg) + +## 2.7 退出功能 + +**内容** + +本功能用于退出软件。 + +**流程** + +程序关闭读卡器连接和数据库连接,最后关闭图形化界面后退出。 + +![quitApp.drawio](image\quitApp.drawio.svg) + + + +# 3 数据设计 + +## 3.1 磁盘文件设计 + +本系统使用数据库保存数据。 + +本系统涉及的实体有: + +- 用户:以学/工号作为唯一标识符,具有属性姓名和学号; +- 卡:以卡号为唯一标识符,具有属性状态(未启用、已启用、已被挂失)和余额; +- 交易记录:以交易记录号为唯一标识符,具有属性交易时间、交易类型(充值、消费)、交易金额(充值为正数,消费为负数)、原金额、交易后余额; +- 设备:以设备编号作为唯一标识符,具有属性名称和充值权限。 + +实体-关系图如下: + +![ER.drawio](image\ER.drawio.svg) + +因此在数据库中建立表: + +- 用户(学/工号,姓名,密码) + - 姓名、密码不能为空 +- 卡片(卡号,状态,余额,用户的学/工号) + - 卡状态、余额不能为空 + - 余额不能小于 0 ;状态必须为 -1 或 0 或 1 (分别表示已被挂失、未启用和启用中) + - 用户的学/工号为外键,引用用户表的学/工号字段 +- 交易记录(交易记录号,卡号,交易时间、交易类型、交易金额、原金额、交易后余额、设备编号) + - 所有属性都不能为空 + - 卡号为外键,引用卡片表的卡号字段;设备编号为外键,引用设备表的编号字段 + - 交易金额不可小于 -300.00 ;原金额加上交易金额必须等于交易后金额;原金额和交易后金额都必须大于等于0 ;交易类型必须为 0 或 1 (分别表示消费和充值) +- 设备(设备编号,名称,充值权限) + - 所有属性不能为空 + - 名称必须互不相同 + - 充值权限必须为 0 或 1 (分别表示仅可消费、可充值) + +数据库初始化脚本见 [initDb.sql](..\database\initDb.sql) 。 + +本软件使用数据库固定使用 `cardManageSystem` 数据库和 `cardManageSystem` 用户。 + +## 3.2 程序内数据设计 + +### 3.2.1 全局变量定义及使用方法 + +由于本软件是基于 QT 的一个 MainWindow 组件类对象搭建,各个子界面都是 MainWindow 的子组件,因此所有的全局变量都被设置为 MainWindow 对象的属性,包括: + +- reader:自定义 Reader 类(定义在 [readerAPI.h](..\readerAPI.h) 中)对象: + - 变量: COM 口号和卡内最大的记录条数限制 + - 方法: + - `void setComNumber(int comNumber)` 设置 COM 口号 + - `int getComNumber()` 获取当前COM口号 + - `bool is_connected()` 判断读卡器是否已连接 + - `bool connect()` 连接读卡器 + - `void disconnect()` 关闭连接 + - `QStringList inventory(int maxViccNum)` 获取读卡器中卡片的UID列表 + - `bool insertRecord(QString record, QString cardId)` 插入一条交易记录 + - `QStringList readAllRecords(QString cardId, bool &ok)` 获取全部记录 + - `bool initCard(QString cardId)`卡初始化 +- db:自定义 Database 类(定义在 [databaseAPI.h](..\databaseAPI.h) 中)指针: + - 变量:QT 的数据库对象、连接状态、固定的数据库名字符串和用户名字符串 + - 方法: + - `Database(QSqlDatabase database)` 使用QSqlDatabase类初始化对象 + - `Database(QSqlDatabase database, QString hostName, int port, QString password)` 使用QSqlDatabase类、IP、端口、密码初始化对象 + - `void setHostName(QString hostName)` 设置hostName属性 + - `void setPort(int port)` 设置port属性 + - `void setPassword(QString password)` 设置password属性 + - `QSqlDatabase getDatabase()` 获取数据库对象属性 + - `int getPort()` 获取port属性 + - `QString getHostName()` 获取hostName属性 + - `bool is_connected()` 数据库是否已经连接 + - `bool open()` 连接数据库 +- device:自定义 Device 类(定义在 [deviceAPI.h](..\deviceAPI.h) 中)对象: + - 变量:设备名认证状态、设备充值权限、设备名称和设备编号 + - 方法: + - `void setDevice(QString name, Database *db)` 设置并认证设备 + - `QString getName()` 获取设备名 + - `QString getNameAndDepositAllowed()` 获取设备名及其充值权限 + - `int getId()` 获取设备ID + - `bool is_verified()` 设备是否已经认证 + - `bool is_depositAllowed()` 设备是否可充值 +- 其他所有子组件的指针,通过 `ui->{组件名}` 的方法调用 + +### 3.2.2 交易记录定义 + +本系统中,交易记录主要存储在数据库中。交易记录作为变量出现在软件中的场景,是查询数据库得到某个用户的交易记录,显示在表格组件中。因此交易记录被定义为 QStringList 类对象(即 QString 类列表)。 + +使用 QSqlQuery 类方法查询到交易记录后,交易记录使用 QSqlQuery 类对象 query 的方法 value() 获取各个字段的值。获取后,将其转换为 QStringList 类对象,即可调用表格组件方法显示。该功能在 [queryPage.cpp](..\queryPage.cpp) 的 MainWindow::transactionRecord2QStringList() 方法中实现。 + + + +## 3.3 卡片内存储空间设计 + +卡片内存储 3 类数据: + +| 内容 | 长度 | 位置 | +| :------------------------: | :--------------------------------------------: | :------------------------------------: | +| 卡内目前存储的交易记录数量 | 4 bits | block0 的 byte0 的 bit0-3 | +| 最近一条交易记录的下标 | 4 bits | block0 的 byte0 的 bit4-7 | +| 交易记录 | 每条记录 4 blocks,最多存 6 条记录占 24 blocks | block1-24,每连续 4 个block 为一条记录 | + +每条交易记录只存储交易记录号(见 *2.3.1* 节)。 + +初始时,卡内目前存储的交易记录数量为 0 ,最近一条交易记录的下标为 5 。 + +写入记录时,程序首先读取最近一条交易记录的下标,然后将其加 1 模 6,在新下标的 blocks 中写入交易记录号,即循环写入,超过 6 条交易记录时,最新的交易记录会覆盖最早的交易记录。例如,最近一条交易记录的下标为 5 ,那么写入新记录时,将会到下标 (5 + 1) % 6 = 0 中写入,实际写入的 blocks 为 1 + 4 * 0 = 1 到 1 + 4 * (0 + 1) - 1 = 4 。 + +读取记录时,同样读取最近一条交易记录的下标,然后读取卡内目前存储的交易记录数量。接着循环读取,将按照读出的交易记录数量来读取交易记录。例如,最近一条交易记录的下标为 5 ,卡内目前存储的交易记录数量为 6 ,那么读取所有记录时,将会到最早的下标 (5 - 6 + 1) % 6 = 0 中开始读取,依次读取 blocks 为 1-4,5-8,9-12,13-16,17-20,21-24 。 + + + +# 4 软件功能概要 + +本系统的功能包括: + +- 设置:连接和读写数据库、HF15693硬件; +- 开卡:创建新用户、挂失重开卡、挂失移资新卡; +- 挂失卡; +- 充值:远程充值、线下充值; +- 消费; +- 查询:查询卡内记录及数据库内记录。 + + + +# 5 收获与建议 + +**收获** + +- 在项目开发过程中,我将课堂上学到的物联网理论知识应用到实际项目中,深刻理解了RFID技术、数据库交互和Qt开发框架的原理和应用。 +- 项目开发中遇到了诸多问题,如数据库连接、数据读写和硬件通信等。通过查阅资料、调试代码,我逐步解决了这些问题,提升了自己的问题解决能力。 +- 通过项目,我熟练掌握了Qt框架的使用,增强了C++编程能力。同时,对数据库设计和SQL查询也有了更深入的理解和实践。 +- 在项目开发过程中,我学会了如何有效地管理项目进度,包括任务分解、时间安排和进度跟踪等。这些经验将对我未来的项目开发和管理大有裨益。 +- 我学会了遵循代码规范,编写清晰、易维护的代码。同时,我也注重项目文档的编写,为项目后续的维护和迭代提供了有力支持。 + +**建议** + +- 一开始我学习了 MFC 开发,但了解 MFC 后,我发现这个开发框架已经过时,存在许多缺点,不满足现代软件开发的需求,因此我选择了使用 Qt 开发。建议以后该课程可以更新学习的开发工具,比如教学使用 Qt 、Electron 等最新开发框架技术。 \ No newline at end of file diff --git a/doc/image/ER.drawio.svg b/doc/image/ER.drawio.svg new file mode 100644 index 0000000..9a955f1 --- /dev/null +++ b/doc/image/ER.drawio.svg @@ -0,0 +1,4 @@ + + + +
1
1
用户
记录
学/工号
余额
卡号
状态
1
绑定
n
存储
编号
金额
原金额
类型
余额
时间
设备名
n
1
产生
编号
充值权限
名称
姓名
密码
\ No newline at end of file diff --git a/doc/image/architecture.drawio.svg b/doc/image/architecture.drawio.svg new file mode 100644 index 0000000..f48b48d --- /dev/null +++ b/doc/image/architecture.drawio.svg @@ -0,0 +1,4 @@ + + + +
一卡通
管理系统
PC
HF15693.dll
硬件驱动程序
OS
读卡器
硬件
数据库
数据库连接插件
TCP
服务器
网络通信
串口通信
串口
\ No newline at end of file diff --git a/doc/image/consume.drawio.svg b/doc/image/consume.drawio.svg new file mode 100644 index 0000000..4597895 --- /dev/null +++ b/doc/image/consume.drawio.svg @@ -0,0 +1,4 @@ + + + +
用户点击消费
获取消费金额和卡号
卡状态异常,弹出提示窗口
未启用 | 已被挂失
启用中
卡状态?
在卡表中查询卡号
N
Y
消费金额是否超出余额?
生成交易号
将交易信息插入数据库
将交易号写入卡
结束
超出余额限制,弹出提示窗口
N
Y
金额是否超出 300.00 限制?
Y
N
金额是否超出 50.00 限制?
Y
N
身份验证是否通过?
用户身份验证失败,弹出提示窗口
超出金额限制,弹出提示窗口
\ No newline at end of file diff --git a/doc/image/deposit-useCard.drawio.svg b/doc/image/deposit-useCard.drawio.svg new file mode 100644 index 0000000..4eb6578 --- /dev/null +++ b/doc/image/deposit-useCard.drawio.svg @@ -0,0 +1,4 @@ + + + +
用户点击充值
Y
N
设备是否有充值权限?
获取充值金额和卡号
N
Y
金额是否超出限制?
卡状态异常,弹出提示窗口
未启用 | 已被挂失
启用中
卡状态?
在卡表中查询卡号
N
Y
原金额加充值金额是否超出限制?
生成交易号
将交易信息插入数据库
将交易号写入卡
结束
设备不支持开卡,弹出提示窗口
超出金额限制,弹出提示窗口
超出金额限制,弹出提示窗口
\ No newline at end of file diff --git a/doc/image/deposit-useId.drawio.svg b/doc/image/deposit-useId.drawio.svg new file mode 100644 index 0000000..7b8759f --- /dev/null +++ b/doc/image/deposit-useId.drawio.svg @@ -0,0 +1,4 @@ + + + +
用户点击充值
Y
N
设备是否有充值权限?
获取充值金额和学/工号
N
Y
金额是否超出限制?
Y
N
是否有记录?
卡状态异常,弹出提示窗口
未启用 | 已被挂失
启用中
卡状态?
在卡表中查询学/工号
N
Y
原金额加充值金额是否超出限制?
生成交易号
将交易信息插入数据库
将交易号写入卡
结束
设备不支持开卡,弹出提示窗口
超出金额限制,弹出提示窗口
用户未绑定卡,弹出提示窗口
超出金额限制,弹出提示窗口
\ No newline at end of file diff --git a/doc/image/newCard.drawio.svg b/doc/image/newCard.drawio.svg new file mode 100644 index 0000000..f0ea5c4 --- /dev/null +++ b/doc/image/newCard.drawio.svg @@ -0,0 +1,4 @@ + + + +
获取卡号和学/工号
在数据库查询卡表
N
Y
是否有记录?
用户点击开卡
结束
将未启用卡信息插入卡表
在数据库查询用户表
卡已被启用,弹出提示窗口
未启用
启用中
已被挂失
卡状态?
在数据库查询用户表
N
Y
是否有用户?
获取用户信息
将用户信息插入用户表并在卡表中将卡与用户绑定
在数据库查询卡表
N

Y
是否有用户绑定的卡?
已启用
已被挂失
用户绑定的卡的状态?
用户已有启用的卡,不能重复开卡,弹出提示窗口
Y
N
询问用户是否移资?
N
Y
用户验证是否通过?
弹出验证失败提示窗口
将用户旧卡的信息移到新卡
删除旧卡信息
Y
N
询问用户是否重开卡?
N
Y
用户验证是否通过?
弹出验证失败提示窗口
将卡的状态置为已启用
Y
N
设备是否有充值权限?
设备不支持开卡,弹出提示窗口
\ No newline at end of file diff --git a/doc/image/query-all.drawio.svg b/doc/image/query-all.drawio.svg new file mode 100644 index 0000000..250512c --- /dev/null +++ b/doc/image/query-all.drawio.svg @@ -0,0 +1,4 @@ + + + +
用户点击查询所有记录
获取卡号
卡状态异常,弹出提示窗口
未启用
启用中 | 已被挂失
卡状态?
在卡表中查询卡号
在数据库中查询对应交易信息
将交易信息显示在表格组件中
结束
将余额和卡状态显示在对应组件中
用户点击查询学/工号记录
获取学/工号
在卡表中查询卡号
Y
N
学/工号是否绑定了卡?
学/工号未绑定卡,弹出提示窗口
\ No newline at end of file diff --git a/doc/image/query-inCard.drawio.svg b/doc/image/query-inCard.drawio.svg new file mode 100644 index 0000000..4fb49c3 --- /dev/null +++ b/doc/image/query-inCard.drawio.svg @@ -0,0 +1,4 @@ + + + +
用户点击查询卡内记录
获取卡号
卡状态异常,弹出提示窗口
未启用
启用中 | 已被挂失
卡状态?
在卡表中查询卡号
调用读卡器接口读取卡内交易编号
在数据库中查询对应交易信息
将交易信息显示在表格组件中
结束
将余额和卡状态显示在对应组件中
\ No newline at end of file diff --git a/doc/image/quitApp.drawio.svg b/doc/image/quitApp.drawio.svg new file mode 100644 index 0000000..028a0b6 --- /dev/null +++ b/doc/image/quitApp.drawio.svg @@ -0,0 +1,4 @@ + + + +
用户点击确认退出
结束
调用读卡器 API 关闭读卡器连接
关闭数据库连接
\ No newline at end of file diff --git a/doc/image/reportLoss.drawio.svg b/doc/image/reportLoss.drawio.svg new file mode 100644 index 0000000..66783bf --- /dev/null +++ b/doc/image/reportLoss.drawio.svg @@ -0,0 +1,4 @@ + + + +
启用中
已被挂失
卡状态?
结束
在数据库中将卡设置为挂失状态
用户点击查询学/工号记录
获取学/工号
在卡表中查询卡号
Y
N
学/工号是否绑定了卡?
学/工号未绑定卡,弹出提示窗口
卡已被挂失,弹出提示窗口
\ No newline at end of file diff --git a/doc/image/setting-setDatabase.drawio.svg b/doc/image/setting-setDatabase.drawio.svg new file mode 100644 index 0000000..cf805c5 --- /dev/null +++ b/doc/image/setting-setDatabase.drawio.svg @@ -0,0 +1,4 @@ + + + +
获取 IP 、端口号和密码
连接数据库
N
Y
连接成功?
提示用户
更新状态栏内容
用户点击连接
获取设备名
查询数据库,认证设备名及其操作权限
N
Y
认证成功?
提示用户
结束
\ No newline at end of file diff --git a/doc/image/setting-setReader.drawio.svg b/doc/image/setting-setReader.drawio.svg new file mode 100644 index 0000000..54aaa92 --- /dev/null +++ b/doc/image/setting-setReader.drawio.svg @@ -0,0 +1,4 @@ + + + +
获取 COM 口号
调用读卡器 API 尝试连接
N
Y
连接成功?
提示用户
更新状态栏内容
用户点击“连接”按钮
结束
\ No newline at end of file diff --git a/doc/source/.$ER.drawio.bkp b/doc/source/.$ER.drawio.bkp deleted file mode 100644 index 1c4cb18..0000000 --- a/doc/source/.$ER.drawio.bkp +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/source/ER.drawio b/doc/source/ER.drawio index 53608a7..0d8f8da 100644 --- a/doc/source/ER.drawio +++ b/doc/source/ER.drawio @@ -1,147 +1,153 @@ - + - + - + - + - + - + - + - + + + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + diff --git a/mainwindow.h b/mainwindow.h index f595755..3647400 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -80,8 +80,6 @@ private: Device device; QStatusBar *statusBar; - QStackedWidget *stackedWidget; - QCheckBox *readerConnectStatusCheckBox; QLabel *comNumberLabel; QCheckBox *databaseConnectStatusCheckBox; diff --git a/queryPage.cpp b/queryPage.cpp index 8b833dc..6b7cac4 100644 --- a/queryPage.cpp +++ b/queryPage.cpp @@ -378,9 +378,7 @@ void MainWindow::on_cardRecordQueryButton_clicked() 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); - + "where id = :recordId;")); std::vector transactionRecordList; for (int i = 0; i < cardRecordIdList.size(); i++) diff --git a/readerAPI.cpp b/readerAPI.cpp index 3531c8f..ec91eb3 100644 --- a/readerAPI.cpp +++ b/readerAPI.cpp @@ -248,13 +248,14 @@ QStringList Reader::readAllRecords(QString cardId, bool &ok) { QStringList recordList; - int recordNum, recordStartIndex; - bool success = readRecordNumber(recordNum, recordStartIndex, cardId); + int recordNum, recordIndex; + bool success = readRecordNumber(recordNum, recordIndex, cardId); if (!success) { ok = false; return recordList; } + int recordStartIndex = ((recordIndex - recordNum + 1) % maxRecordNum + maxRecordNum) % maxRecordNum; uchar_t cardIdHex[8] = {0}; StringToHex(cardId.toLatin1().data(), cardIdHex); diff --git a/readerAPI.h b/readerAPI.h index c891f3c..2770508 100644 --- a/readerAPI.h +++ b/readerAPI.h @@ -36,7 +36,6 @@ public: void disconnect(); QStringList inventory(int maxViccNum); bool insertRecord(QString record, QString cardId); - bool writeRecords(QStringList recordList, QString cardId); QStringList readAllRecords(QString cardId, bool &ok); bool initCard(QString cardId); }; diff --git a/reportLossPage.cpp b/reportLossPage.cpp index b4d5656..fccbc0e 100644 --- a/reportLossPage.cpp +++ b/reportLossPage.cpp @@ -50,7 +50,7 @@ void MainWindow::on_reportLossButton_clicked() // 查询学/工号是否存在 QSqlQuery query(db->getDatabase()); - query.prepare(QString("select id from card " + query.prepare(QString("select `status` from card " "where userId = :userId;")); query.bindValue(":userId", userId); bool success = query.exec(); @@ -64,6 +64,12 @@ void MainWindow::on_reportLossButton_clicked() QMessageBox::warning(this, "提示", "学/工号不存在,挂失失败。"); return; } + int cardStatus = query.value("status").toInt(); + if (cardStatus == -1) + { + QMessageBox::warning(this, "提示", "该卡已挂失,不可重复挂失。"); + return; + } QString info, prompt = QString("如需挂失该学/工号绑定的卡,请输入密码。"); success = verifyUser(userId, prompt, info);