25 KiB
一卡通管理系统说明
1 系统概述
1.1 系统简介
本系统是一个一卡通管理系统,该系统面向学校应用、基于 RFID 技术的构建。该系统基于 ISO/IEC15693 协议的 RFID 芯片( NXP ICS20 或 SL2S2002 )进行卡内数据读写,并实现与数据库交互记录的功能。
1.2 系统组成
1.2.1 系统架构
本系统由一卡通管理系统软件、数据库以及读卡器硬件组成。
其中:
- 一卡通管理系统软件
- 为用户提供操作界面,实现读写数据库和硬件的逻辑;
- 运行在 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 进行连接,得到连接结果。如果连接不成功,弹出提示框。最后根据连接结果更新状态栏信息。
2.1.2 设置数据库连接
内容
该功能用于连接数据库,以及设备名认证。
设备名用于在交易记录中记录操作设备的名称。
对于每个设备名,数据库中设置了其操作权限:
- 可充值:使用该设备名的设备拥有开卡、充值和消费权限(在管理员 PC 中使用);
- 仅可消费:使用该设备名的设备仅有消费权限(在食堂等消费场所的终端机中使用)。
用户首先要填写数据库的 IP 地址和端口,输入数据库的密码和要使用的设备名,点击连接按钮。
-
如果数据库连接不成功,软件弹出提示框,提示用户数据库连接失败,同时软件窗口下方任务栏的数据库连接状态勾选框取消勾选,显示“数据库未连接”、“未指定设备名”;
-
如果数据库连接成功,软件窗口下方任务栏的数据库连接状态勾选框显示勾选,同时显示连接的 IP 地址和端口;
连接成功后,将会进行设备名认证。
-
若设备名验证成功,软件窗口下方任务栏显示设备名及其操作权限;
-
若验证失败,软件弹出提示框,提示用户该设备名无效,同时软件窗口下方任务栏显示“未指定设备名”。
流程
从输入组件获取 IP 地址、端口、密码,连接数据库并更新任务栏显示信息。如果连接不成功,弹出提示框;如果连接成功,继续进行设备名认证。
查询数据库进行设备名认证,按认证结果更新任务栏显示信息。如果认证不成功,还要弹出提示框。
以下功能中,除了仅需输学/工号的功能仅需连接数据库外,其他所有功能都需同时连接读卡器和数据库。
2.2 开卡功能
2.2.1 新用户开卡
内容
本功能的使用条件是:用户使用了未启用卡和新学/工号。
本系统采用卡和用户一对一绑定的机制,即:一个学/工号在任一时刻只能绑定和使用 1 张卡。
卡有三个状态:
- 未启用:该卡未绑定任何用户,仅可用于开卡,不可用于查询、充值或消费。未启用的卡进行开卡操作后将绑定一个用户,进入“启用中”状态;
- 启用中:该卡绑定了一个用户,可以用于查询、充值和消费,不可用于开卡。如果该卡绑定的用户进行了挂失操作,该卡进入“已被挂失”状态;
- 已被挂失:该卡绑定了一个用户但在数据库中被标记为挂失,仅可用于查询和重开,不可用于充值或消费。如果绑定该卡的用户使用了另一卡号进行开卡,本卡在数据库中的余额和所有记录将会被移动到新卡,本卡在数据库中的数据被删除,再次被读取时将处于“未启用”状态。
实体卡内存储只存最多 6 条交易编号,其他信息全部存在数据库中。
用户以学/工号唯一标识,拥有属性“姓名”和“密码”。
- 学/工号支持4到8位数字
- 密码在创建用户时要求输入并二次确认。用于查询用户的所有交易记录和出现大额交易时验证用户身份。
用户需要在有开卡(充值)权限的设备上操作。
用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号,然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。
如果卡号和学/工号满足本功能条件,则在弹出的窗口中输入姓名、密码和二次确认密码。信息无误后,用户信息和卡信息会被写入数据库,卡片会被初始化,开卡成功。此时卡内余额为零。
流程
当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。
在数据库中查询卡号:
- 如果查询不到卡片,则在数据库中插入该卡片信息,设置为未启用状态并继续;
- 如果查询到卡片处于未启用状态,继续;
- 否则提示用户卡片已被启用并退出开卡功能,或进入挂失重开功能。
在数据库中查询学/工号:
- 如果查询不到用户信息(未注册),弹出信息输入框获取数据,并检查二次确认密码。将用户信息写入数据库并继续;
- 如果查询到用户,则在卡表中查询用户:
- 如果查询不到数据,则将刚才插入的卡片记录绑定上用户;
- 如果查询到数据,则视卡的状态进入挂失移资功能或提示用户已经绑定了卡,不可再开卡,并退出开卡功能。
流程图见 2.2.3 节。
2.2.2 重开卡
内容
本功能的使用条件是:卡号处于挂失状态。
用户需要在有开卡(充值)权限的设备上操作。
用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号(尽管程序没有用到),然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。
如果卡号满足本功能条件,则在弹出的窗口中输入密码验证用户身份。信息无误后,卡会被重新置为”启用中“状态,此时卡内余额为挂失前的余额。
流程
当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。
在数据库中查询卡号:
- 如果已被挂失,查询数据库中绑定的用户学/工号和姓名。弹出输入窗口,显示绑定的用户姓名,询问用户是否输入密码并重新开卡:
- 如果用户选择取消则退出;
- 如果用户输入密码并点击确定,则验证用户:
- 验证成功则修改数据库,重新启用该卡;
- 验证失败则弹出提示框并退出。
- 如果是其他状态,则对应进入挂失卡移资或新用户开卡功能。
流程图见 2.2.3 节。
2.2.3 挂失卡移资
内容
本功能的使用条件是:卡号处于未启用状态,且用户学/工号绑定的卡处于挂失状态。
用户需要在有开卡(充值)权限的设备上操作。
用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号,然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。
如果卡号满足本功能条件,则在弹出的窗口中输入密码验证用户身份。信息无误后,用户挂失的旧卡的记录将会被移植给新卡,此时新卡处于”启用中“状态,卡内余额为旧卡挂失前的余额。旧卡记录会被删除。
流程
当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。
在数据库中查询卡号:
- 如果查询不到卡片,则在数据库中插入该卡片信息,设置为未启用状态并继续;
- 如果查询到卡片处于未启用状态,继续;
- 否则提示用户卡片已被启用并退出开卡功能,或进入挂失重开功能。
在数据库中查询学/工号:
-
如果查询不到用户信息(未注册),则进入新用户开卡功能;
-
如果查询到用户,则继续在卡表中查询用户:
- 如果查询到用户,且用户绑定的卡处于挂失状态,则弹出输入框询问用户是否输入密码并重开新卡并移资。用户取消或验证失败则提示或退出;验证成功则修改数据库,将旧卡信息移植到新卡,删除旧卡信息。
- 如果查询不到用户则进入新用户开卡功能;如果查询到用户但用户绑定的卡处于启用状态则弹出提示框,退出。
整个开卡功能的流程图如下:
其中绿色部分对应新用户开卡功能,橙色部分对应重开卡部分,蓝色部分对应挂失卡移资部分。
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 ;
2.3.2 线下充值
内容
本功能用于使用卡片进行余额充值。
用户需要在有充值权限的设备上操作。首先输入金额(限制大于 0.00 元且小于 9999.99 元),然后点击”查询“扫描卡号并选择充值卡号。如果卡处于”已启用“状态,且充值金额加上原金额不超过余额限制 9999.99 元,则充值成功。否则弹窗提示。
流程
首先检查设备是否具有充值权限。如果没有,弹出提示窗口。
获取金额和卡号,检查金额是否超出限制,若超出限制弹出提示窗口并退出。
在数据库中查询卡号。如果未查询到记录或卡的状态不是”启用中“,弹出提示窗口并退出。
检查查询到卡的余额与金额之和是否超过余额限制,若超出限制弹出提示窗口并退出。
生成交易号,将交易记录插入数据库,将交易号写入卡中。
2.4 消费功能
内容
本功能用于使用卡片进行消费。
首先输入金额(限制不超过 300.00 元),然后点击”查询“扫描卡号并选择消费卡号。
- 如果卡处于”已启用“状态:
- 消费金额不超过不超过余额:
- 消费金额不超过 50.00 元,则消费成功;
- 消费金额超过 50.00 元且小于 300.00 元,需要验证用户身份。验证通过后消费成功;
- 消费金额超过限制 300.00 元,消费失败;
- 消费金额超出余额,消费失败;
- 消费金额不超过不超过余额:
- 卡片处于异常状态,消费失败。
流程
获取金额和卡号,在数据库中查询卡号。如果未查询到记录或卡的状态不是”启用中“,弹出提示窗口并退出。
检查金额是否超出余额、超出单次消费限制 300.00 元,若超出限制弹出提示窗口并退出。
检查金额是否超出 50.00 元,如果超出需要弹出输入窗口获取密码验证用户身份。
生成交易号,将交易记录插入数据库,将交易号写入卡中。
2.5 查询功能
2.5.1 查询卡内记录
内容
本功能用于查询卡内交易记录。
用户首先点击”读卡“并选择卡号,然后点击”查询卡内记录“按钮。
如果卡片处于未启用状态,则会弹出提示窗口。
如果卡片处于已启用或已被挂失状态,则余额框内显示余额,卡状态框内会显示卡状态,表格组件内显示卡内存储的交易编号(最多 6 条)对应的交易记录。
流程
程序获取卡号,查询数据库查看卡片状态。如果卡片未启用,弹出提示窗口并退出;
如果卡片已启用或已被挂失,在组件中显示余额、卡状态。
调用读卡器 API 读取卡内的交易编号,对每一条交易编号,查询数据库中的交易记录,并显示在表格组件中。
2.5.2 查询所有记录
内容
本功能用于查询用户所有交易记录。
用户可以通过两种方式触发此功能:
- 点击”读卡“并选择卡号,然后点击”查询所有记录“按钮;
- 输入学/工号,然后点击”查询学/工号记录“按钮;
如果卡片处于未启用状态,则会弹出提示窗口。
如果卡片处于已启用或已被挂失状态,则余额框内显示余额,卡状态框内会显示卡状态,表格组件内显示该用户的所有交易记录。
流程
程序获取卡号或用户学/工号(通过学/工号查到卡号),查询数据库查看卡片状态。如果卡片未启用,弹出提示窗口并退出;
如果卡片已启用或已被挂失,在组件中显示余额、卡状态。
查询该用户在数据库内的所有交易记录,并显示在表格组件中。
2.6 挂失功能
内容
本功能用于挂失用户的卡。
用户输入学/工号,然后点击”挂失“按钮;
如果学/工号未绑定卡或绑定的卡已经被挂失,则会弹出提示窗口并退出。
将该用户的卡设置为已被挂失状态。
流程
程序获取用户学/工号,查询数据库查看卡片状态。
如果学/工号未绑定卡或绑定的卡已经被挂失,弹出提示窗口并退出。
修改数据库,将学/工号绑定的卡设置为挂失状态。
2.7 退出功能
内容
本功能用于退出软件。
流程
程序关闭读卡器连接和数据库连接,最后关闭图形化界面后退出。
3 数据设计
3.1 磁盘文件设计
本系统使用数据库保存数据。
本系统涉及的实体有:
- 用户:以学/工号作为唯一标识符,具有属性姓名和学号;
- 卡:以卡号为唯一标识符,具有属性状态(未启用、已启用、已被挂失)和余额;
- 交易记录:以交易记录号为唯一标识符,具有属性交易时间、交易类型(充值、消费)、交易金额(充值为正数,消费为负数)、原金额、交易后余额;
- 设备:以设备编号作为唯一标识符,具有属性名称和充值权限。
实体-关系图如下:
因此在数据库中建立表:
- 用户(学/工号,姓名,密码)
- 姓名、密码不能为空
- 卡片(卡号,状态,余额,用户的学/工号)
- 卡状态、余额不能为空
- 余额不能小于 0 ;状态必须为 -1 或 0 或 1 (分别表示已被挂失、未启用和启用中)
- 用户的学/工号为外键,引用用户表的学/工号字段
- 交易记录(交易记录号,卡号,交易时间、交易类型、交易金额、原金额、交易后余额、设备编号)
- 所有属性都不能为空
- 卡号为外键,引用卡片表的卡号字段;设备编号为外键,引用设备表的编号字段
- 交易金额不可小于 -300.00 ;原金额加上交易金额必须等于交易后金额;原金额和交易后金额都必须大于等于0 ;交易类型必须为 0 或 1 (分别表示消费和充值)
- 设备(设备编号,名称,充值权限)
- 所有属性不能为空
- 名称必须互不相同
- 充值权限必须为 0 或 1 (分别表示仅可消费、可充值)
数据库初始化脚本见 initDb.sql 。
本软件使用数据库固定使用 cardManageSystem
数据库和 cardManageSystem
用户。
3.2 程序内数据设计
3.2.1 全局变量定义及使用方法
由于本软件是基于 QT 的一个 MainWindow 组件类对象搭建,各个子界面都是 MainWindow 的子组件,因此所有的全局变量都被设置为 MainWindow 对象的属性,包括:
- reader:自定义 Reader 类(定义在 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 中)指针:
- 变量: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 中)对象:
- 变量:设备名认证状态、设备充值权限、设备名称和设备编号
- 方法:
void setDevice(QString name, Database *db)
设置并认证设备QString getName()
获取设备名QString getNameAndDepositAllowed()
获取设备名及其充值权限int getId()
获取设备IDbool is_verified()
设备是否已经认证bool is_depositAllowed()
设备是否可充值
- 其他所有子组件的指针,通过
ui->{组件名}
的方法调用
3.2.2 交易记录定义
本系统中,交易记录主要存储在数据库中。交易记录作为变量出现在软件中的场景,是查询数据库得到某个用户的交易记录,显示在表格组件中。因此交易记录被定义为 QStringList 类对象(即 QString 类列表)。
使用 QSqlQuery 类方法查询到交易记录后,交易记录使用 QSqlQuery 类对象 query 的方法 value() 获取各个字段的值。获取后,将其转换为 QStringList 类对象,即可调用表格组件方法显示。该功能在 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 等最新开发框架技术。