首页 > 其他 > 详细

vue2 + koa2 开发前后端项目记录

时间:2019-06-19 19:28:28      阅读:154      评论:0      收藏:0      [点我收藏+]

背景介绍

最近在重构一个项目,主要工作是去对接后台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
  1. {
  2. "koa": "2.0.0",
  3. "koa-bodyparser": "3.2.0",
  4. "koa-router": "7.0.0",
  5.    "ws":"4.1.0",
  6. "soap": "^0.27.1",
  7. "vue": "^2.5.2",
  8. "vue-router": "^3.0.1"
  9. "axios": "^0.19.0",
  10. "less": "^3.0.1",
  11. "less-loader": "^4.1.0",
  12. "echarts": "^4.2.1",
  13. "element-ui": "^2.4.4",
  14. }

项目结构:

在根目录中增加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 
  1. {
  2. "webservice":{
  3. "ip":"http://192.168.101.87"
  4. "port":"7722",
  5. "wsdl":"webserviceName?wsdl"
  6. }
  7. }

soap模块

连接C++后台的webservice
url是通过读取app.json文件中的配置项组合而成的,这里的接口名是doRequest,所有请求都通过这个接口来获取,只需要传入不同的requestType即可,这样做也方便接口的封装。传参使用json格式,这个是和C++后台人员协定的。
  1. const soap=require("soap");
  2. const fs=require("fs");
  3. let url="";
  4. fs.readFile(__dirname+‘/app.json‘,‘utf-8‘,function(err,data){
  5. if(err){
  6. console.log(err);
  7. }else{
  8. let config=JSON.parse(data).webservice;
  9. url=config.ip+":"+config.port+"/"+config.wsdl;
  10. }
  11. })
  12. module.exports={
  13. getwebservicedata:function (args){
  14. console.log("start get webservice......");
  15. console.log(url);
  16. if(!url){
  17. return {"errCode":400,"ret":"连接错误"}
  18. }
  19. return new Promise((resolve,reject)=>{
  20. soap.createClient(url,function(err,client){
  21. // console.log(client.describe());
  22. if(!client || typeof client.doRequest!="function"){
  23. reject("500 服务连接错误");
  24. }
  25. let params=JSON.stringify(args).toString();
  26. try{
  27. console.log(params);
  28. client.doRequest({"parm":params},function(e,r){
  29. if(e){
  30. reject(e);
  31. }else{
  32. console.log("getdata");
  33. let data=JSON.parse(r.result);
  34. data.errCode=200;
  35. // console.log(data);
  36. resolve(data);
  37. }
  38. })
  39. }catch(e){
  40. console.log(e);
  41. }
  42. })
  43. });
  44. },
  45. addArgs:function(args,obj){
  46. //这里加了一个组合参数的方法,免得每次都写一遍,直接调用就行
  47. if(!obj){
  48. return args;
  49. }
  50. for(let o in obj){
  51. args[o]=obj[o];
  52. }
  53. return args;
  54. }
  55. }

写接口

controllers 文件夹下写接口,并将接口暴露出来
这里只展示出了巡视模块的接口
  1. //patrol.js
  2. const control=require(‘../control‘);
  3. const getdata=control.getwebservicedata;
  4. const addArgs=control.addArgs;
  5. let getAllVIRouteInfo = async (ctx,next)=>{
  6. let args={requestType:"GetAllVIRouteInfo"};
  7. let result=await getdata(args);
  8. ctx.response.status=result.errCode;
  9. ctx.response.body=result;
  10. };
  11. let contrlRoute=async (ctx,next)=>{
  12. let args={requestType:"ContrlRoute"};
  13. args=addArgs(args,ctx.request.query);//加访问webservice的参数
  14. let result=await getdata(args);
  15. ctx.response.status=result.errCode;
  16. ctx.response.body=result;
  17. }
  18. module.exports={
  19. ‘GET /action/GetAllVIRouteInfo‘:getAllVIRouteInfo,
  20. ‘GET /action/ContrlRoute‘:contrlRoute
  21. }

