vue2 + koa2 开发前后端项目记录
背景介绍
最近在重构一个项目,主要工作是去对接后台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退出啊!!!