25 KiB
Raw Blame History

一卡通管理系统说明

1 系统概述

1.1 系统简介

本系统是一个一卡通管理系统,该系统面向学校应用、基于 RFID 技术的构建。该系统基于 ISO/IEC15693 协议的 RFID 芯片( NXP ICS20 或 SL2S2002 )进行卡内数据读写,并实现与数据库交互记录的功能。

1.2 系统组成

1.2.1 系统架构

architecture

本系统由一卡通管理系统软件、数据库以及读卡器硬件组成。

其中:

  • 一卡通管理系统软件
    • 为用户提供操作界面,实现读写数据库和硬件的逻辑;
    • 运行在 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

2.1.2 设置数据库连接

内容

该功能用于连接数据库,以及设备名认证。

设备名用于在交易记录中记录操作设备的名称。

对于每个设备名,数据库中设置了其操作权限:

  • 可充值:使用该设备名的设备拥有开卡、充值和消费权限(在管理员 PC 中使用);
  • 仅可消费:使用该设备名的设备仅有消费权限(在食堂等消费场所的终端机中使用)。

用户首先要填写数据库的 IP 地址和端口,输入数据库的密码和要使用的设备名,点击连接按钮。

  • 如果数据库连接不成功,软件弹出提示框,提示用户数据库连接失败,同时软件窗口下方任务栏的数据库连接状态勾选框取消勾选,显示“数据库未连接”、“未指定设备名”;

  • 如果数据库连接成功,软件窗口下方任务栏的数据库连接状态勾选框显示勾选,同时显示连接的 IP 地址和端口;

连接成功后,将会进行设备名认证。

  • 若设备名验证成功,软件窗口下方任务栏显示设备名及其操作权限;

  • 若验证失败,软件弹出提示框,提示用户该设备名无效,同时软件窗口下方任务栏显示“未指定设备名”。

流程

从输入组件获取 IP 地址、端口、密码,连接数据库并更新任务栏显示信息。如果连接不成功,弹出提示框;如果连接成功,继续进行设备名认证。

查询数据库进行设备名认证,按认证结果更新任务栏显示信息。如果认证不成功,还要弹出提示框。

setting-setDatabase

以下功能中,除了仅需输学/工号的功能仅需连接数据库外,其他所有功能都需同时连接读卡器和数据库。

2.2 开卡功能

2.2.1 新用户开卡

内容

本功能的使用条件是:用户使用了未启用卡和新学/工号。

本系统采用卡和用户一对一绑定的机制,即:一个学/工号在任一时刻只能绑定和使用 1 张卡。

有三个状态:

  • 未启用:该卡未绑定任何用户,仅可用于开卡,不可用于查询、充值或消费。未启用的卡进行开卡操作后将绑定一个用户,进入“启用中”状态;
  • 启用中:该卡绑定了一个用户,可以用于查询、充值和消费,不可用于开卡。如果该卡绑定的用户进行了挂失操作,该卡进入“已被挂失”状态;
  • 已被挂失:该卡绑定了一个用户但在数据库中被标记为挂失,仅可用于查询和重开,不可用于充值或消费。如果绑定该卡的用户使用了另一卡号进行开卡,本卡在数据库中的余额和所有记录将会被移动到新卡,本卡在数据库中的数据被删除,再次被读取时将处于“未启用”状态。

实体卡内存储只存最多 6 条交易编号,其他信息全部存在数据库中。

用户以学/工号唯一标识,拥有属性“姓名”和“密码”。

  • 学/工号支持4到8位数字
  • 密码在创建用户时要求输入并二次确认。用于查询用户的所有交易记录和出现大额交易时验证用户身份。

用户需要在有开卡(充值)权限的设备上操作。

用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号,然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。

如果卡号和学/工号满足本功能条件,则在弹出的窗口中输入姓名、密码和二次确认密码。信息无误后,用户信息和卡信息会被写入数据库,卡片会被初始化,开卡成功。此时卡内余额为零。