处理URL及监听端口

处理URL的这部分我直接写在入口文件中了,也可以对它进行封装一下再引入。功能就是读取server/controllers目录下的所有模块(即js文件),然后注册这些文件中暴露出来的每个URL。别忘了app.use(router.routes())
这部分的相关知识可以参照廖大大的文章,这部分讲解可以说是很详细了。
  1. // koa 入口文件
  2. const fs=require(‘fs‘);
  3. const Koa=require("koa");
  4. const router=require("koa-router")();
  5. const app=new Koa();
  6. //处理 url 开始
  7. // console.log(__dirname);
  8. var files=fs.readdirSync(__dirname+‘/server/controllers‘);//读controllers目录下所有文件
  9. var js_files=files.filter(f=>{
  10. return f.endsWith(".js");
  11. });//找所有js文件
  12. //处理每个js文件
  13. for(var f of js_files){
  14. // console.log(`from controller: ${f}`);
  15. //导入js文件
  16. let mapping=require(__dirname+‘/server/controllers/‘+f);
  17. for(var url in mapping){
  18. // console.log(url);
  19. if(url.startsWith(‘GET ‘)){
  20. let path=url.substring(4);
  21. router.get(path, mapping[url]);
  22. }else if (url.startsWith(‘POST ‘)) {
  23. // 如果url类似"POST xxx":
  24. let path = url.substring(5);
  25. router.post(path, mapping[url]);
  26. console.log(`register URL mapping: POST ${path}`);
  27. } else {
  28. // 无效的URL:
  29. console.log(`invalid URL: ${url}`);
  30. }
  31. }
  32. }
  33. //处理 url 结束
  34. app.listen(9000);
  35. app.use(router.routes());
  36. console.log("koa is listening 9000");
如果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接口,就进行数据推送。
  1. //websocket
  2. // 导入WebSocket模块:
  3. const WebSocket = require(‘ws‘);
  4. // 引用Server类:
  5. const WebSocketServer = WebSocket.Server;
  6. // 实例化:
  7. const wss = new WebSocketServer({
  8. port: 3000
  9. });
  10. var WebSocketEx=null;//暴露ws,供webservice中收到请求使用。
  11. wss.on(‘connection‘, function (ws) {
  12. console.log(`...前端连接websocket成功...`);
  13. // ws.on(‘message‘, function (message) {
  14. // console.log(` Received: ${message}`);
  15. // });
  16. WebSocketEx=ws;
  17. });
  18. //websocket 结束

web后台启一个webservice

依然使用soap模块来实现,这种方式有种弊端,因为wsdl文件无法自动生成(期间也尝试了soap-server模块,生成的wsdl 无法解析,最终还是选用了soap),手写wsdl简直是噩梦,这里拜托C++的同事帮忙生成了一个,然后对其中的接口进行了部分修改,接口名doRequest,要求传入json字符串格式的数据。当C++后台访问时,将传过来的数据通过websocket.send() 推到前端。联调的时候有一些问题,都是命名空间造成的...,主要还是C++后台对命名空间做了修改,然后终于调通了,插播两个用到的测试抓包工具:Wireshark 和 SoapUI 
  1. //web端作为webservice服务器端
  2. const soap=require("soap");
  3. const http = require(‘http‘);
  4. const web={};
  5. web.wsdl = fs.readFileSync(‘static/webInterface.wsdl‘, ‘utf8‘);
  6. web.server=null;
  7. web.service={
  8. doRequest:{
  9. doRequest:{
  10. patrol:function(params,cb,soapHeader){
  11. // console.log("...后台来数据了,马上推送...");
  12. let args={};
  13. if(params.data){
  14. if(params.data.$value){
  15. args=JSON.parse(params.data.$value);
  16. }else{
  17. args=JSON.parse(params.data);
  18. }
  19. }else{
  20. args=params;
  21. }
  22. if(!args.requestType || args.requestType!=="updateRouteState"){
  23. return {result:‘400 No such interface‘};
  24. }
  25. console.log(args);
  26. // console.log("............WebSocketEx............",WebSocketEx);
  27. if(WebSocketEx!=null){//调用websocket服务端向前端推数据
  28. WebSocketEx.send(`${JSON.stringify(args)}`, (err) => {
  29. if (err) {
  30. // console.log(`[SERVER] error: ${err}`);
  31. console.log(` error: ${err}`);
  32. }
  33. });
  34. }
  35. return {result:‘200 ok‘};
  36. }
  37. }
  38. }
  39. }
  40. web.server=http.createServer(function(request,response){
  41. response.end(‘404: Not Found:‘+request.url);
  42. });
  43. web.server.listen(8285);
  44. soap.listen(web.server,‘/doRequest‘,web.service,web.wsdl);
  45. console.log("webservice sarted at port 8285");

