vue2 + koa2 開發前后端項目記錄


背景介紹

最近在重構一個項目,主要工作是去對接后台C++的webservice接口,獲取相關數據,在瀏覽器上顯示數據統計信息,實時更新狀態,完成一些基礎配置,這個過程不需要web后台去連接數據庫,只需要進行數據傳遞。將web端分為前后端,web后台采用node的koa框架來做。前端使用比較快的方式-vue來搭建,數據的獲取走后端API的形式。

node初學者,項目構建得益於一位大牛的文章,此處奉上鏈接
 

開始吧

一些准備工作:

node(版本大於6.X) 安裝  ---   去node中文網
npm的安裝     使用及命令
vue 腳手架 
  1. npm install --global vue-cli
創建一個基於webpack模板的vue項目,項目名 mitweb
  1. vue init webpack mitweb

項目依賴:

進入項目目錄中
安裝vue和koa的依賴包,這個項目中主要包含這些依賴:簡要說明一下,使用koa框架,肯定需要koa,koa-bodyparser是解析request的body,POST請求時會用到。koa-router是為了處理URL映射的。ws啟一個websocket服務,當然,也可以做客戶端。soap是用來請求webservice服務接口的,可以做客戶端,也可以做服務端。vue和vue-router,前端vue項目的依賴,axios用來發送http請求,樣式編寫使用less。需要進行圖表顯示,所以用到echarts,為了快速構建頁面使用了element -ui
{
"koa":"2.0.0",
"koa-bodyparser":"3.2.0",
"koa-router":"7.0.0",
   "ws":"4.1.0",
"soap":"^0.27.1",
"vue":"^2.5.2",
"vue-router":"^3.0.1""axios":"^0.19.0",
"less":"^3.0.1",
"less-loader":"^4.1.0",
"echarts":"^4.2.1",
"element-ui":"^2.4.4",
}

 

項目結構:

在根目錄中增加app.js文件,作為koa的入口文件,增加server文件夾,用於放Koa的API文件
├── build // vue-cli 生成,用於webpack監聽、構建
│   ├── build.js
│   ├── check-versions.js
│   ├── utils.js
│   ├── vue-loader.conf.js
│   ├── webpack.base.conf.js
│   ├── webpack.dev.conf.js
│   └── webpack.prod.conf.js
├── config // vue-cli 生成
│   ├── dev.env.js
│   ├── index.js
│   └── prod.env.js
├── dist // Vue build 后的文件夾
│   ├── index.html // 入口文件
│   └── static // 靜態資源
├──   server  // Koa后端,用於提供Api
  ├── controllers // controller-控制器
  ├── app.json// 配置信息
   └──    control.js// 封裝訪問websevice的方法
├── src // vue-cli 生成&自己添加的utils工具類
│   ├── assets // 相關靜態資源存放
│   ├── components // 單文件組件
│   ├── router //路由
│   ├── App.vue // 主文件
│    └──    main.js // 引入Vue等資源、掛載Vue的入口js
├── static //靜態文件
│   ├── css 
│   ├── img
│   ├──.gitkeep
│    └──    webInterface.wsdl //web端webservice的wsdl文件

├── app.js  // Koa入口文件

├── index.html // vue-cli生成,用於容納Vue組件的主html文件。單頁應用就只有一個html
├── package.json // npm的依賴、項目信息文件
└──  README.md

如何連接 C++后台的webservice呢?

首先需要知道服務的IP,端口號及服務名稱,這些信息由C++后台提供,為了方便日后更改,將這些信息單獨寫在一個json文件中如:app.json 
{
"webservice":{
"ip":"http://192.168.101.87""port":"7722",
"wsdl":"webserviceName?wsdl"
}
}

soap模塊

