说到设计界面的应用程序开发框架,离不开经典的三层架构(界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)),本人前段时间开发了一个收银系统体系下的PC端应用程序,主要基于三层应用程序开发框架,不过是有一些个人修改,更加适合我开发的项目。于此,本人想记录一些想法分享给大家。
Qt是一个近些年来做应用程序不错的选择,有些开发团队可能只用来用Qt画界面,但Qt本身是很强大的,他完全可以满足开发大型应用程序的需求。
本系统是用于PC端收费使用,面向客户的功能主要有购买商品、交易商品等,面向使用者的主要功能主要有登录信息、交易流水、更新商品等。由于本篇旨在分享我的架构思路,详细的功能就不再说明。
首先,整体系统分为两大块,分为业务块和公共块。业务块包括了各种业务,公共块包括了辅助业务块里的各个功能需求产生的各种解决工具或者声明等。
每个涉及界面的业务模块基于三层架构,但有的业务设计的数据较多,本人就添加了Data数据层,专门用于处理数据的存储何使用。涉及界面内容包括控件的显示、控件的属性设置、控件的消息响应、控件的样式设置等全部包含在UI层中;涉及业务逻辑处理等全部包含在BLL层中;涉及数据库数据存取等全部包含在DAL层中;涉及程序内存数据的存储等全部包含在Data层。整体结构由上而下,DAL层服务BLL层,BLL层和Data层服务UI层。以本系统中登录模块为例,简单介绍下具体的实现。
class loginUi : public QDialog // 基类为对话框
{
Q_OBJECT
public:
explicit loginUi(QWidget *parent = 0); // 构造函数
~loginUi(); // 析构
int getShopType(); // 提供的外部接口函数
private slots: // 控件消息函数 即Qt里信号槽的槽函数
void on_btn_exit_clicked();
void on_btn_login_clicked();
void on_le_userName_textEdited(const QString &);
void on_le_password_textEdited(const QString &);
private:
void initUi(); // 界面初始化函数
void keyPressEvent(QKeyEvent *e); // 重写按键事件
private:
Ui::loginUi *ui;
loginBll* m_bll; // 业务逻辑层
};
#include "loginUi.h"
#include "ui_loginUi.h"
#include
#include
loginUi::loginUi(QWidget *parent) :
QDialog(parent),
ui(new Ui::loginUi),m_bll(NULL)
{
ui->setupUi(this);
initUi();
m_bll = new loginBll;
}
loginUi::~loginUi()
{
delete ui;
safe_realse_pointer(m_bll);
}
int loginUi::getShopType()
{
return pub::shopInfo.shopType.toInt();
}
void loginUi::initUi() // 界面初始化
{
QDesktopWidget *deskTop = QApplication::desktop();
pub::curScreenInfo.screenCount = deskTop->screenCount();
int curMonitor = deskTop->screenNumber (this);
QRect rect = deskTop->screenGeometry(curMonitor);
pub::curScreenInfo.width = rect.width();
pub::curScreenInfo.height = rect.height(); // 记录当前分辨率方便自适应使用
ui->le_userName->setClearButtonEnabled(true);
this->setWindowFlags(this->windowFlags() | Qt::FramelessWindowHint | Qt::Window); // 窗口属性设置
this->setWindowState(Qt::WindowFullScreen);
this->setWindowTitle("登录");
ui->frame_pos->setGeometry(0.59 * pub::curScreenInfo.width, 0.41 * pub::curScreenInfo.height, ui->frame_pos->width(), ui->frame_pos->height());
}
void loginUi::keyPressEvent(QKeyEvent *e)
{
if(ui->le_password->hasFocus() || ui->le_userName->hasFocus())
{
if(e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return)
{
if(ui->le_userName->text().isEmpty())
{
ui->le_userName->setStyleSheet("border-style:outset;border-width:0px;"
"border-radius:10px;background-color: rgb(234, 234, 234);border:2px solid red");
}
else
{
ui->le_password->setFocus();
}
if(ui->le_password->text().isEmpty())
{
ui->le_password->setStyleSheet("border-style:outset;border-width:0px;"
"border-radius:10px;background-color: rgb(234, 234, 234);border:2px solid red");
}
else
{
on_btn_login_clicked();
}
}
}
}
void loginUi::on_btn_exit_clicked()
{
this->reject();
}
void loginUi::on_btn_login_clicked()
{
if(ui->le_password->text().isEmpty() || ui->le_userName->text().isEmpty())
{
infoMsgBox::information(NULL, tr("提示"), tr("请填写完整信息!"), tr("是"));
}
else
{
if(m_bll->identityCheck(ui->le_userName->text(), ui->le_password->text())) // 逻辑层处理业务
{
m_bll->uploadOperatorInfo();
logging::logInfo("登录成功!\n User:" + pub::identityInfo.userName);
m_bll->getDecodeTerminalInfo();
m_bll->clearOrderInfo();
return accept();
}
else
{
infoMsgBox::information(NULL, tr("提示"), tr("用户或密码错误"), tr("是"));
ui->le_password->clear();
ui->le_password->setFocus();
}
}
}
void loginUi::on_le_userName_textEdited(const QString &)
{
ui->le_userName->setStyleSheet("border-style:outset;border-width:0px;"
"border-radius:10px;background-color: rgb(234, 234, 234);");
}
void loginUi::on_le_password_textEdited(const QString &)
{
ui->le_password->setStyleSheet("border-style:outset;border-width:0px;"
"border-radius:10px;background-color: rgb(234, 234, 234);");
}
以上是登录模块界面层的主要内容,下面简单贴几段DAL层服务BLL层的代码:
bool loginBll::identityCheck(const QString _userName, const QString _password)
{
TIdentityInfo identityInfo = m_dal.getIdentityInfo(_userName);
if(identityInfo.password.isEmpty() || _password != identityInfo.password)
{
return false;
}
else
{
pub::identityInfo = identityInfo;
return true;
}
}
void loginBll::encodeTerminalInfo()
{
TTerminalInfo _terminalInfo;
m_dal.getTernimalInfo(_terminalInfo);
if(_terminalInfo.ifEnCode == 0)
{
_terminalInfo.merchantCode = QEncryption::encodeAes128(_terminalInfo.merchantCode,"123");
_terminalInfo.terminalCode = QEncryption::encodeAes128(_terminalInfo.terminalCode,"123");
m_dal.updateTerminalInfo(_terminalInfo);
}
}
void loginBll::uploadOperatorInfo()
{
m_dal.getShopInfo(pub::shopInfo);
m_dal.uploadOperatorInfo(pub::identityInfo.userName, QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm"),
pub::identityInfo.userName, pub::shopInfo.areaid);
}
BLL层有些业务可能只是去获取数据库的一些信息,但也是通过DAL层去访问数据库。登录模块稍微简单些,并没有需要存储在内存中的复杂数据即没有Data层。
下面简单贴一下包含Data层的代码:
class checkoutData
{
public:
checkoutData();
~checkoutData();
double payMoney() const;
void setPayMoney(double payMoney);
QList<TTradeProductInfo> &proInfoList();
QString getSerialNumber() const;
void setSerialNumber(const QString &value);
QString getOrderID() const;
void setOrderID(const QString &orderID);
int getTradeMode() const;
private:
checkoutData(const checkoutData&);
checkoutData& operator=(const checkoutData&);
double m_payMoney;
QList<TTradeProductInfo> m_proInfoList;
QString m_serialNumber;
QString m_orderID;
int m_tradeMode;
};
Data层的主要接口就是set/get。
公共模块包含的内容比较杂,主要包括全局变量类、常用函数工具类、数据库连接管理类等。工具类、数据库管理类主要使用单例模式;全局变量类主要使用静态成员变量,其头文件中包括一些结构体、枚举、宏定义、内联函数、全局函数等的声明。
下面简单贴两个常用工具类函数:
QString commonTools::readIni(QString group, QString key) // 读取配置文件内容
{
QString ret = "default";
QSettings setting("config.ini", QSettings::IniFormat);
ret = setting.value(group+'/'+key).toString();
return ret;
}
QDateTime commonTools::getNetworkTime() // 获取网络时间
{
QUdpSocket udpSocket;
udpSocket.connectToHost("time.windows.com", 123);
if(udpSocket.waitForConnected(3000))
{
qint8 LI = 0;
qint8 VN = 3;
qint8 MODE = 3;
qint8 STRATUM = 0;
qint8 POLL = 4;
qint8 PREC = - 6;
QDateTime epoch(QDate(1900,1,1));
qint32 second = quint32(epoch.secsTo(QDateTime::currentDateTime()));
qint32 temp = 0;
QByteArray timeRequest(48, 0);
timeRequest[0] = (LI << 6) | (VN << 3) | (MODE);
timeRequest[1] = STRATUM;
timeRequest[2] = POLL;
timeRequest[3] = PREC & 0xff;
timeRequest[5] = 1;
timeRequest[9] = 1;
timeRequest[40] = (temp = (second & 0xff000000) >> 24);
temp = 0;
timeRequest[41] = (temp = (second & 0x00ff0000) >> 16);
temp = 0;
timeRequest[42] = (temp = (second & 0x0000ff00) >> 8);
temp = 0;
timeRequest[43] = ((second & 0x000000ff));
udpSocket.flush();
udpSocket.write(timeRequest);
udpSocket.flush();
if(udpSocket.waitForReadyRead(3000))
{
QByteArray newTime;
QDateTime epoch(QDate(1900, 1, 1));
QDateTime unixStart(QDate(1970, 1, 1));
do
{
newTime.resize(udpSocket.pendingDatagramSize());
udpSocket.read(newTime.data(), newTime.size());
}while(udpSocket.hasPendingDatagrams());
QByteArray TransmitTimeStamp ;
TransmitTimeStamp = newTime.right(8);
quint32 seconds = TransmitTimeStamp[0];
quint8 temp = 0;
for(int j=1;j<=3;j++)
{
seconds = seconds << 8;
temp = TransmitTimeStamp[j];
seconds = seconds + temp;
}
quint32 t = seconds - epoch.secsTo(unixStart);
return QDateTime::fromTime_t(t);
}
}
return QDateTime::currentDateTime();
}