流程

当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。

在数据库中查询卡号:

  • 如果查询不到卡片,则在数据库中插入该卡片信息,设置为未启用状态并继续;
  • 如果查询到卡片处于未启用状态,继续;
  • 否则提示用户卡片已被启用并退出开卡功能,或进入挂失重开功能。

在数据库中查询学/工号:

  • 如果查询不到用户信息(未注册),弹出信息输入框获取数据,并检查二次确认密码。将用户信息写入数据库并继续;
  • 如果查询到用户,则在卡表中查询用户:
    • 如果查询不到数据,则将刚才插入的卡片记录绑定上用户;
    • 如果查询到数据,则视卡的状态进入挂失移资功能或提示用户已经绑定了卡,不可再开卡,并退出开卡功能。

流程图见 2.2.3 节。

2.2.2 重开卡

内容

本功能的使用条件是:卡号处于挂失状态。

用户需要在有开卡(充值)权限的设备上操作。

用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号(尽管程序没有用到),然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。

如果卡号满足本功能条件,则在弹出的窗口中输入密码验证用户身份。信息无误后,卡会被重新置为”启用中“状态,此时卡内余额为挂失前的余额。

流程

当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。

在数据库中查询卡号:

  • 如果已被挂失,查询数据库中绑定的用户学/工号和姓名。弹出输入窗口,显示绑定的用户姓名,询问用户是否输入密码并重新开卡:
    • 如果用户选择取消则退出;
    • 如果用户输入密码并点击确定,则验证用户:
      • 验证成功则修改数据库,重新启用该卡;
      • 验证失败则弹出提示框并退出。
  • 如果是其他状态,则对应进入挂失卡移资或新用户开卡功能。

流程图见 2.2.3 节。

2.2.3 挂失卡移资

内容

本功能的使用条件是:卡号处于未启用状态,且用户学/工号绑定的卡处于挂失状态。

用户需要在有开卡(充值)权限的设备上操作。

用户点击“查询”按钮扫描读卡器上的卡片,输入学/工号,然后点击“开卡”。如果用户没有点击”查询“按钮扫描,点击”开卡“按钮时会弹出提示框。

如果卡号满足本功能条件,则在弹出的窗口中输入密码验证用户身份。信息无误后,用户挂失的旧卡的记录将会被移植给新卡,此时新卡处于”启用中“状态,卡内余额为旧卡挂失前的余额。旧卡记录会被删除。

流程

当”开卡“按钮被点击,获取界面上选中的卡号和输入的学/工号。如果没有发现选中的卡号(用户没有点击”查询“按钮),则弹出提示框。

在数据库中查询卡号:

  • 如果查询不到卡片,则在数据库中插入该卡片信息,设置为未启用状态并继续;
  • 如果查询到卡片处于未启用状态,继续;
  • 否则提示用户卡片已被启用并退出开卡功能,或进入挂失重开功能。

在数据库中查询学/工号:

  • 如果查询不到用户信息(未注册),则进入新用户开卡功能;

  • 如果查询到用户,则继续在卡表中查询用户:

    • 如果查询到用户,且用户绑定的卡处于挂失状态,则弹出输入框询问用户是否输入密码并重开新卡并移资。用户取消或验证失败则提示或退出;验证成功则修改数据库,将旧卡信息移植到新卡,删除旧卡信息。
    • 如果查询不到用户则进入新用户开卡功能;如果查询到用户但用户绑定的卡处于启用状态则弹出提示框,退出。

整个开卡功能的流程图如下:

newCard

其中绿色部分对应新用户开卡功能,橙色部分对应重开卡部分,蓝色部分对应挂失卡移资部分。

2.3 充值功能

2.3.1 远程充值

内容

本功能用于远程为卡余额充值。

卡余额为小数点后两位精度的小数。金额不得超过 9999.99 元,不得低于 0.00 元。

本功能使用学/工号为卡充值,无需使用实体卡。

