做者背景描述:html
本人就任於外資IT企業,擔任電商訂單處理產品開發經理一職,領導過很是屢次大小項目的開發工做,對電商平臺訂單處理流程很是熟悉。前端
公司專一鞋服行業相關軟件開發和服務,公司規模100多人以上,在臺北,廣州,成都,上海,北京,國外等均有分公司。vue
爲何寫此係列文章?c++
本人在學校至工做到如今十餘年時間,使用.net C# 開發語言,結合在公司實際開發,和市場的需求中,NET.開發的商業企業級系統遇到的缺點有以下:web
1.程序首次加載慢,由於虛擬機編譯的緣由。sql
2.WINFORM界面開發不夠炫麗,精美。數據庫
3.WINFORM界面設計人員難找。element-ui
4.程序能夠被反編譯。json
5.安裝包過大,部署麻煩,framework.api
6.跨平臺不夠好。
結論:
結合近年來前端設計的走向,最終選擇了qt+vue+element UI+sqlite(數據庫根據須要狀況選擇)
qt負責接口和硬件處理
sqlite作數據存儲
vue+element UI 實現前端。
結果預覽
使用微軟mvc 的同窗都知道編寫一個web api 是很是方便的,本文經過使用qt 實現 Controller 和action 開發web api。
MVC 結構圖以下:
下載QtWebApp(本文最後有下載方式),集成到項目中,這個庫主要是實現http 協議,和 QT Web Api庫支持。
對於單機版本,不須要用戶安裝iis 或 tomcat,簡化了部署的難度。
QtWebApp目錄結構以下:
把QtWebApp解壓放到BitPos(上一章節建立的項目)目錄下。
效果以下:
打開上一章節建立的解決方案:BitPos 。
在解決方案資源管理器,右鍵》添加》現有項目 ,以下:
把BitPos.vcxproj 添加如今項目 。
以下圖:
選擇QtWebApp ,右鍵》屬性
在常規選項中修改sdk 版本,和平臺工具集,配置類型,輸出爲靜態庫,以下:
在qt project settings 選項中,修改 qt installation 爲咱們上一節配置的qt版本,以下:
編譯源碼,這裏可以一次編譯成功,以下圖:
點擊BitPos項目,右鍵》添加》引用
爲BitPos添加 包含目錄,由於後面會用QtWebApp的頭文件。
選擇BitPos 》屬性》vc++目錄》包含目錄》輸入QtWebApp 以下圖:
修改BitPos運行庫爲靜態連接。
按上面的步驟,修改這2個項目的配置爲release 模式,重複一次操做。不然release編譯會報錯。
接下來演示如何添加一個用戶登入時密碼驗證的接口。
這個接口有2個參數,分別是user_code ,password
返回爲json ,返回了用戶代碼和用戶名稱 ,分別是user_code,user_name
請求描述以下圖:
接下來進行實操:添加API 控制器 和登入接口Login的方法
新增 src 目錄,而後在目錄下建立 controller
以下圖:
選中controller 右鍵》添加》Add qt class
輸入類名:ApiController
以下圖:
點add 以下圖:
點擊next:以下圖
把Base class 修改成HttpRequestHandler
最後貼出這2個類的源碼分別是:
ApiController.h的代碼以下:
1 #pragma once 2 //解決中文亂碼問題。 3 #pragma execution_character_set("utf-8") 4 #include <httpserver/httprequesthandler.h> 5 6 using namespace stefanfrings; 7 8 //控制器 http://localhost:5050/vue-element-admin/api 9 class ApiController : 10 public HttpRequestHandler 11 { 12 13 Q_OBJECT 14 public: 15 Q_INVOKABLE ApiController(const ApiController & v) 16 { 17 *this = v; 18 } 19 Q_INVOKABLE ApiController &operator=(const ApiController &v) 20 { 21 return *this; 22 } 23 24 Q_INVOKABLE ApiController() 25 { 26 27 } 28 29 //action 登入接口。http://localhost:5050/vue-element-admin/api/login 30 Q_INVOKABLE void Login(HttpRequest & request, HttpResponse & response); 31 32 Q_INVOKABLE void ApiResult(const QString& msg, int code,HttpRequest & request, HttpResponse & response); 33 /** Generates the response */ 34 Q_INVOKABLE void service(HttpRequest& request, HttpResponse& response); 35 36 void Result(QString msg, int code, HttpResponse & response); 37 38 ~ApiController(); 39 };
ApiController.cpp的代碼以下:
1 #include "ApiController.h" 2 #include <qjsondocument.h> 3 #include <qjsonobject.h> 4 #include <qcryptographichash.h> 5 #include <qlist.h> 6 7 8 9 10 11 12 13 Q_INVOKABLE void ApiController::Login(HttpRequest & request, HttpResponse & response) 14 { 15 //request. 16 17 //獲取post請求的表單 。 18 QMultiMap<QByteArray, QByteArray> forms = request.getParameterMap(); 19 //獲取用戶提交表單中的user_code 20 auto usercode = forms.value("user_code").trimmed(); 21 if (usercode.isEmpty()) 22 { 23 Result("用戶代碼不能爲空!", 1, response); 24 return; 25 } 26 //獲取用戶提交表單中的password 27 auto password = forms.value("password").trimmed(); 28 if (password.isEmpty()) 29 { 30 Result("密碼不能爲空!", 1, response); 31 return; 32 } 33 //二次判斷 34 QByteArray hash = QCryptographicHash::hash(password, QCryptographicHash::Algorithm::Sha256).toBase64(); 35 36 //驗證用戶代碼和密碼。 37 38 QJsonObject object 39 { 40 {"code", 0}, 41 {"msg", QJsonValue::Null} 42 }; 43 QJsonObject user 44 { 45 {"user_code", "admin"}, 46 {"user_name", "admin"} 47 }; 48 object.insert("user", user); 49 50 QJsonDocument usermodel(object); 51 52 //返回用戶信息。 53 QByteArray body = usermodel.toJson(QJsonDocument::JsonFormat::Indented); 54 55 response.write(body, true); 56 } 57 58 59 Q_INVOKABLE void ApiController::service(HttpRequest & request, HttpResponse & response) 60 { 61 62 } 63 64 void ApiController::Result(QString msg, int code, HttpResponse & response) 65 { 66 QJsonObject object 67 { 68 {"code", code}, 69 {"msg", QJsonValue(msg)} 70 }; 71 72 73 QJsonDocument usermodel(object); 74 75 QByteArray body = usermodel.toJson(QJsonDocument::JsonFormat::Indented); 76 77 response.write(body, true); 78 } 79 80 Q_INVOKABLE void ApiController::ApiResult(const QString& msg,int code,HttpRequest & request, HttpResponse & response) 81 { 82 Result(msg, code, response); 83 } 84 85 86 87 ApiController::~ApiController() 88 { 89 }
這2個文件的代碼屬於業務類的代碼,都比較簡單,實現了用戶登入,用戶名和密碼的驗證,上面已經有註釋,這裏就不進行重點講解。
添加通用控制器處理類 requestmapper.cpp ,該類與業務無關。
主要實現接收請求,並查找控制器和接口方法,並進行轉發請求,文件內容以下:
1 /** 2 @file 3 @author Stefan Frings 4 */ 5 6 #include <QCoreApplication> 7 #include "requestmapper.h" 8 #include "controller/ApiController.h" 9 #include <qmetaobject.h> 10 11 //靜態變量 12 QMultiMap<QString, QString> RequestMapper :: Area; 13 template <typename T> 14 int RegisterController(const char *typeName,const QString& area) 15 { 16 QByteArray tmp=typeName; 17 tmp = tmp.toLower(); 18 auto type= tmp.constData(); 19 20 int v=qRegisterMetaType<T>(type); 21 if (!area.isEmpty()) 22 { 23 RequestMapper::RegisterArea(area, type); 24 } 25 return v; 26 } 27 template <typename T> 28 int RegisterController(const QString& area) 29 { 30 return RegisterController<T>(T::staticMetaObject.className(), area); 31 } 32 RequestMapper::RequestMapper(QObject* parent) 33 :HttpRequestHandler(parent) 34 { 35 qDebug("RequestMapper: created"); 36 //註冊Api控制器,域爲vue-element-admin。訪問格式爲: 37 //http://localhost:5050/{域}/{控制器} 38 //例如 http://localhost:5050/vue-element-admin/api 39 RegisterController<ApiController>("apicontroller", "vue-element-admin"); 40 } 41 42 43 RequestMapper::~RequestMapper() 44 { 45 qDebug("RequestMapper: deleted"); 46 } 47 48 void RequestMapper::RegisterArea(const QString &area, const QString& classname) 49 { 50 Area.insertMulti(area, classname); 51 } 52 53 //查找控制器,並調用接口方法。 54 void RequestMapper::service(HttpRequest& request, HttpResponse& response) 55 { 56 QByteArray path=request.getPath().toLower(); 57 qDebug("RequestMapper: path=%s",path.data()); 58 fprintf(stderr, "request: %s\n", path.data()); 59 //實現跨域訪問,js 調用API 提供了支持。 60 response.setHeader("Connection", "keep-alive"); 61 auto origin = request.getHeader("Origin"); 62 response.setHeader("Access-Control-Allow-Origin", origin); 63 response.setHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS"); 64 response.setHeader("Access-Control-Allow-Headers", "X-PINGOTHER,Content-Type,x-token"); 65 response.setHeader("Access-Control-Max-Age", "86400"); 66 response.setHeader("Vary", "Accept-Encoding,Origin"); 67 response.setHeader("Keep-Alive", "timeout=2,max=99"); 68 //set api header 69 response.setHeader("Content-Type", "application/json; charset=utf-8"); 70 //response.setHeader("Access-Control-Allow-Origin", "*"); // also important , if not set , the html application wont run. 71 if (request.getMethod() == "OPTIONS") 72 { 73 response.setStatus(200,"OK"); 74 qDebug("RequestMapper: finished request"); 75 // Clear the log buffer 76 77 return; 78 } 79 else 80 { 81 82 } 83 84 // For the following pathes, each request gets its own new instance of the related controller. 85 QByteArrayList items = path.split('/'); 86 QByteArray areaname; 87 QByteArray controlname; 88 QByteArray actionname; 89 QByteArray a, b, c; 90 91 for (int i = 0; i < items.length(); i++) 92 { 93 QByteArray first = items[i]; 94 if (first.isEmpty()) 95 continue; 96 else 97 { 98 //get control and action of name. 99 a = first; 100 if(i+1<items.length()) 101 b = items[i + 1].toLower(); 102 103 if (i + 2 < items.length()) 104 c = items[i + 2].toLower(); 105 break; 106 } 107 } 108 QList<QString> controls; 109 //判斷是不是路由。 110 if (Area.contains(a)) 111 { 112 areaname = a; 113 controlname = b; 114 actionname = c; 115 controls=Area.values(a); 116 } 117 else 118 { 119 controlname = a; 120 actionname = b; 121 } 122 123 QString className = (controlname + "Controller").toLower(); 124 125 int id = QMetaType::type(className.toLatin1()); 126 HttpRequestHandler* result = NULL; 127 //判斷area 128 if (id != QMetaType::UnknownType) 129 { 130 if (controls.count() > 0 && !controls.contains(className)) 131 { 132 133 qDebug("RequestMapper: finished request"); 134 135 return; 136 } 137 } 138 139 if (id != QMetaType::UnknownType) 140 { 141 result = static_cast<HttpRequestHandler*>(QMetaType::create(id)); 142 const QMetaObject * theMetaObject = result->metaObject(); 143 int nMetathodCount = theMetaObject->methodCount(); 144 QByteArray method; 145 //查找方法 146 for (int nMetathodIndex = 0; nMetathodIndex < nMetathodCount; nMetathodIndex++) 147 { 148 QByteArray oneMethod = theMetaObject->method(nMetathodIndex).name(); 149 if (actionname.compare(oneMethod, Qt::CaseSensitivity::CaseInsensitive)==0) 150 { 151 method = oneMethod; 152 break; 153 } 154 } 155 if (!method.isEmpty()) 156 { 157 auto token=request.getHeader("X - Token"); 158 //判斷token是不是可用。 159 auto v = QMetaObject::invokeMethod(result, method.data(), Qt::DirectConnection, 160 Q_ARG(HttpRequest &, request), 161 Q_ARG(HttpResponse &, response)); 162 if (!v) 163 qDebug() << method.data()<<" method invokeMethod is error!"; 164 } 165 else 166 { 167 //不存在的方法。 168 auto v = QMetaObject::invokeMethod(result, "ApiResult", Qt::DirectConnection, 169 Q_ARG(const QString&, actionname+" action not found !"), 170 Q_ARG(int, 1), 171 Q_ARG(HttpRequest &, request), 172 Q_ARG(HttpResponse &, response)); 173 if (!v) 174 qDebug() << " service method invokeMethod is error!"; 175 } 176 delete result; 177 } 178 else 179 { 180 qDebug() << "UnknownType service method invokeMethod is error!"; 181 } 182 183 qDebug("RequestMapper: finished request"); 184 185 }
特別說明:當有新的控制器添加的時候,只須要在requestmapper.cpp
文件註冊控制器便可。
在BitPos 項目 main.cpp 源碼修改以下:
1 #include "BitPos.h" 2 #include <QtWidgets/QApplication> 3 #include <QWebEngineView> 4 #include <httpserver/httplistener.h> 5 #include <logging/filelogger.h> 6 #include <qdir.h> 7 #include "src/requestmapper.h" 8 using namespace stefanfrings; 9 10 /** Search the configuration file */ 11 QString searchConfigFile() 12 { 13 QString binDir = QCoreApplication::applicationDirPath(); 14 QString appName = QCoreApplication::applicationName(); 15 QString fileName(appName + ".ini"); 16 17 QStringList searchList; 18 searchList.append(binDir); 19 searchList.append(binDir + "/etc"); 20 searchList.append(binDir + "/../etc"); 21 searchList.append(binDir + "/../../etc"); // for development without shadow build 22 searchList.append(binDir + "/../" + appName + "/etc"); // for development with shadow build 23 searchList.append(binDir + "/../../" + appName + "/etc"); // for development with shadow build 24 searchList.append(binDir + "/../../../" + appName + "/etc"); // for development with shadow build 25 searchList.append(binDir + "/../../../../" + appName + "/etc"); // for development with shadow build 26 searchList.append(binDir + "/../../../../../" + appName + "/etc"); // for development with shadow build 27 searchList.append(QDir::rootPath() + "etc/opt"); 28 searchList.append(QDir::rootPath() + "etc"); 29 30 foreach(QString dir, searchList) 31 { 32 QFile file(dir + "/" + fileName); 33 if (file.exists()) 34 { 35 // found 36 fileName = QDir(file.fileName()).canonicalPath(); 37 qDebug("Using config file %s", qPrintable(fileName)); 38 return fileName; 39 } 40 } 41 42 // not found 43 foreach(QString dir, searchList) 44 { 45 qWarning("%s/%s not found", qPrintable(dir), qPrintable(fileName)); 46 } 47 qFatal("Cannot find config file %s", qPrintable(fileName)); 48 } 49 50 int main(int argc, char* argv[]) 51 { 52 QApplication a(argc, argv); 53 // Find the configuration file 54 QString configFileName = searchConfigFile(); 55 56 // Configure and start the TCP listener 57 QSettings* listenerSettings = new QSettings(configFileName, QSettings::IniFormat, &a); 58 listenerSettings->beginGroup("listener"); 59 new HttpListener(listenerSettings, new RequestMapper(&a), &a); 60 //瀏覽器 61 QWebEngineView view; 62 //設置訪問地址 63 view.setUrl(QUrl("http://localhost:5050/vue-element-admin/api/Login?user_code=333&password=3445")); 64 //顯示瀏覽器窗口。 65 view.show(); 66 return a.exec(); 67 }
文件目錄以下圖:
按f5運行,便可看到接口返回(也能夠用瀏覽器看接口返回),以下圖:
若是後面添加更多的模塊的接口,只須要按模板添加控制器,按操做在控制器內添加方法便可。
至此,qt 接口開發演示完畢,下一節講解如何使用postman 進行接口調試。
後面的文章主要與技術有關,索取源碼,技術溝通,編譯報錯,請加QQ羣561506606 加羣無需驗證。
點擊連接加入羣聊【企業級系統實戰-qt vue.j】:https://jq.qq.com/?_wv=1027&k=CCmkgYYu