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
 
 





转载于:https://www.cnblogs.com/huijihuijidahuiji/p/89f13e98413184d0d3a60b5bfeed5c2c.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值