用户需要在有充值权限的设备上操作。首先输入金额(限制大于 0.00 元且小于 9999.99 元),然后输入学/工号。如果学/工号对应的卡处于”已启用“状态,且充值金额加上原金额不超过余额限制 9999.99 元,则充值成功。否则弹窗提示。

流程

首先检查设备是否具有充值权限。如果没有,弹出提示窗口。

获取金额和学/工号,检查金额是否超出限制,若超出限制弹出提示窗口并退出。

在数据库中查询与该学/工号绑定的卡。如果未查询到记录或卡的状态不是”启用中“,弹出提示窗口并退出。

检查查询到卡的余额与金额之和是否超过余额限制,若超出限制弹出提示窗口并退出。

生成交易号,将交易记录插入数据库,将交易号写入卡中。

交易号为 30 位十六进制数:

  • 第 0 - 3 位:随机数;
  • 第 4 位0 或 11 表示该交易类型为充值0 表示该交易类型为消费;
  • 第 5 - 15 位:学/工号,将学/工号(十进制)的每一位数字依次填入(不转换为十六进制);
  • 第 16 - 29 位:时间,从高到低分别是 yyyyMMddhhmmss

deposit-useId

2.3.2 线下充值

内容

本功能用于使用卡片进行余额充值。

用户需要在有充值权限的设备上操作。首先输入金额(限制大于 0.00 元且小于 9999.99 元),然后点击”查询“扫描卡号并选择充值卡号。如果卡处于”已启用“状态,且充值金额加上原金额不超过余额限制 9999.99 元,则充值成功。否则弹窗提示。

流程

首先检查设备是否具有充值权限。如果没有,弹出提示窗口。

获取金额和卡号,检查金额是否超出限制,若超出限制弹出提示窗口并退出。

在数据库中查询卡号。如果未查询到记录或卡的状态不是”启用中“,弹出提示窗口并退出。

检查查询到卡的余额与金额之和是否超过余额限制,若超出限制弹出提示窗口并退出。

生成交易号,将交易记录插入数据库,将交易号写入卡中。

deposit-useCard

2.4 消费功能

内容

本功能用于使用卡片进行消费。

首先输入金额(限制不超过 300.00 元),然后点击”查询“扫描卡号并选择消费卡号。

  • 如果卡处于”已启用“状态:
    • 消费金额不超过不超过余额:
      • 消费金额不超过 50.00 元,则消费成功;
      • 消费金额超过 50.00 元且小于 300.00 元,需要验证用户身份。验证通过后消费成功;
      • 消费金额超过限制 300.00 元,消费失败;
    • 消费金额超出余额,消费失败;
  • 卡片处于异常状态,消费失败。

流程

获取金额和卡号,在数据库中查询卡号。如果未查询到记录或卡的状态不是”启用中“,弹出提示窗口并退出。

检查金额是否超出余额、超出单次消费限制 300.00 元,若超出限制弹出提示窗口并退出。

检查金额是否超出 50.00 元,如果超出需要弹出输入窗口获取密码验证用户身份。

生成交易号,将交易记录插入数据库,将交易号写入卡中。

consume

2.5 查询功能

2.5.1 查询卡内记录

内容

本功能用于查询卡内交易记录。

用户首先点击”读卡“并选择卡号,然后点击”查询卡内记录“按钮。

如果卡片处于未启用状态,则会弹出提示窗口。

如果卡片处于已启用或已被挂失状态,则余额框内显示余额,卡状态框内会显示卡状态,表格组件内显示卡内存储的交易编号(最多 6 条)对应的交易记录。

流程

程序获取卡号,查询数据库查看卡片状态。如果卡片未启用,弹出提示窗口并退出;

如果卡片已启用或已被挂失,在组件中显示余额、卡状态。

调用读卡器 API 读取卡内的交易编号,对每一条交易编号,查询数据库中的交易记录,并显示在表格组件中。

query-inCard

2.5.2 查询所有记录

内容