連接C++后台的webservice
url是通過讀取app.json文件中的配置項組合而成的,這里的接口名是doRequest,所有請求都通過這個接口來獲取,只需要傳入不同的requestType即可,這樣做也方便接口的封裝。傳參使用json格式,這個是和C++后台人員協定的。
const soap=require("soap");
const fs=require("fs");
let url="";
fs.readFile(__dirname+'/app.json','utf-8',function(err,data){
if(err){
console.log(err);
}else{
let config=JSON.parse(data).webservice;
url=config.ip+":"+config.port+"/"+config.wsdl;
}
})
module.exports={
getwebservicedata:function(args){
console.log("start get webservice......");
console.log(url);
if(!url){
return{"errCode":400,"ret":"連接錯誤"}
}
returnnewPromise((resolve,reject)=>{
soap.createClient(url,function(err,client){
// console.log(client.describe());
if(!client ||typeof client.doRequest!="function"){
reject("500 服務連接錯誤");
}
let params=JSON.stringify(args).toString();
try{
console.log(params);
client.doRequest({"parm":params},function(e,r){
if(e){
reject(e);
}else{
console.log("getdata");
let data=JSON.parse(r.result);
data.errCode=200;
// console.log(data);
resolve(data);
}
})
}catch(e){
console.log(e);
}
})
});
},
addArgs:function(args,obj){
//這里加了一個組合參數的方法,免得每次都寫一遍,直接調用就行
if(!obj){
return args;
}
for(let o in obj){
args[o]=obj[o];
}
return args;
}
}
View Code

寫接口

controllers 文件夾下寫接口,並將接口暴露出來
這里只展示出了巡視模塊的接口
//patrol.js
const control=require('../control');
const getdata=control.getwebservicedata;
const addArgs=control.addArgs;
let getAllVIRouteInfo = async (ctx,next)=>{
let args={requestType:"GetAllVIRouteInfo"};
let result=await getdata(args);
ctx.response.status=result.errCode;
ctx.response.body=result;
};
let contrlRoute=async (ctx,next)=>{
let args={requestType:"ContrlRoute"};
args=addArgs(args,ctx.request.query);//加訪問webservice的參數
let result=await getdata(args);
ctx.response.status=result.errCode;
ctx.response.body=result;
}
module.exports={
'GET /action/GetAllVIRouteInfo':getAllVIRouteInfo,
'GET /action/ContrlRoute':contrlRoute
}
View Code

 

處理URL及監聽端口

處理URL的這部分我直接寫在入口文件中了,也可以對它進行封裝一下再引入。功能就是讀取server/controllers目錄下的所有模塊(即js文件),然后注冊這些文件中暴露出來的 每個URL。別忘了app.use(router.routes())
這部分的相關知識可以參照廖大大的文章,這部分講解可以說是很詳細了。
// koa 入口文件
const fs=require('fs');
constKoa=require("koa");
const router=require("koa-router")();
const app=newKoa();
//處理 url 開始
// console.log(__dirname);
var files=fs.readdirSync(__dirname+'/server/controllers');//讀controllers目錄下所有文件
var js_files=files.filter(f=>{
return f.endsWith(".js");
});//找所有js文件
//處理每個js文件
for(var f of js_files){
// console.log(`from controller: ${f}`);
//導入js文件
let mapping=require(__dirname+'/server/controllers/'+f);
for(var url in mapping){
// console.log(url);
if(url.startsWith('GET ')){
let path=url.substring(4);
router.get(path, mapping[url]);
}elseif(url.startsWith('POST ')){
// 如果url類似"POST xxx":
let path = url.substring(5);
router.post(path, mapping[url]);
console.log(`register URL mapping: POST ${path}`);
}else{
// 無效的URL:
console.log(`invalid URL: ${url}`);
}
}
}
//處理 url 結束
app.listen(9000);
app.use(router.routes());
console.log("koa is listening 9000");
View Code

 

 

如果c++后台啟動服務,終端執行node app.js成功,通過瀏覽器訪問http://localhost:9000/action/GetAllVIRouteInfo/    就能取到相應的數據了。

如果需要C++后台主動將數據推到web后台該如何實現呢?

這里我采用了將web后台作為webservice服務端,c++后台作為客戶端的方式,有數據就訪問web后台提供的 webservice接口。將數據傳到web后台,web后台通過websocket實時推送到前端。

websocket

web后台啟動websocket服務,等待web前端連接,只要連接成功且 c++后台訪問了 webservice接口,就進行數據推送。

 

//websocket
// 導入WebSocket模塊:
constWebSocket= require('ws');
// 引用Server類:
constWebSocketServer=WebSocket.Server;
// 實例化:
const wss =newWebSocketServer({
port:3000
});
varWebSocketEx=null;//暴露ws,供webservice中收到請求使用。
wss.on('connection',function(ws){
console.log(`...前端連接websocket成功...`);
// ws.on('message', function (message) {
// console.log(` Received: ${message}`);
// });
WebSocketEx=ws;
});
//websocket 結束
View Code

 

