開發環境:Qt5.6.3 + Windows系統 + SQLite
說到設計界面的應用程序開發框架,離不開經典的三層架構(界面層(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 <QDesktopWidget>
#include <QDebug>
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();
}