本功能用于查询用户所有交易记录。

用户可以通过两种方式触发此功能:

  • 点击”读卡“并选择卡号,然后点击”查询所有记录“按钮;
  • 输入学/工号,然后点击”查询学/工号记录“按钮;

如果卡片处于未启用状态,则会弹出提示窗口。

如果卡片处于已启用或已被挂失状态,则余额框内显示余额,卡状态框内会显示卡状态,表格组件内显示该用户的所有交易记录。

流程

程序获取卡号或用户学/工号(通过学/工号查到卡号),查询数据库查看卡片状态。如果卡片未启用,弹出提示窗口并退出;

如果卡片已启用或已被挂失,在组件中显示余额、卡状态。

查询该用户在数据库内的所有交易记录,并显示在表格组件中。

query-all

2.6 挂失功能

内容

本功能用于挂失用户的卡。

用户输入学/工号,然后点击”挂失“按钮;

如果学/工号未绑定卡或绑定的卡已经被挂失,则会弹出提示窗口并退出。

将该用户的卡设置为已被挂失状态。

流程

程序获取用户学/工号,查询数据库查看卡片状态。

如果学/工号未绑定卡或绑定的卡已经被挂失,弹出提示窗口并退出。

修改数据库,将学/工号绑定的卡设置为挂失状态。

reportLoss

2.7 退出功能

内容

本功能用于退出软件。

流程

程序关闭读卡器连接和数据库连接,最后关闭图形化界面后退出。

quitApp

3 数据设计

3.1 磁盘文件设计

本系统使用数据库保存数据。

本系统涉及的实体有:

  • 用户:以学/工号作为唯一标识符,具有属性姓名和学号;
  • 卡:以卡号为唯一标识符,具有属性状态(未启用、已启用、已被挂失)和余额;
  • 交易记录:以交易记录号为唯一标识符,具有属性交易时间、交易类型(充值、消费)、交易金额(充值为正数,消费为负数)、原金额、交易后余额;
  • 设备:以设备编号作为唯一标识符,具有属性名称和充值权限。

实体-关系图如下:

ER

因此在数据库中建立表:

  • 用户(学/工号,姓名,密码)
    • 姓名、密码不能为空
  • 卡片(卡号,状态,余额,用户的学/工号)
    • 卡状态、余额不能为空
    • 余额不能小于 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() 获取设备ID
      • bool 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-45-89-1213-1617-2021-24 。

4 软件功能概要

本系统的功能包括:

  • 设置连接和读写数据库、HF15693硬件
  • 开卡:创建新用户、挂失重开卡、挂失移资新卡;
  • 挂失卡;
  • 充值:远程充值、线下充值;
  • 消费;
  • 查询:查询卡内记录及数据库内记录。

5 收获与建议

收获

  • 在项目开发过程中我将课堂上学到的物联网理论知识应用到实际项目中深刻理解了RFID技术、数据库交互和Qt开发框架的原理和应用。
  • 项目开发中遇到了诸多问题,如数据库连接、数据读写和硬件通信等。通过查阅资料、调试代码,我逐步解决了这些问题,提升了自己的问题解决能力。
  • 通过项目我熟练掌握了Qt框架的使用增强了C++编程能力。同时对数据库设计和SQL查询也有了更深入的理解和实践。
  • 在项目开发过程中,我学会了如何有效地管理项目进度,包括任务分解、时间安排和进度跟踪等。这些经验将对我未来的项目开发和管理大有裨益。
  • 我学会了遵循代码规范,编写清晰、易维护的代码。同时,我也注重项目文档的编写,为项目后续的维护和迭代提供了有力支持。

建议

  • 一开始我学习了 MFC 开发,但了解 MFC 后,我发现这个开发框架已经过时,存在许多缺点,不满足现代软件开发的需求,因此我选择了使用 Qt 开发。建议以后该课程可以更新学习的开发工具,比如教学使用 Qt 、Electron 等最新开发框架技术。