web后台啟一個webservice

依然使用soap模塊來實現,這種方式有種弊端,因為wsdl文件無法自動生成(期間也嘗試了soap-server模塊,生成的wsdl 無法解析,最終還是選用了soap),手寫wsdl簡直是噩夢,這里拜托C++的同事幫忙生成了一個,然后對其中的接口進行了部分修改,接口名doRequest,要求傳入json字符串格式的數據。當C++后台訪問時,將傳過來的數據通過websocket.send() 推到前端。聯調的時候有一些問題,都是命名空間造成的...,主要還是C++后台對 命名空間 做了修改,然后終於調通了,插播兩個用到的測試抓包工具:Wireshark 和 SoapUI 
//web端作為webservice服務器端
const soap=require("soap");
const http = require('http');
const web={};
web.wsdl = fs.readFileSync('static/webInterface.wsdl','utf8');
web.server=null;
web.service={
doRequest:{
doRequest:{
patrol:function(params,cb,soapHeader){
// console.log("...后台來數據了,馬上推送...");
let args={};
if(params.data){
if(params.data.$value){
args=JSON.parse(params.data.$value);
}else{
args=JSON.parse(params.data);
}
}else{
args=params;
}
if(!args.requestType || args.requestType!=="updateRouteState"){
return{result:'400 No such interface'};
}
console.log(args);
// console.log("............WebSocketEx............",WebSocketEx);
if(WebSocketEx!=null){//調用websocket服務端向前端推數據
WebSocketEx.send(`${JSON.stringify(args)}`,(err)=>{
if(err){
// console.log(`[SERVER] error: ${err}`);
console.log(` error: ${err}`);
}
});
}
return{result:'200 ok'};
}
}
}
}
web.server=http.createServer(function(request,response){
response.end('404: Not Found:'+request.url);
});
web.server.listen(8285);
soap.listen(web.server,'/doRequest',web.service,web.wsdl);
console.log("webservice sarted at port 8285");
View Code

前端頁面搭建

根據接口和協議文件寫完了后台的功能,終於能喘口氣了.....
前端的頁面比較簡單,結構也簡單,常見的左右布局,左側導航右側自適應。為了方便構建和風格統一,直接選用了element-UI,css預處理我習慣用less ,數據請求axios。
在vue項目的入口文件中引入這些,axios寫在vue的原型上,方便使用。
importVue from 'vue'
importApp from './App'
import router from './router'
import axios from 'axios'
import elementUI from 'element-ui'
import'element-ui/lib/theme-chalk/index.css'
Vue.config.productionTip =false
Vue.use(elementUI)
// axios.defaults.withCredentials=true;
Vue.prototype.$axios=axios;
/* eslint-disable no-new */
newVue({
el:'#app',
router,
components:{App},
template:'<App/>'
})
View Code

 

目錄結構

貼張圖吧

路由

 

// router/index.js
importVue from 'vue'
importRouter from 'vue-router'
import index from '@/components/index'
import banner from '@/components/banner'
import patrol from '@/components/patrol/patrol'
import baseconfig from '@/components/system/baseconfig'
import sysconfig from '@/components/system/sysconfig'
import sysmain from '@/components/system/sysmain'
import camera from '@/components/condition/camera'
import distuse from '@/components/condition/distuse'
import patrolsuccess from '@/components/condition/patrolsuccess'
import about from '@/components/about/about'
Vue.use(Router)
exportdefaultnewRouter({
routes:[
{
path:'/',
name:'index',
component: banner,
children:[
{
path:'/',
name:'index',
component:index,
children:[
{
path:"/patrol",
alias:"",
component:patrol,
},
{
path:"/baseconfig",
component:baseconfig,
},
{
path:"/sysconfig",
component:sysconfig,
},
{
path:"/sysmain",
component:sysmain,
},
{
path:"/camera",
component:camera,
},
{
path:"/distuse",
component:distuse,
},
{
path:"/patrolsuccess",
component:patrolsuccess,
},
{
path:"/about",
component:about,
}]
}
]
}
]
})
View Code

 

數據請求主要寫一下websocket連接和舉一個請求的例子吧

一個get請求