前端页面搭建

根据接口和协议文件写完了后台的功能,终于能喘口气了.....
前端的页面比较简单,结构也简单,常见的左右布局,左侧导航右侧自适应。为了方便构建和风格统一,直接选用了element-UI,css预处理我习惯用less ,数据请求axios。
在vue项目的入口文件中引入这些,axios写在vue的原型上,方便使用。
  1. import Vue from ‘vue‘
  2. import App from ‘./App‘
  3. import router from ‘./router‘
  4. import axios from ‘axios‘
  5. import elementUI from ‘element-ui‘
  6. import ‘element-ui/lib/theme-chalk/index.css‘
  7. Vue.config.productionTip = false
  8. Vue.use(elementUI)
  9. // axios.defaults.withCredentials=true;
  10. Vue.prototype.$axios=axios;
  11. /* eslint-disable no-new */
  12. new Vue({
  13. el: ‘#app‘,
  14. router,
  15. components: { App },
  16. template: ‘<App/>‘
  17. })
项目主要分了四块:巡视路线,系统配置,数据统计和关于。

目录结构

贴张图吧
技术分享图片

路由

  1. // router/index.js
  2. import Vue from ‘vue‘
  3. import Router from ‘vue-router‘
  4. import index from ‘@/components/index‘
  5. import banner from ‘@/components/banner‘
  6. import patrol from ‘@/components/patrol/patrol‘
  7. import baseconfig from ‘@/components/system/baseconfig‘
  8. import sysconfig from ‘@/components/system/sysconfig‘
  9. import sysmain from ‘@/components/system/sysmain‘
  10. import camera from ‘@/components/condition/camera‘
  11. import distuse from ‘@/components/condition/distuse‘
  12. import patrolsuccess from ‘@/components/condition/patrolsuccess‘
  13. import about from ‘@/components/about/about‘
  14. Vue.use(Router)
  15. export default new Router({
  16. routes: [
  17. {
  18. path: ‘/‘,
  19. name: ‘index‘,
  20. component: banner,
  21. children:[
  22. {
  23. path:‘/‘,
  24. name:‘index‘,
  25. component:index,
  26. children:[
  27. {
  28. path:"/patrol",
  29. alias:"",
  30. component:patrol,
  31. name:"巡视路线"
  32. },
  33. {
  34. path:"/baseconfig",
  35. component:baseconfig,
  36. name:"基本配置"
  37. },
  38. {
  39. path:"/sysconfig",
  40. component:sysconfig,
  41. name:"系统配置"
  42. },
  43. {
  44. path:"/sysmain",
  45. component:sysmain,
  46. name:"系统维护"
  47. },
  48. {
  49. path:"/camera",
  50. component:camera,
  51. name:"摄像机在线率"
  52. },
  53. {
  54. path:"/distuse",
  55. component:distuse,
  56. name:"磁盘使用率"
  57. },
  58. {
  59. path:"/patrolsuccess",
  60. component:patrolsuccess,
  61. name:"巡视成功率"
  62. },
  63. {
  64. path:"/about",
  65. component:about,
  66. name:"关于"
  67. }]
  68. }
  69. ]
  70. }
  71. ]
  72. })
