背景介紹
最近在重構一個項目,主要工作是去對接后台C++的webservice接口,獲取相關數據,在瀏覽器上顯示數據統計信息,實時更新狀態,完成一些基礎配置,這個過程不需要web后台去連接數據庫,只需要進行數據傳遞。將web端分為前后端,web后台采用node的koa框架來做。前端使用比較快的方式-vue來搭建,數據的獲取走后端API的形式。
開始吧
一些准備工作:
npm install --global vue-cli
vue init webpack mitweb
項目依賴:
{ "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入口文件
如何連接 C++后台的webservice呢?
{ "webservice":{ "ip":"http://192.168.101.87", "port":"7722", "wsdl":"webserviceName?wsdl" } }
soap模塊
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;
}
}
寫接口
//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 }
處理URL及監聽端口
// 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");
如果需要C++后台主動將數據推到web后台該如何實現呢?
websocket
//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 結束
web后台啟一個webservice
//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");
前端頁面搭建
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/>' })
目錄結構
路由
// 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, }] } ] } ] })
一個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 }); }) }, } }
一個有參數的get請求
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 }); }) }, } }
跨域問題
proxyTable:{ '/action':{ target:"http://localhost:9000/", changeOrigin:true, } },
websocket連接
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(); } } }
項目部署
webpack 取消輸出map文件
使用koa-static靜態文件
const path =require('path') , serve = require('koa-static'); // 靜態文件serve在koa-router的其他規則之上 app.use(serve(path.resolve('dist')));
linux(ubuntu)搭建nodejs環境
tar zxvf node-v10.16.0.tar.gz
cd node-v10.16.0
./configure
make
sudo make install
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退出啊!!!