exportdefault{
methods:{
getAllVIRouteInfo(){
let _this=this;
this.loading=true;
_this.$axios.get("action/GetAllVIRouteInfo/").then(res=>{
_this.loading=false;
let data=res.data;
// let data={
// routeInfo:[
// {routeCode:"200410000191",routeName:"#2主變高壓側",routeState:1,routeTime:"2018/9/5",routeType:"例行巡視",successRate:0},
// {routeCode:"200410000190000002",routeName:"#3主變高壓側",routeState:0,routeTime:"2018/9/6",routeType:"例行巡視",successRate:0},
// ]
// }
data.routeInfo.forEach(item=>{
if(item.routeState==0){
item.currentSuccessRate="未運行";
}else{
item.currentSuccessRate=Number(item.successRate);
}
})
this.tableData=data.routeInfo;
}).catch(err=>{
_this.loading=false;
_this.$message({
type:'error',
message: err,
showClose:true
});
})
},
}
}
View Code

 

一個有參數的get請求

axios的get方式,如果傳參數必須用{params:{}}
exportdefault{
methods:{
handleRoute(index,row,handle){
let _this=this;
// console.log(row);
let code=row.routeCode;
let par={
routeCode:code,
operationFlag:handle
}
this.$axios.get("action/ContrlRoute",{
params:{
routeCode:code,
operationFlag:handle
}
}).then(res=>{
let data=res.data;
if(data.ret==200){
_this.getAllVIRouteInfo();
_this.$message({
type:'success',
message:"操作成功!",
showClose:true
});
}
}).catch(err=>{
_this.$message({
type:'error',
message: err,
showClose:true
});
})
},
}
}
View Code

 

跨域問題

涉及到前后端請求就一定會有跨域問題,因為在將來部署的時候,是要把vue項目打包到dist目錄中,放入項目的,請求的地址也是寫的相對地址。所以最簡單的辦法就是將請求變為同域,也就是不管web服務端端口號怎么變,只要是同域都可以請求到。
在根目錄下的config/index.js,找到dev下的proxyTable
proxyTable:{
'/action':{
target:"http://localhost:9000/",
changeOrigin:true,
}
},
 

websocket連接

建立連接:
websocket=new WebSocket("ws://127.0.0.1:3000");
連接成功:
websocket.onopen = function () {
    if(_this.websocket.readyState===1){
    console.log("websock連接成功");
    }
};
有數據推過來了:
websocket.onmessage = function (message) {
//數據處理
}
連接斷開了:
websocket.onclose=function(event){
  // 處理
}
在項目中做了一個簡單的斷線重連
importVue from 'vue';
exportdefault{
name:'patrol',
data (){
return{
websocket:null,//websocket
address:"",//websocket地址端口號
tableData:[],//表格數據
tableHeight:(document.documentElement.clientHeight-100)<150?150:(document.documentElement.clientHeight-100),//表格高度
mytableStyle:{
"background":"#f1f1f1",
"color":"#333333"
},
loading:false,//表格是否顯示加載...
wsNum:0,//記錄重連次數
}
},
created(){
this.getAllVIRouteInfo();
this.address=window.location.hostname+":3000/";
this.initWebSocket();
},
methods:{
initWebSocket(){
var _this=this;
if('WebSocket' in window){
this.websocket=newWebSocket("ws://"+this.address);
}elseif('MozWebSocket' in window){
this.websocket=newWebSocket("ws://"+this.address);
}else{
console.log("當前瀏覽器不支持websocket");
}
this.websocket.onopen =function(){
console.log("websock連接 狀態 ",_this.websocket.readyState);
let reconnectTimer=null;
if(_this.websocket.readyState===0){
if(reconnectTimer){
clearTimeout(reconnectTimer);
}
reconnectTimer=setTimeout(function(){
_this.initWebSocket();
reconnectTimer=null;
},500);
}
if(_this.websocket.readyState===1){
console.log("websock連接成功");
}
};
this.websocket.onmessage =function(message){
let data =JSON.parse(message.data);
_this.loading=false;
if(data.VIRouteInfo.length!=0){
data.VIRouteInfo.forEach(item=>{
if(_this.tableData.length!=0){
_this.tableData.forEach((op,index)=>{
if(item.routeCode==op.routeCode){
if(item.routeSattion==1){
op.routeState=item.routeSattion;
op.successRate=item.successRate
op.currentSuccessRate=Number(item.successRate);
}else{
op.routeState=item.routeSattion;
op.successRate=item.successRate
op.currentSuccessRate="未運行";
}
Vue.set(_this.tableData,index,op);
}
})
}else{
_this.getAllVIRouteInfo();
if(item.routeSattion==1){
item.currentSuccessRate=Number(item.successRate);
}else{
item.currentSuccessRate="未運行";
}
_this.tableData.push(item);
}
})
}
}
this.websocket.onclose=function(event){
//斷開重連
_this.reconnect();
}
},
//websocket重連
reconnect(){
let _this=this;
// console.log(`重連 ${_this.wsNum} 次`);
// if(this.wsNum>30){
// return false;
// }
this.wsNum++;
this.initWebSocket();
}
}
}
View Code

 