数据请求主要写一下websocket连接和举一个请求的例子吧

一个get请求

  1. export default{
  2. methods:{
  3. getAllVIRouteInfo(){
  4. let _this=this;
  5. this.loading=true;
  6. _this.$axios.get("action/GetAllVIRouteInfo/").then(res=>{
  7. _this.loading=false;
  8. let data=res.data;
  9. // let data={
  10. // routeInfo:[
  11. // {routeCode:"200410000191",routeName:"#2主变高压侧",routeState:1,routeTime:"2018/9/5",routeType:"例行巡视",successRate:0},
  12. // {routeCode:"200410000190000002",routeName:"#3主变高压侧",routeState:0,routeTime:"2018/9/6",routeType:"例行巡视",successRate:0},
  13. // ]
  14. // }
  15. data.routeInfo.forEach(item=>{
  16. if(item.routeState==0){
  17. item.currentSuccessRate="未运行";
  18. }else{
  19. item.currentSuccessRate=Number(item.successRate);
  20. }
  21. })
  22. this.tableData=data.routeInfo;
  23. }).catch(err=>{
  24. _this.loading=false;
  25. _this.$message({
  26. type: ‘error‘,
  27. message: err,
  28. showClose: true
  29. });
  30. })
  31. },
  32. }
  33. }

一个有参数的get请求

axios的get方式,如果传参数必须用{params:{}}
  1. export default{
  2. methods:{
  3. handleRoute(index,row,handle){
  4. let _this=this;
  5. // console.log(row);
  6. let code=row.routeCode;
  7. let par={
  8. routeCode:code,
  9. operationFlag:handle
  10. }
  11. this.$axios.get("action/ContrlRoute",{
  12. params:{
  13. routeCode:code,
  14. operationFlag:handle
  15. }
  16. }).then(res=>{
  17. let data=res.data;
  18. if(data.ret==200){
  19. _this.getAllVIRouteInfo();
  20. _this.$message({
  21. type: ‘success‘,
  22. message: "操作成功!",
  23. showClose: true
  24. });
  25. }
  26. }).catch(err=>{
  27. _this.$message({
  28. type: ‘error‘,
  29. message: err,
  30. showClose: true
  31. });
  32. })
  33. },
  34. }
  35. }

跨域问题

涉及到前后端请求就一定会有跨域问题,因为在将来部署的时候,是要把vue项目打包到dist目录中,放入项目的,请求的地址也是写的相对地址。所以最简单的办法就是将请求变为同域,也就是不管web服务端端口号怎么变,只要是同域都可以请求到。
在根目录下的config/index.js,找到dev下的proxyTable
  1. proxyTable: {
  2. ‘/action‘:{
  3. target:"http://localhost:9000/",
  4. changeOrigin:true,
  5. }
  6. },

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){
  //处理
}
在项目中做了一个简单的断线重连
  1. import Vue from ‘vue‘;
  2. export default {
  3. name: ‘patrol‘,
  4. data () {
  5. return {
  6. websocket:null,//websocket
  7. address:"",//websocket地址端口号
  8. tableData:[],//表格数据
  9. tableHeight:(document.documentElement.clientHeight-100)<150?150:(document.documentElement.clientHeight-100),//表格高度
  10. mytableStyle:{
  11. "background":"#f1f1f1",
  12. "color":"#333333"
  13. },
  14. loading:false,//表格是否显示加载...
  15. wsNum:0,//记录重连次数
  16. }
  17. },
  18. created(){
  19. this.getAllVIRouteInfo();
  20. this.address=window.location.hostname+":3000/";
  21. this.initWebSocket();
  22. },
  23. methods:{
  24. initWebSocket(){
  25. var _this=this;
  26. if(‘WebSocket‘ in window){
  27. this.websocket=new WebSocket("ws://"+this.address);
  28. }else if(‘MozWebSocket‘ in window){
  29. this.websocket=new WebSocket("ws://"+this.address);
  30. }else{
  31. console.log("当前浏览器不支持websocket");
  32. }
  33. this.websocket.onopen = function () {
  34. console.log("websock连接 状态 ",_this.websocket.readyState);
  35. let reconnectTimer=null;
  36. if(_this.websocket.readyState===0){
  37. if(reconnectTimer){
  38. clearTimeout(reconnectTimer);
  39. }
  40. reconnectTimer=setTimeout(function(){
  41. _this.initWebSocket();
  42. reconnectTimer=null;
  43. },500);
  44. }
  45. if(_this.websocket.readyState===1){
  46. console.log("websock连接成功");
  47. }
  48. };
  49. this.websocket.onmessage = function (message) {
  50. let data =JSON.parse(message.data);
  51. _this.loading=false;
  52. if(data.VIRouteInfo.length!=0) {
  53. data.VIRouteInfo.forEach(item=>{
  54. if(_this.tableData.length!=0){
  55. _this.tableData.forEach((op,index)=>{
  56. if(item.routeCode==op.routeCode){
  57. if(item.routeSattion==1){
  58. op.routeState=item.routeSattion;
  59. op.successRate=item.successRate
  60. op.currentSuccessRate=Number(item.successRate);
  61. }else{
  62. op.routeState=item.routeSattion;
  63. op.successRate=item.successRate
  64. op.currentSuccessRate="未运行";
  65. }
  66. Vue.set(_this.tableData,index,op);
  67. }
  68. })
  69. }else{
  70. _this.getAllVIRouteInfo();
  71. if(item.routeSattion==1){
  72. item.currentSuccessRate=Number(item.successRate);
  73. }else{
  74. item.currentSuccessRate="未运行";
  75. }
  76. _this.tableData.push(item);
  77. }
  78. })
  79. }
  80. }
  81. this.websocket.onclose=function(event){
  82. //断开重连
  83. _this.reconnect();
  84. }
  85. },
  86. //websocket重连
  87. reconnect(){
  88. let _this=this;
  89. // console.log(`重连 ${_this.wsNum} 次`);
  90. // if(this.wsNum>30){
  91. // return false;
  92. // }
  93. this.wsNum++;
  94. this.initWebSocket();
  95. }
  96. }
  97. }

项目部署

要求部署到linux系统下。先不管什么系统吧,通俗地讲,先要把之前写的web前后端两个项目合到一起:vue项目打包,用Koa的静态资源服务中间件托管构建好的Vue文件。具体办法在文章开始的链接中讲解的比较好,这里再写一下。

webpack 取消输出map文件

webpack打包,发现map文件比较大。修改一下webpack的输出设置,取消输出map文件。
根目录下的config/index.js:productionSourceMap: false
然后再执行 npm run bulid 感觉好多了。

使用koa-static静态文件

  1. const path =require(‘path‘)
  2. , serve = require(‘koa-static‘);
  3. // 静态文件serve在koa-router的其他规则之上
  4. app.use(serve(path.resolve(‘dist‘)));

linux(ubuntu)搭建nodejs环境

要部署到linux上的话,需要在linux上先安装一下node,这里用 默认路径安装:/usr/local/bin
1 去node官网上下载源码 source code那个
2 放到Ubuntu上。在这个压缩包所在目录打开终端,输入解压命令
  1. tar zxvf node-v10.16.0.tar.gz
3 解压完了进入目录
  1. cd node-v10.16.0
4 输入
  1. ./configure
5 继续输入 make命令编译
  1. make
6 安装
  1. sudo make install
7 检查安装是否成功
  1. node -v
  1. npm -v

部署未完待续...

代码在github上, https://github.com/xhh007/MitWeb 运行暂时没有C++后台,后期测试完后会增加一个静态的webservice






vue2 + koa2 开发前后端项目记录

原文:https://www.cnblogs.com/huijihuijidahuiji/p/89f13e98413184d0d3a60b5bfeed5c2c.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!