項目部署

要求部署到linux系統下。先不管什么系統吧, 通俗地講,先要把之前寫的web前后端兩個項目合到一起: vue項目打包,用Koa的靜態資源服務中間件托管構建好的Vue文件。具體辦法在文章開始的鏈接中講解的比較好,這里再寫一下。

webpack 取消輸出map文件

webpack打包,發現map文件比較大。修改一下webpack的輸出設置,取消輸出map文件。
根目錄下的config/index.js:productionSourceMap: false
然后再執行 npm run bulid 感覺好多了。

使用koa-static靜態文件

const path =require('path')
, serve = require('koa-static');
// 靜態文件serve在koa-router的其他規則之上
app.use(serve(path.resolve('dist')));
 

linux(ubuntu)搭建nodejs環境

要部署到linux上的話,需要在linux上先安裝一下node,這里用 默認路徑安裝:/usr/local/bin
1 去node官網上下載源碼 source code那個
2 放到Ubuntu上。在這個壓縮包所在目錄打開終端,輸入解壓命令
tar zxvf node-v10.16.0.tar.gz
3 解壓完了進入目錄
cd node-v10.16.0

 

4 輸入
./configure

 

5 繼續輸入 make命令編譯
make

 

6 安裝
sudo make install

 

7 檢查安裝是否成功
node -v
npm -v

8 補上一部分部署:
這個是直接把項目放在服務器(Linux)上的部署方式。
1 把項目上傳到服務器,可以用工具。我選擇了使用 WinSCP 工具,很方便。
2 登錄到服務器,用的Xshell工具,Xshell沒有界面,純命令的方式。(也可以使用VNC)進入上傳的項目目錄中
3 執行 node app.js & 啟動項目並置於后台運行,只要服務器不關機,項目就會一直運行。如果啟服務的時候發現端口被占用了。可以查看一下被哪個進程占用了,比如查看9000端口。

sudo netstat -tnlp | grep :9000 


4 然后可以在瀏覽器輸入服務器的公網ip和服務的端口號,就能訪問了。
5 結束node進程
可以先查看一下進程 :

ps -ef|grep node

想關掉它使用 kill 14951 就行了

emm....這樣一直打開一個終端好像哪里不太對勁,當我把Xshell關了,就沒法訪問了呀,所以想找一個退出Xshell也能保證服務不斷的方式
在網上找了很多方法,基本圍繞 nohup 命令,但是嘗試了多次 ,只要我關掉終端,服務就斷了。反正都沒效果
研究了一下午,后來發現是我的打開方式不對,應該是這樣滴:分兩步
1 nohup node app.js >myout.file 2>&1 &
2 exit
這兩個命令是說,使用nohup命令提交作業,(在缺省情況下該作業的所有輸出都被重定向到一個名為out.file的文件中),這里指明了輸出到 myout.file 文件中,然后 2>&1 是將標准出錯重定向到標准輸出,這里的標准輸出已經重定向到了myout.file文件,即將標准出錯也輸出到myout.file文件中。最后一個&, 是讓該命令在后台執行。
另外,我多次嘗試失敗的原因是在使用nohup 后台運行命令之后,直接關掉了終端窗口,命令就這樣被kill了,需要使用 exit 正常退出當前賬戶,這樣才能保證命令一直在后台運行 漲姿勢了~~~~ 敲完后台運行命令一定要exit退出啊!!!

 
 
 
 
代碼在github上, https://github.com/xhh007/MitWeb  運行暫時沒有C++后台,后期測試完后會增加一個靜態的webservice
 
 






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM